tractatus/tests/integration/sync-instructions.test.js
TheFlow 0958d8d2cd fix(mongodb): resolve production connection drops and add governance sync system
- Fixed sync script disconnecting Mongoose (prevents production errors)
- Created text search index (fixes search in rule-manager)
- Enhanced inst_024 with closedown protocol, added inst_061
- Added sync infrastructure: API routes, dashboard widget, auto-sync
- Fixed MemoryProxy tests MongoDB connection
- Created ADR-001 and integration tests

Result: Production stable, 52 rules synced, search working

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:39:05 +13:00

290 lines
9.6 KiB
JavaScript

/**
* Integration Test: File-to-Database Sync
* Tests the dual governance architecture synchronization
*/
const fs = require('fs');
const path = require('path');
const mongoose = require('mongoose');
const { syncInstructions } = require('../../scripts/sync-instructions-to-db.js');
const GovernanceRule = require('../../src/models/GovernanceRule.model');
require('dotenv').config();
const INSTRUCTION_FILE = path.join(__dirname, '../../.claude/instruction-history.json');
const TEST_DB = 'tractatus_test_sync';
describe('Instruction Sync Integration Tests', () => {
let originalDb;
beforeAll(async () => {
// Connect to test database
const mongoUri = process.env.MONGODB_URI?.replace(/\/[^/]+$/, `/${TEST_DB}`) ||
`mongodb://localhost:27017/${TEST_DB}`;
await mongoose.connect(mongoUri);
originalDb = mongoose.connection.db.databaseName;
});
afterAll(async () => {
// Clean up test database
await mongoose.connection.db.dropDatabase();
await mongoose.disconnect();
});
beforeEach(async () => {
// Clear database before each test
await GovernanceRule.deleteMany({});
});
describe('File Reading', () => {
test('instruction file exists', () => {
expect(fs.existsSync(INSTRUCTION_FILE)).toBe(true);
});
test('instruction file is valid JSON', () => {
const fileData = fs.readFileSync(INSTRUCTION_FILE, 'utf8');
expect(() => JSON.parse(fileData)).not.toThrow();
});
test('instruction file has expected structure', () => {
const fileData = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8'));
expect(fileData).toHaveProperty('version');
expect(fileData).toHaveProperty('instructions');
expect(Array.isArray(fileData.instructions)).toBe(true);
});
test('all instructions have required fields', () => {
const fileData = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8'));
fileData.instructions.forEach(inst => {
expect(inst).toHaveProperty('id');
expect(inst).toHaveProperty('text');
expect(inst).toHaveProperty('quadrant');
expect(inst).toHaveProperty('persistence');
});
});
});
describe('Initial Sync', () => {
test('syncs all instructions from file to empty database', async () => {
const result = await syncInstructions({ silent: true });
expect(result.success).toBe(true);
expect(result.added).toBeGreaterThan(0);
expect(result.updated).toBe(0); // First sync, nothing to update
expect(result.finalCount).toBeGreaterThan(0);
// Verify database has same count as file
const fileData = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8'));
const activeFileCount = fileData.instructions.filter(i => i.active !== false).length;
expect(result.finalCount).toBe(activeFileCount);
});
test('creates rules with correct schema', async () => {
await syncInstructions({ silent: true });
const rules = await GovernanceRule.find({}).lean();
expect(rules.length).toBeGreaterThan(0);
rules.forEach(rule => {
// Required fields
expect(rule).toHaveProperty('id');
expect(rule).toHaveProperty('text');
expect(rule).toHaveProperty('quadrant');
expect(rule).toHaveProperty('persistence');
expect(rule).toHaveProperty('source');
expect(rule).toHaveProperty('active');
// Source enum validation
expect(['user_instruction', 'framework_default', 'automated', 'migration', 'claude_md_migration', 'test'])
.toContain(rule.source);
});
});
});
describe('Update Sync', () => {
test('updates existing rules without duplicates', async () => {
// First sync
const result1 = await syncInstructions({ silent: true });
const count1 = result1.finalCount;
// Second sync (should update, not add)
const result2 = await syncInstructions({ silent: true });
expect(result2.success).toBe(true);
expect(result2.added).toBe(0); // Nothing new to add
expect(result2.updated).toBe(count1); // All rules updated
expect(result2.finalCount).toBe(count1); // Same count
});
test('preserves validation scores on update', async () => {
// First sync
await syncInstructions({ silent: true });
// Update a rule with validation scores
const rule = await GovernanceRule.findOne({});
await GovernanceRule.findByIdAndUpdate(rule._id, {
clarityScore: 85,
specificityScore: 90,
actionabilityScore: 80,
validationStatus: 'VALIDATED',
lastValidated: new Date()
});
// Second sync
await syncInstructions({ silent: true });
// Verify scores preserved
const updatedRule = await GovernanceRule.findById(rule._id);
expect(updatedRule.clarityScore).toBe(85);
expect(updatedRule.specificityScore).toBe(90);
expect(updatedRule.actionabilityScore).toBe(80);
expect(updatedRule.validationStatus).toBe('VALIDATED');
});
});
describe('Orphan Handling', () => {
test('deactivates rules not in file', async () => {
// Create an orphan rule directly in DB
await GovernanceRule.create({
id: 'test_orphan_001',
text: 'This rule does not exist in the file',
scope: 'PROJECT_SPECIFIC',
applicableProjects: ['*'],
quadrant: 'TACTICAL',
persistence: 'MEDIUM',
category: 'test',
priority: 50,
active: true,
source: 'test',
createdBy: 'test'
});
// Sync from file
const result = await syncInstructions({ silent: true });
expect(result.deactivated).toBe(1);
// Verify orphan is inactive
const orphan = await GovernanceRule.findOne({ id: 'test_orphan_001' });
expect(orphan.active).toBe(false);
expect(orphan.notes).toContain('AUTO-DEACTIVATED');
});
test('exports orphans to backup file', async () => {
// Create orphan
await GovernanceRule.create({
id: 'test_orphan_002',
text: 'Another orphan rule',
scope: 'PROJECT_SPECIFIC',
applicableProjects: ['*'],
quadrant: 'TACTICAL',
persistence: 'MEDIUM',
category: 'test',
priority: 50,
active: true,
source: 'test',
createdBy: 'test'
});
// Sync
await syncInstructions({ silent: true });
// Check backup directory exists
const backupDir = path.join(__dirname, '../../.claude/backups');
expect(fs.existsSync(backupDir)).toBe(true);
// Check latest backup file contains orphan
const backupFiles = fs.readdirSync(backupDir)
.filter(f => f.startsWith('orphaned-rules-'))
.sort()
.reverse();
if (backupFiles.length > 0) {
const latestBackup = JSON.parse(
fs.readFileSync(path.join(backupDir, backupFiles[0]), 'utf8')
);
expect(latestBackup.rules.some(r => r.id === 'test_orphan_002')).toBe(true);
}
});
});
describe('Source Mapping', () => {
test('maps file source values to MongoDB enum values', async () => {
// This test assumes there are instructions with different source values in the file
await syncInstructions({ silent: true });
const rules = await GovernanceRule.find({}).lean();
// All sources should be valid enum values
const validSources = ['user_instruction', 'framework_default', 'automated', 'migration', 'claude_md_migration', 'test'];
rules.forEach(rule => {
expect(validSources).toContain(rule.source);
});
});
});
describe('Error Handling', () => {
test('handles missing instruction file gracefully', async () => {
// Temporarily rename file
const tempFile = INSTRUCTION_FILE + '.tmp';
fs.renameSync(INSTRUCTION_FILE, tempFile);
try {
const result = await syncInstructions({ silent: true });
expect(result.success).toBe(false);
expect(result.error).toContain('not found');
} finally {
// Restore file
fs.renameSync(tempFile, INSTRUCTION_FILE);
}
});
test('handles invalid JSON gracefully', async () => {
// Temporarily replace file with invalid JSON
const originalContent = fs.readFileSync(INSTRUCTION_FILE, 'utf8');
fs.writeFileSync(INSTRUCTION_FILE, 'INVALID JSON{{{');
try {
const result = await syncInstructions({ silent: true });
expect(result.success).toBe(false);
} finally {
// Restore file
fs.writeFileSync(INSTRUCTION_FILE, originalContent);
}
});
});
describe('Programmatic Options', () => {
test('respects silent mode', async () => {
const consoleSpy = jest.spyOn(console, 'log');
await syncInstructions({ silent: true });
// Silent mode should not log
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
test('dry run does not modify database', async () => {
const result = await syncInstructions({ silent: true, dryRun: true });
expect(result.success).toBe(true);
// Database should still be empty
const count = await GovernanceRule.countDocuments({});
expect(count).toBe(0);
});
});
describe('Idempotency', () => {
test('multiple syncs produce same result', async () => {
const result1 = await syncInstructions({ silent: true });
const result2 = await syncInstructions({ silent: true });
const result3 = await syncInstructions({ silent: true });
expect(result1.finalCount).toBe(result2.finalCount);
expect(result2.finalCount).toBe(result3.finalCount);
});
});
});