tasq/node_modules/@claude-flow/neural/__tests__/sona.test.ts

446 lines
13 KiB
TypeScript

/**
* SONA Learning Engine Tests
*
* Tests for SONA integration with @ruvector/sona package.
* Covers initialization, learning, adaptation, mode switching, and performance.
*
* Performance targets:
* - learn(): <0.05ms
* - adapt(): <0.1ms
* - Full learning cycle: <10ms
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
SONALearningEngine,
createSONALearningEngine,
type Context,
type AdaptedBehavior,
} from '../src/sona-integration.js';
import type { Trajectory, SONAMode, SONAModeConfig } from '../src/types.js';
// Create a reusable mock engine factory
function createMockEngine() {
let enabled = true;
return {
beginTrajectory: vi.fn().mockReturnValue(1),
addTrajectoryStep: vi.fn(),
addTrajectoryContext: vi.fn(),
endTrajectory: vi.fn(),
flush: vi.fn(),
applyMicroLora: vi.fn((arr: number[]) => arr),
findPatterns: vi.fn().mockReturnValue([
{
patternType: 'test-pattern',
avgQuality: 0.85,
embedding: new Float32Array(768),
usageCount: 5,
},
]),
forceLearn: vi.fn().mockReturnValue('Learning complete'),
tick: vi.fn().mockReturnValue(null),
getStats: vi.fn().mockReturnValue(JSON.stringify({
total_trajectories: 10,
patterns_learned: 5,
avg_quality: 0.75,
})),
isEnabled: vi.fn(() => enabled),
setEnabled: vi.fn((value: boolean) => { enabled = value; }),
};
}
// Mock @ruvector/sona
vi.mock('@ruvector/sona', () => {
return {
SonaEngine: {
withConfig: vi.fn(() => createMockEngine()),
},
};
});
describe('SONALearningEngine', () => {
let engine: SONALearningEngine;
let modeConfig: SONAModeConfig;
beforeEach(() => {
modeConfig = {
mode: 'balanced',
loraRank: 4,
learningRate: 0.002,
batchSize: 32,
trajectoryCapacity: 3000,
patternClusters: 50,
qualityThreshold: 0.5,
maxLatencyMs: 18,
memoryBudgetMb: 50,
ewcLambda: 2000,
};
engine = new SONALearningEngine('balanced', modeConfig);
});
describe('Initialization', () => {
it('should initialize with balanced mode', () => {
expect(engine).toBeDefined();
expect(engine.isEnabled()).toBe(true);
});
it('should initialize with real-time mode', () => {
const rtEngine = createSONALearningEngine('real-time', {
mode: 'real-time',
loraRank: 2,
learningRate: 0.001,
batchSize: 32,
trajectoryCapacity: 1000,
patternClusters: 25,
qualityThreshold: 0.7,
maxLatencyMs: 0.5,
memoryBudgetMb: 25,
ewcLambda: 2000,
});
expect(rtEngine).toBeDefined();
expect(rtEngine.isEnabled()).toBe(true);
});
it('should initialize with research mode', () => {
const researchEngine = createSONALearningEngine('research', {
mode: 'research',
loraRank: 16,
learningRate: 0.002,
batchSize: 64,
trajectoryCapacity: 10000,
patternClusters: 100,
qualityThreshold: 0.2,
maxLatencyMs: 100,
memoryBudgetMb: 100,
ewcLambda: 2500,
});
expect(researchEngine).toBeDefined();
});
it('should initialize with edge mode', () => {
const edgeEngine = createSONALearningEngine('edge', {
mode: 'edge',
loraRank: 1,
learningRate: 0.001,
batchSize: 16,
trajectoryCapacity: 200,
patternClusters: 15,
qualityThreshold: 0.8,
maxLatencyMs: 1,
memoryBudgetMb: 5,
ewcLambda: 1500,
});
expect(edgeEngine).toBeDefined();
});
it('should initialize with batch mode', () => {
const batchEngine = createSONALearningEngine('batch', {
mode: 'batch',
loraRank: 8,
learningRate: 0.002,
batchSize: 128,
trajectoryCapacity: 5000,
patternClusters: 75,
qualityThreshold: 0.4,
maxLatencyMs: 50,
memoryBudgetMb: 75,
ewcLambda: 2000,
});
expect(batchEngine).toBeDefined();
});
});
describe('Learning from Trajectories', () => {
it('should learn from a complete trajectory', async () => {
const trajectory: Trajectory = {
trajectoryId: 'test-traj-1',
context: 'Test task',
domain: 'code',
steps: [
{
stepId: 'step-1',
timestamp: Date.now(),
action: 'analyze',
stateBefore: new Float32Array(768).fill(0.1),
stateAfter: new Float32Array(768).fill(0.2),
reward: 0.8,
},
{
stepId: 'step-2',
timestamp: Date.now(),
action: 'implement',
stateBefore: new Float32Array(768).fill(0.2),
stateAfter: new Float32Array(768).fill(0.3),
reward: 0.9,
},
],
qualityScore: 0.85,
isComplete: true,
startTime: Date.now() - 1000,
endTime: Date.now(),
};
await expect(engine.learn(trajectory)).resolves.not.toThrow();
});
it('should complete learning under performance target (<0.05ms)', async () => {
const trajectory: Trajectory = {
trajectoryId: 'perf-test',
context: 'Performance test',
domain: 'code',
steps: [
{
stepId: 'step-1',
timestamp: Date.now(),
action: 'test',
stateBefore: new Float32Array(768).fill(0.5),
stateAfter: new Float32Array(768).fill(0.6),
reward: 0.7,
},
],
qualityScore: 0.7,
isComplete: true,
startTime: Date.now(),
};
const startTime = performance.now();
await engine.learn(trajectory);
const elapsed = performance.now() - startTime;
// Note: This is the JS call overhead, actual SONA is <0.05ms
expect(elapsed).toBeLessThan(10); // Allow overhead for mocking
expect(engine.getLearningTime()).toBeDefined();
});
it('should handle empty trajectories gracefully', async () => {
const emptyTrajectory: Trajectory = {
trajectoryId: 'empty-traj',
context: 'Empty test',
domain: 'general',
steps: [],
qualityScore: 0,
isComplete: true,
startTime: Date.now(),
};
await expect(engine.learn(emptyTrajectory)).resolves.not.toThrow();
});
it('should learn from multi-step trajectories', async () => {
const multiStepTrajectory: Trajectory = {
trajectoryId: 'multi-step',
context: 'Complex task',
domain: 'reasoning',
steps: Array.from({ length: 10 }, (_, i) => ({
stepId: `step-${i}`,
timestamp: Date.now() + i * 100,
action: `action-${i}`,
stateBefore: new Float32Array(768).fill(i * 0.1),
stateAfter: new Float32Array(768).fill((i + 1) * 0.1),
reward: 0.5 + i * 0.05,
})),
qualityScore: 0.9,
isComplete: true,
startTime: Date.now(),
endTime: Date.now() + 1000,
};
await expect(engine.learn(multiStepTrajectory)).resolves.not.toThrow();
});
});
describe('Adaptation', () => {
it('should adapt behavior based on context', async () => {
const context: Context = {
domain: 'code',
queryEmbedding: new Float32Array(768).fill(0.5),
metadata: { task: 'implementation' },
};
const result = await engine.adapt(context);
expect(result).toBeDefined();
expect(result.transformedQuery).toBeInstanceOf(Float32Array);
expect(result.patterns).toBeDefined();
expect(result.confidence).toBeGreaterThanOrEqual(0);
expect(result.confidence).toBeLessThanOrEqual(1);
});
it('should complete adaptation under performance target (<0.1ms)', async () => {
const context: Context = {
domain: 'code',
queryEmbedding: new Float32Array(768).fill(0.5),
};
const startTime = performance.now();
await engine.adapt(context);
const elapsed = performance.now() - startTime;
expect(elapsed).toBeLessThan(10); // Allow overhead for mocking
expect(engine.getAdaptationTime()).toBeDefined();
});
it('should find similar patterns during adaptation', async () => {
const context: Context = {
domain: 'code',
queryEmbedding: new Float32Array(768).fill(0.7),
};
const result = await engine.adapt(context);
expect(result.patterns).toHaveLength(1);
expect(result.patterns[0].patternType).toBe('test-pattern');
expect(result.patterns[0].avgQuality).toBeCloseTo(0.85);
});
it('should infer suggested route from patterns', async () => {
const context: Context = {
domain: 'creative',
queryEmbedding: new Float32Array(768).fill(0.3),
};
const result = await engine.adapt(context);
expect(result.suggestedRoute).toBeDefined();
expect(typeof result.suggestedRoute).toBe('string');
});
it('should handle adaptation with no patterns found', async () => {
const { SonaEngine } = await import('@ruvector/sona');
// Create a complete mock with findPatterns returning empty array
const emptyPatternsEngine = {
beginTrajectory: vi.fn().mockReturnValue(1),
addTrajectoryStep: vi.fn(),
addTrajectoryContext: vi.fn(),
endTrajectory: vi.fn(),
flush: vi.fn(),
applyMicroLora: vi.fn((arr: number[]) => arr),
findPatterns: vi.fn().mockReturnValue([]),
forceLearn: vi.fn().mockReturnValue('Learning complete'),
tick: vi.fn().mockReturnValue(null),
getStats: vi.fn().mockReturnValue(JSON.stringify({
total_trajectories: 0,
patterns_learned: 0,
avg_quality: 0,
})),
isEnabled: vi.fn().mockReturnValue(true),
setEnabled: vi.fn(),
};
vi.mocked(SonaEngine.withConfig).mockReturnValueOnce(emptyPatternsEngine as any);
const freshEngine = new SONALearningEngine('balanced', modeConfig);
const context: Context = {
domain: 'code',
queryEmbedding: new Float32Array(768).fill(0.5),
};
const result = await freshEngine.adapt(context);
expect(result.patterns).toHaveLength(0);
expect(result.confidence).toBeCloseTo(0.5); // Default confidence
});
});
describe('Mode Switching', () => {
it('should switch between modes correctly', () => {
const modes: SONAMode[] = ['real-time', 'balanced', 'research', 'edge', 'batch'];
modes.forEach(mode => {
const testEngine = createSONALearningEngine(mode, {
mode,
loraRank: 4,
learningRate: 0.002,
batchSize: 32,
trajectoryCapacity: 3000,
patternClusters: 50,
qualityThreshold: 0.5,
maxLatencyMs: 18,
memoryBudgetMb: 50,
ewcLambda: 2000,
});
expect(testEngine).toBeDefined();
});
});
it('should reset learning state', () => {
expect(() => engine.resetLearning()).not.toThrow();
expect(engine.getLearningTime()).toBe(0);
expect(engine.getAdaptationTime()).toBe(0);
});
});
describe('Statistics and Monitoring', () => {
it('should return engine statistics', () => {
const stats = engine.getStats();
expect(stats).toBeDefined();
expect(stats.totalTrajectories).toBe(10);
expect(stats.patternsLearned).toBe(5);
expect(stats.avgQuality).toBeCloseTo(0.75);
expect(stats.enabled).toBe(true);
});
it('should track learning time', async () => {
const trajectory: Trajectory = {
trajectoryId: 'timing-test',
context: 'Timing test',
domain: 'code',
steps: [
{
stepId: 'step-1',
timestamp: Date.now(),
action: 'test',
stateBefore: new Float32Array(768).fill(0.5),
stateAfter: new Float32Array(768).fill(0.6),
reward: 0.7,
},
],
qualityScore: 0.7,
isComplete: true,
startTime: Date.now(),
};
await engine.learn(trajectory);
expect(engine.getLearningTime()).toBeGreaterThan(0);
});
it('should track adaptation time', async () => {
const context: Context = {
domain: 'code',
queryEmbedding: new Float32Array(768).fill(0.5),
};
await engine.adapt(context);
expect(engine.getAdaptationTime()).toBeGreaterThan(0);
});
});
describe('Engine Control', () => {
it('should enable and disable engine', () => {
engine.setEnabled(false);
expect(engine.isEnabled()).toBe(false);
engine.setEnabled(true);
expect(engine.isEnabled()).toBe(true);
});
it('should force immediate learning', () => {
const result = engine.forceLearning();
expect(result).toBe('Learning complete');
});
it('should tick background learning', () => {
const result = engine.tick();
expect(result).toBeNull(); // No learning needed yet
});
it('should find patterns by query embedding', () => {
const queryEmbedding = new Float32Array(768).fill(0.5);
const patterns = engine.findPatterns(queryEmbedding, 5);
expect(patterns).toBeDefined();
expect(Array.isArray(patterns)).toBe(true);
});
});
});