mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
00e4d2cfa6
125 test files had beforeEach/beforeAll but no afterEach/afterAll, leaking state between tests and causing order-dependent failures. Added cleanup to 104 files (skipped 4 archived, 16 import-only): - 2 critical DB handle leaks (witness-chain): added db.close() - 7 high-risk service files: added vi.restoreAllMocks() - 7 beforeAll files: added matching afterAll - 88 medium/low risk: added afterEach with appropriate cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
371 lines
12 KiB
TypeScript
371 lines
12 KiB
TypeScript
/**
|
|
* Agentic QE v3 - Coverage Router Unit Tests
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach} 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());
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Reset state to prevent leaks between tests
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|