Files
proffesor-for-testing__agen…/tests/unit/optimization/auto-tuner.test.ts
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
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>
2026-03-01 11:35:41 +00:00

507 lines
15 KiB
TypeScript

/**
* Unit Tests for Auto-Tuner
* ADR-024: Self-Optimization Engine
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
AQEAutoTuner,
createAutoTuner,
DEFAULT_TUNABLE_PARAMETERS,
DEFAULT_TUNING_CONFIG,
} from '../../../src/optimization/index.js';
import type { TunableParameter, TuningConfig } from '../../../src/optimization/types.js';
describe('AQEAutoTuner', () => {
let tuner: AQEAutoTuner;
beforeEach(() => {
tuner = createAutoTuner();
});
afterEach(() => {
tuner.stop();
});
describe('Initialization', () => {
it('should create with default parameters', () => {
const state = tuner.getState();
expect(state.status).toBe('idle');
expect(Object.keys(state.currentParameters).length).toBeGreaterThan(0);
});
it('should create with custom parameters', () => {
const customParams: TunableParameter[] = [
{
type: 'numeric',
name: 'custom.param',
description: 'Custom parameter',
current: 50,
min: 0,
max: 100,
metric: 'custom_metric',
target: 80,
higherIsBetter: true,
weight: 1,
enabled: true,
},
];
const customTuner = createAutoTuner(customParams);
const state = customTuner.getState();
expect(state.currentParameters['custom.param']).toBe(50);
customTuner.stop();
});
it('should create with custom config', () => {
const customConfig: Partial<TuningConfig> = {
minSamplesBeforeTuning: 100,
autoApply: true,
};
const customTuner = createAutoTuner(undefined, customConfig);
const stats = customTuner.getStats();
expect(stats.parametersTracked).toBeGreaterThan(0);
customTuner.stop();
});
});
describe('Parameter Management', () => {
it('should get current parameter values', () => {
const values = tuner.getCurrentParameterValues();
expect(values['hnsw.efSearch']).toBeDefined();
expect(values['routing.confidenceThreshold']).toBeDefined();
});
it('should get a specific parameter', () => {
const param = tuner.getParameter('hnsw.efSearch');
expect(param).toBeDefined();
expect(param?.type).toBe('numeric');
expect(param?.name).toBe('hnsw.efSearch');
});
it('should return undefined for unknown parameter', () => {
const param = tuner.getParameter('unknown.param');
expect(param).toBeUndefined();
});
it('should update numeric parameter value', () => {
const success = tuner.setParameter('hnsw.efSearch', 200);
expect(success).toBe(true);
const param = tuner.getParameter('hnsw.efSearch');
expect(param?.current).toBe(200);
});
it('should reject out-of-range numeric value', () => {
const success = tuner.setParameter('hnsw.efSearch', 1000); // max is 500
expect(success).toBe(false);
});
it('should update categorical parameter value', () => {
const success = tuner.setParameter('testGen.complexityLimit', 'simple');
expect(success).toBe(true);
const param = tuner.getParameter('testGen.complexityLimit');
expect(param?.current).toBe('simple');
});
it('should reject invalid categorical value', () => {
const success = tuner.setParameter('testGen.complexityLimit', 'invalid');
expect(success).toBe(false);
});
it('should add new parameter', () => {
tuner.addParameter({
type: 'numeric',
name: 'new.param',
description: 'New parameter',
current: 10,
min: 0,
max: 20,
metric: 'new_metric',
target: 15,
higherIsBetter: true,
weight: 0.5,
enabled: true,
});
const param = tuner.getParameter('new.param');
expect(param).toBeDefined();
});
it('should remove parameter', () => {
tuner.addParameter({
type: 'numeric',
name: 'temp.param',
description: 'Temp parameter',
current: 10,
min: 0,
max: 20,
metric: 'temp_metric',
target: 15,
higherIsBetter: true,
weight: 0.5,
enabled: true,
});
const removed = tuner.removeParameter('temp.param');
expect(removed).toBe(true);
expect(tuner.getParameter('temp.param')).toBeUndefined();
});
it('should enable/disable parameter', () => {
tuner.setParameterEnabled('hnsw.efSearch', false);
const param = tuner.getParameter('hnsw.efSearch');
expect(param?.enabled).toBe(false);
tuner.setParameterEnabled('hnsw.efSearch', true);
expect(tuner.getParameter('hnsw.efSearch')?.enabled).toBe(true);
});
it('should track parameter history on update', () => {
tuner.setParameter('hnsw.efSearch', 150);
tuner.setParameter('hnsw.efSearch', 200);
const history = tuner.getParameterHistory();
expect(history.length).toBe(2);
expect(history[0].parameter).toBe('hnsw.efSearch');
expect(history[0].reason).toBe('Manual update');
});
});
describe('Lifecycle', () => {
it('should start and stop', () => {
expect(tuner.isRunning()).toBe(false);
tuner.start();
expect(tuner.isRunning()).toBe(true);
expect(tuner.getState().status).toBe('collecting');
tuner.stop();
expect(tuner.isRunning()).toBe(false);
expect(tuner.getState().status).toBe('idle');
});
it('should not start twice', () => {
tuner.start();
tuner.start(); // Should be no-op
expect(tuner.isRunning()).toBe(true);
});
});
describe('Statistics', () => {
it('should return initial stats', () => {
const stats = tuner.getStats();
expect(stats.totalCycles).toBe(0);
expect(stats.successfulCycles).toBe(0);
expect(stats.failedCycles).toBe(0);
expect(stats.totalImprovements).toBe(0);
expect(stats.parametersTracked).toBeGreaterThan(0);
expect(stats.metricsCollected).toBeGreaterThan(0);
});
});
describe('State Export/Import', () => {
it('should export state', () => {
tuner.setParameter('hnsw.efSearch', 150);
const exported = tuner.exportState();
expect(exported.parameters.length).toBeGreaterThan(0);
expect(exported.parameterHistory.length).toBe(1);
});
it('should import state', () => {
const exported = tuner.exportState();
exported.parameters[0].current = 999; // Modify
const newTuner = createAutoTuner();
newTuner.importState(exported);
expect(newTuner.getParameter(exported.parameters[0].name)?.current).toBe(999);
newTuner.stop();
});
it('should clear history', () => {
tuner.setParameter('hnsw.efSearch', 150);
expect(tuner.getParameterHistory().length).toBe(1);
tuner.clearHistory();
expect(tuner.getParameterHistory().length).toBe(0);
expect(tuner.getEvaluationHistory().length).toBe(0);
});
});
describe('Event Handling', () => {
it('should emit and receive events', async () => {
const events: string[] = [];
const unsubscribe = tuner.on((event) => {
events.push(event.type);
});
tuner.setParameter('hnsw.efSearch', 150);
// Trigger a cycle (will be skipped due to no data but events still emitted)
await tuner.runTuningCycle();
// At minimum, cycle-started should be emitted
expect(events).toContain('cycle-started');
// cycle-completed is also emitted even for skipped cycles
expect(events.length).toBeGreaterThanOrEqual(1);
unsubscribe();
});
it('should unsubscribe from events', () => {
const events: string[] = [];
const unsubscribe = tuner.on((event) => {
events.push(event.type);
});
unsubscribe();
tuner.setParameter('hnsw.efSearch', 150);
// Events should not be captured after unsubscribe
expect(events).toHaveLength(0);
});
});
});
describe('Default Configuration', () => {
it('should have 4 default tunable parameters', () => {
expect(DEFAULT_TUNABLE_PARAMETERS.length).toBe(4);
});
it('should have correct parameter names', () => {
const names = DEFAULT_TUNABLE_PARAMETERS.map(p => p.name);
expect(names).toContain('hnsw.efSearch');
expect(names).toContain('routing.confidenceThreshold');
expect(names).toContain('pattern.promotionThreshold');
expect(names).toContain('testGen.complexityLimit');
});
it('should have valid numeric ranges', () => {
for (const param of DEFAULT_TUNABLE_PARAMETERS) {
if (param.type === 'numeric') {
expect(param.min).toBeLessThan(param.max);
expect(param.current).toBeGreaterThanOrEqual(param.min);
expect(param.current).toBeLessThanOrEqual(param.max);
}
}
});
it('should have valid weights summing close to 1', () => {
const totalWeight = DEFAULT_TUNABLE_PARAMETERS.reduce((sum, p) => sum + p.weight, 0);
expect(totalWeight).toBeCloseTo(1, 1);
});
it('should have valid default tuning config', () => {
expect(DEFAULT_TUNING_CONFIG.minSamplesBeforeTuning).toBeGreaterThan(0);
expect(DEFAULT_TUNING_CONFIG.tuningIntervalMs).toBeGreaterThan(0);
expect(DEFAULT_TUNING_CONFIG.maxChangePerCycle).toBeGreaterThan(0);
expect(DEFAULT_TUNING_CONFIG.maxChangePerCycle).toBeLessThanOrEqual(1);
expect(DEFAULT_TUNING_CONFIG.explorationRate).toBeGreaterThanOrEqual(0);
expect(DEFAULT_TUNING_CONFIG.explorationRate).toBeLessThanOrEqual(1);
});
});
describe('Parameter Applicator Registry', () => {
let tuner: AQEAutoTuner;
beforeEach(() => {
tuner = createAutoTuner();
});
afterEach(() => {
tuner.stop();
});
it('should register and retrieve applicators', () => {
const mockApplicator = {
parameterName: 'test.param',
getCurrentValue: vi.fn().mockResolvedValue(100),
setValue: vi.fn().mockResolvedValue(undefined),
};
tuner.registerApplicator(mockApplicator);
const registry = tuner.getApplicatorRegistry();
expect(registry.get('test.param')).toBe(mockApplicator);
});
it('should apply configuration through applicators', async () => {
const mockApplicator = {
parameterName: 'hnsw.efSearch',
getCurrentValue: vi.fn().mockResolvedValue(100),
setValue: vi.fn().mockResolvedValue(undefined),
};
tuner.registerApplicator(mockApplicator);
const registry = tuner.getApplicatorRegistry();
await registry.applyConfiguration({ 'hnsw.efSearch': 200 });
expect(mockApplicator.setValue).toHaveBeenCalledWith(200);
});
it('should validate before applying if validator exists', async () => {
const mockApplicator = {
parameterName: 'hnsw.efSearch',
getCurrentValue: vi.fn().mockResolvedValue(100),
setValue: vi.fn().mockResolvedValue(undefined),
validate: vi.fn().mockReturnValue(false), // Validation fails
};
tuner.registerApplicator(mockApplicator);
const registry = tuner.getApplicatorRegistry();
await expect(registry.applyConfiguration({ 'hnsw.efSearch': 200 }))
.rejects.toThrow('Failed to apply');
// setValue should NOT be called when validation fails
expect(mockApplicator.setValue).not.toHaveBeenCalled();
});
});
describe('Type-Safe Metric Recording', () => {
let tuner: AQEAutoTuner;
beforeEach(() => {
tuner = createAutoTuner();
});
afterEach(() => {
tuner.stop();
});
it('should record search latency', async () => {
tuner.recordSearchLatency(5.5);
tuner.recordSearchLatency(6.5);
await tuner.collectMetrics();
// Verify by collecting and checking (indirectly through tuning)
const stats = tuner.getStats();
expect(stats.metricsCollected).toBe(4);
});
it('should record routing outcomes with both flags', async () => {
tuner.recordRoutingOutcome(true, true); // Followed and succeeded
tuner.recordRoutingOutcome(true, false); // Followed but failed
tuner.recordRoutingOutcome(false, true); // Not followed but succeeded
await tuner.collectMetrics();
// The routing accuracy collector should have recorded these
const stats = tuner.getStats();
expect(stats.metricsCollected).toBe(4);
});
it('should record pattern quality', async () => {
tuner.recordPatternQuality(0.85);
tuner.recordPatternQuality(0.90);
await tuner.collectMetrics();
const stats = tuner.getStats();
expect(stats.metricsCollected).toBe(4);
});
it('should record test maintainability', async () => {
tuner.recordTestMaintainability(0.75);
tuner.recordTestMaintainability(0.80);
await tuner.collectMetrics();
const stats = tuner.getStats();
expect(stats.metricsCollected).toBe(4);
});
it('should warn when using recordMetric for routing_accuracy', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
tuner.recordMetric('routing_accuracy', 0.8);
expect(warnSpy).toHaveBeenCalledWith(
'Use recordRoutingOutcome(followed, success) for routing_accuracy metric'
);
warnSpy.mockRestore();
});
});
describe('Real Evaluation with Applicators', () => {
it('should apply configuration when applicators are registered', async () => {
const appliedValues: Record<string, number | string> = {};
const tuner = createAutoTuner(
DEFAULT_TUNABLE_PARAMETERS,
{
minSamplesBeforeTuning: 1,
evaluationsPerCycle: 1,
evaluationPeriodMs: 10, // Short for testing
}
);
// Register an applicator
tuner.registerApplicator({
parameterName: 'hnsw.efSearch',
getCurrentValue: async () => 100,
setValue: async (value) => {
appliedValues['hnsw.efSearch'] = value;
},
});
// Add some metric data
for (let i = 0; i < 60; i++) {
tuner.recordSearchLatency(5 + Math.random());
tuner.recordPatternQuality(0.8 + Math.random() * 0.1);
tuner.recordTestMaintainability(0.7 + Math.random() * 0.1);
tuner.recordRoutingOutcome(true, Math.random() > 0.2);
await tuner.collectMetrics();
}
// Run a tuning cycle
await tuner.runTuningCycle();
// The applicator should have been called with a new value
expect(appliedValues['hnsw.efSearch']).toBeDefined();
tuner.stop();
});
it('should fall back to simulation mode without applicators', async () => {
const tuner = createAutoTuner(
DEFAULT_TUNABLE_PARAMETERS,
{
minSamplesBeforeTuning: 1,
evaluationsPerCycle: 1,
}
);
// Add some metric data (no applicators registered)
for (let i = 0; i < 60; i++) {
tuner.recordSearchLatency(5 + Math.random());
tuner.recordPatternQuality(0.8);
tuner.recordTestMaintainability(0.7);
tuner.recordRoutingOutcome(true, true);
await tuner.collectMetrics();
}
// Should complete without error in simulation mode
const result = await tuner.runTuningCycle();
expect(result.evaluationsPerformed).toBe(1);
expect(result.bestScore).toBeGreaterThan(0);
tuner.stop();
});
});