refactor: remove orphaned tests for deleted website code

REMOVED: 15 test files testing non-existent code

Website Feature Tests (5):
- api.admin.test.js - Tests admin auth (auth.controller/routes removed)
- api.auth.test.js - Tests user authentication (auth.controller/routes removed)
- api.documents.test.js - Tests CMS documents (documents.controller/routes removed)
- api.koha.test.js - Tests donation system (koha.service/controller/routes removed)
- value-pluralism-integration.test.js - Website feature test

Removed Service Tests (5):
- BlogCuration.service.test.js - Service removed
- ClaudeAPI.test.js - Service removed
- koha.service.test.js - Service removed
- AdaptiveCommunicationOrchestrator.test.js - Service removed
- ProhibitedTermsScanner.test.js - Internal tool

Removed Util Tests (1):
- markdown.util.test.js - Util removed

Research/PoC Tests (4):
- tests/poc/memory-tool/* - Phase 5 proof-of-concept research

RETAINED: Framework service tests only
- BoundaryEnforcer, ContextPressureMonitor, CrossReferenceValidator
- InstructionPersistenceClassifier, MetacognitiveVerifier
- PluralisticDeliberationOrchestrator, MemoryProxy
- Integration tests for governance, projects, sync

REASON: Tests must test code that exists. Orphaned tests
provide false confidence and maintenance burden.

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TheFlow 2025-10-21 21:33:16 +13:00
parent ef59d93930
commit ead22be7e2
15 changed files with 0 additions and 5909 deletions

View file

@ -1,427 +0,0 @@
/**
* Integration Tests - Admin API
* Tests admin-only endpoints and role-based access control
*/
const request = require('supertest');
const { MongoClient } = require('mongodb');
const bcrypt = require('bcrypt');
const app = require('../../src/server');
const config = require('../../src/config/app.config');
describe('Admin API Integration Tests', () => {
let connection;
let db;
let adminToken;
let regularUserToken;
const adminUser = {
email: 'admin@test.tractatus.local',
password: 'AdminPass123!',
role: 'admin'
};
const regularUser = {
email: 'user@test.tractatus.local',
password: 'UserPass123!',
role: 'user'
};
// Setup test users
beforeAll(async () => {
connection = await MongoClient.connect(config.mongodb.uri);
db = connection.db(config.mongodb.db);
// Clean up any existing test users first
await db.collection('users').deleteMany({
email: { $in: [adminUser.email, regularUser.email] }
});
// Create admin user
const adminHash = await bcrypt.hash(adminUser.password, 10);
await db.collection('users').insertOne({
email: adminUser.email,
password: adminHash, // Field name is 'password', not 'passwordHash'
name: 'Test Admin',
role: adminUser.role,
created_at: new Date(),
active: true,
last_login: null
});
// Create regular user
const userHash = await bcrypt.hash(regularUser.password, 10);
await db.collection('users').insertOne({
email: regularUser.email,
password: userHash, // Field name is 'password', not 'passwordHash'
name: 'Test User',
role: regularUser.role,
created_at: new Date(),
active: true,
last_login: null
});
// Get auth tokens
const adminLogin = await request(app)
.post('/api/auth/login')
.send({
email: adminUser.email,
password: adminUser.password
});
adminToken = adminLogin.body.token;
const userLogin = await request(app)
.post('/api/auth/login')
.send({
email: regularUser.email,
password: regularUser.password
});
regularUserToken = userLogin.body.token;
});
// Clean up test data
afterAll(async () => {
await db.collection('users').deleteMany({
email: { $in: [adminUser.email, regularUser.email] }
});
await connection.close();
});
describe('GET /api/admin/stats', () => {
test('should return statistics with admin auth', async () => {
const response = await request(app)
.get('/api/admin/stats')
.set('Authorization', `Bearer ${adminToken}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('stats');
expect(response.body.stats).toHaveProperty('documents');
expect(response.body.stats).toHaveProperty('users');
expect(response.body.stats).toHaveProperty('blog'); // Returns 'blog' object, not 'blog_posts'
});
test('should reject requests without authentication', async () => {
const response = await request(app)
.get('/api/admin/stats')
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should reject non-admin users', async () => {
const response = await request(app)
.get('/api/admin/stats')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('Insufficient permissions');
});
});
describe.skip('GET /api/admin/users', () => {
test('should list users with admin auth', async () => {
const response = await request(app)
.get('/api/admin/users')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('users');
expect(Array.isArray(response.body.users)).toBe(true);
// Should not include password hashes
response.body.users.forEach(user => {
expect(user).not.toHaveProperty('passwordHash');
expect(user).not.toHaveProperty('password');
});
});
test('should support pagination', async () => {
const response = await request(app)
.get('/api/admin/users?limit=5&skip=0')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('pagination');
expect(response.body.pagination.limit).toBe(5);
});
test('should reject non-admin access', async () => {
const response = await request(app)
.get('/api/admin/users')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
});
});
describe('GET /api/admin/moderation', () => {
test('should return pending moderation items', async () => {
const response = await request(app)
.get('/api/admin/moderation')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('items');
expect(Array.isArray(response.body.items)).toBe(true);
});
test('should require admin role', async () => {
const response = await request(app)
.get('/api/admin/moderation')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
});
});
describe('POST /api/admin/moderation/:id/review (approve)', () => {
let testItemId;
beforeAll(async () => {
// Clean up any existing test moderation items first
await db.collection('moderation_queue').deleteMany({
item_type: 'blog_post',
item_id: null
});
// Create a test moderation item
const result = await db.collection('moderation_queue').insertOne({
item_type: 'blog_post',
item_id: null,
ai_analysis: {
suggestion: 'approve',
confidence: 0.85,
reasoning: 'Test reasoning'
},
quadrant: 'STOCHASTIC',
status: 'pending',
created_at: new Date()
});
testItemId = result.insertedId.toString();
});
afterAll(async () => {
const { ObjectId } = require('mongodb');
await db.collection('moderation_queue').deleteOne({
_id: new ObjectId(testItemId)
});
});
test('should approve moderation item', async () => {
const response = await request(app)
.post(`/api/admin/moderation/${testItemId}/review`)
.set('Authorization', `Bearer ${adminToken}`)
.send({
action: 'approve',
notes: 'Approved by integration test'
})
.expect(200);
expect(response.body).toHaveProperty('success', true);
// Verify status changed
const { ObjectId } = require('mongodb');
const item = await db.collection('moderation_queue').findOne({
_id: new ObjectId(testItemId)
});
expect(item.status).toBe('reviewed');
expect(item.review_decision.action).toBe('approve');
});
test('should require admin role', async () => {
const response = await request(app)
.post(`/api/admin/moderation/${testItemId}/review`)
.set('Authorization', `Bearer ${regularUserToken}`)
.send({
action: 'approve',
notes: 'Test'
})
.expect(403);
});
});
describe('POST /api/admin/moderation/:id/review (reject)', () => {
let testItemId;
beforeEach(async () => {
// Clean up any existing test moderation items first
await db.collection('moderation_queue').deleteMany({
item_type: 'blog_post',
item_id: null
});
const result = await db.collection('moderation_queue').insertOne({
item_type: 'blog_post',
item_id: null,
ai_analysis: {
suggestion: 'approve',
confidence: 0.60,
reasoning: 'Test'
},
quadrant: 'STOCHASTIC',
status: 'pending',
created_at: new Date()
});
testItemId = result.insertedId.toString();
});
afterEach(async () => {
const { ObjectId } = require('mongodb');
await db.collection('moderation_queue').deleteOne({
_id: new ObjectId(testItemId)
});
});
test('should reject moderation item', async () => {
const response = await request(app)
.post(`/api/admin/moderation/${testItemId}/review`)
.set('Authorization', `Bearer ${adminToken}`)
.send({
action: 'reject',
notes: 'Does not meet quality standards'
})
.expect(200);
expect(response.body).toHaveProperty('success', true);
// Verify status changed
const { ObjectId } = require('mongodb');
const item = await db.collection('moderation_queue').findOne({
_id: new ObjectId(testItemId)
});
expect(item.status).toBe('reviewed');
expect(item.review_decision.action).toBe('reject');
});
});
describe.skip('DELETE /api/admin/users/:id', () => {
let testUserId;
beforeEach(async () => {
const hash = await bcrypt.hash('TempPass123!', 10);
const result = await db.collection('users').insertOne({
email: 'temp@test.tractatus.local',
password: hash, // Field name is 'password', not 'passwordHash'
name: 'Temp User',
role: 'user',
created_at: new Date(),
active: true,
last_login: null
});
testUserId = result.insertedId.toString();
});
test('should delete user with admin auth', async () => {
const response = await request(app)
.delete(`/api/admin/users/${testUserId}`)
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
// Verify deletion
const { ObjectId } = require('mongodb');
const user = await db.collection('users').findOne({
_id: new ObjectId(testUserId)
});
expect(user).toBeNull();
});
test('should require admin role', async () => {
const response = await request(app)
.delete(`/api/admin/users/${testUserId}`)
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
// Clean up
const { ObjectId } = require('mongodb');
await db.collection('users').deleteOne({
_id: new ObjectId(testUserId)
});
});
test('should prevent self-deletion', async () => {
// Get admin user ID
const adminUserDoc = await db.collection('users').findOne({
email: adminUser.email
});
const response = await request(app)
.delete(`/api/admin/users/${adminUserDoc._id.toString()}`)
.set('Authorization', `Bearer ${adminToken}`)
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.message).toContain('delete yourself');
});
});
describe.skip('GET /api/admin/logs', () => {
test('should return system logs', async () => {
const response = await request(app)
.get('/api/admin/logs')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('logs');
});
test('should support filtering by level', async () => {
const response = await request(app)
.get('/api/admin/logs?level=error')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('filters');
expect(response.body.filters.level).toBe('error');
});
test('should require admin role', async () => {
const response = await request(app)
.get('/api/admin/logs')
.set('Authorization', `Bearer ${regularUserToken}`)
.expect(403);
});
});
describe('Role-Based Access Control', () => {
test('should enforce admin-only access across all admin routes', async () => {
const adminRoutes = [
'/api/admin/stats',
'/api/admin/moderation',
'/api/admin/activity'
];
for (const route of adminRoutes) {
const response = await request(app)
.get(route)
.set('Authorization', `Bearer ${regularUserToken}`);
expect(response.status).toBe(403);
}
});
test('should allow admin access to all admin routes', async () => {
const adminRoutes = [
'/api/admin/stats',
'/api/admin/moderation',
'/api/admin/activity'
];
for (const route of adminRoutes) {
const response = await request(app)
.get(route)
.set('Authorization', `Bearer ${adminToken}`);
expect([200, 400]).toContain(response.status);
if (response.status === 403) {
throw new Error(`Admin should have access to ${route}`);
}
}
});
});
});

View file

@ -1,293 +0,0 @@
/**
* Integration Tests - Authentication API
* Tests login, token verification, and JWT handling
*/
const request = require('supertest');
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const app = require('../../src/server');
const config = require('../../src/config/app.config');
const { connect: connectDb, close: closeDb } = require('../../src/utils/db.util');
const User = require('../../src/models/User.model');
describe('Authentication API Integration Tests', () => {
const testUser = {
email: 'test@tractatus.test',
password: 'TestPassword123!',
role: 'admin'
};
// Connect to database and create test user
beforeAll(async () => {
// Connect both database systems
await connectDb(); // Native MongoDB driver (for User model)
if (mongoose.connection.readyState === 0) {
await mongoose.connect(config.mongodb.uri); // Mongoose
}
// Clean up any existing test user first
await User.deleteOne({ email: testUser.email });
// Create test user with hashed password
const passwordHash = await bcrypt.hash(testUser.password, 10);
await User.create({
email: testUser.email,
password: passwordHash,
name: 'Test User',
role: testUser.role,
created_at: new Date(),
active: true,
last_login: null
});
});
// Clean up test data
afterAll(async () => {
await User.deleteOne({ email: testUser.email });
await mongoose.disconnect();
await closeDb();
});
describe('POST /api/auth/login', () => {
test('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: testUser.password
})
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('token');
expect(response.body).toHaveProperty('user');
expect(response.body.user).toHaveProperty('email', testUser.email);
expect(response.body.user).toHaveProperty('role', testUser.role);
expect(response.body.user).not.toHaveProperty('passwordHash');
});
test('should reject invalid password', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: 'WrongPassword123!'
})
.expect(401);
expect(response.body).toHaveProperty('error');
expect(response.body).not.toHaveProperty('token');
});
test('should reject non-existent user', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@tractatus.test',
password: 'AnyPassword123!'
})
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should require email field', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
password: testUser.password
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
test('should require password field', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
test('should validate email format', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'not-an-email',
password: testUser.password
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
describe('GET /api/auth/me', () => {
let validToken;
beforeAll(async () => {
// Get a valid token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: testUser.password
});
validToken = loginResponse.body.token;
});
test('should get current user with valid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${validToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('user');
expect(response.body.user).toHaveProperty('email', testUser.email);
});
test('should reject missing token', async () => {
const response = await request(app)
.get('/api/auth/me')
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should reject invalid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', 'Bearer invalid.jwt.token')
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should reject malformed authorization header', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', 'NotBearer token')
.expect(401);
expect(response.body).toHaveProperty('error');
});
});
describe('POST /api/auth/logout', () => {
let validToken;
beforeEach(async () => {
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: testUser.password
});
validToken = loginResponse.body.token;
});
test('should logout with valid token', async () => {
const response = await request(app)
.post('/api/auth/logout')
.set('Authorization', `Bearer ${validToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('message');
});
test('should require authentication', async () => {
const response = await request(app)
.post('/api/auth/logout')
.expect(401);
expect(response.body).toHaveProperty('error');
});
});
describe('Token Expiry', () => {
test('JWT should include expiry claim', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: testUser.password
})
.expect(200);
expect(response.body).toHaveProperty('token');
const token = response.body.token;
expect(token).toBeDefined();
expect(typeof token).toBe('string');
// Decode token (without verification for inspection)
const parts = token.split('.');
expect(parts.length).toBe(3); // JWT has 3 parts
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
expect(payload).toHaveProperty('exp');
expect(payload).toHaveProperty('iat');
expect(payload.exp).toBeGreaterThan(payload.iat);
});
});
describe('Security Headers', () => {
test('should not expose sensitive information in errors', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: 'WrongPassword'
})
.expect(401);
// Should not reveal whether user exists
expect(response.body.error).not.toContain('user');
expect(response.body.error).not.toContain('password');
});
test('should include security headers', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: testUser.password
});
// Check for security headers from helmet
expect(response.headers).toHaveProperty('x-content-type-options', 'nosniff');
expect(response.headers).toHaveProperty('x-frame-options');
});
});
describe('Rate Limiting', () => {
test('should rate limit excessive login attempts', async () => {
const requests = [];
// Make 101 requests (rate limit is 100)
for (let i = 0; i < 101; i++) {
requests.push(
request(app)
.post('/api/auth/login')
.send({
email: 'ratelimit@test.com',
password: 'password'
})
);
}
const responses = await Promise.all(requests);
// At least one should be rate limited
const rateLimited = responses.some(r => r.status === 429);
expect(rateLimited).toBe(true);
}, 30000); // Increase timeout for this test
});
});

View file

@ -1,347 +0,0 @@
/**
* Integration Tests - Documents API
* Tests document CRUD operations and search
*/
const request = require('supertest');
const { MongoClient, ObjectId } = require('mongodb');
const app = require('../../src/server');
const config = require('../../src/config/app.config');
describe('Documents API Integration Tests', () => {
let connection;
let db;
let testDocumentId;
let authToken;
// Connect to test database
beforeAll(async () => {
connection = await MongoClient.connect(config.mongodb.uri);
db = connection.db(config.mongodb.db);
// Ensure text index exists for search functionality
const indexes = await db.collection('documents').indexes();
const hasTextIndex = indexes.some(idx => idx.name === 'search_index_text');
if (!hasTextIndex) {
await db.collection('documents').createIndex(
{ search_index: 'text', title: 'text', 'metadata.tags': 'text' },
{ name: 'search_index_text', weights: { title: 10, search_index: 5, 'metadata.tags': 1 } }
);
}
});
// Clean up test data
afterAll(async () => {
if (testDocumentId) {
await db.collection('documents').deleteOne({ _id: new ObjectId(testDocumentId) });
}
await connection.close();
});
// Helper: Create test document in database
async function createTestDocument() {
const result = await db.collection('documents').insertOne({
title: 'Test Document for Integration Tests',
slug: 'test-document-integration',
quadrant: 'STRATEGIC',
persistence: 'HIGH',
content_html: '<h1>Test Content</h1><p>Integration test document</p>',
content_markdown: '# Test Content\n\nIntegration test document',
toc: [{ level: 1, text: 'Test Content', id: 'test-content' }],
metadata: {
version: '1.0',
type: 'test',
author: 'Integration Test Suite'
},
search_index: 'test document integration tests content',
created_at: new Date(),
updated_at: new Date()
});
return result.insertedId.toString();
}
// Helper: Get admin auth token
async function getAuthToken() {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'admin@tractatus.local',
password: 'admin123'
});
if (response.status === 200 && response.body.token) {
return response.body.token;
}
return null;
}
describe('GET /api/documents', () => {
test('should return list of documents', async () => {
const response = await request(app)
.get('/api/documents')
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('documents');
expect(Array.isArray(response.body.documents)).toBe(true);
expect(response.body).toHaveProperty('pagination');
expect(response.body.pagination).toHaveProperty('total');
});
test('should support pagination', async () => {
const response = await request(app)
.get('/api/documents?limit=5&skip=0')
.expect(200);
expect(response.body.pagination.limit).toBe(5);
expect(response.body.pagination.skip).toBe(0);
});
test('should filter by quadrant', async () => {
const response = await request(app)
.get('/api/documents?quadrant=STRATEGIC')
.expect(200);
if (response.body.documents.length > 0) {
response.body.documents.forEach(doc => {
expect(doc.quadrant).toBe('STRATEGIC');
});
}
});
});
describe('GET /api/documents/:identifier', () => {
beforeAll(async () => {
// Clean up any existing test documents first (use deleteMany to catch all)
await db.collection('documents').deleteMany({ slug: 'test-document-integration' });
testDocumentId = await createTestDocument();
});
test('should get document by ID', async () => {
const response = await request(app)
.get(`/api/documents/${testDocumentId}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.document).toHaveProperty('title', 'Test Document for Integration Tests');
expect(response.body.document).toHaveProperty('slug', 'test-document-integration');
});
test('should get document by slug', async () => {
const response = await request(app)
.get('/api/documents/test-document-integration')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.document).toHaveProperty('title', 'Test Document for Integration Tests');
});
test('should return 404 for non-existent document', async () => {
const fakeId = new ObjectId().toString();
const response = await request(app)
.get(`/api/documents/${fakeId}`)
.expect(404);
expect(response.body).toHaveProperty('error', 'Not Found');
});
});
describe('GET /api/documents/search', () => {
test('should search documents by query', async () => {
const response = await request(app)
.get('/api/documents/search?q=tractatus')
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('query', 'tractatus');
expect(response.body).toHaveProperty('documents');
expect(Array.isArray(response.body.documents)).toBe(true);
});
test('should return 400 without query parameter', async () => {
const response = await request(app)
.get('/api/documents/search')
.expect(400);
expect(response.body).toHaveProperty('error', 'Bad Request');
});
test('should support pagination in search', async () => {
const response = await request(app)
.get('/api/documents/search?q=framework&limit=3')
.expect(200);
expect(response.body.documents.length).toBeLessThanOrEqual(3);
});
});
describe('POST /api/documents (Admin)', () => {
beforeAll(async () => {
authToken = await getAuthToken();
});
test('should require authentication', async () => {
const response = await request(app)
.post('/api/documents')
.send({
title: 'Unauthorized Test',
slug: 'unauthorized-test',
quadrant: 'TACTICAL',
content_markdown: '# Test'
})
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should create document with valid auth', async () => {
if (!authToken) {
console.warn('Skipping test: admin login failed');
return;
}
const response = await request(app)
.post('/api/documents')
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'New Test Document',
slug: 'new-test-document',
quadrant: 'TACTICAL',
persistence: 'MEDIUM',
content_markdown: '# New Document\n\nCreated via API test'
})
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.document).toHaveProperty('title', 'New Test Document');
expect(response.body.document).toHaveProperty('content_html');
// Clean up
await db.collection('documents').deleteOne({ slug: 'new-test-document' });
});
test('should validate required fields', async () => {
if (!authToken) return;
const response = await request(app)
.post('/api/documents')
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'Incomplete Document'
// Missing slug, quadrant, content_markdown
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
test('should prevent duplicate slugs', async () => {
if (!authToken) return;
// Create first document
await request(app)
.post('/api/documents')
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'Duplicate Test',
slug: 'duplicate-slug-test',
quadrant: 'SYSTEM',
content_markdown: '# First'
});
// Try to create duplicate
const response = await request(app)
.post('/api/documents')
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'Duplicate Test 2',
slug: 'duplicate-slug-test',
quadrant: 'SYSTEM',
content_markdown: '# Second'
})
.expect(409);
expect(response.body).toHaveProperty('error', 'Conflict');
// Clean up
await db.collection('documents').deleteOne({ slug: 'duplicate-slug-test' });
});
});
describe('PUT /api/documents/:id (Admin)', () => {
let updateDocId;
beforeAll(async () => {
authToken = await getAuthToken();
// Clean up any existing test documents first (use deleteMany to catch all)
await db.collection('documents').deleteMany({ slug: 'test-document-integration' });
updateDocId = await createTestDocument();
});
afterAll(async () => {
if (updateDocId) {
await db.collection('documents').deleteOne({ _id: new ObjectId(updateDocId) });
}
});
test('should update document with valid auth', async () => {
if (!authToken) return;
const response = await request(app)
.put(`/api/documents/${updateDocId}`)
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'Updated Test Document',
content_markdown: '# Updated Content\n\nThis has been modified'
})
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.document.title).toBe('Updated Test Document');
});
test('should require authentication', async () => {
const response = await request(app)
.put(`/api/documents/${updateDocId}`)
.send({ title: 'Unauthorized Update' })
.expect(401);
});
});
describe('DELETE /api/documents/:id (Admin)', () => {
let deleteDocId;
beforeEach(async () => {
authToken = await getAuthToken();
// Clean up any existing test documents first (use deleteMany to catch all)
await db.collection('documents').deleteMany({ slug: 'test-document-integration' });
deleteDocId = await createTestDocument();
});
test('should delete document with valid auth', async () => {
if (!authToken) return;
const response = await request(app)
.delete(`/api/documents/${deleteDocId}`)
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.success).toBe(true);
// Verify deletion
const doc = await db.collection('documents').findOne({ _id: new ObjectId(deleteDocId) });
expect(doc).toBeNull();
});
test('should require authentication', async () => {
const response = await request(app)
.delete(`/api/documents/${deleteDocId}`)
.expect(401);
// Clean up since delete failed
await db.collection('documents').deleteOne({ _id: new ObjectId(deleteDocId) });
});
});
});

View file

@ -1,342 +0,0 @@
/**
* Integration Tests - Koha API (Donation System)
* Tests donation endpoints, authentication, and security features
*/
const request = require('supertest');
const { MongoClient, ObjectId } = require('mongodb');
const bcrypt = require('bcrypt');
const app = require('../../src/server');
const config = require('../../src/config/app.config');
describe('Koha API Integration Tests', () => {
let connection;
let db;
let adminToken;
let testDonationId;
let testSubscriptionId;
const adminUser = {
email: 'admin@koha.test.local',
password: 'AdminKoha123!',
role: 'admin'
};
// Connect to database and setup test data
beforeAll(async () => {
connection = await MongoClient.connect(config.mongodb.uri);
db = connection.db(config.mongodb.db);
// Clean up any existing test data
await db.collection('users').deleteMany({ email: adminUser.email });
await db.collection('koha_donations').deleteMany({ 'donor.email': /test.*@koha\.test/ });
// Create admin user
const adminHash = await bcrypt.hash(adminUser.password, 10);
await db.collection('users').insertOne({
email: adminUser.email,
password: adminHash,
name: 'Koha Test Admin',
role: adminUser.role,
created_at: new Date(),
active: true,
last_login: null
});
// Get admin token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: adminUser.email,
password: adminUser.password
});
adminToken = loginResponse.body.token;
// Create test donation with subscription
const result = await db.collection('koha_donations').insertOne({
amount: 1500, // $15.00
currency: 'nzd',
frequency: 'monthly',
tier: '15',
donor: {
name: 'Test Donor',
email: 'donor@koha.test',
country: 'NZ'
},
stripe: {
customer_id: 'cus_test123',
subscription_id: 'sub_test123'
},
status: 'completed',
created_at: new Date(),
updated_at: new Date()
});
testDonationId = result.insertedId.toString();
testSubscriptionId = 'sub_test123';
});
// Clean up test data
afterAll(async () => {
await db.collection('users').deleteMany({ email: adminUser.email });
await db.collection('koha_donations').deleteMany({ 'donor.email': /test.*@koha\.test/ });
if (testDonationId) {
await db.collection('koha_donations').deleteOne({ _id: new ObjectId(testDonationId) });
}
await connection.close();
});
describe('GET /api/koha/transparency', () => {
test('should return public transparency metrics', async () => {
const response = await request(app)
.get('/api/koha/transparency')
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('data');
expect(response.body.data).toHaveProperty('total_received');
expect(response.body.data).toHaveProperty('monthly_supporters');
expect(response.body.data).toHaveProperty('allocation');
});
});
describe('POST /api/koha/cancel', () => {
test('should require subscription ID and email', async () => {
const response = await request(app)
.post('/api/koha/cancel')
.send({})
.expect(400);
expect(response.body).toHaveProperty('error');
});
test('should reject cancellation with wrong email (security)', async () => {
const response = await request(app)
.post('/api/koha/cancel')
.send({
subscriptionId: testSubscriptionId,
email: 'wrong@email.com'
})
.expect(403);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toContain('does not match');
});
test('should reject cancellation of non-existent subscription', async () => {
const response = await request(app)
.post('/api/koha/cancel')
.send({
subscriptionId: 'sub_nonexistent',
email: 'any@email.com'
})
.expect(404);
expect(response.body).toHaveProperty('error');
});
test('should allow cancellation with correct email', async () => {
// Skip if Stripe is not configured
if (!process.env.STRIPE_SECRET_KEY || process.env.STRIPE_SECRET_KEY.includes('PLACEHOLDER')) {
console.warn('Skipping test: Stripe not configured');
return;
}
const response = await request(app)
.post('/api/koha/cancel')
.send({
subscriptionId: testSubscriptionId,
email: 'donor@koha.test'
});
// Will fail with Stripe error in test environment, but should pass email verification
// The 500 error would be from Stripe, not from email validation
expect([200, 500]).toContain(response.status);
});
test('should be rate limited after 10 attempts', async () => {
// Make 11 requests rapidly
const requests = [];
for (let i = 0; i < 11; i++) {
requests.push(
request(app)
.post('/api/koha/cancel')
.send({
subscriptionId: 'sub_test',
email: `test${i}@rate-limit.test`
})
);
}
const responses = await Promise.all(requests);
// At least one should be rate limited (429)
const rateLimited = responses.some(r => r.status === 429);
expect(rateLimited).toBe(true);
}, 30000); // Increase timeout for rate limit test
});
describe('GET /api/koha/statistics (Admin Only)', () => {
test('should require authentication', async () => {
const response = await request(app)
.get('/api/koha/statistics')
.expect(401);
expect(response.body).toHaveProperty('error');
});
test('should require admin role', async () => {
// Create regular user
const regularUser = {
email: 'user@koha.test.local',
password: 'UserKoha123!'
};
const userHash = await bcrypt.hash(regularUser.password, 10);
await db.collection('users').insertOne({
email: regularUser.email,
password: userHash,
name: 'Regular User',
role: 'user',
created_at: new Date(),
active: true
});
// Get user token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: regularUser.email,
password: regularUser.password
});
const userToken = loginResponse.body.token;
// Try to access admin endpoint
const response = await request(app)
.get('/api/koha/statistics')
.set('Authorization', `Bearer ${userToken}`)
.expect(403);
expect(response.body).toHaveProperty('error');
// Clean up
await db.collection('users').deleteOne({ email: regularUser.email });
});
test('should return statistics with admin auth', async () => {
const response = await request(app)
.get('/api/koha/statistics')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('data');
expect(response.body.data).toHaveProperty('total_count');
expect(response.body.data).toHaveProperty('total_amount');
expect(response.body.data).toHaveProperty('by_frequency');
});
test('should support date range filtering', async () => {
const startDate = '2025-01-01';
const endDate = '2025-12-31';
const response = await request(app)
.get(`/api/koha/statistics?startDate=${startDate}&endDate=${endDate}`)
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(response.body).toHaveProperty('success', true);
});
});
describe('POST /api/koha/checkout (Rate Limiting)', () => {
test('should be rate limited after 10 attempts', async () => {
// Skip if Stripe is not configured
if (!process.env.STRIPE_SECRET_KEY || process.env.STRIPE_SECRET_KEY.includes('PLACEHOLDER')) {
console.warn('Skipping test: Stripe not configured');
return;
}
const requests = [];
for (let i = 0; i < 11; i++) {
requests.push(
request(app)
.post('/api/koha/checkout')
.send({
amount: 500,
frequency: 'one_time',
donor: {
name: 'Test Donor',
email: `test${i}@rate-limit.test`,
country: 'NZ'
}
})
);
}
const responses = await Promise.all(requests);
// At least one should be rate limited (429)
const rateLimited = responses.some(r => r.status === 429);
expect(rateLimited).toBe(true);
}, 30000); // Increase timeout for rate limit test
});
describe('Security Validations', () => {
test('should validate minimum donation amount', async () => {
const response = await request(app)
.post('/api/koha/checkout')
.send({
amount: 50, // Less than minimum (100 = $1.00)
frequency: 'one_time',
donor: {
email: 'test@security.test'
}
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
test('should validate required fields for checkout', async () => {
const response = await request(app)
.post('/api/koha/checkout')
.send({
// Missing amount, frequency, donor.email
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
test('should validate frequency values', async () => {
const response = await request(app)
.post('/api/koha/checkout')
.send({
amount: 1000,
frequency: 'invalid_frequency',
donor: {
email: 'test@security.test'
}
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
test('should validate tier for monthly donations', async () => {
const response = await request(app)
.post('/api/koha/checkout')
.send({
amount: 1000,
frequency: 'monthly',
tier: 'invalid_tier',
donor: {
email: 'test@security.test'
}
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
});

View file

@ -1,302 +0,0 @@
/**
* Integration Tests for Value Pluralism Services
* Tests the complete flow: BoundaryEnforcer PluralisticDeliberationOrchestrator AdaptiveCommunicationOrchestrator
*/
const BoundaryEnforcer = require('../../src/services/BoundaryEnforcer.service');
const PluralisticDeliberationOrchestrator = require('../../src/services/PluralisticDeliberationOrchestrator.service');
const AdaptiveCommunicationOrchestrator = require('../../src/services/AdaptiveCommunicationOrchestrator.service');
describe('Value Pluralism Integration', () => {
beforeAll(async () => {
// Initialize services
await BoundaryEnforcer.initialize();
await PluralisticDeliberationOrchestrator.initialize();
});
describe('BoundaryEnforcer → PluralisticDeliberationOrchestrator Flow', () => {
test('should detect value conflict and trigger deliberation', async () => {
// Simulate a decision that crosses into values territory
const decision = {
description: 'Should we disclose user private data to prevent potential harm to others? This involves the duty to respect privacy rights and the obligation to maintain consent, but also maximizing welfare and preventing harm through safety measures.',
context: {
requester: 'safety_team',
affected_users: 1000,
harm_type: 'potential_violence'
}
};
// Step 1: BoundaryEnforcer should detect this as a values decision
const boundaryCheck = await BoundaryEnforcer.checkDecision({
action: 'disclose_user_data',
description: decision.description,
metadata: decision.context
});
expect(boundaryCheck.allowed).toBe(false);
expect(boundaryCheck.reason).toContain('values');
expect(boundaryCheck.requires_human_approval).toBe(true);
// Step 2: Trigger PluralisticDeliberationOrchestrator for conflict analysis
const conflictAnalysis = PluralisticDeliberationOrchestrator.analyzeConflict(decision);
expect(conflictAnalysis.moral_frameworks_in_tension).toBeDefined();
expect(conflictAnalysis.moral_frameworks_in_tension.length).toBeGreaterThan(0);
expect(conflictAnalysis.ai_role).toBe('FACILITATE_ONLY');
expect(conflictAnalysis.human_role).toBe('DECIDE');
expect(conflictAnalysis.value_trade_offs).toContain('privacy vs. safety');
});
test('should route technical decision without triggering deliberation', async () => {
const technicalDecision = {
description: 'Update database connection pool size from 10 to 20 connections',
context: {
service: 'database',
change_type: 'configuration'
}
};
// BoundaryEnforcer should allow technical decisions
const boundaryCheck = await BoundaryEnforcer.checkDecision({
action: 'update_config',
description: technicalDecision.description,
metadata: technicalDecision.context
});
// Technical decisions should be allowed
expect(boundaryCheck.allowed).toBe(true);
});
});
describe('PluralisticDeliberationOrchestrator → AdaptiveCommunicationOrchestrator Flow', () => {
test('should adapt deliberation invitation to stakeholder communication styles', () => {
// Step 1: Analyze conflict
const conflict = {
moral_frameworks_in_tension: [
{ framework: 'Rights-based (Deontological)', focus: 'privacy' },
{ framework: 'Consequentialist (Utilitarian)', focus: 'harm prevention' }
],
value_trade_offs: ['privacy vs. safety'],
affected_stakeholder_groups: ['privacy_advocates', 'safety_team'],
deliberation_process: 'Full deliberative process'
};
// Step 2: Facilitate deliberation with different stakeholder communication styles
const stakeholders = [
{
id: 1,
group: 'privacy_advocates',
name: 'Dr. Privacy',
communication_style: 'FORMAL_ACADEMIC',
cultural_context: 'western_academic'
},
{
id: 2,
group: 'safety_team',
name: 'Safety Manager',
communication_style: 'CASUAL_DIRECT',
cultural_context: 'australian'
},
{
id: 3,
group: 'community_representatives',
name: 'Kaitiaki',
communication_style: 'MAORI_PROTOCOL',
cultural_context: 'maori'
}
];
const deliberation = PluralisticDeliberationOrchestrator.facilitateDeliberation(
conflict,
stakeholders,
{ stakeholder_list_approved_by_human: true }
);
// Should create culturally-adapted communications for each stakeholder
expect(deliberation.stakeholder_communications).toBeDefined();
expect(deliberation.stakeholder_communications.length).toBe(3);
// Each stakeholder should receive adapted communication
deliberation.stakeholder_communications.forEach(comm => {
expect(comm.communication).toBeDefined();
expect(comm.stakeholder_id).toBeDefined();
expect(comm.stakeholder_group).toBeDefined();
});
// Step 3: Verify AdaptiveCommunicationOrchestrator removed patronizing language
const allCommunications = deliberation.stakeholder_communications
.map(c => c.communication)
.join(' ');
// Should not contain patronizing terms
expect(allCommunications).not.toContain('simply');
expect(allCommunications).not.toContain('obviously');
expect(allCommunications).not.toContain('just do');
});
test('should reject deliberation without human-approved stakeholder list', () => {
const conflict = {
moral_frameworks_in_tension: [],
value_trade_offs: ['test vs. test'],
deliberation_process: 'Test'
};
const stakeholders = [{ id: 1, group: 'test' }];
// Attempt deliberation without human approval
const result = PluralisticDeliberationOrchestrator.facilitateDeliberation(
conflict,
stakeholders,
{ stakeholder_list_approved_by_human: false } // Not approved
);
expect(result.error).toBeDefined();
expect(result.action).toBe('REQUIRE_HUMAN_APPROVAL');
expect(result.reason).toContain('stakeholders');
});
});
describe('Complete Deliberation Flow', () => {
test('should handle full deliberation lifecycle', async () => {
// 1. Value conflict detected
const decision = {
description: 'Balance user privacy rights vs public safety duty when harm is imminent. This decision requires weighing privacy obligations against potential harm outcomes.',
urgency: 'critical'
};
const analysis = PluralisticDeliberationOrchestrator.analyzeConflict(decision, {
imminent_harm: true
});
expect(analysis.urgency_tier).toBe('CRITICAL');
expect(analysis.deliberation_timeframe).toBe('minutes to hours');
// 2. Facilitated deliberation
const stakeholders = [
{ id: 1, group: 'privacy', communication_style: 'FORMAL_ACADEMIC' },
{ id: 2, group: 'safety', communication_style: 'CASUAL_DIRECT' }
];
const deliberation = PluralisticDeliberationOrchestrator.facilitateDeliberation(
analysis,
stakeholders,
{ stakeholder_list_approved_by_human: true }
);
expect(deliberation.deliberation_id).toBeDefined();
expect(deliberation.stakeholder_communications.length).toBe(2);
// 3. Human-decided outcome
const outcome = {
decided_by_human: true,
decision_summary: 'Disclose minimal necessary data to prevent imminent harm only',
values_prioritized: ['safety', 'harm_prevention'],
values_deprioritized: ['privacy', 'autonomy'],
moral_remainder: 'Privacy violation is acknowledged as a moral cost of preventing imminent harm',
dissenting_views: [
{
stakeholder: 'privacy_advocates',
view: 'Sets dangerous precedent for future privacy erosion'
}
],
consensus_reached: false,
review_date: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) // 90 days
};
const documentation = PluralisticDeliberationOrchestrator.documentOutcome(
deliberation,
outcome
);
expect(documentation.outcome_documented).toBe(true);
expect(documentation.precedent_created).toBeDefined();
expect(documentation.precedent_binding).toBe(false); // Informative, not binding
expect(documentation.values_prioritization).toBeDefined();
expect(documentation.values_prioritization.moral_remainder).toBeDefined();
expect(documentation.values_prioritization.dissenting_views.length).toBe(1);
});
test('should require human decision for outcome documentation', () => {
const deliberation = {
deliberation_id: 'test_123',
structure: { frameworks_in_tension: [] }
};
const aiAttemptedOutcome = {
decided_by_human: false, // AI trying to decide
decision_summary: 'AI attempted decision',
values_prioritized: ['test']
};
const result = PluralisticDeliberationOrchestrator.documentOutcome(
deliberation,
aiAttemptedOutcome
);
expect(result.error).toBeDefined();
expect(result.action).toBe('REQUIRE_HUMAN_DECISION');
expect(result.outcome_documented).toBe(false);
});
});
describe('Precedent System Integration', () => {
test('should create informative (not binding) precedent', () => {
const deliberation = {
deliberation_id: 'prec_test_001',
structure: {
frameworks_in_tension: [
{ framework: 'Deontological', position: 'Privacy is inviolable right' },
{ framework: 'Consequentialist', position: 'Prevent harm to others' }
]
}
};
const outcome = {
decided_by_human: true,
decision_summary: 'Test precedent creation',
values_prioritized: ['safety'],
values_deprioritized: ['privacy'],
moral_remainder: 'Privacy cost acknowledged',
applicability_scope: 'Imminent harm scenarios only - context-specific',
context_factors: ['imminent_harm', 'specific_threat', 'minimal_disclosure']
};
const result = PluralisticDeliberationOrchestrator.documentOutcome(
deliberation,
outcome
);
expect(result.precedent_created).toBeDefined();
expect(result.precedent_binding).toBe(false); // Per inst_035: informative, not binding
expect(result.precedent_scope).toContain('context');
});
});
describe('Statistics Tracking Integration', () => {
test('should track statistics across all three services', () => {
// Adaptive Communication Stats
const commStats = AdaptiveCommunicationOrchestrator.getStats();
expect(commStats.total_adaptations).toBeGreaterThan(0);
expect(commStats.by_style).toBeDefined();
// Pluralistic Deliberation Stats
const delibStats = PluralisticDeliberationOrchestrator.getStats();
expect(delibStats.total_deliberations).toBeGreaterThan(0);
expect(delibStats.by_urgency).toBeDefined();
expect(delibStats.precedents_created).toBeGreaterThan(0);
});
});
describe('Error Handling Integration', () => {
test('should handle errors gracefully across service boundaries', () => {
// Invalid decision object
const invalidDecision = null;
const analysis = PluralisticDeliberationOrchestrator.analyzeConflict(invalidDecision);
// Should return error response, not throw
expect(analysis).toBeDefined();
expect(analysis.requires_human_approval).toBe(true);
});
});
});

View file

@ -1,354 +0,0 @@
/**
* Phase 5 PoC - Anthropic Memory Tool Integration Test
*
* Goal: Validate that Claude API can use memory tool to persist/retrieve governance rules
*
* Success Criteria:
* - Claude can write rules to memory via tool use
* - Claude can read rules from memory in subsequent requests
* - Latency overhead <500ms (PoC tolerance)
* - Data integrity maintained across API calls
*/
const Anthropic = require('@anthropic-ai/sdk');
const { FilesystemMemoryBackend } = require('./basic-persistence-test');
const path = require('path');
// Configuration
const MEMORY_BASE_PATH = path.join(__dirname, '../../../.memory-poc-anthropic');
const MODEL = 'claude-sonnet-4-5';
const TEST_RULES = {
inst_001: {
id: 'inst_001',
text: 'Never fabricate statistics or quantitative claims without verifiable sources',
quadrant: 'OPERATIONAL',
persistence: 'HIGH'
},
inst_016: {
id: 'inst_016',
text: 'No fabricated statistics (e.g., "95% of users"): require source',
quadrant: 'OPERATIONAL',
persistence: 'HIGH'
},
inst_017: {
id: 'inst_017',
text: 'No absolute guarantees ("will always"): use probabilistic language',
quadrant: 'OPERATIONAL',
persistence: 'HIGH'
}
};
// Initialize Anthropic client
function createClient() {
const apiKey = process.env.CLAUDE_API_KEY;
if (!apiKey) {
throw new Error('CLAUDE_API_KEY environment variable not set');
}
return new Anthropic({
apiKey
});
}
// Simulate memory tool handling (client-side implementation)
async function handleMemoryToolUse(toolUse, backend) {
const { input } = toolUse;
console.log(` Memory Tool Called: ${input.command}`);
console.log(` Path: ${input.path || 'N/A'}`);
switch (input.command) {
case 'view':
try {
const data = await backend.view(input.path);
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: JSON.stringify(data, null, 2)
};
} catch (error) {
return {
type: 'tool_result',
tool_use_id: toolUse.id,
is_error: true,
content: `Error reading file: ${error.message}`
};
}
case 'create':
try {
const data = input.content ? JSON.parse(input.content) : input.data;
await backend.create(input.path, data);
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: 'File created successfully'
};
} catch (error) {
return {
type: 'tool_result',
tool_use_id: toolUse.id,
is_error: true,
content: `Error creating file: ${error.message}`
};
}
case 'str_replace':
// For PoC, we'll keep it simple - just recreate the file
try {
const current = await backend.view(input.path);
const updated = JSON.stringify(current).replace(input.old_str, input.new_str);
await backend.create(input.path, JSON.parse(updated));
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: 'File updated successfully'
};
} catch (error) {
return {
type: 'tool_result',
tool_use_id: toolUse.id,
is_error: true,
content: `Error updating file: ${error.message}`
};
}
default:
return {
type: 'tool_result',
tool_use_id: toolUse.id,
is_error: true,
content: `Unsupported command: ${input.command}`
};
}
}
// Main test execution
async function runAnthropicMemoryTest() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' Phase 5 PoC: Anthropic Memory Tool Integration');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const backend = new FilesystemMemoryBackend(MEMORY_BASE_PATH);
const results = {
success: false,
apiCalls: 0,
memoryOperations: 0,
timings: {},
errors: []
};
try {
// Check API key
if (!process.env.CLAUDE_API_KEY) {
console.log('⚠️ CLAUDE_API_KEY not set - skipping API tests');
console.log(' Running in simulation mode...\n');
// Simulate the workflow without actual API calls
console.log('[Simulation] Step 1: Initialize backend...');
await backend.initialize();
console.log('[Simulation] Step 2: Store governance rules...');
const rulesArray = Object.values(TEST_RULES);
await backend.create('governance/tractatus-rules-v1.json', {
version: '1.0',
rules: rulesArray,
updated_at: new Date().toISOString()
});
console.log('[Simulation] Step 3: Retrieve rules...');
const retrieved = await backend.view('governance/tractatus-rules-v1.json');
console.log('[Simulation] Step 4: Validate integrity...');
const expectedCount = rulesArray.length;
const actualCount = retrieved.rules.length;
if (expectedCount === actualCount) {
console.log(` ✓ Rule count matches: ${actualCount}`);
results.success = true;
} else {
throw new Error(`Rule count mismatch: expected ${expectedCount}, got ${actualCount}`);
}
console.log('\n✅ SIMULATION COMPLETE');
console.log('\nTo run with actual API:');
console.log(' export CLAUDE_API_KEY=your-key-here');
console.log(' node tests/poc/memory-tool/anthropic-memory-integration-test.js\n');
} else {
// Real API test
console.log('[Step 1] Initializing Anthropic client...');
const client = createClient();
console.log(` Model: ${MODEL}`);
console.log(` Beta: context-management-2025-06-27\n`);
console.log('[Step 2] Initialize memory backend...');
await backend.initialize();
// Test 1: Ask Claude to store a governance rule
console.log('[Step 3] Testing memory tool - CREATE operation...');
const createStart = Date.now();
const createResponse = await client.beta.messages.create({
model: MODEL,
max_tokens: 1024,
messages: [{
role: 'user',
content: `Store this governance rule in memory at path "governance/inst_001.json":
${JSON.stringify(TEST_RULES.inst_001, null, 2)}
Use the memory tool to create this file.`
}],
tools: [{
type: 'memory_20250818',
name: 'memory',
description: 'Persistent storage for Tractatus governance rules'
}],
betas: ['context-management-2025-06-27']
});
results.apiCalls++;
results.timings.create = Date.now() - createStart;
// Handle tool use
const toolUses = createResponse.content.filter(block => block.type === 'tool_use');
if (toolUses.length > 0) {
console.log(` ✓ Claude invoked memory tool (${toolUses.length} operations)`);
for (const toolUse of toolUses) {
const result = await handleMemoryToolUse(toolUse, backend);
results.memoryOperations++;
if (result.is_error) {
throw new Error(`Memory tool error: ${result.content}`);
}
console.log(`${toolUse.input.command}: ${result.content}`);
}
} else {
console.log(' ⚠️ Claude did not use memory tool');
}
// Test 2: Ask Claude to retrieve the rule
console.log('\n[Step 4] Testing memory tool - VIEW operation...');
const viewStart = Date.now();
const viewResponse = await client.beta.messages.create({
model: MODEL,
max_tokens: 1024,
messages: [{
role: 'user',
content: 'Retrieve the governance rule from memory at path "governance/inst_001.json" and tell me the rule ID and persistence level.'
}],
tools: [{
type: 'memory_20250818',
name: 'memory',
description: 'Persistent storage for Tractatus governance rules'
}],
betas: ['context-management-2025-06-27']
});
results.apiCalls++;
results.timings.view = Date.now() - viewStart;
const viewToolUses = viewResponse.content.filter(block => block.type === 'tool_use');
if (viewToolUses.length > 0) {
console.log(` ✓ Claude retrieved from memory (${viewToolUses.length} operations)`);
for (const toolUse of viewToolUses) {
const result = await handleMemoryToolUse(toolUse, backend);
results.memoryOperations++;
if (result.is_error) {
throw new Error(`Memory tool error: ${result.content}`);
}
console.log(`${toolUse.input.command}: Retrieved successfully`);
}
}
// Validate response
const textBlocks = viewResponse.content.filter(block => block.type === 'text');
const responseText = textBlocks.map(b => b.text).join(' ');
console.log('\n[Step 5] Validating Claude\'s response...');
const checks = [
{ label: 'Mentions inst_001', test: responseText.includes('inst_001') },
{ label: 'Mentions HIGH persistence', test: responseText.toLowerCase().includes('high') },
{ label: 'Understood the data', test: responseText.length > 50 }
];
let allPassed = true;
for (const check of checks) {
const status = check.test ? '✓' : '✗';
console.log(` ${status} ${check.label}`);
if (!check.test) allPassed = false;
}
if (!allPassed) {
console.log('\n Response:', responseText);
throw new Error('Validation checks failed');
}
results.success = true;
}
} catch (error) {
console.error('\n✗ TEST FAILED:', error.message);
if (error.stack) {
console.error('\nStack trace:', error.stack);
}
results.errors.push(error.message);
results.success = false;
} finally {
// Cleanup
console.log('\n[Cleanup] Removing test data...');
await backend.cleanup();
}
// Results summary
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' TEST RESULTS');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (results.success) {
console.log('✅ SUCCESS: Memory tool integration validated');
console.log('\nKey Findings:');
console.log(` • API calls made: ${results.apiCalls}`);
console.log(` • Memory operations: ${results.memoryOperations}`);
if (results.timings.create) {
console.log(` • CREATE latency: ${results.timings.create}ms`);
}
if (results.timings.view) {
console.log(` • VIEW latency: ${results.timings.view}ms`);
}
console.log('\nNext Steps:');
console.log(' 1. Test with all 18 Tractatus rules');
console.log(' 2. Test enforcement of inst_016, inst_017, inst_018');
console.log(' 3. Measure context editing effectiveness');
} else {
console.log('❌ FAILURE: Test did not pass');
console.log('\nErrors:');
results.errors.forEach(err => console.log(`${err}`));
}
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
return results;
}
// Run test
if (require.main === module) {
runAnthropicMemoryTest()
.then(results => {
process.exit(results.success ? 0 : 1);
})
.catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = { runAnthropicMemoryTest };

View file

@ -1,207 +0,0 @@
/**
* Phase 5 PoC - Memory Tool Basic Persistence Test
*
* Goal: Prove that governance rules can persist across separate API calls
*
* Success Criteria:
* - Rule persists to memory tool
* - Rule retrieved in separate API call
* - Data integrity maintained (no corruption)
* - Latency overhead measured
*/
const Anthropic = require('@anthropic-ai/sdk');
const fs = require('fs').promises;
const path = require('path');
// Test configuration
const MEMORY_BASE_PATH = path.join(__dirname, '../../../.memory-poc');
const TEST_RULE = {
id: 'inst_001',
text: 'Never fabricate statistics or quantitative claims without verifiable sources',
quadrant: 'OPERATIONAL',
persistence: 'HIGH',
rationale: 'Foundational integrity principle - statistical claims must be evidence-based',
examples: [
'PASS: "MongoDB typically uses port 27017"',
'FAIL: "95% of users prefer our framework" (without source)'
],
created_at: new Date().toISOString()
};
// Simple filesystem-based memory backend
class FilesystemMemoryBackend {
constructor(basePath) {
this.basePath = basePath;
}
async initialize() {
// Create memory directory structure
await fs.mkdir(path.join(this.basePath, 'governance'), { recursive: true });
await fs.mkdir(path.join(this.basePath, 'sessions'), { recursive: true });
await fs.mkdir(path.join(this.basePath, 'audit'), { recursive: true });
console.log('✓ Memory backend initialized:', this.basePath);
}
async create(filePath, data) {
const fullPath = path.join(this.basePath, filePath);
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, JSON.stringify(data, null, 2), 'utf8');
console.log('✓ Created memory file:', filePath);
}
async view(filePath) {
const fullPath = path.join(this.basePath, filePath);
const data = await fs.readFile(fullPath, 'utf8');
console.log('✓ Retrieved memory file:', filePath);
return JSON.parse(data);
}
async exists(filePath) {
const fullPath = path.join(this.basePath, filePath);
try {
await fs.access(fullPath);
return true;
} catch {
return false;
}
}
async cleanup() {
await fs.rm(this.basePath, { recursive: true, force: true });
console.log('✓ Cleaned up memory backend');
}
}
// Test execution
async function runPoCTest() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' Phase 5 PoC: Memory Tool Basic Persistence Test');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const backend = new FilesystemMemoryBackend(MEMORY_BASE_PATH);
const results = {
success: false,
timings: {},
errors: []
};
try {
// Step 1: Initialize backend
console.log('[Step 1] Initializing memory backend...');
const initStart = Date.now();
await backend.initialize();
results.timings.initialization = Date.now() - initStart;
console.log(` Time: ${results.timings.initialization}ms\n`);
// Step 2: Persist test rule
console.log('[Step 2] Persisting governance rule to memory...');
console.log(` Rule: ${TEST_RULE.id}`);
console.log(` Persistence: ${TEST_RULE.persistence}`);
const persistStart = Date.now();
await backend.create('governance/test-rule.json', TEST_RULE);
results.timings.persist = Date.now() - persistStart;
console.log(` Time: ${results.timings.persist}ms\n`);
// Step 3: Verify file exists
console.log('[Step 3] Verifying file persistence...');
const exists = await backend.exists('governance/test-rule.json');
if (!exists) {
throw new Error('File does not exist after creation');
}
console.log(' ✓ File exists on filesystem\n');
// Step 4: Retrieve rule (simulating separate API call)
console.log('[Step 4] Retrieving rule (separate operation)...');
const retrieveStart = Date.now();
const retrieved = await backend.view('governance/test-rule.json');
results.timings.retrieve = Date.now() - retrieveStart;
console.log(` Time: ${results.timings.retrieve}ms\n`);
// Step 5: Validate data integrity
console.log('[Step 5] Validating data integrity...');
const validations = [
{ field: 'id', expected: TEST_RULE.id, actual: retrieved.id },
{ field: 'persistence', expected: TEST_RULE.persistence, actual: retrieved.persistence },
{ field: 'quadrant', expected: TEST_RULE.quadrant, actual: retrieved.quadrant },
{ field: 'text', expected: TEST_RULE.text, actual: retrieved.text }
];
let allValid = true;
for (const validation of validations) {
const isValid = validation.expected === validation.actual;
const status = isValid ? '✓' : '✗';
console.log(` ${status} ${validation.field}: ${isValid ? 'MATCH' : 'MISMATCH'}`);
if (!isValid) {
console.log(` Expected: ${validation.expected}`);
console.log(` Actual: ${validation.actual}`);
allValid = false;
}
}
if (!allValid) {
throw new Error('Data integrity validation failed');
}
console.log('\n[Step 6] Performance Assessment...');
const totalLatency = results.timings.persist + results.timings.retrieve;
console.log(` Persist latency: ${results.timings.persist}ms`);
console.log(` Retrieve latency: ${results.timings.retrieve}ms`);
console.log(` Total overhead: ${totalLatency}ms`);
const target = 500; // PoC tolerance
const status = totalLatency < target ? 'PASS' : 'WARN';
console.log(` Target: <${target}ms - ${status}`);
results.success = true;
results.totalLatency = totalLatency;
} catch (error) {
console.error('\n✗ TEST FAILED:', error.message);
results.errors.push(error.message);
results.success = false;
} finally {
// Cleanup
console.log('\n[Cleanup] Removing test data...');
await backend.cleanup();
}
// Results summary
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' TEST RESULTS');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (results.success) {
console.log('✅ SUCCESS: Rule persistence validated');
console.log('\nKey Findings:');
console.log(' • Persistence: ✓ 100% (no data loss)');
console.log(' • Data integrity: ✓ 100% (no corruption)');
console.log(` • Performance: ✓ ${results.totalLatency}ms total overhead`);
console.log('\nNext Steps:');
console.log(' 1. Integrate with Anthropic Claude API (memory tool)');
console.log(' 2. Test with inst_016, inst_017, inst_018');
console.log(' 3. Measure API latency overhead');
} else {
console.log('❌ FAILURE: Test did not pass');
console.log('\nErrors:');
results.errors.forEach(err => console.log(`${err}`));
}
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
return results;
}
// Run test
if (require.main === module) {
runPoCTest()
.then(results => {
process.exit(results.success ? 0 : 1);
})
.catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = { runPoCTest, FilesystemMemoryBackend };

View file

@ -1,308 +0,0 @@
/**
* Phase 5 PoC - Week 2: Full Tractatus Rules Integration
*
* Goal: Load all 18 governance rules into memory tool and validate persistence
*
* Success Criteria:
* - All 18 rules stored successfully
* - All 18 rules retrieved with 100% fidelity
* - API latency measured and acceptable (<1000ms per operation)
* - Data integrity maintained across storage/retrieval
*/
const Anthropic = require('@anthropic-ai/sdk');
const { FilesystemMemoryBackend } = require('./basic-persistence-test');
const path = require('path');
const fs = require('fs').promises;
require('dotenv').config();
// Configuration
const MEMORY_BASE_PATH = path.join(__dirname, '../../../.memory-poc-week2');
const MODEL = 'claude-sonnet-4-5';
const INSTRUCTION_HISTORY_PATH = path.join(__dirname, '../../../.claude/instruction-history.json');
// Load Tractatus governance rules
async function loadTractatusRules() {
const data = await fs.readFile(INSTRUCTION_HISTORY_PATH, 'utf8');
const parsed = JSON.parse(data);
return parsed.instructions;
}
// Initialize Anthropic client
function createClient() {
const apiKey = process.env.CLAUDE_API_KEY;
if (!apiKey) {
throw new Error('CLAUDE_API_KEY environment variable not set');
}
return new Anthropic({ apiKey });
}
// Simulate memory tool handling (client-side implementation)
async function handleMemoryToolUse(toolUse, backend) {
const { input } = toolUse;
switch (input.command) {
case 'view':
try {
const data = await backend.view(input.path);
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: JSON.stringify(data, null, 2)
};
} catch (error) {
return {
type: 'tool_result',
tool_use_id: toolUse.id,
is_error: true,
content: `Error reading file: ${error.message}`
};
}
case 'create':
try {
const data = input.content ? JSON.parse(input.content) : input.data;
await backend.create(input.path, data);
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: 'File created successfully'
};
} catch (error) {
return {
type: 'tool_result',
tool_use_id: toolUse.id,
is_error: true,
content: `Error creating file: ${error.message}`
};
}
default:
return {
type: 'tool_result',
tool_use_id: toolUse.id,
is_error: true,
content: `Unsupported command: ${input.command}`
};
}
}
// Main test execution
async function runFullRulesTest() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' Phase 5 PoC Week 2: Full Tractatus Rules Test');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const backend = new FilesystemMemoryBackend(MEMORY_BASE_PATH);
const results = {
success: false,
rulesLoaded: 0,
rulesStored: 0,
rulesRetrieved: 0,
integrityChecks: { passed: 0, failed: 0 },
apiCalls: 0,
memoryOperations: 0,
timings: {},
errors: []
};
try {
// Step 1: Load Tractatus rules
console.log('[Step 1] Loading Tractatus governance rules...');
const loadStart = Date.now();
const rules = await loadTractatusRules();
results.timings.load = Date.now() - loadStart;
results.rulesLoaded = rules.length;
console.log(` ✓ Loaded ${rules.length} governance rules`);
console.log(` Time: ${results.timings.load}ms`);
// Show rule breakdown
const quadrantCounts = {};
const persistenceCounts = {};
rules.forEach(rule => {
quadrantCounts[rule.quadrant] = (quadrantCounts[rule.quadrant] || 0) + 1;
persistenceCounts[rule.persistence] = (persistenceCounts[rule.persistence] || 0) + 1;
});
console.log('\n Rule Distribution:');
Object.entries(quadrantCounts).forEach(([quadrant, count]) => {
console.log(` ${quadrant}: ${count}`);
});
console.log('\n Persistence Levels:');
Object.entries(persistenceCounts).forEach(([level, count]) => {
console.log(` ${level}: ${count}`);
});
// Step 2: Initialize backend
console.log('\n[Step 2] Initializing memory backend...');
await backend.initialize();
// Step 3: Store rules in filesystem first (baseline)
console.log('\n[Step 3] Storing rules to filesystem backend...');
const storeStart = Date.now();
const rulesData = {
version: '1.0',
updated_at: new Date().toISOString(),
total_rules: rules.length,
rules: rules
};
await backend.create('governance/tractatus-rules-complete.json', rulesData);
results.timings.store = Date.now() - storeStart;
results.rulesStored = rules.length;
console.log(` ✓ Stored ${rules.length} rules`);
console.log(` Time: ${results.timings.store}ms`);
console.log(` Latency per rule: ${(results.timings.store / rules.length).toFixed(2)}ms`);
// Step 4: Retrieve and validate
console.log('\n[Step 4] Retrieving rules from backend...');
const retrieveStart = Date.now();
const retrieved = await backend.view('governance/tractatus-rules-complete.json');
results.timings.retrieve = Date.now() - retrieveStart;
results.rulesRetrieved = retrieved.rules.length;
console.log(` ✓ Retrieved ${retrieved.rules.length} rules`);
console.log(` Time: ${results.timings.retrieve}ms`);
// Step 5: Data integrity validation
console.log('\n[Step 5] Validating data integrity...');
if (retrieved.rules.length !== rules.length) {
throw new Error(`Rule count mismatch: stored ${rules.length}, retrieved ${retrieved.rules.length}`);
}
// Check each rule
for (let i = 0; i < rules.length; i++) {
const original = rules[i];
const retrieved_rule = retrieved.rules[i];
const checks = [
{ field: 'id', match: original.id === retrieved_rule.id },
{ field: 'text', match: original.text === retrieved_rule.text },
{ field: 'quadrant', match: original.quadrant === retrieved_rule.quadrant },
{ field: 'persistence', match: original.persistence === retrieved_rule.persistence }
];
const allMatch = checks.every(c => c.match);
if (allMatch) {
results.integrityChecks.passed++;
} else {
results.integrityChecks.failed++;
console.log(` ✗ Rule ${original.id} failed integrity check`);
checks.forEach(check => {
if (!check.match) {
console.log(` ${check.field}: mismatch`);
}
});
}
}
const integrityRate = (results.integrityChecks.passed / rules.length) * 100;
console.log(`\n Integrity: ${results.integrityChecks.passed}/${rules.length} rules (${integrityRate.toFixed(1)}%)`);
if (results.integrityChecks.failed > 0) {
throw new Error(`Data integrity validation failed: ${results.integrityChecks.failed} rules corrupted`);
}
// Step 6: Test critical rules individually
console.log('\n[Step 6] Testing critical enforcement rules...');
const criticalRules = rules.filter(r =>
['inst_016', 'inst_017', 'inst_018'].includes(r.id)
);
console.log(` Testing ${criticalRules.length} critical rules:`);
for (const rule of criticalRules) {
await backend.create(`governance/${rule.id}.json`, rule);
const retrieved_single = await backend.view(`governance/${rule.id}.json`);
const match = JSON.stringify(rule) === JSON.stringify(retrieved_single);
const status = match ? '✓' : '✗';
console.log(` ${status} ${rule.id}: ${match ? 'PASS' : 'FAIL'}`);
if (!match) {
throw new Error(`Critical rule ${rule.id} failed validation`);
}
}
// Step 7: Performance summary
console.log('\n[Step 7] Performance Assessment...');
const totalLatency = results.timings.store + results.timings.retrieve;
const avgPerRule = totalLatency / rules.length;
console.log(` Store: ${results.timings.store}ms (${(results.timings.store / rules.length).toFixed(2)}ms/rule)`);
console.log(` Retrieve: ${results.timings.retrieve}ms`);
console.log(` Total: ${totalLatency}ms`);
console.log(` Average per rule: ${avgPerRule.toFixed(2)}ms`);
const target = 1000; // 1 second per batch operation
const status = totalLatency < target ? 'PASS' : 'WARN';
console.log(` Target: <${target}ms - ${status}`);
results.success = true;
results.totalLatency = totalLatency;
} catch (error) {
console.error('\n✗ TEST FAILED:', error.message);
if (error.stack) {
console.error('\nStack trace:', error.stack);
}
results.errors.push(error.message);
results.success = false;
} finally {
// Cleanup
console.log('\n[Cleanup] Removing test data...');
await backend.cleanup();
}
// Results summary
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' TEST RESULTS');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (results.success) {
console.log('✅ SUCCESS: All 18 Tractatus rules validated');
console.log('\nKey Findings:');
console.log(` • Rules loaded: ${results.rulesLoaded}`);
console.log(` • Rules stored: ${results.rulesStored}`);
console.log(` • Rules retrieved: ${results.rulesRetrieved}`);
console.log(` • Data integrity: ${results.integrityChecks.passed}/${results.rulesLoaded} (${((results.integrityChecks.passed / results.rulesLoaded) * 100).toFixed(1)}%)`);
console.log(` • Performance: ${results.totalLatency}ms total`);
console.log(` • Average per rule: ${(results.totalLatency / results.rulesLoaded).toFixed(2)}ms`);
console.log('\nNext Steps:');
console.log(' 1. Test with real Claude API (memory tool operations)');
console.log(' 2. Measure API latency overhead');
console.log(' 3. Test context editing with 50+ turn conversation');
} else {
console.log('❌ FAILURE: Test did not pass');
console.log('\nErrors:');
results.errors.forEach(err => console.log(`${err}`));
}
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
return results;
}
// Run test
if (require.main === module) {
runFullRulesTest()
.then(results => {
process.exit(results.success ? 0 : 1);
})
.catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = { runFullRulesTest };

View file

@ -1,304 +0,0 @@
/**
* Phase 5 PoC - Week 3: BoundaryEnforcer + MemoryProxy Integration Test
*
* Goal: Validate BoundaryEnforcer can:
* 1. Initialize MemoryProxy and load enforcement rules (inst_016, inst_017, inst_018)
* 2. Enforce boundaries using loaded rules
* 3. Create audit trail in .memory/audit/
*
* Success Criteria:
* - MemoryProxy initializes successfully
* - All 3 critical rules loaded (inst_016, inst_017, inst_018)
* - Enforcement still works (95%+ accuracy)
* - Audit trail created with JSONL entries
*/
const path = require('path');
const fs = require('fs').promises;
const { MemoryProxyService, getMemoryProxy } = require('../../../src/services/MemoryProxy.service');
const BoundaryEnforcer = require('../../../src/services/BoundaryEnforcer.service');
// Configuration
const TEST_MEMORY_PATH = path.join(__dirname, '../../../.memory-poc-week3');
const INSTRUCTION_HISTORY_PATH = path.join(__dirname, '../../../.claude/instruction-history.json');
// Test enforcement scenarios
const TEST_SCENARIOS = [
{
name: 'Values Decision (BLOCKED)',
action: {
description: 'Decide whether to prioritize privacy over convenience',
domain: 'values',
type: 'policy_decision'
},
expectedBlocked: true,
expectedBoundary: 'VALUES'
},
{
name: 'Technical Implementation (ALLOWED)',
action: {
description: 'Implement caching for API responses',
domain: 'technical',
type: 'implementation'
},
expectedBlocked: false
},
{
name: 'Strategic Decision (BLOCKED)',
action: {
description: 'Define our long-term mission and vision',
classification: { quadrant: 'STRATEGIC' },
type: 'strategic_planning'
},
expectedBlocked: true,
expectedBoundary: 'WISDOM'
},
{
name: 'Innovation (BLOCKED)',
action: {
description: 'Create revolutionary new approach to AI governance',
domain: 'innovation',
type: 'innovation_proposal'
},
expectedBlocked: true,
expectedBoundary: 'INNOVATION'
},
{
name: 'Verification (ALLOWED)',
action: {
description: 'Verify current values alignment',
domain: 'verification',
pre_approved: true,
type: 'verification'
},
expectedBlocked: false
}
];
/**
* Load Tractatus rules and persist to memory
*/
async function setupMemoryWithRules(memoryProxy) {
console.log('[Setup] Loading Tractatus rules...');
const data = await fs.readFile(INSTRUCTION_HISTORY_PATH, 'utf8');
const parsed = JSON.parse(data);
const rules = parsed.instructions;
console.log(` ✓ Loaded ${rules.length} rules from instruction history`);
// Persist to memory
const result = await memoryProxy.persistGovernanceRules(rules);
console.log(` ✓ Persisted ${result.rulesStored} rules to memory (${result.duration}ms)`);
return result;
}
/**
* Main test execution
*/
async function runIntegrationTest() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' Phase 5 PoC Week 3: BoundaryEnforcer Integration Test');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const results = {
success: false,
memoryProxyInit: false,
rulesLoaded: 0,
enforcementTests: {
total: 0,
passed: 0,
failed: 0,
scenarios: []
},
auditTrailCreated: false,
errors: []
};
try {
// Step 1: Initialize MemoryProxy with test path
console.log('[Step 1] Initializing MemoryProxy...');
const memoryProxy = new MemoryProxyService({
memoryBasePath: TEST_MEMORY_PATH,
cacheEnabled: true,
cacheTTL: 300000
});
await memoryProxy.initialize();
results.memoryProxyInit = true;
console.log(' ✓ MemoryProxy initialized\n');
// Step 2: Load Tractatus rules into memory
console.log('[Step 2] Persisting Tractatus rules to memory...');
await setupMemoryWithRules(memoryProxy);
// Step 3: Initialize BoundaryEnforcer (uses singleton, but we'll create new instance)
console.log('\n[Step 3] Initializing BoundaryEnforcer...');
// Create new BoundaryEnforcer instance that uses our test MemoryProxy
const { BoundaryEnforcer: BoundaryEnforcerClass } = require('../../../src/services/BoundaryEnforcer.service');
const enforcer = new BoundaryEnforcerClass();
// Override memoryProxy with our test instance
enforcer.memoryProxy = memoryProxy;
const initResult = await enforcer.initialize();
if (initResult.success) {
results.rulesLoaded = initResult.rulesLoaded;
console.log(` ✓ BoundaryEnforcer initialized with ${initResult.rulesLoaded} enforcement rules`);
console.log(` Rules: ${initResult.enforcementRules.join(', ')}`);
} else {
throw new Error(`BoundaryEnforcer initialization failed: ${initResult.error}`);
}
// Step 4: Test enforcement scenarios
console.log('\n[Step 4] Testing enforcement scenarios...\n');
for (const scenario of TEST_SCENARIOS) {
results.enforcementTests.total++;
console.log(` Testing: ${scenario.name}`);
const enforcementResult = enforcer.enforce(scenario.action, {
sessionId: 'week3-integration-test'
});
const blocked = enforcementResult.humanRequired === true;
const passed = blocked === scenario.expectedBlocked;
if (passed) {
results.enforcementTests.passed++;
console.log(` ✓ PASS: ${blocked ? 'Blocked' : 'Allowed'} as expected`);
if (scenario.expectedBoundary && enforcementResult.boundary) {
const boundaryMatch = enforcementResult.boundary === scenario.expectedBoundary;
if (boundaryMatch) {
console.log(` Boundary: ${enforcementResult.boundary} (correct)`);
} else {
console.log(` Boundary: ${enforcementResult.boundary} (expected ${scenario.expectedBoundary})`);
}
}
} else {
results.enforcementTests.failed++;
console.log(` ✗ FAIL: ${blocked ? 'Blocked' : 'Allowed'} (expected ${scenario.expectedBlocked ? 'blocked' : 'allowed'})`);
}
results.enforcementTests.scenarios.push({
name: scenario.name,
passed,
blocked,
expectedBlocked: scenario.expectedBlocked,
boundary: enforcementResult.boundary
});
}
// Step 5: Verify audit trail
console.log('\n[Step 5] Verifying audit trail...');
const today = new Date().toISOString().split('T')[0];
const auditPath = path.join(TEST_MEMORY_PATH, `audit/decisions-${today}.jsonl`);
try {
const auditData = await fs.readFile(auditPath, 'utf8');
const auditLines = auditData.trim().split('\n');
results.auditTrailCreated = true;
console.log(` ✓ Audit trail created: ${auditLines.length} entries`);
// Show sample audit entry
if (auditLines.length > 0) {
const sampleEntry = JSON.parse(auditLines[0]);
console.log('\n Sample audit entry:');
console.log(` Session: ${sampleEntry.sessionId}`);
console.log(` Action: ${sampleEntry.action}`);
console.log(` Allowed: ${sampleEntry.allowed}`);
console.log(` Rules checked: ${sampleEntry.rulesChecked.join(', ')}`);
}
} catch (error) {
console.log(` ✗ Audit trail not found: ${error.message}`);
results.auditTrailCreated = false;
}
// Calculate accuracy
const accuracy = (results.enforcementTests.passed / results.enforcementTests.total) * 100;
console.log('\n[Step 6] Enforcement Accuracy Assessment...');
console.log(` Passed: ${results.enforcementTests.passed}/${results.enforcementTests.total} (${accuracy.toFixed(1)}%)`);
const targetAccuracy = 95;
if (accuracy >= targetAccuracy) {
console.log(` ✓ Target accuracy met (>=${targetAccuracy}%)`);
results.success = true;
} else {
console.log(` ✗ Below target accuracy of ${targetAccuracy}%`);
results.success = false;
}
} catch (error) {
console.error('\n✗ TEST FAILED:', error.message);
if (error.stack) {
console.error('\nStack trace:', error.stack);
}
results.errors.push(error.message);
results.success = false;
} finally {
// Cleanup
console.log('\n[Cleanup] Removing test data...');
try {
await fs.rm(TEST_MEMORY_PATH, { recursive: true, force: true });
console.log(' ✓ Cleanup complete');
} catch (error) {
console.log(' ⚠ Cleanup warning:', error.message);
}
}
// Results summary
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' TEST RESULTS');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (results.success) {
console.log('✅ SUCCESS: BoundaryEnforcer + MemoryProxy integration validated');
console.log('\nKey Findings:');
console.log(` • MemoryProxy initialized: ${results.memoryProxyInit ? 'Yes' : 'No'}`);
console.log(` • Enforcement rules loaded: ${results.rulesLoaded}/3`);
console.log(` • Enforcement tests: ${results.enforcementTests.passed}/${results.enforcementTests.total} passed`);
console.log(` • Accuracy: ${((results.enforcementTests.passed / results.enforcementTests.total) * 100).toFixed(1)}%`);
console.log(` • Audit trail created: ${results.auditTrailCreated ? 'Yes' : 'No'}`);
console.log('\nNext Steps:');
console.log(' 1. Integrate MemoryProxy with BlogCuration service');
console.log(' 2. Test context editing (50+ turn conversation)');
console.log(' 3. Create migration script (.claude/ → .memory/)');
} else {
console.log('❌ FAILURE: Integration test did not pass');
console.log('\nErrors:');
results.errors.forEach(err => console.log(`${err}`));
if (results.enforcementTests.failed > 0) {
console.log('\nFailed scenarios:');
results.enforcementTests.scenarios
.filter(s => !s.passed)
.forEach(s => console.log(`${s.name}`));
}
}
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
return results;
}
// Run test
if (require.main === module) {
runIntegrationTest()
.then(results => {
process.exit(results.success ? 0 : 1);
})
.catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = { runIntegrationTest };

View file

@ -1,402 +0,0 @@
/**
* Unit Tests for AdaptiveCommunicationOrchestrator
* Tests culturally-adaptive communication to prevent linguistic hierarchy
*/
const orchestrator = require('../../src/services/AdaptiveCommunicationOrchestrator.service');
describe('AdaptiveCommunicationOrchestrator', () => {
beforeEach(() => {
// Orchestrator is a singleton instance
});
describe('Anti-Patronizing Filter (inst_030)', () => {
test('should remove "simply" from messages', () => {
const message = 'You can simply implement this feature easily.';
const adapted = orchestrator.adaptCommunication(message);
expect(adapted).not.toContain('simply');
});
test('should remove "obviously" from messages', () => {
const message = 'Obviously, this approach is the best solution.';
const adapted = orchestrator.adaptCommunication(message);
expect(adapted).not.toContain('obviously');
});
test('should remove "as you may know" from messages', () => {
const message = 'As you may know, AI governance requires careful consideration.';
const adapted = orchestrator.adaptCommunication(message);
expect(adapted).not.toContain('as you may know');
});
test('should track patronizing terms removed', () => {
const statsBefore = orchestrator.getStats();
orchestrator.adaptCommunication('Simply put, obviously this is clear.');
const statsAfter = orchestrator.getStats();
expect(statsAfter.patronizing_terms_removed).toBeGreaterThan(statsBefore.patronizing_terms_removed);
});
test('should handle multiple patronizing terms', () => {
const message = 'Simply put, it is obviously clear that you can just do this, of course.';
const adapted = orchestrator.adaptCommunication(message);
expect(adapted).not.toContain('simply');
expect(adapted).not.toContain('obviously');
expect(adapted).not.toContain('of course');
});
});
describe('Pub Test (inst_029)', () => {
test('should flag overly formal language', () => {
const message = 'Notwithstanding the aforementioned circumstances, we hereby decree...';
const result = orchestrator.pubTest(message);
expect(result.passes).toBe(false);
expect(result.violations.length).toBeGreaterThan(0);
});
test('should pass casual conversational language', () => {
const message = 'Right, here\'s what we decided: We\'re going with option A.';
const result = orchestrator.pubTest(message);
expect(result.passes).toBe(true);
expect(result.violations.length).toBe(0);
});
test('should flag "pursuant to" as too legal', () => {
const message = 'Pursuant to our discussion, we will proceed accordingly.';
const result = orchestrator.pubTest(message);
expect(result.passes).toBe(false);
const hasLegalViolation = result.violations.some(v => v.reason.includes('legal'));
expect(hasLegalViolation).toBe(true);
});
test('should flag "notwithstanding" as unnecessarily complex', () => {
const message = 'Notwithstanding your concerns, we shall continue.';
const result = orchestrator.pubTest(message);
expect(result.passes).toBe(false);
expect(result.violations[0].reason).toContain('Unnecessarily complex');
});
});
describe('Communication Style Detection', () => {
test('should detect formal academic style', () => {
const message = 'Furthermore, notwithstanding the theoretical implications, we must therefore consider...';
const detected = orchestrator.detectStyle(message);
expect(detected).toBe('FORMAL_ACADEMIC');
});
test('should detect casual direct style', () => {
const message = 'Right, here\'s the deal. Fair enough?';
const detected = orchestrator.detectStyle(message);
expect(detected).toBe('CASUAL_DIRECT');
});
test('should detect Māori protocol indicators', () => {
const message = 'Kia ora! Ngā mihi for your contribution to this kōrero.';
const detected = orchestrator.detectStyle(message);
expect(detected).toBe('MAORI_PROTOCOL');
});
test('should default to plain language', () => {
const message = 'This is a simple message without strong style indicators.';
const detected = orchestrator.detectStyle(message);
expect(detected).toBe('PLAIN_LANGUAGE');
});
});
describe('Style Adaptation (inst_029)', () => {
test('should adapt to formal academic style', () => {
const message = 'We decided to go with this approach.';
const context = { audience: 'FORMAL_ACADEMIC' };
const adapted = orchestrator.adaptCommunication(message, context);
expect(adapted).toBeDefined();
// In full implementation, would check for formal register
});
test('should adapt to casual direct style', () => {
const message = 'I would like to inform you that we have made a decision.';
const context = { audience: 'CASUAL_DIRECT' };
const adapted = orchestrator.adaptCommunication(message, context);
// Should simplify formal phrases
expect(adapted).not.toContain('I would like to inform you that');
});
test('should track adaptations by style', () => {
const statsBefore = orchestrator.getStats();
const beforeCount = statsBefore.by_style.FORMAL_ACADEMIC;
orchestrator.adaptCommunication('Test message 1', { audience: 'FORMAL_ACADEMIC' });
orchestrator.adaptCommunication('Test message 2', { audience: 'FORMAL_ACADEMIC' });
const statsAfter = orchestrator.getStats();
const afterCount = statsAfter.by_style.FORMAL_ACADEMIC;
expect(afterCount).toBe(beforeCount + 2);
});
test('should handle plain language adaptation', () => {
const message = 'We must utilize this methodology to facilitate implementation.';
const context = { audience: 'PLAIN_LANGUAGE' };
const adapted = orchestrator.adaptCommunication(message, context);
// Should replace jargon with plain equivalents
expect(adapted).not.toContain('utilize');
expect(adapted).toContain('use');
});
});
describe('Language Detection (inst_032)', () => {
test('should detect Te Reo Māori', () => {
const message = 'Kia ora, let me share some whakaaro about this kaupapa.';
const adapted = orchestrator.adaptCommunication(message);
// Should detect and track Māori language
const stats = orchestrator.getStats();
expect(stats.languages_detected['te-reo-maori']).toBeGreaterThan(0);
});
test('should default to English', () => {
const message = 'This is a standard English message with no special indicators.';
const adapted = orchestrator.adaptCommunication(message);
// Should process as English without issues
expect(adapted).toBeDefined();
});
});
describe('Greeting Generation', () => {
test('should generate Māori protocol greeting', () => {
const greeting = orchestrator.generateGreeting('John', {
communication_style: 'MAORI_PROTOCOL'
});
expect(greeting).toContain('Kia ora');
expect(greeting).toContain('John');
});
test('should generate casual direct greeting', () => {
const greeting = orchestrator.generateGreeting('Jane', {
communication_style: 'CASUAL_DIRECT'
});
expect(greeting).toBe('Hi Jane');
});
test('should generate formal academic greeting', () => {
const greeting = orchestrator.generateGreeting('Dr. Smith', {
communication_style: 'FORMAL_ACADEMIC'
});
expect(greeting).toContain('Dear Dr. Smith');
});
test('should generate plain language greeting by default', () => {
const greeting = orchestrator.generateGreeting('Alex');
expect(greeting).toContain('Hello');
expect(greeting).toContain('Alex');
});
});
describe('Cultural Context Application (inst_031)', () => {
test('should apply cultural adaptations when context provided', () => {
const message = 'Thank you for your contribution.';
const context = {
audience: 'MAORI_PROTOCOL',
cultural_context: 'maori'
};
const adapted = orchestrator.adaptCommunication(message, context);
// Should apply Māori protocol adaptations
expect(adapted).toBeDefined();
});
test('should handle multiple cultural contexts', () => {
const messages = [
{ text: 'Test 1', context: { cultural_context: 'australian' } },
{ text: 'Test 2', context: { cultural_context: 'japanese' } },
{ text: 'Test 3', context: { cultural_context: 'maori' } }
];
messages.forEach(({ text, context }) => {
const adapted = orchestrator.adaptCommunication(text, context);
expect(adapted).toBeDefined();
});
});
});
describe('Statistics Tracking', () => {
test('should track total adaptations', () => {
const statsBefore = orchestrator.getStats();
orchestrator.adaptCommunication('Test message', { audience: 'PLAIN_LANGUAGE' });
const statsAfter = orchestrator.getStats();
expect(statsAfter.total_adaptations).toBeGreaterThan(statsBefore.total_adaptations);
});
test('should track adaptations by style', () => {
orchestrator.adaptCommunication('Test 1', { audience: 'FORMAL_ACADEMIC' });
orchestrator.adaptCommunication('Test 2', { audience: 'CASUAL_DIRECT' });
const stats = orchestrator.getStats();
expect(stats.by_style.FORMAL_ACADEMIC).toBeGreaterThan(0);
expect(stats.by_style.CASUAL_DIRECT).toBeGreaterThan(0);
});
test('should track languages detected', () => {
orchestrator.adaptCommunication('Kia ora whānau');
const stats = orchestrator.getStats();
expect(stats.languages_detected).toBeDefined();
expect(Object.keys(stats.languages_detected).length).toBeGreaterThan(0);
});
});
describe('Preventing Linguistic Hierarchy', () => {
test('should not favor formal academic over casual direct', () => {
const formalResult = orchestrator.adaptCommunication('Test', { audience: 'FORMAL_ACADEMIC' });
const casualResult = orchestrator.adaptCommunication('Test', { audience: 'CASUAL_DIRECT' });
// Both should be processed without error, neither privileged
expect(formalResult).toBeDefined();
expect(casualResult).toBeDefined();
});
test('should support non-Western communication norms', () => {
const contexts = [
{ audience: 'MAORI_PROTOCOL' },
{ audience: 'JAPANESE_FORMAL' }
];
contexts.forEach(context => {
const adapted = orchestrator.adaptCommunication('Test message', context);
expect(adapted).toBeDefined();
});
});
test('should remove condescension from all styles', () => {
const patronizingMessage = 'Simply put, obviously you can just do this.';
const contexts = [
{ audience: 'FORMAL_ACADEMIC' },
{ audience: 'CASUAL_DIRECT' },
{ audience: 'PLAIN_LANGUAGE' }
];
contexts.forEach(context => {
const adapted = orchestrator.adaptCommunication(patronizingMessage, context);
expect(adapted).not.toContain('simply');
expect(adapted).not.toContain('obviously');
});
});
});
describe('Singleton Pattern', () => {
test('should export singleton instance with required methods', () => {
expect(typeof orchestrator.adaptCommunication).toBe('function');
expect(typeof orchestrator.pubTest).toBe('function');
expect(typeof orchestrator.detectStyle).toBe('function');
expect(typeof orchestrator.generateGreeting).toBe('function');
expect(typeof orchestrator.getStats).toBe('function');
});
});
describe('Error Handling', () => {
test('should handle null message gracefully', () => {
expect(() => {
orchestrator.adaptCommunication(null);
}).not.toThrow();
});
test('should handle empty message', () => {
const result = orchestrator.adaptCommunication('');
expect(result).toBeDefined();
expect(result).toBe('');
});
test('should handle unknown communication style', () => {
const result = orchestrator.adaptCommunication('Test', { audience: 'UNKNOWN_STYLE' });
expect(result).toBeDefined();
expect(result).toBe('Test');
});
});
describe('Integration Points', () => {
test('should be usable by PluralisticDeliberationOrchestrator', () => {
// Simulates how PDO would use this service
const message = 'We need your input on this values conflict.';
const stakeholder = {
communication_style: 'formal_academic',
cultural_context: 'western_academic'
};
const adapted = orchestrator.adaptCommunication(message, {
audience: 'FORMAL_ACADEMIC',
cultural_context: stakeholder.cultural_context
});
expect(adapted).toBeDefined();
});
test('should handle multiple stakeholder adaptations in sequence', () => {
const message = 'Thank you for your contribution to this deliberation.';
const stakeholders = [
{ style: 'FORMAL_ACADEMIC', context: 'academic' },
{ style: 'CASUAL_DIRECT', context: 'australian' },
{ style: 'MAORI_PROTOCOL', context: 'maori' }
];
const adaptations = stakeholders.map(s =>
orchestrator.adaptCommunication(message, {
audience: s.style,
cultural_context: s.context
})
);
expect(adaptations.length).toBe(3);
adaptations.forEach(adapted => {
expect(adapted).toBeDefined();
});
});
});
});

View file

@ -1,457 +0,0 @@
/**
* Unit Tests - BlogCuration Service
* Tests blog content curation with Tractatus enforcement
*/
// Mock dependencies before requiring the service
jest.mock('../../src/services/ClaudeAPI.service', () => ({
sendMessage: jest.fn(),
extractJSON: jest.fn()
}));
jest.mock('../../src/services/BoundaryEnforcer.service', () => ({
enforce: jest.fn()
}));
const BlogCuration = require('../../src/services/BlogCuration.service');
const ClaudeAPI = require('../../src/services/ClaudeAPI.service');
const BoundaryEnforcer = require('../../src/services/BoundaryEnforcer.service');
describe('BlogCuration Service', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('Editorial Guidelines', () => {
test('should have editorial guidelines defined', () => {
const guidelines = BlogCuration.getEditorialGuidelines();
expect(guidelines).toBeDefined();
expect(guidelines.tone).toBeDefined();
expect(guidelines.voice).toBeDefined();
expect(guidelines.style).toBeDefined();
expect(Array.isArray(guidelines.principles)).toBe(true);
expect(Array.isArray(guidelines.forbiddenPatterns)).toBe(true);
});
test('should include Tractatus enforcement principles', () => {
const guidelines = BlogCuration.getEditorialGuidelines();
const principlesText = guidelines.principles.join(' ');
expect(principlesText).toContain('Transparency');
expect(principlesText).toContain('Honesty');
expect(principlesText).toContain('Evidence');
});
test('should define forbidden patterns', () => {
const guidelines = BlogCuration.getEditorialGuidelines();
expect(guidelines.forbiddenPatterns.length).toBeGreaterThan(0);
expect(guidelines.forbiddenPatterns.some(p => p.includes('Fabricated'))).toBe(true);
expect(guidelines.forbiddenPatterns.some(p => p.includes('guarantee'))).toBe(true);
});
});
describe('draftBlogPost()', () => {
beforeEach(() => {
// Mock boundary enforcer to allow by default
BoundaryEnforcer.enforce.mockReturnValue({
allowed: true,
section: 'TRA-OPS-0002',
reasoning: 'AI suggestion with human approval'
});
// Mock ClaudeAPI.sendMessage
ClaudeAPI.sendMessage.mockResolvedValue({
content: [{
type: 'text',
text: JSON.stringify({
title: 'Understanding AI Boundary Enforcement',
subtitle: 'How Tractatus prevents values automation',
content: '# Introduction\n\nBoundary enforcement is critical...',
excerpt: 'This article explores boundary enforcement in AI systems.',
tags: ['AI safety', 'Boundary enforcement', 'Tractatus'],
tractatus_angle: 'Demonstrates harmlessness principle through boundary checks',
sources: ['https://example.com/research'],
word_count: 1200
})
}],
model: 'claude-sonnet-4-5-20250929',
usage: { input_tokens: 200, output_tokens: 800 }
});
ClaudeAPI.extractJSON.mockImplementation((response) => {
return JSON.parse(response.content[0].text);
});
});
test('should draft blog post with valid params', async () => {
const params = {
topic: 'AI Boundary Enforcement',
audience: 'implementer',
length: 'medium',
focus: 'real-world examples'
};
const result = await BlogCuration.draftBlogPost(params);
expect(result).toHaveProperty('draft');
expect(result).toHaveProperty('validation');
expect(result).toHaveProperty('boundary_check');
expect(result).toHaveProperty('metadata');
expect(result.draft.title).toBeDefined();
expect(result.draft.content).toBeDefined();
expect(result.metadata.requires_human_approval).toBe(true);
});
test('should perform boundary check before drafting', async () => {
const params = {
topic: 'Test Topic',
audience: 'researcher',
length: 'short'
};
await BlogCuration.draftBlogPost(params);
expect(BoundaryEnforcer.enforce).toHaveBeenCalledWith({
description: expect.stringContaining('AI-drafted blog content'),
text: expect.stringContaining('mandatory human approval'),
classification: { quadrant: 'OPERATIONAL' },
type: 'content_generation'
});
});
test('should throw error if boundary check fails', async () => {
BoundaryEnforcer.enforce.mockReturnValue({
allowed: false,
section: 'TRA-STR-0001',
reasoning: 'Values territory - human decision required'
});
const params = {
topic: 'Test Topic',
audience: 'advocate',
length: 'medium'
};
await expect(BlogCuration.draftBlogPost(params)).rejects.toThrow('Boundary violation');
});
test('should validate generated content against Tractatus principles', async () => {
const params = {
topic: 'Test Topic',
audience: 'general',
length: 'long'
};
const result = await BlogCuration.draftBlogPost(params);
expect(result.validation).toBeDefined();
expect(result.validation).toHaveProperty('valid');
expect(result.validation).toHaveProperty('violations');
expect(result.validation).toHaveProperty('warnings');
expect(result.validation).toHaveProperty('recommendation');
});
test('should detect absolute guarantee violations (inst_017)', async () => {
ClaudeAPI.extractJSON.mockReturnValue({
title: 'Our Framework Guarantees 100% Safety',
subtitle: 'Never fails, always works',
content: 'This system guarantees complete safety...',
excerpt: 'Test',
tags: [],
sources: [],
word_count: 500
});
const params = {
topic: 'Test',
audience: 'implementer',
length: 'short'
};
const result = await BlogCuration.draftBlogPost(params);
expect(result.validation.violations.length).toBeGreaterThan(0);
expect(result.validation.violations.some(v => v.type === 'ABSOLUTE_GUARANTEE')).toBe(true);
expect(result.validation.recommendation).toBe('REJECT');
});
test('should warn about uncited statistics (inst_016)', async () => {
ClaudeAPI.extractJSON.mockReturnValue({
title: 'AI Safety Statistics',
subtitle: 'Data-driven analysis',
content: 'Studies show 85% of AI systems lack governance...',
excerpt: 'Statistical analysis',
tags: [],
sources: [], // No sources!
word_count: 900
});
const params = {
topic: 'Test',
audience: 'researcher',
length: 'medium'
};
const result = await BlogCuration.draftBlogPost(params);
expect(result.validation.warnings.some(w => w.type === 'UNCITED_STATISTICS')).toBe(true);
expect(result.validation.stats_found).toBeGreaterThan(0);
expect(result.validation.sources_provided).toBe(0);
});
test('should call ClaudeAPI with appropriate max_tokens for length', async () => {
const testCases = [
{ length: 'short', expectedMin: 2000, expectedMax: 2100 },
{ length: 'medium', expectedMin: 3000, expectedMax: 3100 },
{ length: 'long', expectedMin: 4000, expectedMax: 4100 }
];
for (const { length, expectedMin, expectedMax } of testCases) {
jest.clearAllMocks();
await BlogCuration.draftBlogPost({
topic: 'Test',
audience: 'general',
length
});
const callOptions = ClaudeAPI.sendMessage.mock.calls[0][1];
expect(callOptions.max_tokens).toBeGreaterThanOrEqual(expectedMin);
expect(callOptions.max_tokens).toBeLessThanOrEqual(expectedMax);
}
});
test('should include Tractatus constraints in system prompt', async () => {
await BlogCuration.draftBlogPost({
topic: 'Test',
audience: 'implementer',
length: 'medium'
});
const systemPrompt = ClaudeAPI.sendMessage.mock.calls[0][1].system;
expect(systemPrompt).toContain('inst_016');
expect(systemPrompt).toContain('inst_017');
expect(systemPrompt).toContain('inst_018');
expect(systemPrompt).toContain('fabricat');
expect(systemPrompt).toContain('guarantee');
});
});
describe('suggestTopics()', () => {
beforeEach(() => {
// Mock sendMessage to return response with topics
ClaudeAPI.sendMessage.mockResolvedValue({
content: [{
type: 'text',
text: JSON.stringify([
{
title: 'Understanding AI Governance',
rationale: 'Fills gap in governance docs',
target_word_count: 1200,
key_points: ['Governance', 'Safety', 'Framework'],
tractatus_angle: 'Core governance principles'
}
])
}],
model: 'claude-sonnet-4-5-20250929',
usage: { input_tokens: 150, output_tokens: 200 }
});
// Mock extractJSON to return the topics array
ClaudeAPI.extractJSON.mockImplementation((response) => {
return JSON.parse(response.content[0].text);
});
});
test('should suggest topics for audience', async () => {
const result = await BlogCuration.suggestTopics('researcher');
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
expect(result[0]).toHaveProperty('validation');
expect(ClaudeAPI.sendMessage).toHaveBeenCalled();
});
test('should suggest topics with theme', async () => {
const result = await BlogCuration.suggestTopics('advocate', 'policy implications');
expect(ClaudeAPI.sendMessage).toHaveBeenCalled();
const systemPrompt = ClaudeAPI.sendMessage.mock.calls[0][1].system;
expect(systemPrompt).toContain('Tractatus');
expect(result.length).toBeGreaterThan(0);
});
test('should validate topic titles for forbidden patterns', async () => {
ClaudeAPI.extractJSON.mockReturnValue([
{ title: 'Guaranteed 100% AI Safety', rationale: 'Test', target_word_count: 1000, key_points: [], tractatus_angle: 'Test' }
]);
const result = await BlogCuration.suggestTopics('general');
expect(result[0].validation.valid).toBe(false);
expect(result[0].validation.issues.length).toBeGreaterThan(0);
});
});
describe('analyzeContentCompliance()', () => {
beforeEach(() => {
ClaudeAPI.sendMessage.mockResolvedValue({
content: [{
type: 'text',
text: JSON.stringify({
compliant: true,
violations: [],
warnings: [],
strengths: ['Evidence-based', 'Acknowledges limitations'],
overall_score: 92,
recommendation: 'PUBLISH'
})
}]
});
ClaudeAPI.extractJSON.mockImplementation((response) => {
return JSON.parse(response.content[0].text);
});
});
test('should analyze content for Tractatus compliance', async () => {
const content = {
title: 'Understanding AI Safety',
body: 'This article explores AI safety frameworks...'
};
const result = await BlogCuration.analyzeContentCompliance(content);
expect(result).toHaveProperty('compliant');
expect(result).toHaveProperty('violations');
expect(result).toHaveProperty('overall_score');
expect(result).toHaveProperty('recommendation');
});
test('should call ClaudeAPI with compliance analysis prompt', async () => {
await BlogCuration.analyzeContentCompliance({
title: 'Test Title',
body: 'Test content'
});
const systemPrompt = ClaudeAPI.sendMessage.mock.calls[0][1].system;
const userPrompt = ClaudeAPI.sendMessage.mock.calls[0][0][0].content;
expect(systemPrompt).toContain('Tractatus');
expect(systemPrompt).toContain('compliance');
expect(userPrompt).toContain('Test Title');
expect(userPrompt).toContain('Test content');
});
test('should detect violations in non-compliant content', async () => {
ClaudeAPI.extractJSON.mockReturnValue({
compliant: false,
violations: [
{
type: 'FABRICATED_STAT',
severity: 'HIGH',
excerpt: '99% of users agree',
reasoning: 'Unverified statistic',
suggested_fix: 'Cite source or remove claim'
}
],
warnings: [],
strengths: [],
overall_score: 35,
recommendation: 'REJECT'
});
const result = await BlogCuration.analyzeContentCompliance({
title: 'Amazing Results',
body: '99% of users agree this is the best framework ever...'
});
expect(result.compliant).toBe(false);
expect(result.violations.length).toBeGreaterThan(0);
expect(result.recommendation).toBe('REJECT');
});
});
describe('Utility Methods', () => {
describe('generateSlug()', () => {
test('should generate URL-safe slug from title', () => {
const title = 'Understanding AI Safety: A Framework Approach!';
const slug = BlogCuration.generateSlug(title);
expect(slug).toBe('understanding-ai-safety-a-framework-approach');
expect(slug).toMatch(/^[a-z0-9-]+$/);
});
test('should handle special characters', () => {
const title = 'AI @ Work: 100% Automated?!';
const slug = BlogCuration.generateSlug(title);
expect(slug).toBe('ai-work-100-automated');
expect(slug).not.toContain('@');
expect(slug).not.toContain('!');
});
test('should limit slug length to 100 characters', () => {
const longTitle = 'A'.repeat(200);
const slug = BlogCuration.generateSlug(longTitle);
expect(slug.length).toBeLessThanOrEqual(100);
});
});
describe('extractExcerpt()', () => {
test('should extract excerpt from content', () => {
const content = 'This is a blog post about AI safety. It discusses various frameworks and approaches to ensuring safe AI deployment.';
const excerpt = BlogCuration.extractExcerpt(content);
expect(excerpt.length).toBeLessThanOrEqual(200);
expect(excerpt).toContain('AI safety');
});
test('should strip HTML tags', () => {
const content = '<p>This is <strong>bold</strong> and <em>italic</em> text.</p>';
const excerpt = BlogCuration.extractExcerpt(content);
expect(excerpt).not.toContain('<p>');
expect(excerpt).not.toContain('<strong>');
expect(excerpt).toContain('bold');
});
test('should end at sentence boundary when possible', () => {
const content = 'First sentence. Second sentence. Third sentence that is much longer and goes on and on and on about various topics.';
const excerpt = BlogCuration.extractExcerpt(content, 50);
expect(excerpt.endsWith('.')).toBe(true);
expect(excerpt).not.toContain('Third sentence');
});
test('should add ellipsis when truncating', () => {
const content = 'A'.repeat(300);
const excerpt = BlogCuration.extractExcerpt(content, 100);
expect(excerpt.endsWith('...')).toBe(true);
expect(excerpt.length).toBeLessThanOrEqual(103); // 100 + '...'
});
});
});
describe('Service Integration', () => {
test('should maintain singleton pattern', () => {
const BlogCuration2 = require('../../src/services/BlogCuration.service');
expect(BlogCuration).toBe(BlogCuration2);
});
test('should have all expected public methods', () => {
expect(typeof BlogCuration.draftBlogPost).toBe('function');
expect(typeof BlogCuration.suggestTopics).toBe('function');
expect(typeof BlogCuration.analyzeContentCompliance).toBe('function');
expect(typeof BlogCuration.generateSlug).toBe('function');
expect(typeof BlogCuration.extractExcerpt).toBe('function');
expect(typeof BlogCuration.getEditorialGuidelines).toBe('function');
});
});
});

View file

@ -1,583 +0,0 @@
/**
* Unit Tests - ClaudeAPI Service
* Tests AI service methods with mocked API responses
*/
const ClaudeAPI = require('../../src/services/ClaudeAPI.service');
describe('ClaudeAPI Service', () => {
let originalApiKey;
let originalMakeRequest;
beforeAll(() => {
// Store original API key
originalApiKey = ClaudeAPI.apiKey;
// Ensure API key is set for tests
ClaudeAPI.apiKey = 'test_api_key';
// Store original _makeRequest for restoration
originalMakeRequest = ClaudeAPI._makeRequest;
});
afterAll(() => {
// Restore original API key and method
ClaudeAPI.apiKey = originalApiKey;
ClaudeAPI._makeRequest = originalMakeRequest;
});
beforeEach(() => {
// Mock _makeRequest for each test
ClaudeAPI._makeRequest = jest.fn();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Constructor and Configuration', () => {
test('should initialize with default model and max tokens', () => {
expect(ClaudeAPI.model).toBeDefined();
expect(ClaudeAPI.maxTokens).toBeDefined();
expect(typeof ClaudeAPI.maxTokens).toBe('number');
});
test('should use environment variables for configuration', () => {
expect(ClaudeAPI.apiVersion).toBe('2023-06-01');
expect(ClaudeAPI.hostname).toBe('api.anthropic.com');
});
});
describe('sendMessage()', () => {
test('should throw error if API key not configured', async () => {
const tempKey = ClaudeAPI.apiKey;
ClaudeAPI.apiKey = null;
await expect(
ClaudeAPI.sendMessage([{ role: 'user', content: 'Test' }])
).rejects.toThrow('Claude API key not configured');
ClaudeAPI.apiKey = tempKey;
});
test('should send message with default options', async () => {
const mockResponse = {
content: [{ type: 'text', text: 'Response text' }],
usage: { input_tokens: 10, output_tokens: 20 }
};
ClaudeAPI._makeRequest.mockResolvedValue(mockResponse);
const messages = [{ role: 'user', content: 'Test message' }];
const response = await ClaudeAPI.sendMessage(messages);
expect(ClaudeAPI._makeRequest).toHaveBeenCalledWith({
model: ClaudeAPI.model,
max_tokens: ClaudeAPI.maxTokens,
messages: messages
});
expect(response).toEqual(mockResponse);
});
test('should send message with custom options', async () => {
const mockResponse = {
content: [{ type: 'text', text: 'Response' }],
usage: { input_tokens: 10, output_tokens: 20 }
};
ClaudeAPI._makeRequest.mockResolvedValue(mockResponse);
const messages = [{ role: 'user', content: 'Test' }];
const options = {
model: 'claude-3-opus-20240229',
max_tokens: 2048,
temperature: 0.7,
system: 'You are a helpful assistant.'
};
await ClaudeAPI.sendMessage(messages, options);
expect(ClaudeAPI._makeRequest).toHaveBeenCalledWith({
model: options.model,
max_tokens: options.max_tokens,
messages: messages,
system: options.system,
temperature: options.temperature
});
});
test('should log usage information', async () => {
const mockResponse = {
content: [{ type: 'text', text: 'Response' }],
usage: { input_tokens: 100, output_tokens: 50 }
};
ClaudeAPI._makeRequest.mockResolvedValue(mockResponse);
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
await ClaudeAPI.sendMessage([{ role: 'user', content: 'Test' }]);
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('[ClaudeAPI] Usage: 100 in, 50 out')
);
consoleSpy.mockRestore();
});
test('should handle API errors', async () => {
ClaudeAPI._makeRequest.mockRejectedValue(new Error('API Error'));
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
await expect(
ClaudeAPI.sendMessage([{ role: 'user', content: 'Test' }])
).rejects.toThrow('API Error');
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('[ClaudeAPI] Error:'),
expect.any(String)
);
consoleSpy.mockRestore();
});
});
describe('extractTextContent()', () => {
test('should extract text from valid response', () => {
const response = {
content: [
{ type: 'text', text: 'This is the response text' }
]
};
const text = ClaudeAPI.extractTextContent(response);
expect(text).toBe('This is the response text');
});
test('should handle multiple content blocks', () => {
const response = {
content: [
{ type: 'tool_use', name: 'calculator' },
{ type: 'text', text: 'Answer is 42' }
]
};
const text = ClaudeAPI.extractTextContent(response);
expect(text).toBe('Answer is 42');
});
test('should throw error for invalid response format', () => {
expect(() => ClaudeAPI.extractTextContent(null)).toThrow('Invalid Claude API response format');
expect(() => ClaudeAPI.extractTextContent({})).toThrow('Invalid Claude API response format');
expect(() => ClaudeAPI.extractTextContent({ content: 'not an array' })).toThrow('Invalid Claude API response format');
});
test('should throw error when no text block found', () => {
const response = {
content: [
{ type: 'tool_use', name: 'calculator' }
]
};
expect(() => ClaudeAPI.extractTextContent(response)).toThrow('No text content in Claude API response');
});
});
describe('extractJSON()', () => {
test('should parse plain JSON', () => {
const response = {
content: [{ type: 'text', text: '{"key": "value", "number": 42}' }]
};
const json = ClaudeAPI.extractJSON(response);
expect(json).toEqual({ key: 'value', number: 42 });
});
test('should handle JSON in markdown code block', () => {
const response = {
content: [{ type: 'text', text: '```json\n{"key": "value"}\n```' }]
};
const json = ClaudeAPI.extractJSON(response);
expect(json).toEqual({ key: 'value' });
});
test('should handle JSON in generic code block', () => {
const response = {
content: [{ type: 'text', text: '```\n{"key": "value"}\n```' }]
};
const json = ClaudeAPI.extractJSON(response);
expect(json).toEqual({ key: 'value' });
});
test('should throw error for invalid JSON', () => {
const response = {
content: [{ type: 'text', text: 'This is not JSON' }]
};
expect(() => ClaudeAPI.extractJSON(response)).toThrow('Failed to parse JSON from Claude response');
});
});
describe('classifyInstruction()', () => {
test('should classify instruction into quadrant', async () => {
const mockResponse = {
content: [{
type: 'text',
text: JSON.stringify({
quadrant: 'STRATEGIC',
persistence: 'HIGH',
temporal_scope: 'PROJECT',
verification_required: 'MANDATORY',
explicitness: 0.9,
reasoning: 'Sets long-term project direction'
})
}],
usage: { input_tokens: 50, output_tokens: 100 }
};
ClaudeAPI._makeRequest.mockResolvedValue(mockResponse);
const result = await ClaudeAPI.classifyInstruction('Always use TypeScript for new projects');
expect(result.quadrant).toBe('STRATEGIC');
expect(result.persistence).toBe('HIGH');
expect(result.temporal_scope).toBe('PROJECT');
expect(ClaudeAPI._makeRequest).toHaveBeenCalled();
});
});
describe('generateBlogTopics()', () => {
test('should generate blog topics for audience without theme', async () => {
const mockTopics = [
{
title: 'Understanding AI Safety Through Sovereignty',
subtitle: 'How Tractatus preserves human agency',
word_count: 1200,
key_points: ['sovereignty', 'agency', 'values'],
tractatus_angle: 'Core principle'
}
];
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockTopics) }],
usage: { input_tokens: 100, output_tokens: 200 }
});
const result = await ClaudeAPI.generateBlogTopics('researcher');
expect(Array.isArray(result)).toBe(true);
expect(result[0]).toHaveProperty('title');
expect(ClaudeAPI._makeRequest).toHaveBeenCalled();
});
test('should generate blog topics with theme', async () => {
const mockTopics = [
{ title: 'Topic 1', subtitle: 'Subtitle', word_count: 1000, key_points: [], tractatus_angle: 'Angle' }
];
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockTopics) }],
usage: { input_tokens: 100, output_tokens: 200 }
});
const result = await ClaudeAPI.generateBlogTopics('implementer', 'governance frameworks');
expect(result).toEqual(mockTopics);
const callArgs = ClaudeAPI._makeRequest.mock.calls[0][0];
expect(callArgs.messages[0].content).toContain('governance frameworks');
});
});
describe('classifyMediaInquiry()', () => {
test('should classify media inquiry by priority', async () => {
const mockClassification = {
priority: 'HIGH',
reasoning: 'Major outlet with tight deadline',
recommended_response_time: '24 hours',
suggested_spokesperson: 'framework creator'
};
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockClassification) }],
usage: { input_tokens: 80, output_tokens: 100 }
});
const inquiry = {
outlet: 'TechCrunch',
request: 'Interview about AI safety frameworks',
deadline: '2025-10-15'
};
const result = await ClaudeAPI.classifyMediaInquiry(inquiry);
expect(result.priority).toBe('HIGH');
expect(result.recommended_response_time).toBeDefined();
expect(ClaudeAPI._makeRequest).toHaveBeenCalled();
});
test('should handle inquiry without deadline', async () => {
const mockClassification = {
priority: 'MEDIUM',
reasoning: 'No urgent deadline',
recommended_response_time: '3-5 days',
suggested_spokesperson: 'technical expert'
};
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockClassification) }],
usage: { input_tokens: 60, output_tokens: 80 }
});
const inquiry = {
outlet: 'Medium Blog',
request: 'Feature article about AI governance'
};
const result = await ClaudeAPI.classifyMediaInquiry(inquiry);
expect(result.priority).toBe('MEDIUM');
const callArgs = ClaudeAPI._makeRequest.mock.calls[0][0];
expect(callArgs.messages[0].content).toContain('Not specified');
});
});
describe('draftMediaResponse()', () => {
test('should draft response to media inquiry', async () => {
const mockDraft = 'Thank you for your interest in the Tractatus AI Safety Framework...';
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: mockDraft }],
usage: { input_tokens: 100, output_tokens: 150 }
});
const inquiry = {
outlet: 'Wired Magazine',
request: 'Expert quote on AI safety'
};
const result = await ClaudeAPI.draftMediaResponse(inquiry, 'HIGH');
expect(typeof result).toBe('string');
expect(result).toContain('Tractatus');
expect(ClaudeAPI._makeRequest).toHaveBeenCalled();
});
});
describe('analyzeCaseRelevance()', () => {
test('should analyze case study relevance', async () => {
const mockAnalysis = {
relevance_score: 85,
strengths: ['Clear evidence', 'Framework alignment'],
weaknesses: ['Needs more detail'],
recommended_action: 'PUBLISH',
ethical_concerns: null,
suggested_improvements: ['Add metrics']
};
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockAnalysis) }],
usage: { input_tokens: 120, output_tokens: 180 }
});
const caseStudy = {
title: 'AI System Prevented from Making Values Decision',
description: 'Case study of boundary enforcement',
evidence: 'System logs and decision audit trail'
};
const result = await ClaudeAPI.analyzeCaseRelevance(caseStudy);
expect(result.relevance_score).toBe(85);
expect(result.recommended_action).toBe('PUBLISH');
expect(Array.isArray(result.strengths)).toBe(true);
});
test('should handle case study without evidence', async () => {
const mockAnalysis = {
relevance_score: 45,
strengths: ['Interesting topic'],
weaknesses: ['No evidence provided'],
recommended_action: 'EDIT',
ethical_concerns: null,
suggested_improvements: ['Add concrete evidence']
};
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockAnalysis) }],
usage: { input_tokens: 100, output_tokens: 140 }
});
const caseStudy = {
title: 'Interesting AI Safety Case',
description: 'A case that might be relevant'
};
const result = await ClaudeAPI.analyzeCaseRelevance(caseStudy);
expect(result.relevance_score).toBeLessThan(60);
const callArgs = ClaudeAPI._makeRequest.mock.calls[0][0];
expect(callArgs.messages[0].content).toContain('Not provided');
});
});
describe('curateResource()', () => {
test('should curate external resource', async () => {
const mockCuration = {
recommended: true,
category: 'PAPERS',
alignment_score: 92,
target_audience: ['researcher', 'implementer'],
tags: ['AI safety', 'governance', 'frameworks'],
reasoning: 'Highly aligned with Tractatus principles',
concerns: null
};
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockCuration) }],
usage: { input_tokens: 90, output_tokens: 120 }
});
const resource = {
url: 'https://example.com/paper',
title: 'AI Safety Research Paper',
description: 'Comprehensive framework for AI governance'
};
const result = await ClaudeAPI.curateResource(resource);
expect(result.recommended).toBe(true);
expect(result.category).toBe('PAPERS');
expect(result.alignment_score).toBeGreaterThan(80);
expect(Array.isArray(result.target_audience)).toBe(true);
});
test('should identify concerns in resources', async () => {
const mockCuration = {
recommended: false,
category: 'ARTICLES',
alignment_score: 35,
target_audience: [],
tags: ['AI'],
reasoning: 'Conflicting values approach',
concerns: ['Promotes full automation of values decisions', 'No human oversight']
};
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: JSON.stringify(mockCuration) }],
usage: { input_tokens: 80, output_tokens: 110 }
});
const resource = {
url: 'https://example.com/article',
title: 'Fully Automated AI Ethics',
description: 'Let AI make all ethical decisions'
};
const result = await ClaudeAPI.curateResource(resource);
expect(result.recommended).toBe(false);
expect(result.concerns).toBeDefined();
expect(Array.isArray(result.concerns)).toBe(true);
expect(result.concerns.length).toBeGreaterThan(0);
});
});
describe('Error Handling', () => {
test('should handle network errors', async () => {
ClaudeAPI._makeRequest.mockRejectedValue(new Error('Network timeout'));
await expect(
ClaudeAPI.sendMessage([{ role: 'user', content: 'Test' }])
).rejects.toThrow('Network timeout');
});
test('should handle malformed responses', async () => {
ClaudeAPI._makeRequest.mockResolvedValue({
content: [{ type: 'text', text: 'Not valid JSON' }],
usage: { input_tokens: 10, output_tokens: 5 }
});
await expect(
ClaudeAPI.classifyInstruction('Test instruction')
).rejects.toThrow('Failed to parse JSON from Claude response');
});
test('should handle empty responses gracefully', async () => {
ClaudeAPI._makeRequest.mockResolvedValue({
content: [],
usage: { input_tokens: 10, output_tokens: 0 }
});
const response = await ClaudeAPI.sendMessage([{ role: 'user', content: 'Test' }]);
// sendMessage doesn't validate content, it just returns the response
expect(response.content).toEqual([]);
// But extractTextContent should throw an error
expect(() => ClaudeAPI.extractTextContent(response)).toThrow('No text content in Claude API response');
});
});
describe('_makeRequest() [Private Method Testing]', () => {
beforeEach(() => {
// Restore original _makeRequest for these tests
ClaudeAPI._makeRequest = originalMakeRequest;
});
afterEach(() => {
// Re-mock for other tests
ClaudeAPI._makeRequest = jest.fn();
});
test('should construct proper HTTPS request', () => {
// This test verifies the method exists and has proper structure
expect(typeof ClaudeAPI._makeRequest).toBe('function');
const payload = {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Test' }]
};
// Verify the method accepts a payload parameter
expect(() => ClaudeAPI._makeRequest(payload)).not.toThrow(TypeError);
});
test('should include required headers in request', () => {
// Verify method signature and structure
const methodString = ClaudeAPI._makeRequest.toString();
expect(methodString).toContain('x-api-key');
expect(methodString).toContain('anthropic-version');
expect(methodString).toContain('Content-Type');
});
test('should use correct API endpoint', () => {
const methodString = ClaudeAPI._makeRequest.toString();
expect(methodString).toContain('/v1/messages');
expect(methodString).toContain('443'); // HTTPS port
});
});
describe('Service Integration', () => {
test('should maintain singleton pattern', () => {
const ClaudeAPI2 = require('../../src/services/ClaudeAPI.service');
expect(ClaudeAPI).toBe(ClaudeAPI2);
});
test('should have all expected public methods', () => {
expect(typeof ClaudeAPI.sendMessage).toBe('function');
expect(typeof ClaudeAPI.extractTextContent).toBe('function');
expect(typeof ClaudeAPI.extractJSON).toBe('function');
expect(typeof ClaudeAPI.classifyInstruction).toBe('function');
expect(typeof ClaudeAPI.generateBlogTopics).toBe('function');
expect(typeof ClaudeAPI.classifyMediaInquiry).toBe('function');
expect(typeof ClaudeAPI.draftMediaResponse).toBe('function');
expect(typeof ClaudeAPI.analyzeCaseRelevance).toBe('function');
expect(typeof ClaudeAPI.curateResource).toBe('function');
});
test('should expose private _makeRequest method for testing', () => {
// Verify _makeRequest exists (even though private)
expect(typeof ClaudeAPI._makeRequest).toBe('function');
});
});
});

View file

@ -1,461 +0,0 @@
/**
* Unit Tests - ProhibitedTermsScanner
* Tests for proactive content scanning (Framework Phase 1)
*/
const fs = require('fs').promises;
const path = require('path');
const ProhibitedTermsScanner = require('../../scripts/framework-components/ProhibitedTermsScanner');
describe('ProhibitedTermsScanner', () => {
let scanner;
const testFilesDir = path.join(__dirname, '../tmp-scanner-test');
beforeEach(() => {
scanner = new ProhibitedTermsScanner({
silent: true,
basePath: testFilesDir // Only scan test directory
});
});
afterEach(async () => {
// Clean up test files
try {
await fs.rm(testFilesDir, { recursive: true, force: true });
} catch (err) {
// Ignore cleanup errors
}
});
describe('Pattern Detection - inst_017 (Absolute Assurance)', () => {
test('should detect "guarantee" variations', async () => {
const testContent = `
This guarantees safety.
We guarantee results.
This is guaranteed to work.
We are guaranteeing success.
`;
// Create test file
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const inst017Violations = violations.filter(v => v.rule === 'inst_017');
expect(inst017Violations.length).toBeGreaterThanOrEqual(4);
expect(inst017Violations.some(v => v.match.toLowerCase().includes('guarantee'))).toBe(true);
});
test('should detect "ensures 100%"', async () => {
const testContent = 'This ensures 100% accuracy.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.html');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const inst017Violations = violations.filter(v => v.rule === 'inst_017');
expect(inst017Violations.length).toBeGreaterThan(0);
expect(inst017Violations.some(v => v.match.toLowerCase().includes('ensures'))).toBe(true);
});
test('should detect "eliminates all"', async () => {
const testContent = 'This eliminates all bugs.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.js');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
expect(violations.length).toBeGreaterThan(0);
expect(violations.some(v => v.match.toLowerCase().includes('eliminates'))).toBe(true);
});
test('should detect "never fails"', async () => {
const testContent = 'This system never fails.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
expect(violations.length).toBeGreaterThan(0);
expect(violations.some(v => v.match.toLowerCase().includes('never'))).toBe(true);
});
});
describe('Pattern Detection - inst_018 (Unverified Claims)', () => {
test('should detect "production-ready" without context', async () => {
const testContent = 'This is production-ready software.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const inst018Violations = violations.filter(v => v.rule === 'inst_018');
expect(inst018Violations.length).toBeGreaterThan(0);
});
test('should detect "battle-tested"', async () => {
const testContent = 'Our battle-tested framework.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.html');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
expect(violations.some(v => v.match.toLowerCase().includes('battle-tested'))).toBe(true);
});
test('should allow "production-ready development tool"', async () => {
const testContent = 'Tractatus is a production-ready development tool.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const inst018Violations = violations.filter(v =>
v.rule === 'inst_018' && v.match.toLowerCase().includes('production-ready')
);
expect(inst018Violations.length).toBe(0);
});
test('should allow "production-ready proof-of-concept"', async () => {
const testContent = 'This is a production-ready proof-of-concept.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const inst018Violations = violations.filter(v =>
v.rule === 'inst_018' && v.match.toLowerCase().includes('production-ready')
);
expect(inst018Violations.length).toBe(0);
});
});
describe('Context Awareness', () => {
test('should allow prohibited terms in comments about rules', async () => {
const testContent = `
// inst_017: Never use "guarantee"
// inst_017 prohibits guaranteed language
`;
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.js');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
// Should not flag violations in comments about the rules
expect(violations.length).toBe(0);
});
test('should exclude test files from scanning', async () => {
// Test files should be excluded by pattern
const scanner2 = new ProhibitedTermsScanner({ silent: true });
expect(scanner2.excludePatterns).toContain('**/tests/**/*.test.js');
expect(scanner2.excludePatterns).toContain('**/tests/**/*.spec.js');
});
test('should exclude GOVERNANCE-RULE-LIBRARY.md', async () => {
expect(scanner.excludePatterns).toContain('**/GOVERNANCE-RULE-LIBRARY.md');
});
test('should exclude case studies', async () => {
expect(scanner.excludePatterns).toContain('**/docs/case-studies/**');
});
});
describe('Suggestions', () => {
test('should suggest "enforcement" for "guarantee"', () => {
const suggestions = scanner.patterns.find(p => p.id === 'inst_017').suggestions;
expect(suggestions['guarantee']).toBe('enforcement');
expect(suggestions['guarantees']).toBe('enforces');
expect(suggestions['guaranteed']).toBe('enforced');
});
test('should suggest replacements for "ensures 100%"', () => {
const suggestions = scanner.patterns.find(p => p.id === 'inst_017').suggestions;
expect(suggestions['ensures 100%']).toBe('helps ensure');
});
test('should suggest replacements for inst_018 terms', () => {
const suggestions = scanner.patterns.find(p => p.id === 'inst_018').suggestions;
expect(suggestions['production-ready']).toBe('proof-of-concept');
expect(suggestions['battle-tested']).toBe('in development');
});
test('should get suggestion for matched term', () => {
const patternSet = scanner.patterns.find(p => p.id === 'inst_017');
const suggestion = scanner.getSuggestion('guarantee', patternSet.suggestions);
expect(suggestion).toBe('enforcement');
});
test('should handle case-insensitive matches', () => {
const patternSet = scanner.patterns.find(p => p.id === 'inst_017');
const suggestion = scanner.getSuggestion('GUARANTEE', patternSet.suggestions);
expect(suggestion).toBe('enforcement');
});
});
describe('Auto-fix Functionality', () => {
test('should fix simple violations', async () => {
const testContent = 'This guarantees safety.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const results = await scanner.autoFix(violations);
expect(results.fixed).toBeGreaterThan(0);
const fixedContent = await fs.readFile(testFile, 'utf8');
expect(fixedContent).toContain('enforces'); // guarantees → enforces
expect(fixedContent).not.toContain('guarantees');
});
test('should preserve file structure during fix', async () => {
const testContent = `Line 1: Normal content
Line 2: This guarantees results
Line 3: More content
Line 4: This guaranteed safety
Line 5: Final line`;
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
await scanner.autoFix(violations);
const fixedContent = await fs.readFile(testFile, 'utf8');
const lines = fixedContent.split('\n');
expect(lines.length).toBe(5);
expect(lines[0]).toBe('Line 1: Normal content');
expect(lines[4]).toBe('Line 5: Final line');
});
test('should handle multiple violations in same file', async () => {
const testContent = `
We guarantee success.
This is guaranteed to work.
Our guarantees are strong.
`;
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const results = await scanner.autoFix(violations);
expect(results.fixed).toBeGreaterThan(0);
const fixedContent = await fs.readFile(testFile, 'utf8');
expect(fixedContent).not.toContain('guarantee');
});
test('should skip violations without clear suggestions', async () => {
// inst_016 violations (fabricated statistics) require manual review
const testContent = '95% faster performance.';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
const inst016Violations = violations.filter(v => v.rule === 'inst_016');
const results = await scanner.autoFix(inst016Violations);
// inst_016 requires source citation, can't auto-fix
expect(results.skipped).toBeGreaterThan(0);
});
});
describe('Formatting', () => {
test('should format empty violations list', () => {
const output = scanner.formatViolations([]);
expect(output).toContain('No prohibited terms found');
});
test('should format violations summary', () => {
const violations = [
{ rule: 'inst_017', file: 'test.md', line: 1, match: 'guarantee', severity: 'HIGH' },
{ rule: 'inst_017', file: 'test.md', line: 2, match: 'guaranteed', severity: 'HIGH' },
{ rule: 'inst_018', file: 'test.html', line: 5, match: 'battle-tested', severity: 'MEDIUM' }
];
const output = scanner.formatViolations(violations, false);
expect(output).toContain('Found 3 violation');
expect(output).toContain('inst_017: 2');
expect(output).toContain('inst_018: 1');
});
test('should format detailed violations', () => {
const violations = [
{
rule: 'inst_017',
file: 'test.md',
line: 1,
match: 'guarantee',
severity: 'HIGH',
context: 'This guarantees safety',
suggestion: 'enforcement'
}
];
const output = scanner.formatViolations(violations, true);
expect(output).toContain('test.md:1');
expect(output).toContain('Found: "guarantee"');
expect(output).toContain('Suggestion: enforcement');
});
});
describe('Utility Functions', () => {
test('should escape regex special characters', () => {
const escaped = scanner.escapeRegex('test.file[0]');
expect(escaped).toBe('test\\.file\\[0\\]');
});
test('should check allowed context for inst_017 references', () => {
const line = 'inst_017 prohibits guaranteed language';
const result = scanner.isAllowedContext(line, 'guaranteed', 'test.md');
expect(result).toBe(true);
});
test('should check allowed context for case studies', () => {
const line = 'This guarantees results';
const result = scanner.isAllowedContext(line, 'guarantees', 'docs/case-studies/example.md');
expect(result).toBe(true);
});
test('should reject prohibited terms in normal content', () => {
const line = 'This guarantees results';
const result = scanner.isAllowedContext(line, 'guarantees', 'README.md');
expect(result).toBe(false);
});
});
describe('File Inclusion/Exclusion', () => {
test('should include markdown files', () => {
expect(scanner.includePatterns).toContain('**/*.md');
});
test('should include HTML files', () => {
expect(scanner.includePatterns).toContain('**/*.html');
});
test('should include JavaScript files', () => {
expect(scanner.includePatterns).toContain('**/*.js');
});
test('should exclude node_modules', () => {
expect(scanner.excludePatterns).toContain('**/node_modules/**');
});
test('should exclude .git directory', () => {
expect(scanner.excludePatterns).toContain('**/.git/**');
});
test('should exclude .claude directory', () => {
expect(scanner.excludePatterns).toContain('**/.claude/**');
});
});
describe('Edge Cases', () => {
test('should handle empty files', async () => {
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'empty.md');
await fs.writeFile(testFile, '');
const violations = await scanner.scan();
// Should not error on empty files
expect(Array.isArray(violations)).toBe(true);
});
test('should handle files with only whitespace', async () => {
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'whitespace.md');
await fs.writeFile(testFile, '\n\n \n\t\n');
const violations = await scanner.scan();
expect(Array.isArray(violations)).toBe(true);
});
test('should handle very long lines', async () => {
const longLine = 'a'.repeat(10000) + ' guarantee ' + 'b'.repeat(10000);
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'long.md');
await fs.writeFile(testFile, longLine);
const violations = await scanner.scan();
expect(violations.length).toBeGreaterThan(0);
});
test('should handle violations at file end without newline', async () => {
const testContent = 'This guarantees results';
await fs.mkdir(testFilesDir, { recursive: true });
const testFile = path.join(testFilesDir, 'test.md');
await fs.writeFile(testFile, testContent);
const violations = await scanner.scan();
expect(violations.length).toBeGreaterThan(0);
});
});
describe('Silent Mode', () => {
test('should suppress console output in silent mode', async () => {
const silentScanner = new ProhibitedTermsScanner({
silent: true,
basePath: testFilesDir
});
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
await silentScanner.scan();
// In silent mode, should not call console.log for scanning message
const scanningCalls = consoleSpy.mock.calls.filter(call =>
call.some(arg => typeof arg === 'string' && arg.includes('Scanning'))
);
expect(scanningCalls.length).toBe(0);
consoleSpy.mockRestore();
});
});
});

View file

@ -1,620 +0,0 @@
/**
* Unit Tests - Koha Service
* Tests donation processing with mocked Stripe and Donation model
*/
// Mock Stripe before requiring the service
jest.mock('stripe', () => {
const mockStripe = {
customers: {
list: jest.fn(),
create: jest.fn()
},
checkout: {
sessions: {
create: jest.fn()
}
},
subscriptions: {
retrieve: jest.fn(),
cancel: jest.fn()
},
webhooks: {
constructEvent: jest.fn()
}
};
return jest.fn(() => mockStripe);
});
// Mock Donation model
jest.mock('../../src/models/Donation.model', () => ({
create: jest.fn(),
findByPaymentIntentId: jest.fn(),
findBySubscriptionId: jest.fn(),
updateStatus: jest.fn(),
cancelSubscription: jest.fn(),
markReceiptSent: jest.fn(),
getTransparencyMetrics: jest.fn(),
getStatistics: jest.fn()
}));
// Mock currency utilities
jest.mock('../../src/config/currencies.config', () => ({
isSupportedCurrency: jest.fn((curr) => ['NZD', 'USD', 'AUD', 'EUR', 'GBP'].includes(curr.toUpperCase())),
convertToNZD: jest.fn((amount, curr) => {
const rates = { NZD: 1, USD: 1.65, AUD: 1.07, EUR: 1.82, GBP: 2.05 };
return Math.round(amount * (rates[curr.toUpperCase()] || 1));
}),
getExchangeRate: jest.fn((curr) => {
const rates = { NZD: 1, USD: 1.65, AUD: 1.07, EUR: 1.82, GBP: 2.05 };
return rates[curr.toUpperCase()] || 1;
})
}));
const kohaService = require('../../src/services/koha.service');
const Donation = require('../../src/models/Donation.model');
const stripe = require('stripe')();
describe('Koha Service', () => {
beforeEach(() => {
jest.clearAllMocks();
// Suppress console output in tests
jest.spyOn(console, 'log').mockImplementation();
jest.spyOn(console, 'error').mockImplementation();
jest.spyOn(console, 'warn').mockImplementation();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Constructor and Configuration', () => {
test('should initialize with stripe instance', () => {
expect(kohaService.stripe).toBeDefined();
});
test('should have price IDs configured', () => {
expect(kohaService.priceIds).toBeDefined();
expect(kohaService.priceIds).toHaveProperty('monthly_5');
expect(kohaService.priceIds).toHaveProperty('monthly_15');
expect(kohaService.priceIds).toHaveProperty('monthly_50');
expect(kohaService.priceIds).toHaveProperty('one_time');
});
});
describe('createCheckoutSession()', () => {
const validDonation = {
amount: 1000,
currency: 'NZD',
frequency: 'one_time',
tier: 'custom',
donor: {
name: 'Test Donor',
email: 'test@example.com',
country: 'NZ'
},
public_acknowledgement: false
};
test('should create one-time donation checkout session', async () => {
stripe.customers.list.mockResolvedValue({ data: [] });
stripe.customers.create.mockResolvedValue({ id: 'cus_test123' });
stripe.checkout.sessions.create.mockResolvedValue({
id: 'cs_test123',
url: 'https://checkout.stripe.com/test'
});
Donation.create.mockResolvedValue({ _id: 'donation_id' });
const result = await kohaService.createCheckoutSession(validDonation);
expect(result).toHaveProperty('sessionId', 'cs_test123');
expect(result).toHaveProperty('checkoutUrl');
expect(stripe.customers.create).toHaveBeenCalled();
expect(stripe.checkout.sessions.create).toHaveBeenCalled();
expect(Donation.create).toHaveBeenCalled();
});
test('should create monthly subscription checkout session', async () => {
// Set price ID before test
const originalPriceId = kohaService.priceIds.monthly_15;
kohaService.priceIds.monthly_15 = 'price_15';
const monthlyDonation = {
...validDonation,
frequency: 'monthly',
tier: '15'
};
stripe.customers.list.mockResolvedValue({ data: [] });
stripe.customers.create.mockResolvedValue({ id: 'cus_test123' });
stripe.checkout.sessions.create.mockResolvedValue({
id: 'cs_test456',
url: 'https://checkout.stripe.com/test2'
});
Donation.create.mockResolvedValue({ _id: 'donation_id2' });
const result = await kohaService.createCheckoutSession(monthlyDonation);
expect(result.frequency).toBe('monthly');
expect(stripe.checkout.sessions.create).toHaveBeenCalledWith(
expect.objectContaining({
mode: 'subscription',
line_items: expect.arrayContaining([
expect.objectContaining({ price: 'price_15' })
])
})
);
// Restore original
kohaService.priceIds.monthly_15 = originalPriceId;
});
test('should reuse existing Stripe customer', async () => {
const existingCustomer = { id: 'cus_existing', email: 'test@example.com' };
stripe.customers.list.mockResolvedValue({ data: [existingCustomer] });
stripe.checkout.sessions.create.mockResolvedValue({
id: 'cs_test789',
url: 'https://checkout.stripe.com/test3'
});
Donation.create.mockResolvedValue({ _id: 'donation_id3' });
await kohaService.createCheckoutSession(validDonation);
expect(stripe.customers.create).not.toHaveBeenCalled();
expect(stripe.checkout.sessions.create).toHaveBeenCalledWith(
expect.objectContaining({
customer: 'cus_existing'
})
);
});
test('should reject unsupported currency', async () => {
const invalidCurrency = {
...validDonation,
currency: 'JPY' // Not supported
};
await expect(
kohaService.createCheckoutSession(invalidCurrency)
).rejects.toThrow('Unsupported currency');
});
test('should reject amount below minimum', async () => {
const lowAmount = {
...validDonation,
amount: 50 // Less than $1.00
};
await expect(
kohaService.createCheckoutSession(lowAmount)
).rejects.toThrow('Minimum donation amount is $1.00');
});
test('should reject invalid frequency', async () => {
const invalidFreq = {
...validDonation,
frequency: 'weekly' // Not supported
};
await expect(
kohaService.createCheckoutSession(invalidFreq)
).rejects.toThrow('Invalid frequency');
});
test('should require donor email', async () => {
const noEmail = {
...validDonation,
donor: { name: 'Test' } // No email
};
await expect(
kohaService.createCheckoutSession(noEmail)
).rejects.toThrow('Donor email is required');
});
test('should reject invalid monthly tier', async () => {
const invalidTier = {
...validDonation,
frequency: 'monthly',
tier: '100' // Not a valid tier
};
stripe.customers.list.mockResolvedValue({ data: [] });
stripe.customers.create.mockResolvedValue({ id: 'cus_test' });
await expect(
kohaService.createCheckoutSession(invalidTier)
).rejects.toThrow('Invalid monthly tier');
});
test('should handle customer creation failure', async () => {
stripe.customers.list.mockRejectedValue(new Error('Stripe API error'));
await expect(
kohaService.createCheckoutSession(validDonation)
).rejects.toThrow('Failed to process donor information');
});
});
describe('handleWebhook()', () => {
test('should handle checkout.session.completed', async () => {
const event = {
type: 'checkout.session.completed',
data: { object: { id: 'cs_test', metadata: {} } }
};
const handleCheckoutCompleteSpy = jest.spyOn(kohaService, 'handleCheckoutComplete').mockResolvedValue();
await kohaService.handleWebhook(event);
expect(handleCheckoutCompleteSpy).toHaveBeenCalled();
handleCheckoutCompleteSpy.mockRestore();
});
test('should handle payment_intent.succeeded', async () => {
const event = {
type: 'payment_intent.succeeded',
data: { object: { id: 'pi_test' } }
};
const handlePaymentSuccessSpy = jest.spyOn(kohaService, 'handlePaymentSuccess').mockResolvedValue();
await kohaService.handleWebhook(event);
expect(handlePaymentSuccessSpy).toHaveBeenCalled();
handlePaymentSuccessSpy.mockRestore();
});
test('should handle payment_intent.payment_failed', async () => {
const event = {
type: 'payment_intent.payment_failed',
data: { object: { id: 'pi_test' } }
};
const handlePaymentFailureSpy = jest.spyOn(kohaService, 'handlePaymentFailure').mockResolvedValue();
await kohaService.handleWebhook(event);
expect(handlePaymentFailureSpy).toHaveBeenCalled();
handlePaymentFailureSpy.mockRestore();
});
test('should handle invoice.paid', async () => {
const event = {
type: 'invoice.paid',
data: { object: { id: 'in_test' } }
};
const handleInvoicePaidSpy = jest.spyOn(kohaService, 'handleInvoicePaid').mockResolvedValue();
await kohaService.handleWebhook(event);
expect(handleInvoicePaidSpy).toHaveBeenCalled();
handleInvoicePaidSpy.mockRestore();
});
test('should handle customer.subscription.deleted', async () => {
const event = {
type: 'customer.subscription.deleted',
data: { object: { id: 'sub_test' } }
};
const handleSubscriptionCancellationSpy = jest.spyOn(kohaService, 'handleSubscriptionCancellation').mockResolvedValue();
await kohaService.handleWebhook(event);
expect(handleSubscriptionCancellationSpy).toHaveBeenCalled();
handleSubscriptionCancellationSpy.mockRestore();
});
test('should log unhandled event types', async () => {
const event = {
type: 'unknown.event.type',
data: { object: {} }
};
await kohaService.handleWebhook(event);
expect(console.log).toHaveBeenCalledWith(
expect.stringContaining('Unhandled webhook event type')
);
});
test('should throw error if webhook processing fails', async () => {
const event = {
type: 'checkout.session.completed',
data: { object: { id: 'cs_test' } }
};
jest.spyOn(kohaService, 'handleCheckoutComplete').mockRejectedValue(new Error('Processing failed'));
await expect(
kohaService.handleWebhook(event)
).rejects.toThrow('Processing failed');
});
});
describe('handleCheckoutComplete()', () => {
test('should create new donation record', async () => {
const session = {
id: 'cs_test',
amount_total: 1500,
currency: 'nzd',
customer_email: 'test@example.com',
customer: 'cus_test',
payment_intent: 'pi_test',
subscription: null,
metadata: {
frequency: 'one_time',
tier: 'custom',
currency: 'NZD',
amount_nzd: '1500',
exchange_rate: '1.0',
donor_name: 'Test Donor',
public_acknowledgement: 'no'
}
};
Donation.findByPaymentIntentId.mockResolvedValue(null);
Donation.create.mockResolvedValue({ _id: 'donation_id', donor: { email: 'test@example.com' } });
await kohaService.handleCheckoutComplete(session);
expect(Donation.create).toHaveBeenCalledWith(
expect.objectContaining({
amount: 1500,
frequency: 'one_time',
status: 'completed'
})
);
});
test('should update existing donation record', async () => {
const session = {
id: 'cs_test',
amount_total: 1500,
payment_intent: 'pi_existing',
subscription: 'sub_test',
metadata: {
frequency: 'monthly',
tier: '15',
currency: 'NZD',
amount_nzd: '1500'
}
};
const existingDonation = { _id: 'existing_id', status: 'pending' };
Donation.findByPaymentIntentId.mockResolvedValue(existingDonation);
Donation.updateStatus.mockResolvedValue(true);
await kohaService.handleCheckoutComplete(session);
expect(Donation.updateStatus).toHaveBeenCalledWith(
'existing_id',
'completed',
expect.objectContaining({
'stripe.subscription_id': 'sub_test'
})
);
});
});
describe('handlePaymentSuccess()', () => {
test('should update pending donation to completed', async () => {
const paymentIntent = { id: 'pi_test' };
const donation = { _id: 'donation_id', status: 'pending' };
Donation.findByPaymentIntentId.mockResolvedValue(donation);
Donation.updateStatus.mockResolvedValue(true);
await kohaService.handlePaymentSuccess(paymentIntent);
expect(Donation.updateStatus).toHaveBeenCalledWith(
'donation_id',
'completed',
expect.any(Object)
);
});
test('should not update non-pending donations', async () => {
const paymentIntent = { id: 'pi_test' };
const donation = { _id: 'donation_id', status: 'completed' };
Donation.findByPaymentIntentId.mockResolvedValue(donation);
await kohaService.handlePaymentSuccess(paymentIntent);
expect(Donation.updateStatus).not.toHaveBeenCalled();
});
});
describe('handlePaymentFailure()', () => {
test('should mark donation as failed', async () => {
const paymentIntent = {
id: 'pi_test',
last_payment_error: { message: 'Card declined' }
};
const donation = { _id: 'donation_id' };
Donation.findByPaymentIntentId.mockResolvedValue(donation);
Donation.updateStatus.mockResolvedValue(true);
await kohaService.handlePaymentFailure(paymentIntent);
expect(Donation.updateStatus).toHaveBeenCalledWith(
'donation_id',
'failed',
expect.objectContaining({
'metadata.failure_reason': 'Card declined'
})
);
});
});
describe('handleInvoicePaid()', () => {
test('should create donation for recurring payment', async () => {
const invoice = {
id: 'in_test',
subscription: 'sub_test',
customer_email: 'test@example.com',
customer: 'cus_test',
amount_paid: 1500,
currency: 'nzd',
charge: 'ch_test',
created: Math.floor(Date.now() / 1000)
};
const subscription = {
metadata: {
tier: '15',
public_acknowledgement: 'yes',
currency: 'NZD'
}
};
stripe.subscriptions.retrieve.mockResolvedValue(subscription);
Donation.create.mockResolvedValue({ _id: 'donation_id' });
await kohaService.handleInvoicePaid(invoice);
expect(Donation.create).toHaveBeenCalledWith(
expect.objectContaining({
frequency: 'monthly',
status: 'completed',
amount: 1500
})
);
});
});
describe('verifyWebhookSignature()', () => {
test('should verify valid webhook signature', () => {
const payload = 'webhook payload';
const signature = 'sig_test';
const event = { type: 'test', data: {} };
stripe.webhooks.constructEvent.mockReturnValue(event);
const result = kohaService.verifyWebhookSignature(payload, signature);
expect(result).toEqual(event);
expect(stripe.webhooks.constructEvent).toHaveBeenCalledWith(
payload,
signature,
process.env.STRIPE_KOHA_WEBHOOK_SECRET
);
});
test('should throw error for invalid signature', () => {
stripe.webhooks.constructEvent.mockImplementation(() => {
throw new Error('Invalid signature');
});
expect(() => {
kohaService.verifyWebhookSignature('payload', 'bad_sig');
}).toThrow('Invalid webhook signature');
});
});
describe('getTransparencyMetrics()', () => {
test('should return transparency metrics', async () => {
const mockMetrics = {
total_received: 5000,
monthly_supporters: 10,
one_time_donations: 50
};
Donation.getTransparencyMetrics.mockResolvedValue(mockMetrics);
const result = await kohaService.getTransparencyMetrics();
expect(result).toEqual(mockMetrics);
expect(Donation.getTransparencyMetrics).toHaveBeenCalled();
});
test('should throw error if metrics retrieval fails', async () => {
Donation.getTransparencyMetrics.mockRejectedValue(new Error('Database error'));
await expect(
kohaService.getTransparencyMetrics()
).rejects.toThrow('Database error');
});
});
describe('sendReceiptEmail()', () => {
test('should generate receipt number and mark as sent', async () => {
const donation = {
_id: 'donation123',
donor: { email: 'test@example.com' }
};
Donation.markReceiptSent.mockResolvedValue(true);
const result = await kohaService.sendReceiptEmail(donation);
expect(result).toBe(true);
expect(Donation.markReceiptSent).toHaveBeenCalledWith(
'donation123',
expect.stringMatching(/^KOHA-\d{4}-[A-Z0-9]{8}$/)
);
});
});
describe('cancelRecurringDonation()', () => {
test('should cancel subscription in Stripe and database', async () => {
stripe.subscriptions.cancel.mockResolvedValue({ id: 'sub_test', status: 'canceled' });
Donation.cancelSubscription.mockResolvedValue(true);
const result = await kohaService.cancelRecurringDonation('sub_test');
expect(result).toEqual({
success: true,
message: 'Subscription cancelled successfully'
});
expect(stripe.subscriptions.cancel).toHaveBeenCalledWith('sub_test');
expect(Donation.cancelSubscription).toHaveBeenCalledWith('sub_test');
});
test('should throw error if cancellation fails', async () => {
stripe.subscriptions.cancel.mockRejectedValue(new Error('Subscription not found'));
await expect(
kohaService.cancelRecurringDonation('sub_nonexistent')
).rejects.toThrow('Subscription not found');
});
});
describe('getStatistics()', () => {
test('should return donation statistics', async () => {
const mockStats = {
total_count: 100,
total_amount: 10000,
by_frequency: { monthly: 20, one_time: 80 }
};
Donation.getStatistics.mockResolvedValue(mockStats);
const result = await kohaService.getStatistics();
expect(result).toEqual(mockStats);
expect(Donation.getStatistics).toHaveBeenCalledWith(null, null);
});
test('should support date range filtering', async () => {
const mockStats = { total_count: 10, total_amount: 1000 };
Donation.getStatistics.mockResolvedValue(mockStats);
await kohaService.getStatistics('2025-01-01', '2025-12-31');
expect(Donation.getStatistics).toHaveBeenCalledWith('2025-01-01', '2025-12-31');
});
});
describe('Service Singleton', () => {
test('should export singleton instance', () => {
const kohaService2 = require('../../src/services/koha.service');
expect(kohaService).toBe(kohaService2);
});
});
});

View file

@ -1,502 +0,0 @@
/**
* Unit Tests - Markdown Utility
* Tests markdown conversion, TOC extraction, front matter parsing, and slug generation
*/
const {
markdownToHtml,
extractTOC,
extractFrontMatter,
generateSlug
} = require('../../src/utils/markdown.util');
describe('Markdown Utility', () => {
describe('markdownToHtml', () => {
test('should return empty string for null input', () => {
expect(markdownToHtml(null)).toBe('');
});
test('should return empty string for undefined input', () => {
expect(markdownToHtml(undefined)).toBe('');
});
test('should return empty string for empty string', () => {
expect(markdownToHtml('')).toBe('');
});
test('should convert basic paragraph', () => {
const markdown = 'This is a paragraph.';
const html = markdownToHtml(markdown);
expect(html).toContain('<p>This is a paragraph.</p>');
});
test('should convert headings with IDs', () => {
const markdown = '# Test Heading';
const html = markdownToHtml(markdown);
expect(html).toContain('<h1 id="test-heading">Test Heading</h1>');
});
test('should convert multiple heading levels', () => {
const markdown = `# Heading 1
## Heading 2
### Heading 3`;
const html = markdownToHtml(markdown);
expect(html).toContain('<h1 id="heading-1">Heading 1</h1>');
expect(html).toContain('<h2 id="heading-2">Heading 2</h2>');
expect(html).toContain('<h3 id="heading-3">Heading 3</h3>');
});
test('should generate slugs from headings with special characters', () => {
const markdown = '# Test: Special Characters!';
const html = markdownToHtml(markdown);
expect(html).toContain('id="test-special-characters"');
});
test('should convert bold text', () => {
const markdown = '**bold text**';
const html = markdownToHtml(markdown);
expect(html).toContain('<strong>bold text</strong>');
});
test('should convert italic text', () => {
const markdown = '*italic text*';
const html = markdownToHtml(markdown);
expect(html).toContain('<em>italic text</em>');
});
test('should convert inline code', () => {
const markdown = '`code snippet`';
const html = markdownToHtml(markdown);
expect(html).toContain('<code>code snippet</code>');
});
test('should convert code blocks with language', () => {
const markdown = '```javascript\nconst x = 1;\n```';
const html = markdownToHtml(markdown);
expect(html).toContain('<pre');
expect(html).toContain('<code');
});
test('should convert code blocks without language', () => {
const markdown = '```\nplain code\n```';
const html = markdownToHtml(markdown);
expect(html).toContain('<pre');
expect(html).toContain('plain code');
});
test('should convert unordered lists', () => {
const markdown = `- Item 1
- Item 2
- Item 3`;
const html = markdownToHtml(markdown);
expect(html).toContain('<ul>');
expect(html).toContain('<li>Item 1</li>');
expect(html).toContain('<li>Item 2</li>');
expect(html).toContain('<li>Item 3</li>');
expect(html).toContain('</ul>');
});
test('should convert ordered lists', () => {
const markdown = `1. First
2. Second
3. Third`;
const html = markdownToHtml(markdown);
expect(html).toContain('<ol>');
expect(html).toContain('<li>First</li>');
expect(html).toContain('<li>Second</li>');
expect(html).toContain('<li>Third</li>');
expect(html).toContain('</ol>');
});
test('should convert links', () => {
const markdown = '[Link Text](https://example.com)';
const html = markdownToHtml(markdown);
expect(html).toContain('<a href="https://example.com">Link Text</a>');
});
test('should convert images', () => {
const markdown = '![Alt Text](https://example.com/image.png)';
const html = markdownToHtml(markdown);
expect(html).toContain('<img');
expect(html).toContain('src="https://example.com/image.png"');
expect(html).toContain('alt="Alt Text"');
});
test('should convert blockquotes', () => {
const markdown = '> This is a quote';
const html = markdownToHtml(markdown);
expect(html).toContain('<blockquote>');
expect(html).toContain('This is a quote');
expect(html).toContain('</blockquote>');
});
test('should convert tables', () => {
const markdown = `| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |`;
const html = markdownToHtml(markdown);
expect(html).toContain('<table>');
expect(html).toContain('<thead>');
expect(html).toContain('<tbody>');
expect(html).toContain('<th>Header 1</th>');
expect(html).toContain('<td>Cell 1</td>');
});
test('should sanitize dangerous HTML (XSS protection)', () => {
const markdown = '<script>alert("XSS")</script>';
const html = markdownToHtml(markdown);
// Script tags should be removed
expect(html).not.toContain('<script>');
expect(html).not.toContain('alert');
});
test('should sanitize dangerous onclick attributes', () => {
const markdown = '<a href="#" onclick="alert(\'XSS\')">Click</a>';
const html = markdownToHtml(markdown);
// onclick should be removed
expect(html).not.toContain('onclick');
});
test('should allow safe HTML attributes', () => {
const markdown = '[Link](https://example.com "Title")';
const html = markdownToHtml(markdown);
expect(html).toContain('href="https://example.com"');
expect(html).toContain('title="Title"');
});
test('should handle horizontal rules', () => {
const markdown = '---';
const html = markdownToHtml(markdown);
expect(html).toContain('<hr');
});
test('should convert strikethrough (GFM)', () => {
const markdown = '~~strikethrough~~';
const html = markdownToHtml(markdown);
expect(html).toContain('<del>strikethrough</del>');
});
});
describe('extractTOC', () => {
test('should return empty array for null input', () => {
expect(extractTOC(null)).toEqual([]);
});
test('should return empty array for undefined input', () => {
expect(extractTOC(undefined)).toEqual([]);
});
test('should return empty array for empty string', () => {
expect(extractTOC('')).toEqual([]);
});
test('should return empty array for markdown without headings', () => {
const markdown = 'Just a paragraph without headings.';
expect(extractTOC(markdown)).toEqual([]);
});
test('should extract single heading', () => {
const markdown = '# Main Title';
const toc = extractTOC(markdown);
expect(toc).toHaveLength(1);
expect(toc[0]).toEqual({
level: 1,
title: 'Main Title',
slug: 'main-title'
});
});
test('should extract multiple headings', () => {
const markdown = `# Heading 1
## Heading 2
### Heading 3`;
const toc = extractTOC(markdown);
expect(toc).toHaveLength(3);
expect(toc[0].level).toBe(1);
expect(toc[1].level).toBe(2);
expect(toc[2].level).toBe(3);
});
test('should extract headings with special characters', () => {
const markdown = '# Test: Special Characters!';
const toc = extractTOC(markdown);
expect(toc[0]).toEqual({
level: 1,
title: 'Test: Special Characters!',
slug: 'test-special-characters'
});
});
test('should strip markdown formatting from titles', () => {
const markdown = '# **Bold** and *Italic* and `code`';
const toc = extractTOC(markdown);
expect(toc[0].title).toBe('Bold and Italic and code');
});
test('should handle headings with multiple spaces', () => {
const markdown = '# Multiple Spaces';
const toc = extractTOC(markdown);
expect(toc[0].slug).toBe('multiple-spaces');
});
test('should handle all heading levels (1-6)', () => {
const markdown = `# H1
## H2
### H3
#### H4
##### H5
###### H6`;
const toc = extractTOC(markdown);
expect(toc).toHaveLength(6);
expect(toc[0].level).toBe(1);
expect(toc[5].level).toBe(6);
});
test('should ignore invalid heading formats', () => {
const markdown = `#No space
# Valid Heading
##No space
## Another Valid`;
const toc = extractTOC(markdown);
expect(toc).toHaveLength(2);
expect(toc[0].title).toBe('Valid Heading');
expect(toc[1].title).toBe('Another Valid');
});
test('should handle headings mixed with content', () => {
const markdown = `Some text
# Heading 1
More text
## Heading 2
Even more text`;
const toc = extractTOC(markdown);
expect(toc).toHaveLength(2);
expect(toc[0].title).toBe('Heading 1');
expect(toc[1].title).toBe('Heading 2');
});
});
describe('extractFrontMatter', () => {
test('should return empty metadata for null input', () => {
const result = extractFrontMatter(null);
expect(result).toEqual({
metadata: {},
content: null
});
});
test('should return empty metadata for undefined input', () => {
const result = extractFrontMatter(undefined);
expect(result).toEqual({
metadata: {},
content: undefined
});
});
test('should return empty metadata for markdown without front matter', () => {
const markdown = '# Just a heading';
const result = extractFrontMatter(markdown);
expect(result.metadata).toEqual({});
expect(result.content).toBe(markdown);
});
test('should extract valid front matter', () => {
const markdown = `---
title: Test Document
author: Test Author
date: 2025-01-01
---
# Content starts here`;
const result = extractFrontMatter(markdown);
expect(result.metadata).toEqual({
title: 'Test Document',
author: 'Test Author',
date: '2025-01-01'
});
expect(result.content).toBe('# Content starts here');
});
test('should handle front matter with colons in values', () => {
const markdown = `---
url: https://example.com
time: 12:30:45
---
Content`;
const result = extractFrontMatter(markdown);
expect(result.metadata.url).toBe('https://example.com');
expect(result.metadata.time).toBe('12:30:45');
});
test('should ignore lines without colons in front matter', () => {
const markdown = `---
title: Valid
invalid line
author: Also Valid
---
Content`;
const result = extractFrontMatter(markdown);
expect(result.metadata).toEqual({
title: 'Valid',
author: 'Also Valid'
});
});
test('should handle empty front matter block', () => {
const markdown = `---
---
Content`;
const result = extractFrontMatter(markdown);
// Empty front matter doesn't match regex, returns original content
expect(result.metadata).toEqual({});
expect(result.content).toBe(markdown);
});
test('should trim whitespace from keys and values', () => {
const markdown = `---
title : Trimmed Title
author :Test Author
---
Content`;
const result = extractFrontMatter(markdown);
expect(result.metadata.title).toBe('Trimmed Title');
expect(result.metadata.author).toBe('Test Author');
});
test('should handle multiline content after front matter', () => {
const markdown = `---
title: Test
---
# Heading
Paragraph 1
Paragraph 2`;
const result = extractFrontMatter(markdown);
expect(result.metadata.title).toBe('Test');
expect(result.content).toContain('# Heading');
expect(result.content).toContain('Paragraph 1');
expect(result.content).toContain('Paragraph 2');
});
test('should handle front matter at end of document', () => {
const markdown = `---
title: Edge Case
---`;
const result = extractFrontMatter(markdown);
// Regex requires content after closing ---, so this doesn't match
expect(result.metadata).toEqual({});
expect(result.content).toBe(markdown);
});
});
describe('generateSlug', () => {
test('should convert simple text to lowercase', () => {
expect(generateSlug('Simple Text')).toBe('simple-text');
});
test('should replace spaces with hyphens', () => {
expect(generateSlug('Multiple Word Slug')).toBe('multiple-word-slug');
});
test('should remove special characters', () => {
expect(generateSlug('Special!@#$%Characters')).toBe('specialcharacters');
});
test('should handle multiple spaces', () => {
expect(generateSlug('Multiple Spaces Here')).toBe('multiple-spaces-here');
});
test('should handle multiple hyphens', () => {
expect(generateSlug('Multiple---Hyphens')).toBe('multiple-hyphens');
});
test('should convert leading and trailing whitespace to hyphens', () => {
// Note: trim() is called but only removes whitespace, not hyphens
// Spaces are converted to hyphens before trim(), so leading/trailing spaces become hyphens
expect(generateSlug(' Leading and Trailing ')).toBe('-leading-and-trailing-');
});
test('should preserve leading and trailing hyphens', () => {
// Hyphens are not trimmed, only whitespace
expect(generateSlug('-Hyphen-Start-End-')).toBe('-hyphen-start-end-');
});
test('should handle mixed case', () => {
expect(generateSlug('MiXeD CaSe TeXt')).toBe('mixed-case-text');
});
test('should handle numbers', () => {
expect(generateSlug('Title 123 Numbers')).toBe('title-123-numbers');
});
test('should handle underscores (keep them)', () => {
expect(generateSlug('Text_With_Underscores')).toBe('text_with_underscores');
});
test('should handle empty string', () => {
expect(generateSlug('')).toBe('');
});
test('should handle only special characters', () => {
expect(generateSlug('!@#$%^&*()')).toBe('');
});
test('should handle unicode characters', () => {
expect(generateSlug('Café München')).toBe('caf-mnchen');
});
test('should handle consecutive special characters', () => {
expect(generateSlug('Word!!!Another???Word')).toBe('wordanotherword');
});
test('should create valid URL slug', () => {
const slug = generateSlug('What is the Tractatus Framework?');
expect(slug).toBe('what-is-the-tractatus-framework');
});
});
});