tractatus/tests/unit/MemoryProxy.service.test.js
TheFlow ba6722f256 fix(tests): update MemoryProxy tests for v3 MongoDB architecture
PROBLEM: Tests written for filesystem-based v1/v2, but service refactored to MongoDB v3
- 18/25 tests failing (expected filesystem, got MongoDB)
- Tests checking for .json files that no longer exist
- Response format mismatches (rulesStored vs inserted/modified)

SOLUTION: Complete test rewrite for MongoDB architecture
- Use GovernanceRule and AuditLog models directly
- Test data isolation with test_ prefix and cleanup hooks
- Updated assertions for MongoDB response formats
- Filter results to exclude non-test data from tractatus_test DB
- Removed filesystem-specific tests (directory creation, file I/O)

RESULT: 26/26 tests passing in 1.079s (from 7/25 in 250s timeout)

Tests now verify:
✓ MongoDB persistence and retrieval
✓ Rule filtering (quadrant, persistence)
✓ Cache management (TTL, clear, stats)
✓ Audit logging to MongoDB
✓ Data integrity across persist/load cycles

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 12:14:57 +13:00

383 lines
12 KiB
JavaScript

/**
* Unit Tests - MemoryProxy Service v3
* Tests MongoDB-backed governance rule persistence and retrieval
*/
const { MemoryProxyService } = require('../../src/services/MemoryProxy.service');
const GovernanceRule = require('../../src/models/GovernanceRule.model');
const AuditLog = require('../../src/models/AuditLog.model');
const mongoose = require('mongoose');
// Increase timeout for MongoDB operations
jest.setTimeout(30000);
describe('MemoryProxyService v3 (MongoDB)', () => {
let memoryProxy;
// Connect to test database before all tests
beforeAll(async () => {
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/tractatus_test';
await mongoose.connect(mongoUri);
});
// Disconnect after all tests
afterAll(async () => {
await mongoose.disconnect();
});
const testRules = [
{
id: 'test_inst_001',
text: 'Test rule 1',
quadrant: 'STRATEGIC',
persistence: 'HIGH',
active: true
},
{
id: 'test_inst_002',
text: 'Test rule 2',
quadrant: 'OPERATIONAL',
persistence: 'HIGH',
active: true
},
{
id: 'test_inst_003',
text: 'Test rule 3',
quadrant: 'SYSTEM',
persistence: 'MEDIUM',
active: true
}
];
beforeEach(async () => {
// Clear test data from previous tests
await GovernanceRule.deleteMany({ id: /^test_/ });
await AuditLog.deleteMany({ sessionId: /^test-/ });
memoryProxy = new MemoryProxyService({
cacheEnabled: true,
cacheTTL: 1000, // 1 second for testing
anthropicEnabled: false // Disable Anthropic API for tests
});
await memoryProxy.initialize();
});
afterEach(async () => {
// Cleanup test data
await GovernanceRule.deleteMany({ id: /^test_/ });
await AuditLog.deleteMany({ sessionId: /^test-/ });
});
describe('Initialization', () => {
test('should connect to MongoDB successfully', async () => {
expect(mongoose.connection.readyState).toBe(1); // 1 = connected
});
test('should initialize with correct configuration', () => {
expect(memoryProxy.cacheEnabled).toBe(true);
expect(memoryProxy.cacheTTL).toBe(1000);
expect(memoryProxy.anthropicEnabled).toBe(false);
});
});
describe('persistGovernanceRules', () => {
test('should persist rules successfully', async () => {
const result = await memoryProxy.persistGovernanceRules(testRules);
expect(result.success).toBe(true);
expect(result.total).toBe(3);
expect(result.inserted + result.modified).toBe(3);
expect(result.duration).toBeGreaterThanOrEqual(0);
});
test('should store rules in MongoDB', async () => {
await memoryProxy.persistGovernanceRules(testRules);
const storedRules = await GovernanceRule.find({ id: /^test_/ }).lean();
expect(storedRules).toHaveLength(3);
expect(storedRules[0].text).toBeDefined();
expect(storedRules[0].quadrant).toBeDefined();
expect(storedRules[0].persistence).toBeDefined();
});
test('should update existing rules on re-persist', async () => {
// First persist
await memoryProxy.persistGovernanceRules(testRules);
// Update and re-persist
const updatedRules = testRules.map(r => ({
...r,
text: r.text + ' UPDATED'
}));
const result = await memoryProxy.persistGovernanceRules(updatedRules);
expect(result.success).toBe(true);
expect(result.modified).toBe(3);
const storedRules = await GovernanceRule.find({ id: /^test_/ }).lean();
expect(storedRules.every(r => r.text.includes('UPDATED'))).toBe(true);
});
test('should reject empty rules array', async () => {
await expect(memoryProxy.persistGovernanceRules([]))
.rejects
.toThrow('Cannot persist empty rules array');
});
test('should reject non-array input', async () => {
await expect(memoryProxy.persistGovernanceRules({ invalid: 'input' }))
.rejects
.toThrow('Rules must be an array');
});
test('should clear cache after persisting', async () => {
// Load rules to populate cache
await memoryProxy.persistGovernanceRules(testRules);
await memoryProxy.loadGovernanceRules();
const statsBefore = memoryProxy.getCacheStats();
expect(statsBefore.entries).toBeGreaterThan(0);
// Persist again (should clear cache)
await memoryProxy.persistGovernanceRules(testRules);
const statsAfter = memoryProxy.getCacheStats();
expect(statsAfter.entries).toBe(0);
});
});
describe('loadGovernanceRules', () => {
beforeEach(async () => {
await memoryProxy.persistGovernanceRules(testRules);
});
test('should load rules successfully', async () => {
const allRules = await memoryProxy.loadGovernanceRules();
const testRulesLoaded = allRules.filter(r => r.id.startsWith('test_'));
expect(testRulesLoaded).toHaveLength(3);
expect(testRulesLoaded.find(r => r.id === 'test_inst_001')).toBeDefined();
expect(testRulesLoaded.find(r => r.id === 'test_inst_002')).toBeDefined();
expect(testRulesLoaded.find(r => r.id === 'test_inst_003')).toBeDefined();
});
test('should load from cache on second call', async () => {
// First call - from MongoDB
await memoryProxy.loadGovernanceRules();
// Second call - from cache (much faster)
const startTime = Date.now();
const allRules = await memoryProxy.loadGovernanceRules();
const duration = Date.now() - startTime;
const testRulesLoaded = allRules.filter(r => r.id.startsWith('test_'));
expect(testRulesLoaded).toHaveLength(3);
expect(duration).toBeLessThan(10); // Cache should be very fast
});
test('should bypass cache when skipCache option is true', async () => {
// Load to populate cache
await memoryProxy.loadGovernanceRules();
// Clear cache
memoryProxy.clearCache();
// Load with skipCache should work
const allRules = await memoryProxy.loadGovernanceRules({ skipCache: true });
const testRulesLoaded = allRules.filter(r => r.id.startsWith('test_'));
expect(testRulesLoaded).toHaveLength(3);
});
test('should return empty array when no test rules exist', async () => {
// Clear all test rules
await GovernanceRule.deleteMany({ id: /^test_/ });
const rules = await memoryProxy.loadGovernanceRules();
expect(rules.filter(r => r.id.startsWith('test_'))).toEqual([]);
});
test('should maintain data integrity across persist/load cycle', async () => {
const rules = await memoryProxy.loadGovernanceRules();
for (const testRule of testRules) {
const loaded = rules.find(r => r.id === testRule.id);
expect(loaded).toBeDefined();
expect(loaded.text).toBe(testRule.text);
expect(loaded.quadrant).toBe(testRule.quadrant);
expect(loaded.persistence).toBe(testRule.persistence);
}
});
});
describe('getRule', () => {
beforeEach(async () => {
await memoryProxy.persistGovernanceRules(testRules);
});
test('should get specific rule by ID', async () => {
const rule = await memoryProxy.getRule('test_inst_002');
expect(rule).toBeDefined();
expect(rule.id).toBe('test_inst_002');
expect(rule.text).toBe('Test rule 2');
expect(rule.quadrant).toBe('OPERATIONAL');
});
test('should return null for non-existent rule', async () => {
const rule = await memoryProxy.getRule('test_inst_999');
expect(rule).toBeNull();
});
});
describe('getRulesByQuadrant', () => {
beforeEach(async () => {
await memoryProxy.persistGovernanceRules(testRules);
});
test('should filter rules by quadrant', async () => {
const strategicRules = await memoryProxy.getRulesByQuadrant('STRATEGIC');
const testStrategicRules = strategicRules.filter(r => r.id.startsWith('test_'));
expect(testStrategicRules).toHaveLength(1);
expect(testStrategicRules[0].id).toBe('test_inst_001');
expect(testStrategicRules[0].quadrant).toBe('STRATEGIC');
});
test('should return empty array for non-existent quadrant', async () => {
const rules = await memoryProxy.getRulesByQuadrant('NONEXISTENT');
expect(rules.filter(r => r.id.startsWith('test_'))).toEqual([]);
});
});
describe('getRulesByPersistence', () => {
beforeEach(async () => {
await memoryProxy.persistGovernanceRules(testRules);
});
test('should filter rules by persistence level', async () => {
const highRules = await memoryProxy.getRulesByPersistence('HIGH');
const testHighRules = highRules.filter(r => r.id.startsWith('test_'));
expect(testHighRules).toHaveLength(2);
expect(testHighRules.every(r => r.persistence === 'HIGH')).toBe(true);
});
test('should return empty array for non-existent persistence level', async () => {
const rules = await memoryProxy.getRulesByPersistence('LOW');
expect(rules.filter(r => r.id.startsWith('test_'))).toEqual([]);
});
});
describe('auditDecision', () => {
test('should audit decision successfully', async () => {
const decision = {
sessionId: 'test-session-001',
action: 'blog_post_generation',
rulesChecked: ['inst_016', 'inst_017'],
violations: [],
allowed: true,
metadata: {
user: 'test-user',
timestamp: new Date().toISOString()
}
};
const result = await memoryProxy.auditDecision(decision);
expect(result.success).toBe(true);
expect(result.audited).toBe(true);
expect(result.auditId).toBeDefined();
expect(result.duration).toBeGreaterThanOrEqual(0);
});
test('should create audit log in MongoDB', async () => {
const decision = {
sessionId: 'test-session-002',
action: 'test_action',
allowed: true
};
const result = await memoryProxy.auditDecision(decision);
const storedLog = await AuditLog.findById(result.auditId).lean();
expect(storedLog).toBeDefined();
expect(storedLog.sessionId).toBe('test-session-002');
expect(storedLog.action).toBe('test_action');
expect(storedLog.allowed).toBe(true);
expect(storedLog.timestamp).toBeDefined();
});
test('should store multiple audit entries', async () => {
const decision1 = { sessionId: 'test-session-multi-1', action: 'action-1', allowed: true };
const decision2 = { sessionId: 'test-session-multi-2', action: 'action-2', allowed: false };
await memoryProxy.auditDecision(decision1);
await memoryProxy.auditDecision(decision2);
const logs = await AuditLog.find({
sessionId: { $in: ['test-session-multi-1', 'test-session-multi-2'] }
}).lean();
expect(logs).toHaveLength(2);
});
test('should reject decision without required fields', async () => {
const invalidDecision = { sessionId: 'test', /* missing action */ };
await expect(memoryProxy.auditDecision(invalidDecision))
.rejects
.toThrow('Decision must include sessionId and action');
});
});
describe('Cache Management', () => {
test('should clear cache', async () => {
await memoryProxy.persistGovernanceRules(testRules);
// Load rules to populate cache
await memoryProxy.loadGovernanceRules();
expect(memoryProxy.getCacheStats().entries).toBeGreaterThan(0);
memoryProxy.clearCache();
expect(memoryProxy.getCacheStats().entries).toBe(0);
});
test('should expire cache after TTL', async () => {
// Create proxy with 100ms TTL
const shortTTLProxy = new MemoryProxyService({
cacheEnabled: true,
cacheTTL: 100,
anthropicEnabled: false
});
await shortTTLProxy.initialize();
await shortTTLProxy.persistGovernanceRules(testRules);
// Load to populate cache
await shortTTLProxy.loadGovernanceRules();
// Wait for cache to expire
await new Promise(resolve => setTimeout(resolve, 150));
// Should reload from MongoDB (cache expired)
const allRules = await shortTTLProxy.loadGovernanceRules();
const testRulesLoaded = allRules.filter(r => r.id.startsWith('test_'));
expect(testRulesLoaded).toHaveLength(3);
});
test('should get cache statistics', () => {
const stats = memoryProxy.getCacheStats();
expect(stats.enabled).toBe(true);
expect(stats.ttl).toBe(1000);
expect(stats.entries).toBeGreaterThanOrEqual(0);
expect(stats.keys).toBeDefined();
});
});
});