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>
248 lines
7.4 KiB
TypeScript
248 lines
7.4 KiB
TypeScript
/**
|
|
* Unit Tests for TestOutcomeTracker
|
|
* ADR-023: Quality Feedback Loop System
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
import {
|
|
TestOutcomeTracker,
|
|
createTestOutcomeTracker,
|
|
} from '../../../src/feedback/test-outcome-tracker.js';
|
|
import type { TestOutcome } from '../../../src/feedback/types.js';
|
|
|
|
describe('TestOutcomeTracker', () => {
|
|
let tracker: TestOutcomeTracker;
|
|
|
|
beforeEach(() => {
|
|
tracker = createTestOutcomeTracker();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Reset state to prevent leaks between tests
|
|
});
|
|
|
|
function createOutcome(overrides: Partial<TestOutcome> = {}): TestOutcome {
|
|
return {
|
|
id: `outcome-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
testId: `test-${Date.now()}`,
|
|
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,
|
|
};
|
|
}
|
|
|
|
describe('track', () => {
|
|
it('should track a passing test outcome', async () => {
|
|
const outcome = createOutcome({
|
|
passed: true,
|
|
});
|
|
|
|
await tracker.track(outcome);
|
|
const stats = tracker.getStats();
|
|
|
|
expect(stats.totalTests).toBe(1);
|
|
expect(stats.passedTests).toBe(1);
|
|
expect(stats.passRate).toBe(1);
|
|
});
|
|
|
|
it('should track a failing test outcome', async () => {
|
|
const outcome = createOutcome({
|
|
passed: false,
|
|
errorMessage: 'Test failed',
|
|
});
|
|
|
|
await tracker.track(outcome);
|
|
const stats = tracker.getStats();
|
|
|
|
expect(stats.totalTests).toBe(1);
|
|
expect(stats.passedTests).toBe(0);
|
|
expect(stats.passRate).toBe(0);
|
|
});
|
|
|
|
it('should track flaky tests', async () => {
|
|
const outcome = createOutcome({
|
|
passed: true,
|
|
flaky: true,
|
|
flakinessScore: 0.6,
|
|
});
|
|
|
|
await tracker.track(outcome);
|
|
const stats = tracker.getStats();
|
|
|
|
expect(stats.flakyTests).toBe(1);
|
|
});
|
|
|
|
it('should track outcomes with pattern associations', async () => {
|
|
const outcome = createOutcome({
|
|
patternId: 'pattern-123',
|
|
passed: true,
|
|
});
|
|
|
|
await tracker.track(outcome);
|
|
const metrics = tracker.getPatternMetrics('pattern-123');
|
|
|
|
expect(metrics).toBeDefined();
|
|
expect(metrics!.successCount).toBe(1);
|
|
expect(metrics!.failureCount).toBe(0);
|
|
expect(metrics!.successRate).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('trackBatch', () => {
|
|
it('should track multiple outcomes at once', async () => {
|
|
const outcomes: TestOutcome[] = [
|
|
createOutcome({ id: 'batch-1', testId: 'test-1', passed: true }),
|
|
createOutcome({ id: 'batch-2', testId: 'test-2', passed: false }),
|
|
createOutcome({ id: 'batch-3', testId: 'test-3', passed: true }),
|
|
];
|
|
|
|
await tracker.trackBatch(outcomes);
|
|
const stats = tracker.getStats();
|
|
|
|
expect(stats.totalTests).toBe(3);
|
|
expect(stats.passedTests).toBe(2);
|
|
expect(stats.passRate).toBeCloseTo(2 / 3, 2);
|
|
});
|
|
});
|
|
|
|
describe('getStats', () => {
|
|
it('should return correct aggregate statistics', async () => {
|
|
// Add mixed outcomes
|
|
await tracker.track(createOutcome({
|
|
id: 't1', testId: 'test-1', passed: true, executionTimeMs: 100, maintainabilityScore: 0.7,
|
|
}));
|
|
await tracker.track(createOutcome({
|
|
id: 't2', testId: 'test-2', passed: true, executionTimeMs: 50, maintainabilityScore: 0.9,
|
|
}));
|
|
await tracker.track(createOutcome({
|
|
id: 't3', testId: 'test-3', passed: false, executionTimeMs: 200, maintainabilityScore: 0.5,
|
|
}));
|
|
await tracker.track(createOutcome({
|
|
id: 't4', testId: 'test-4', passed: true, flaky: true, executionTimeMs: 150, maintainabilityScore: 0.65,
|
|
}));
|
|
|
|
const stats = tracker.getStats();
|
|
|
|
expect(stats.totalTests).toBe(4);
|
|
expect(stats.passedTests).toBe(3);
|
|
expect(stats.flakyTests).toBe(1);
|
|
expect(stats.passRate).toBe(0.75);
|
|
expect(stats.avgExecutionTimeMs).toBe(125); // (100+50+200+150)/4
|
|
expect(stats.avgMaintainability).toBeCloseTo(0.6875, 2);
|
|
});
|
|
|
|
it('should return zero stats for empty tracker', () => {
|
|
const stats = tracker.getStats();
|
|
|
|
expect(stats.totalTests).toBe(0);
|
|
expect(stats.passedTests).toBe(0);
|
|
expect(stats.passRate).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getPatternMetrics', () => {
|
|
it('should aggregate metrics for a pattern', async () => {
|
|
const patternId = 'pattern-abc';
|
|
|
|
// Track multiple outcomes for the same pattern
|
|
await tracker.track(createOutcome({
|
|
id: 'p1', testId: 'test-1', patternId, passed: true, maintainabilityScore: 0.8,
|
|
}));
|
|
await tracker.track(createOutcome({
|
|
id: 'p2', testId: 'test-2', patternId, passed: true, maintainabilityScore: 0.85,
|
|
}));
|
|
await tracker.track(createOutcome({
|
|
id: 'p3', testId: 'test-3', patternId, passed: false, maintainabilityScore: 0.6,
|
|
}));
|
|
|
|
const metrics = tracker.getPatternMetrics(patternId);
|
|
|
|
expect(metrics).toBeDefined();
|
|
expect(metrics!.successCount).toBe(2);
|
|
expect(metrics!.failureCount).toBe(1);
|
|
expect(metrics!.successRate).toBeCloseTo(2 / 3, 2);
|
|
});
|
|
|
|
it('should return null for unknown pattern', () => {
|
|
const metrics = tracker.getPatternMetrics('unknown-pattern');
|
|
expect(metrics).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('getRecentOutcomes', () => {
|
|
it('should return the most recent outcomes', async () => {
|
|
for (let i = 0; i < 10; i++) {
|
|
await tracker.track(createOutcome({
|
|
id: `recent-${i}`,
|
|
testId: `test-${i}`,
|
|
executionTimeMs: 50 + i * 10,
|
|
timestamp: new Date(Date.now() + i * 1000),
|
|
}));
|
|
}
|
|
|
|
const recent = tracker.getRecentOutcomes(5);
|
|
|
|
expect(recent).toHaveLength(5);
|
|
expect(recent[0].testId).toBe('test-5');
|
|
expect(recent[4].testId).toBe('test-9');
|
|
});
|
|
});
|
|
|
|
describe('export/import', () => {
|
|
it('should export and import outcomes', async () => {
|
|
await tracker.track(createOutcome({
|
|
id: 'export-1', testId: 'export-test',
|
|
}));
|
|
|
|
const exported = tracker.exportOutcomes();
|
|
expect(exported).toHaveLength(1);
|
|
|
|
// Create new tracker and import
|
|
const newTracker = createTestOutcomeTracker();
|
|
newTracker.importOutcomes(exported);
|
|
|
|
const stats = newTracker.getStats();
|
|
expect(stats.totalTests).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('clear', () => {
|
|
it('should clear all tracked data', async () => {
|
|
await tracker.track(createOutcome({
|
|
id: 'clear-1', testId: 'clear-test',
|
|
}));
|
|
|
|
tracker.clear();
|
|
const stats = tracker.getStats();
|
|
|
|
expect(stats.totalTests).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getTrackerStats', () => {
|
|
it('should return tracker statistics', async () => {
|
|
await tracker.track(createOutcome({
|
|
id: 'tracker-1', testId: 'test-1', patternId: 'p1',
|
|
}));
|
|
await tracker.track(createOutcome({
|
|
id: 'tracker-2', testId: 'test-2', patternId: 'p2',
|
|
}));
|
|
|
|
const trackerStats = tracker.getTrackerStats();
|
|
|
|
expect(trackerStats.totalOutcomes).toBe(2);
|
|
expect(trackerStats.patternsTracked).toBe(2);
|
|
expect(trackerStats.hasReasoningBank).toBe(false);
|
|
});
|
|
});
|
|
});
|