Files
proffesor-for-testing__agen…/tests/unit/feedback/feedback-loop.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

459 lines
16 KiB
TypeScript

/**
* Unit Tests for QualityFeedbackLoop
* ADR-023: Quality Feedback Loop System
*/
import { describe, it, expect, beforeEach } from 'vitest';
import {
QualityFeedbackLoop,
createQualityFeedbackLoop,
} from '../../../src/feedback/feedback-loop.js';
import type { TestOutcome, CoverageSession } from '../../../src/feedback/types.js';
describe('QualityFeedbackLoop', () => {
let loop: QualityFeedbackLoop;
beforeEach(() => {
loop = createQualityFeedbackLoop();
});
function createOutcome(overrides: Partial<TestOutcome> = {}): TestOutcome {
return {
id: `outcome-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
testId: `test-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
testName: 'test case',
generatedBy: 'test-agent',
framework: 'vitest',
language: 'typescript',
domain: 'test-generation',
passed: true,
flaky: false,
executionTimeMs: 100,
coverage: { lines: 80, branches: 70, functions: 85 },
maintainabilityScore: 0.8,
timestamp: new Date(),
...overrides,
};
}
function createSession(overrides: Partial<CoverageSession> = {}): CoverageSession {
return {
id: `session-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
agentId: 'test-agent',
targetPath: 'src/services/user.ts',
technique: 'gap-analysis',
beforeCoverage: { lines: 60, branches: 50, functions: 55 },
afterCoverage: { lines: 80, branches: 70, functions: 75 },
testsGenerated: 5,
testsPassed: 4,
gapsTargeted: [
{ id: 'gap-1', type: 'uncovered-branch', filePath: 'src/services/user.ts', startLine: 42, riskScore: 0.8, addressed: true },
{ id: 'gap-2', type: 'uncovered-function', filePath: 'src/services/user.ts', startLine: 100, riskScore: 0.5, addressed: true },
],
durationMs: 60000,
startedAt: new Date(Date.now() - 60000),
completedAt: new Date(),
...overrides,
};
}
describe('recordTestOutcome', () => {
it('should record outcome and return quality score', async () => {
const result = await loop.recordTestOutcome(createOutcome());
expect(result.qualityScore).toBeDefined();
expect(result.qualityScore.overall).toBeGreaterThan(0);
expect(result.qualityScore.dimensions).toBeDefined();
});
it('should track pattern when provided', async () => {
const patternId = 'test-pattern-123';
await loop.recordTestOutcome(createOutcome({ id: 'o1', testId: 't1', patternId, passed: true }));
await loop.recordTestOutcome(createOutcome({ id: 'o2', testId: 't2', patternId, passed: true }));
await loop.recordTestOutcome(createOutcome({ id: 'o3', testId: 't3', patternId, passed: false }));
const stats = loop.getStats();
expect(stats.testOutcomes.total).toBe(3);
});
it('should calculate different scores for different outcomes', async () => {
const goodResult = await loop.recordTestOutcome(createOutcome({
id: 'good-1',
testId: 'good-test-1',
passed: true,
executionTimeMs: 50,
coverage: { lines: 95, branches: 90, functions: 98 },
maintainabilityScore: 0.95,
}));
const badResult = await loop.recordTestOutcome(createOutcome({
id: 'bad-1',
testId: 'bad-test-1',
passed: false,
executionTimeMs: 5000,
coverage: { lines: 40, branches: 30, functions: 35 },
maintainabilityScore: 0.3,
}));
expect(goodResult.qualityScore.overall).toBeGreaterThan(badResult.qualityScore.overall);
});
});
describe('recordCoverageSession', () => {
it('should record session and return improvement', async () => {
const result = await loop.recordCoverageSession(createSession());
expect(result.improvement).toBeGreaterThan(0);
expect(typeof result.strategyLearned).toBe('boolean');
});
it('should learn strategy from successful session', async () => {
const result = await loop.recordCoverageSession(createSession({
beforeCoverage: { lines: 40, branches: 30, functions: 35 },
afterCoverage: { lines: 75, branches: 65, functions: 70 }, // Big improvement
}));
expect(result.strategyLearned).toBe(true);
expect(result.strategyId).toBeDefined();
});
it('should not learn strategy from low-improvement session', async () => {
const result = await loop.recordCoverageSession(createSession({
beforeCoverage: { lines: 80, branches: 75, functions: 82 },
afterCoverage: { lines: 81, branches: 76, functions: 83 }, // Tiny improvement
}));
expect(result.strategyLearned).toBe(false);
expect(result.strategyId).toBeUndefined();
});
});
describe('getRecommendedCoverageStrategy', () => {
it('should return recommended strategy after learning', async () => {
await loop.recordCoverageSession(createSession({
targetPath: 'src/services/auth.ts',
beforeCoverage: { lines: 40, branches: 30, functions: 35 },
afterCoverage: { lines: 80, branches: 70, functions: 75 },
}));
const strategy = loop.getRecommendedCoverageStrategy('src/services/user.ts');
expect(strategy).not.toBeNull();
expect(strategy!.technique).toBe('gap-analysis');
});
it('should return null when no strategies learned', () => {
const strategy = loop.getRecommendedCoverageStrategy('src/unknown/file.ts');
expect(strategy).toBeNull();
});
});
describe('getQualityRecommendations', () => {
it('should return recommendations based on outcomes', async () => {
// Add some outcomes with low coverage
for (let i = 0; i < 10; i++) {
await loop.recordTestOutcome(createOutcome({
id: `rec-${i}`,
testId: `rec-test-${i}`,
coverage: { lines: 50, branches: 40, functions: 45 }, // Low coverage
}));
}
const recommendations = loop.getQualityRecommendations();
expect(recommendations.length).toBeGreaterThan(0);
expect(recommendations.some(r => r.includes('coverage'))).toBe(true);
});
it('should return helpful message when no outcomes', () => {
const recommendations = loop.getQualityRecommendations();
expect(recommendations.length).toBe(1);
expect(recommendations[0]).toContain('No test outcomes');
});
});
describe('getStats', () => {
it('should return comprehensive statistics', async () => {
// Add test outcomes
await loop.recordTestOutcome(createOutcome({ id: 's1', testId: 'st1', passed: true }));
await loop.recordTestOutcome(createOutcome({ id: 's2', testId: 'st2', passed: true }));
await loop.recordTestOutcome(createOutcome({ id: 's3', testId: 'st3', passed: false }));
// Add coverage session
await loop.recordCoverageSession(createSession({
beforeCoverage: { lines: 40, branches: 30, functions: 35 },
afterCoverage: { lines: 75, branches: 65, functions: 70 },
}));
const stats = loop.getStats();
expect(stats.testOutcomes.total).toBe(3);
expect(stats.testOutcomes.passRate).toBeCloseTo(2 / 3, 2);
expect(stats.coverage.totalSessions).toBe(1);
expect(stats.integrationStatus.reasoningBankConnected).toBe(false);
});
it('should return zero stats when empty', () => {
const stats = loop.getStats();
expect(stats.testOutcomes.total).toBe(0);
expect(stats.coverage.totalSessions).toBe(0);
expect(stats.patterns.tracked).toBe(0);
expect(stats.routing.totalOutcomes).toBe(0);
});
});
describe('export/import data', () => {
it('should export all data', async () => {
await loop.recordTestOutcome(createOutcome({ id: 'export-1', testId: 'export-test-1' }));
await loop.recordCoverageSession(createSession({
beforeCoverage: { lines: 40, branches: 30, functions: 35 },
afterCoverage: { lines: 75, branches: 65, functions: 70 },
}));
const data = loop.exportData();
expect(data.outcomes.length).toBe(1);
expect(data.sessions.length).toBe(1);
expect(data.strategies.length).toBe(1);
});
it('should import data into new loop', async () => {
await loop.recordTestOutcome(createOutcome({ id: 'import-1', testId: 'import-test-1' }));
await loop.recordCoverageSession(createSession({
beforeCoverage: { lines: 40, branches: 30, functions: 35 },
afterCoverage: { lines: 75, branches: 65, functions: 70 },
}));
const data = loop.exportData();
// Create new loop and import
const newLoop = createQualityFeedbackLoop();
newLoop.importData(data);
const stats = newLoop.getStats();
expect(stats.testOutcomes.total).toBe(1);
expect(stats.coverage.totalSessions).toBe(1);
});
});
describe('clear', () => {
it('should clear all data', async () => {
await loop.recordTestOutcome(createOutcome({ id: 'clear-1', testId: 'clear-test-1' }));
await loop.recordCoverageSession(createSession({
beforeCoverage: { lines: 40, branches: 30, functions: 35 },
afterCoverage: { lines: 75, branches: 65, functions: 70 },
}));
loop.clear();
const stats = loop.getStats();
expect(stats.testOutcomes.total).toBe(0);
expect(stats.coverage.totalSessions).toBe(0);
});
});
describe('custom configuration', () => {
it('should accept custom quality weights', () => {
const customLoop = createQualityFeedbackLoop({
qualityWeights: {
effectiveness: 0.5,
coverage: 0.3,
mutationKill: 0.05,
stability: 0.05,
maintainability: 0.05,
performance: 0.05,
},
});
const weights = customLoop.qualityCalculator.getWeights();
expect(weights.effectiveness).toBe(0.5);
expect(weights.coverage).toBe(0.3);
});
it('should accept custom feedback config', () => {
const customLoop = createQualityFeedbackLoop({
maxOutcomesInMemory: 500,
minCoverageImprovementToLearn: 10,
autoDemote: false,
});
// Verify config took effect by checking behavior
expect(customLoop).toBeDefined();
});
});
describe('component access', () => {
it('should provide access to underlying components', () => {
expect(loop.outcomeTracker).toBeDefined();
expect(loop.coverageLearner).toBeDefined();
expect(loop.qualityCalculator).toBeDefined();
expect(loop.promotionManager).toBeDefined();
expect(loop.routingFeedback).toBeDefined();
});
});
describe('routing feedback', () => {
it('should record routing outcomes', async () => {
await loop.recordRoutingOutcome({
taskId: 'task-1',
taskDescription: 'Generate unit tests',
recommendedAgent: 'qe-test-generator',
usedAgent: 'qe-test-generator',
followedRecommendation: true,
success: true,
qualityScore: 0.85,
durationMs: 5000,
timestamp: new Date(),
});
const stats = loop.getStats();
expect(stats.routing.totalOutcomes).toBe(1);
expect(stats.routing.recommendationFollowRate).toBe(1);
});
it('should track when recommendations are overridden', async () => {
await loop.recordRoutingOutcome({
taskId: 'task-1',
taskDescription: 'Generate tests',
recommendedAgent: 'agent-a',
usedAgent: 'agent-a',
followedRecommendation: true,
success: true,
qualityScore: 0.8,
durationMs: 3000,
timestamp: new Date(),
});
await loop.recordRoutingOutcome({
taskId: 'task-2',
taskDescription: 'Analyze coverage',
recommendedAgent: 'agent-a',
usedAgent: 'agent-b',
followedRecommendation: false,
success: true,
qualityScore: 0.9,
durationMs: 2000,
timestamp: new Date(),
});
const stats = loop.getStats();
expect(stats.routing.totalOutcomes).toBe(2);
expect(stats.routing.recommendationFollowRate).toBe(0.5);
});
it('should provide routing analysis', async () => {
// Record multiple outcomes
for (let i = 0; i < 5; i++) {
await loop.recordRoutingOutcome({
taskId: `task-${i}`,
taskDescription: `Task ${i}`,
recommendedAgent: 'agent-a',
usedAgent: i < 3 ? 'agent-a' : 'agent-b',
followedRecommendation: i < 3,
success: i !== 2,
qualityScore: 0.7 + i * 0.05,
durationMs: 1000 + i * 500,
timestamp: new Date(),
});
}
const analysis = loop.getRoutingAnalysis();
expect(analysis.totalOutcomes).toBe(5);
expect(analysis.recommendationFollowRate).toBe(0.6); // 3/5 followed
expect(analysis.recommendations.length).toBeGreaterThan(0);
});
it('should export and import routing outcomes', async () => {
await loop.recordRoutingOutcome({
taskId: 'export-task',
taskDescription: 'Export test',
recommendedAgent: 'agent-a',
usedAgent: 'agent-a',
followedRecommendation: true,
success: true,
qualityScore: 0.85,
durationMs: 2000,
timestamp: new Date(),
});
const data = loop.exportData();
expect(data.routingOutcomes.length).toBe(1);
// Import into new loop
const newLoop = createQualityFeedbackLoop();
newLoop.importData(data);
const stats = newLoop.getStats();
expect(stats.routing.totalOutcomes).toBe(1);
});
it('should clear routing data', async () => {
await loop.recordRoutingOutcome({
taskId: 'clear-task',
taskDescription: 'Clear test',
recommendedAgent: 'agent-a',
usedAgent: 'agent-a',
followedRecommendation: true,
success: true,
qualityScore: 0.85,
durationMs: 2000,
timestamp: new Date(),
});
loop.clear();
const stats = loop.getStats();
expect(stats.routing.totalOutcomes).toBe(0);
});
});
describe('integration flow', () => {
it('should support full feedback loop cycle', async () => {
// 1. Record initial outcomes
for (let i = 0; i < 5; i++) {
await loop.recordTestOutcome(createOutcome({
id: `cycle-${i}`,
testId: `cycle-test-${i}`,
passed: i < 4, // 4 pass, 1 fail
coverage: { lines: 60 + i * 5, branches: 50 + i * 5, functions: 55 + i * 5 },
}));
}
// 2. Get quality recommendations
const recommendations = loop.getQualityRecommendations();
expect(recommendations.length).toBeGreaterThan(0);
// 3. Record coverage improvement session
const coverageResult = await loop.recordCoverageSession(createSession({
beforeCoverage: { lines: 60, branches: 50, functions: 55 },
afterCoverage: { lines: 85, branches: 80, functions: 82 },
}));
expect(coverageResult.strategyLearned).toBe(true);
// 4. Get recommended strategy for similar file
const strategy = loop.getRecommendedCoverageStrategy('src/services/order.ts');
expect(strategy).not.toBeNull();
// 5. Record improved outcomes after applying strategy
for (let i = 0; i < 5; i++) {
await loop.recordTestOutcome(createOutcome({
id: `improved-${i}`,
testId: `improved-test-${i}`,
passed: true,
coverage: { lines: 85 + i, branches: 80 + i, functions: 82 + i },
maintainabilityScore: 0.9,
}));
}
// 6. Verify quality improved
const finalStats = loop.getStats();
expect(finalStats.testOutcomes.total).toBe(10);
expect(finalStats.testOutcomes.passRate).toBeGreaterThan(0.8);
});
});
});