mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
Remove the nested v3/ subdirectory by moving all active source code, tests, configs, and assets directly to the project root. Archive v2/ (dead code, 847MB) to v2-archive.tar.gz. Key changes: - Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/ - Move v3/tsconfig.json, v3/vitest.config.ts to root - Merge v3/package.json into root package.json (ESM, scripts, deps, exports) - Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/ - Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes - Fix all init installer path resolution (3-level-up → 2-level-up) - Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe" - Update settings.json ADR/DDD directories to docs/implementation/ - Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks) - Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces - Update 15 guidance shards: v3/src/ → src/ path references - Fix sync interfaces: remove v3/ and v2/ path references - Fix test path calculations (process.cwd() + '..' no longer needed) - Fix flaky perf test threshold (500ms → 1000ms for CI environments) - Fix complexity-analyzer test to match ADR-051 keyword-based eligibility Build passes, 17,901 tests pass, CLI and MCP bundles verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
817 lines
26 KiB
TypeScript
817 lines
26 KiB
TypeScript
/**
|
|
* Agentic QE v3 - SONA Pattern Persistence Unit Tests
|
|
*
|
|
* Tests for PersistentSONAEngine with SQLite persistence.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import {
|
|
PersistentSONAEngine,
|
|
createPersistentSONAEngine,
|
|
createPersistentSONAEngineSync,
|
|
DEFAULT_PERSISTENT_SONA_CONFIG,
|
|
type PersistentSONAConfig,
|
|
} from '../../../../src/integrations/ruvector/sona-persistence';
|
|
import type { QESONAPattern, QEPatternType } from '../../../../src/integrations/ruvector/sona-wrapper';
|
|
import type { RLState, RLAction, DomainName } from '../../../../src/integrations/rl-suite/interfaces';
|
|
import { resetUnifiedPersistence } from '../../../../src/kernel/unified-persistence';
|
|
import { resetUnifiedMemory } from '../../../../src/kernel/unified-memory';
|
|
|
|
// Test database path
|
|
const TEST_DB_DIR = '/tmp/agentic-qe-test-sona-persistence';
|
|
const TEST_DB_PATH = `${TEST_DB_DIR}/memory.db`;
|
|
|
|
// Ensure test directory exists
|
|
function setupTestDb(): void {
|
|
if (!fs.existsSync(TEST_DB_DIR)) {
|
|
fs.mkdirSync(TEST_DB_DIR, { recursive: true });
|
|
}
|
|
// Clean up any existing test database
|
|
if (fs.existsSync(TEST_DB_PATH)) {
|
|
fs.unlinkSync(TEST_DB_PATH);
|
|
}
|
|
if (fs.existsSync(`${TEST_DB_PATH}-wal`)) {
|
|
fs.unlinkSync(`${TEST_DB_PATH}-wal`);
|
|
}
|
|
if (fs.existsSync(`${TEST_DB_PATH}-shm`)) {
|
|
fs.unlinkSync(`${TEST_DB_PATH}-shm`);
|
|
}
|
|
}
|
|
|
|
// Test fixtures
|
|
function createTestState(overrides: Partial<RLState> = {}): RLState {
|
|
return {
|
|
id: `state-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
features: new Array(384).fill(0).map(() => Math.random()),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createTestAction(overrides: Partial<RLAction> = {}): RLAction {
|
|
return {
|
|
type: 'test-action',
|
|
value: 'execute-test',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createTestOutcome(overrides: Partial<QESONAPattern['outcome']> = {}): QESONAPattern['outcome'] {
|
|
return {
|
|
reward: 0.8,
|
|
success: true,
|
|
quality: 0.9,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createTestConfig(overrides: Partial<PersistentSONAConfig> = {}): PersistentSONAConfig {
|
|
return {
|
|
domain: 'test-generation' as DomainName,
|
|
loadOnInit: true,
|
|
autoSaveInterval: 0, // Immediate saves for testing
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('PersistentSONAEngine', () => {
|
|
beforeEach(async () => {
|
|
// Reset singletons before each test
|
|
resetUnifiedPersistence();
|
|
resetUnifiedMemory();
|
|
|
|
// Setup fresh test database
|
|
setupTestDb();
|
|
|
|
// Set environment to use test database
|
|
process.env.AQE_DB_PATH = TEST_DB_PATH;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
resetUnifiedPersistence();
|
|
resetUnifiedMemory();
|
|
delete process.env.AQE_DB_PATH;
|
|
});
|
|
|
|
describe('Factory Functions', () => {
|
|
it('should create engine with async factory', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
expect(engine).toBeInstanceOf(PersistentSONAEngine);
|
|
expect(engine.isInitialized()).toBe(true);
|
|
expect(engine.getDomain()).toBe('test-generation');
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should create engine with sync factory (requires manual init)', () => {
|
|
const engine = createPersistentSONAEngineSync(createTestConfig());
|
|
|
|
expect(engine).toBeInstanceOf(PersistentSONAEngine);
|
|
expect(engine.isInitialized()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Initialization', () => {
|
|
it('should initialize successfully', async () => {
|
|
const config = createTestConfig();
|
|
const engine = new PersistentSONAEngine(config);
|
|
|
|
await engine.initialize();
|
|
|
|
expect(engine.isInitialized()).toBe(true);
|
|
expect(engine.getDomain()).toBe(config.domain);
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should be idempotent (multiple initialize calls)', async () => {
|
|
const engine = new PersistentSONAEngine(createTestConfig());
|
|
|
|
await engine.initialize();
|
|
await engine.initialize();
|
|
await engine.initialize();
|
|
|
|
expect(engine.isInitialized()).toBe(true);
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should throw if not initialized when storing', async () => {
|
|
const engine = new PersistentSONAEngine(createTestConfig());
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
expect(() => {
|
|
engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
}).toThrow('not initialized');
|
|
});
|
|
});
|
|
|
|
describe('Pattern Storage', () => {
|
|
let engine: PersistentSONAEngine;
|
|
|
|
beforeEach(async () => {
|
|
engine = await createPersistentSONAEngine(createTestConfig());
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await engine?.close();
|
|
});
|
|
|
|
it('should create and store a pattern', () => {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
const pattern = engine.createPattern(
|
|
state,
|
|
action,
|
|
outcome,
|
|
'test-generation',
|
|
'test-generation',
|
|
{ testMetadata: true }
|
|
);
|
|
|
|
expect(pattern).toBeDefined();
|
|
expect(pattern.id).toBeDefined();
|
|
expect(pattern.type).toBe('test-generation');
|
|
expect(pattern.domain).toBe('test-generation');
|
|
expect(pattern.outcome.success).toBe(true);
|
|
expect(pattern.outcome.quality).toBe(0.9);
|
|
expect(pattern.metadata).toEqual({ testMetadata: true });
|
|
});
|
|
|
|
it('should store and retrieve patterns', () => {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
const pattern = engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
|
|
const allPatterns = engine.getAllPatterns();
|
|
|
|
expect(allPatterns.length).toBeGreaterThanOrEqual(1);
|
|
expect(allPatterns.some((p) => p.id === pattern.id)).toBe(true);
|
|
});
|
|
|
|
it('should store multiple patterns in batch', () => {
|
|
const patterns: QESONAPattern[] = [];
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
const state = createTestState();
|
|
const action = createTestAction({ type: `action-${i}` });
|
|
const outcome = createTestOutcome({ quality: 0.5 + i * 0.1 });
|
|
|
|
patterns.push({
|
|
id: `batch-pattern-${i}`,
|
|
type: 'test-generation' as QEPatternType,
|
|
domain: 'test-generation' as DomainName,
|
|
stateEmbedding: state.features,
|
|
action,
|
|
outcome,
|
|
confidence: 0.5,
|
|
usageCount: 0,
|
|
createdAt: new Date(),
|
|
});
|
|
}
|
|
|
|
engine.storePatternsBatch(patterns);
|
|
|
|
const allPatterns = engine.getAllPatterns();
|
|
expect(allPatterns.length).toBeGreaterThanOrEqual(5);
|
|
});
|
|
});
|
|
|
|
describe('Persistence', () => {
|
|
it('should persist patterns to SQLite', async () => {
|
|
const config = createTestConfig();
|
|
const engine1 = await createPersistentSONAEngine(config);
|
|
|
|
// Create a pattern
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
const pattern = engine1.createPattern(
|
|
state,
|
|
action,
|
|
outcome,
|
|
'test-generation',
|
|
'test-generation',
|
|
{ persistent: true }
|
|
);
|
|
|
|
// Get persisted stats
|
|
const stats1 = await engine1.getPersistedStats();
|
|
expect(stats1.totalPatterns).toBeGreaterThanOrEqual(1);
|
|
|
|
await engine1.close();
|
|
|
|
// Create new engine - should load persisted patterns
|
|
const engine2 = await createPersistentSONAEngine({ ...config, loadOnInit: true });
|
|
|
|
const patterns = engine2.getAllPatterns();
|
|
expect(patterns.length).toBeGreaterThanOrEqual(1);
|
|
|
|
const loadedPattern = patterns.find((p) => p.id === pattern.id);
|
|
expect(loadedPattern).toBeDefined();
|
|
expect(loadedPattern?.type).toBe('test-generation');
|
|
|
|
await engine2.close();
|
|
});
|
|
|
|
it('should load patterns on initialization', async () => {
|
|
const config = createTestConfig({ domain: 'test-generation' as DomainName });
|
|
|
|
// Session 1: Create patterns
|
|
const engine1 = await createPersistentSONAEngine(config);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
engine1.createPattern(
|
|
state,
|
|
action,
|
|
outcome,
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
}
|
|
|
|
const count1 = engine1.getAllPatterns().length;
|
|
await engine1.close();
|
|
|
|
// Session 2: Load patterns
|
|
const engine2 = await createPersistentSONAEngine(config);
|
|
|
|
const count2 = engine2.getAllPatterns().length;
|
|
expect(count2).toBeGreaterThanOrEqual(count1);
|
|
|
|
await engine2.close();
|
|
});
|
|
|
|
it('should export and import patterns', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
// Create patterns
|
|
for (let i = 0; i < 3; i++) {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
}
|
|
|
|
// Export patterns
|
|
const exported = engine.exportPatterns();
|
|
expect(exported.length).toBeGreaterThanOrEqual(3);
|
|
|
|
// Clear and reimport
|
|
engine.clearMemory();
|
|
expect(engine.getAllPatterns().length).toBe(0);
|
|
|
|
engine.importPatterns(exported);
|
|
expect(engine.getAllPatterns().length).toBeGreaterThanOrEqual(3);
|
|
|
|
await engine.close();
|
|
});
|
|
});
|
|
|
|
describe('Pattern Operations', () => {
|
|
let engine: PersistentSONAEngine;
|
|
|
|
beforeEach(async () => {
|
|
engine = await createPersistentSONAEngine(createTestConfig());
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await engine?.close();
|
|
});
|
|
|
|
it('should get patterns by type', () => {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
engine.createPattern(state, action, outcome, 'defect-prediction', 'test-generation');
|
|
|
|
const testGenPatterns = engine.getPatternsByType('test-generation');
|
|
expect(testGenPatterns.length).toBeGreaterThanOrEqual(1);
|
|
expect(testGenPatterns.every((p) => p.type === 'test-generation')).toBe(true);
|
|
});
|
|
|
|
it('should get patterns by domain', () => {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
|
|
const domainPatterns = engine.getPatternsByDomain('test-generation');
|
|
expect(domainPatterns.length).toBeGreaterThanOrEqual(1);
|
|
expect(domainPatterns.every((p) => p.domain === 'test-generation')).toBe(true);
|
|
});
|
|
|
|
it('should update pattern with feedback', () => {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
const pattern = engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
const initialConfidence = pattern.confidence;
|
|
|
|
// Positive feedback should increase confidence
|
|
const updated = engine.updatePattern(pattern.id, true, 0.95);
|
|
expect(updated).toBe(true);
|
|
|
|
const updatedPattern = engine.getPattern(pattern.id);
|
|
expect(updatedPattern).toBeDefined();
|
|
// Confidence should change based on feedback
|
|
expect(updatedPattern?.confidence).toBeDefined();
|
|
});
|
|
|
|
it('should delete a pattern', () => {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
const pattern = engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
expect(engine.getPattern(pattern.id)).toBeDefined();
|
|
|
|
const deleted = engine.deletePattern(pattern.id);
|
|
expect(deleted).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Cross-Agent Pattern Sharing', () => {
|
|
it('should access persisted patterns from different domains', async () => {
|
|
// Engine 1: test-generation domain
|
|
const engine1 = await createPersistentSONAEngine(
|
|
createTestConfig({ domain: 'test-generation' as DomainName })
|
|
);
|
|
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
engine1.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
await engine1.close();
|
|
|
|
// Engine 2: defect-prediction domain
|
|
const engine2 = await createPersistentSONAEngine(
|
|
createTestConfig({ domain: 'defect-prediction' as DomainName })
|
|
);
|
|
|
|
// Should be able to access patterns from other domains via persisted query
|
|
const testGenPatterns = await engine2.getPersistedPatternsByDomain('test-generation');
|
|
expect(testGenPatterns.length).toBeGreaterThanOrEqual(1);
|
|
expect(testGenPatterns[0].domain).toBe('test-generation');
|
|
|
|
await engine2.close();
|
|
});
|
|
|
|
it('should import patterns from another domain', async () => {
|
|
// Engine 1: test-generation domain creates patterns
|
|
const engine1 = await createPersistentSONAEngine(
|
|
createTestConfig({ domain: 'test-generation' as DomainName })
|
|
);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
engine1.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
}
|
|
await engine1.close();
|
|
|
|
// Engine 2: defect-prediction domain imports patterns
|
|
const engine2 = await createPersistentSONAEngine(
|
|
createTestConfig({ domain: 'defect-prediction' as DomainName })
|
|
);
|
|
|
|
const imported = await engine2.importPatternsFromDomain('test-generation');
|
|
expect(imported).toBeGreaterThanOrEqual(1);
|
|
|
|
// Imported patterns should now be in the defect-prediction domain
|
|
const domainPatterns = engine2.getPatternsByDomain('defect-prediction');
|
|
expect(domainPatterns.length).toBeGreaterThanOrEqual(imported);
|
|
|
|
await engine2.close();
|
|
});
|
|
|
|
it('should get all persisted patterns', async () => {
|
|
// Create patterns in different domains
|
|
const engine1 = await createPersistentSONAEngine(
|
|
createTestConfig({ domain: 'test-generation' as DomainName })
|
|
);
|
|
engine1.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
await engine1.close();
|
|
|
|
const engine2 = await createPersistentSONAEngine(
|
|
createTestConfig({ domain: 'defect-prediction' as DomainName })
|
|
);
|
|
engine2.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'defect-prediction',
|
|
'defect-prediction'
|
|
);
|
|
|
|
// Get all persisted patterns
|
|
const allPatterns = await engine2.getAllPersistedPatterns();
|
|
expect(allPatterns.length).toBeGreaterThanOrEqual(2);
|
|
|
|
// Should have patterns from both domains
|
|
const domains = new Set(allPatterns.map((p) => p.domain));
|
|
expect(domains.size).toBeGreaterThanOrEqual(2);
|
|
|
|
await engine2.close();
|
|
});
|
|
});
|
|
|
|
describe('Learning Operations', () => {
|
|
let engine: PersistentSONAEngine;
|
|
|
|
beforeEach(async () => {
|
|
engine = await createPersistentSONAEngine(createTestConfig());
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await engine?.close();
|
|
});
|
|
|
|
// Note: Micro-LoRA test skipped - requires specific dimension match from @ruvector/sona
|
|
// The persistence layer is independent of this low-level functionality
|
|
it.skip('should apply Micro-LoRA transformation', () => {
|
|
const input = new Array(256).fill(0).map(() => Math.random());
|
|
const output = engine.applyMicroLora(input);
|
|
|
|
expect(output).toBeDefined();
|
|
expect(output.length).toBe(input.length);
|
|
});
|
|
|
|
it('should run learning cycle', () => {
|
|
// Create some patterns first
|
|
for (let i = 0; i < 5; i++) {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
}
|
|
|
|
// Force learning cycle
|
|
const result = engine.forceLearn();
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it('should tick for background learning', () => {
|
|
// Tick should not throw
|
|
const result = engine.tick();
|
|
// Result is null if no learning was due
|
|
expect(result === null || typeof result === 'string').toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Pattern Adaptation', () => {
|
|
let engine: PersistentSONAEngine;
|
|
|
|
beforeEach(async () => {
|
|
engine = await createPersistentSONAEngine(createTestConfig());
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await engine?.close();
|
|
});
|
|
|
|
it('should adapt pattern based on context', async () => {
|
|
// Store a pattern first
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
|
|
// Adapt using similar state
|
|
const result = await engine.adaptPattern(state, 'test-generation', 'test-generation');
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.adaptationTimeMs).toBeDefined();
|
|
expect(typeof result.adaptationTimeMs).toBe('number');
|
|
expect(result.reasoning).toBeDefined();
|
|
});
|
|
|
|
it('should recall pattern for context', () => {
|
|
const state = createTestState();
|
|
const action = createTestAction();
|
|
const outcome = createTestOutcome();
|
|
|
|
engine.createPattern(state, action, outcome, 'test-generation', 'test-generation');
|
|
|
|
// May return null if no good match found (which is valid)
|
|
const recalled = engine.recallPattern(state, 'test-generation', 'test-generation');
|
|
// Just verify it doesn't throw
|
|
expect(recalled === null || typeof recalled === 'object').toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Statistics', () => {
|
|
it('should return in-memory statistics', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
const stats = engine.getStats();
|
|
|
|
expect(stats).toHaveProperty('totalPatterns');
|
|
expect(stats).toHaveProperty('patternsByType');
|
|
expect(stats).toHaveProperty('avgAdaptationTimeMs');
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should return persisted statistics', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
// Create some patterns
|
|
for (let i = 0; i < 3; i++) {
|
|
engine.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
}
|
|
|
|
const stats = await engine.getPersistedStats();
|
|
|
|
expect(stats).toHaveProperty('totalPatterns');
|
|
expect(stats).toHaveProperty('uniqueTypes');
|
|
expect(stats).toHaveProperty('uniqueDomains');
|
|
expect(stats).toHaveProperty('avgConfidence');
|
|
expect(stats).toHaveProperty('byType');
|
|
expect(stats).toHaveProperty('byDomain');
|
|
expect(stats.totalPatterns).toBeGreaterThanOrEqual(3);
|
|
|
|
await engine.close();
|
|
});
|
|
});
|
|
|
|
describe('Maintenance', () => {
|
|
it('should prune old patterns', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
// Create patterns
|
|
for (let i = 0; i < 3; i++) {
|
|
engine.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
}
|
|
|
|
// Prune patterns older than 30 days (should not delete recent patterns)
|
|
const pruned = await engine.pruneOldPatterns(30);
|
|
expect(pruned).toBe(0); // Nothing should be pruned
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should sync in-memory to SQLite', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
// Create patterns
|
|
engine.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
|
|
// Sync should not throw
|
|
await engine.sync();
|
|
|
|
// Verify persisted
|
|
const stats = await engine.getPersistedStats();
|
|
expect(stats.totalPatterns).toBeGreaterThanOrEqual(1);
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should reload patterns from SQLite', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
// Create and persist
|
|
engine.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
|
|
// Clear in-memory
|
|
engine.clearMemory();
|
|
expect(engine.getAllPatterns().length).toBe(0);
|
|
|
|
// Reload
|
|
await engine.reload();
|
|
expect(engine.getAllPatterns().length).toBeGreaterThanOrEqual(1);
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should clear domain patterns', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
// Create patterns
|
|
engine.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
|
|
expect(engine.getAllPatterns().length).toBeGreaterThanOrEqual(1);
|
|
|
|
// Clear domain patterns
|
|
engine.clearDomainPatterns();
|
|
expect(engine.getAllPatterns().length).toBe(0);
|
|
|
|
await engine.close();
|
|
});
|
|
});
|
|
|
|
describe('Configuration', () => {
|
|
it('should get configuration', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
const config = engine.getConfig();
|
|
|
|
expect(config).toHaveProperty('hiddenDim');
|
|
expect(config).toHaveProperty('embeddingDim');
|
|
|
|
await engine.close();
|
|
});
|
|
|
|
it('should enable/disable engine', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
expect(engine.isEnabled()).toBe(true);
|
|
|
|
engine.setEnabled(false);
|
|
expect(engine.isEnabled()).toBe(false);
|
|
|
|
engine.setEnabled(true);
|
|
expect(engine.isEnabled()).toBe(true);
|
|
|
|
await engine.close();
|
|
});
|
|
});
|
|
|
|
describe('Cleanup', () => {
|
|
it('should close cleanly', async () => {
|
|
const engine = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
await engine.close();
|
|
|
|
expect(engine.isInitialized()).toBe(false);
|
|
});
|
|
|
|
it('should flush pending saves on close', async () => {
|
|
// Create engine with delayed saves
|
|
const engine = await createPersistentSONAEngine(
|
|
createTestConfig({ autoSaveInterval: 5000 }) // 5 second delay
|
|
);
|
|
|
|
// Create pattern (creates pending save)
|
|
engine.createPattern(
|
|
createTestState(),
|
|
createTestAction(),
|
|
createTestOutcome(),
|
|
'test-generation',
|
|
'test-generation'
|
|
);
|
|
|
|
// Close should flush pending saves
|
|
await engine.close();
|
|
|
|
// Verify by creating new engine
|
|
const engine2 = await createPersistentSONAEngine(createTestConfig());
|
|
|
|
// Pattern should be persisted
|
|
const stats = await engine2.getPersistedStats();
|
|
expect(stats.totalPatterns).toBeGreaterThanOrEqual(1);
|
|
|
|
await engine2.close();
|
|
});
|
|
});
|
|
|
|
describe('Integration', () => {
|
|
it('should work end-to-end across sessions', async () => {
|
|
const domain = 'test-generation' as DomainName;
|
|
const config = createTestConfig({ domain });
|
|
|
|
// Session 1: Create patterns
|
|
const engine1 = await createPersistentSONAEngine(config);
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
const state = createTestState();
|
|
const action = createTestAction({ type: `action-${i}` });
|
|
const outcome = createTestOutcome({ quality: 0.6 + i * 0.08 });
|
|
|
|
const pattern = engine1.createPattern(state, action, outcome, 'test-generation', domain);
|
|
|
|
// Update with feedback
|
|
engine1.updatePattern(pattern.id, true, 0.9);
|
|
}
|
|
|
|
const stats1 = await engine1.getPersistedStats();
|
|
expect(stats1.totalPatterns).toBeGreaterThanOrEqual(5);
|
|
await engine1.close();
|
|
|
|
// Session 2: Load and continue
|
|
const engine2 = await createPersistentSONAEngine(config);
|
|
|
|
// Patterns should be loaded
|
|
expect(engine2.getAllPatterns().length).toBeGreaterThanOrEqual(5);
|
|
|
|
// Create more patterns
|
|
for (let i = 5; i < 10; i++) {
|
|
const state = createTestState();
|
|
const action = createTestAction({ type: `action-${i}` });
|
|
const outcome = createTestOutcome({ quality: 0.9 });
|
|
|
|
engine2.createPattern(state, action, outcome, 'test-generation', domain);
|
|
}
|
|
|
|
const stats2 = await engine2.getPersistedStats();
|
|
expect(stats2.totalPatterns).toBeGreaterThanOrEqual(10);
|
|
|
|
// Adapt based on stored patterns
|
|
const result = await engine2.adaptPattern(
|
|
createTestState(),
|
|
'test-generation',
|
|
domain
|
|
);
|
|
expect(result.adaptationTimeMs).toBeDefined();
|
|
|
|
await engine2.close();
|
|
});
|
|
});
|
|
});
|