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>
367 lines
12 KiB
TypeScript
367 lines
12 KiB
TypeScript
/**
|
|
* Agentic QE v3 - Coverage Router Unit Tests
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import {
|
|
createCoverageRouter,
|
|
RuVectorCoverageRouter,
|
|
FallbackCoverageRouter,
|
|
} from '../../../../src/integrations/ruvector';
|
|
import type {
|
|
RuVectorConfig,
|
|
FileCoverage,
|
|
} from '../../../../src/integrations/ruvector';
|
|
|
|
// Test fixtures
|
|
function createFileCoverage(overrides: Partial<FileCoverage> = {}): FileCoverage {
|
|
return {
|
|
filePath: 'src/services/user-service.ts',
|
|
lineCoverage: 75,
|
|
branchCoverage: 60,
|
|
functionCoverage: 80,
|
|
statementCoverage: 70,
|
|
uncoveredLines: [10, 11, 12, 25, 30],
|
|
uncoveredBranches: [
|
|
{ line: 15, branch: 1 },
|
|
{ line: 20, branch: 0 },
|
|
],
|
|
uncoveredFunctions: ['validateUser', 'processPayment'],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createConfig(overrides: Partial<RuVectorConfig> = {}): RuVectorConfig {
|
|
return {
|
|
enabled: true,
|
|
endpoint: 'http://localhost:8080',
|
|
fallbackEnabled: true,
|
|
cacheEnabled: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('Coverage Router', () => {
|
|
describe('Factory Function', () => {
|
|
it('should create RuVectorCoverageRouter when enabled', async () => {
|
|
const router = await createCoverageRouter(createConfig({ enabled: true }));
|
|
expect(router).toBeInstanceOf(RuVectorCoverageRouter);
|
|
});
|
|
|
|
it('should create FallbackCoverageRouter when disabled', async () => {
|
|
const router = await createCoverageRouter(createConfig({ enabled: false }));
|
|
expect(router).toBeInstanceOf(FallbackCoverageRouter);
|
|
});
|
|
});
|
|
|
|
describe('RuVectorCoverageRouter', () => {
|
|
let router: RuVectorCoverageRouter;
|
|
|
|
beforeEach(() => {
|
|
router = new RuVectorCoverageRouter(createConfig());
|
|
});
|
|
|
|
describe('analyzeCoverage', () => {
|
|
it('should analyze coverage data', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({ filePath: 'src/a.ts', lineCoverage: 50 }),
|
|
createFileCoverage({ filePath: 'src/b.ts', lineCoverage: 90 }),
|
|
];
|
|
|
|
const result = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
expect(result).toHaveProperty('prioritizedFiles');
|
|
expect(result).toHaveProperty('testGenerationTargets');
|
|
expect(result).toHaveProperty('agentAssignments');
|
|
expect(result.usedFallback).toBe(false);
|
|
});
|
|
|
|
it('should prioritize files below target coverage', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({ filePath: 'src/low.ts', lineCoverage: 30 }),
|
|
createFileCoverage({ filePath: 'src/medium.ts', lineCoverage: 60 }),
|
|
createFileCoverage({ filePath: 'src/high.ts', lineCoverage: 90 }),
|
|
];
|
|
|
|
const result = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
// Only files below 80% should be prioritized
|
|
const prioritizedPaths = result.prioritizedFiles.map((f) => f.filePath);
|
|
expect(prioritizedPaths).toContain('src/low.ts');
|
|
expect(prioritizedPaths).toContain('src/medium.ts');
|
|
expect(prioritizedPaths).not.toContain('src/high.ts');
|
|
});
|
|
|
|
it('should assign higher priority to lower coverage files', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({ filePath: 'src/a.ts', lineCoverage: 20 }),
|
|
createFileCoverage({ filePath: 'src/b.ts', lineCoverage: 60 }),
|
|
];
|
|
|
|
const result = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
const priorities = result.prioritizedFiles.map((f) => f.priority);
|
|
expect(['p0', 'p1']).toContain(priorities[0]);
|
|
});
|
|
|
|
it('should include gaps for each prioritized file', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
filePath: 'src/test.ts',
|
|
lineCoverage: 50,
|
|
uncoveredLines: [1, 2, 3],
|
|
}),
|
|
];
|
|
|
|
const result = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
expect(result.prioritizedFiles[0].gaps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should identify test generation targets', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
uncoveredFunctions: ['func1', 'func2'],
|
|
}),
|
|
];
|
|
|
|
const result = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
expect(result.testGenerationTargets.length).toBeGreaterThan(0);
|
|
expect(result.testGenerationTargets[0].functions).toContain('func1');
|
|
});
|
|
|
|
it('should assign agents for coverage improvement', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({ filePath: 'src/critical.ts', lineCoverage: 20 }),
|
|
createFileCoverage({ filePath: 'src/high.ts', lineCoverage: 45 }),
|
|
createFileCoverage({ filePath: 'src/medium.ts', lineCoverage: 65 }),
|
|
];
|
|
|
|
const result = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
expect(result.agentAssignments.length).toBeGreaterThan(0);
|
|
|
|
for (const assignment of result.agentAssignments) {
|
|
expect(assignment).toHaveProperty('agentType');
|
|
expect(assignment).toHaveProperty('domain');
|
|
expect(assignment).toHaveProperty('files');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('getCoverageGaps', () => {
|
|
it('should identify line coverage gaps', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
lineCoverage: 50,
|
|
uncoveredLines: [1, 2, 3, 10, 11],
|
|
}),
|
|
];
|
|
|
|
const gaps = await router.getCoverageGaps(coverageData);
|
|
|
|
const lineGaps = gaps.filter((g) => g.gapType === 'line');
|
|
expect(lineGaps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should identify branch coverage gaps', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
branchCoverage: 40,
|
|
uncoveredBranches: [{ line: 5, branch: 0 }],
|
|
}),
|
|
];
|
|
|
|
const gaps = await router.getCoverageGaps(coverageData);
|
|
|
|
const branchGaps = gaps.filter((g) => g.gapType === 'branch');
|
|
expect(branchGaps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should identify function coverage gaps', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
functionCoverage: 60,
|
|
uncoveredFunctions: ['myFunction'],
|
|
}),
|
|
];
|
|
|
|
const gaps = await router.getCoverageGaps(coverageData);
|
|
|
|
const functionGaps = gaps.filter((g) => g.gapType === 'function');
|
|
expect(functionGaps.length).toBeGreaterThan(0);
|
|
expect(functionGaps[0].functions).toContain('myFunction');
|
|
});
|
|
|
|
it('should detect integration test gaps', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
functionCoverage: 85,
|
|
lineCoverage: 45,
|
|
branchCoverage: 30,
|
|
}),
|
|
];
|
|
|
|
const gaps = await router.getCoverageGaps(coverageData);
|
|
|
|
const integrationGaps = gaps.filter((g) => g.gapType === 'integration');
|
|
expect(integrationGaps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should assign severity based on coverage level', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
lineCoverage: 20, // Critical
|
|
uncoveredLines: [1, 2, 3],
|
|
}),
|
|
];
|
|
|
|
const gaps = await router.getCoverageGaps(coverageData);
|
|
|
|
expect(gaps.some((g) => g.severity === 'critical')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('prioritizeForCoverage', () => {
|
|
it('should sort files by coverage ascending', async () => {
|
|
const files = ['src/a.ts', 'src/b.ts', 'src/c.ts'];
|
|
const coverageData = [
|
|
createFileCoverage({ filePath: 'src/a.ts', lineCoverage: 80 }),
|
|
createFileCoverage({ filePath: 'src/b.ts', lineCoverage: 40 }),
|
|
createFileCoverage({ filePath: 'src/c.ts', lineCoverage: 60 }),
|
|
];
|
|
|
|
const prioritized = await router.prioritizeForCoverage(files, coverageData);
|
|
|
|
expect(prioritized[0]).toBe('src/b.ts'); // Lowest coverage
|
|
expect(prioritized[1]).toBe('src/c.ts');
|
|
expect(prioritized[2]).toBe('src/a.ts'); // Highest coverage
|
|
});
|
|
|
|
it('should handle files not in coverage data', async () => {
|
|
const files = ['src/a.ts', 'src/missing.ts'];
|
|
const coverageData = [
|
|
createFileCoverage({ filePath: 'src/a.ts', lineCoverage: 50 }),
|
|
];
|
|
|
|
const prioritized = await router.prioritizeForCoverage(files, coverageData);
|
|
|
|
expect(prioritized).toContain('src/a.ts');
|
|
expect(prioritized).toContain('src/missing.ts');
|
|
});
|
|
});
|
|
|
|
describe('suggestTestsForCoverage', () => {
|
|
it('should suggest unit tests for uncovered functions', async () => {
|
|
const coverage = createFileCoverage({
|
|
uncoveredFunctions: ['funcA', 'funcB'],
|
|
});
|
|
|
|
const suggestions = await router.suggestTestsForCoverage(
|
|
'src/test.ts',
|
|
coverage
|
|
);
|
|
|
|
const unitTests = suggestions.filter((s) => s.testType === 'unit');
|
|
expect(unitTests.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('should suggest branch tests for uncovered branches', async () => {
|
|
const coverage = createFileCoverage({
|
|
branchCoverage: 50,
|
|
uncoveredBranches: [{ line: 10, branch: 0 }],
|
|
});
|
|
|
|
const suggestions = await router.suggestTestsForCoverage(
|
|
'src/test.ts',
|
|
coverage
|
|
);
|
|
|
|
const branchTests = suggestions.filter((s) => s.testType === 'branch');
|
|
expect(branchTests.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should suggest line coverage tests for large gaps', async () => {
|
|
const coverage = createFileCoverage({
|
|
lineCoverage: 40,
|
|
uncoveredLines: Array.from({ length: 20 }, (_, i) => i + 1),
|
|
});
|
|
|
|
const suggestions = await router.suggestTestsForCoverage(
|
|
'src/test.ts',
|
|
coverage
|
|
);
|
|
|
|
const lineTests = suggestions.filter((s) => s.testType === 'line');
|
|
expect(lineTests.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should include expected coverage gain', async () => {
|
|
const coverage = createFileCoverage({
|
|
functionCoverage: 50,
|
|
uncoveredFunctions: ['func'],
|
|
});
|
|
|
|
const suggestions = await router.suggestTestsForCoverage(
|
|
'src/test.ts',
|
|
coverage
|
|
);
|
|
|
|
expect(suggestions[0]).toHaveProperty('expectedCoverageGain');
|
|
expect(suggestions[0].expectedCoverageGain).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('FallbackCoverageRouter', () => {
|
|
let router: FallbackCoverageRouter;
|
|
|
|
beforeEach(() => {
|
|
router = new FallbackCoverageRouter();
|
|
});
|
|
|
|
it('should analyze coverage using rules', async () => {
|
|
const coverageData = [createFileCoverage()];
|
|
const result = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
expect(result.usedFallback).toBe(true);
|
|
expect(result).toHaveProperty('prioritizedFiles');
|
|
});
|
|
|
|
it('should identify coverage gaps', async () => {
|
|
const coverageData = [
|
|
createFileCoverage({
|
|
uncoveredLines: [1, 2, 3],
|
|
uncoveredFunctions: ['test'],
|
|
}),
|
|
];
|
|
|
|
const gaps = await router.getCoverageGaps(coverageData);
|
|
|
|
expect(gaps.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Integration', () => {
|
|
it('should fallback when RuVector disabled', async () => {
|
|
const router = await createCoverageRouter(createConfig({ enabled: false }));
|
|
const result = await router.analyzeCoverage([createFileCoverage()], 80);
|
|
|
|
expect(result.usedFallback).toBe(true);
|
|
});
|
|
|
|
it('should cache results when enabled', async () => {
|
|
const router = new RuVectorCoverageRouter(
|
|
createConfig({ cacheEnabled: true, cacheTtl: 60000 })
|
|
);
|
|
const coverageData = [createFileCoverage()];
|
|
|
|
const result1 = await router.analyzeCoverage(coverageData, 80);
|
|
const result2 = await router.analyzeCoverage(coverageData, 80);
|
|
|
|
expect(result1.prioritizedFiles.length).toBe(result2.prioritizedFiles.length);
|
|
});
|
|
});
|
|
});
|