Files
proffesor-for-testing__agen…/tests/unit/integrations/ruvector/diff-risk-classifier.test.ts
Dragan Spiridonov 00e4d2cfa6 fix(tests): add afterEach cleanup to 104 test files missing it (#374 P2)
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>
2026-03-21 12:51:47 +00:00

345 lines
11 KiB
TypeScript

/**
* Agentic QE v3 - Diff Risk Classifier Unit Tests
*/
import { describe, it, expect, beforeEach, afterEach} from 'vitest';
import {
createDiffRiskClassifier,
RuVectorDiffRiskClassifier,
FallbackDiffRiskClassifier,
} from '../../../../src/integrations/ruvector';
import type {
RuVectorConfig,
DiffContext,
FileChange,
} from '../../../../src/integrations/ruvector';
// Test fixtures
function createFileChange(overrides: Partial<FileChange> = {}): FileChange {
return {
filePath: 'src/services/user-service.ts',
status: 'modified',
additions: 10,
deletions: 5,
...overrides,
};
}
function createDiffContext(overrides: Partial<DiffContext> = {}): DiffContext {
return {
files: [createFileChange()],
commitHash: 'abc123',
message: 'Update user service',
...overrides,
};
}
function createConfig(overrides: Partial<RuVectorConfig> = {}): RuVectorConfig {
return {
enabled: true,
endpoint: 'http://localhost:8080',
fallbackEnabled: true,
cacheEnabled: false,
...overrides,
};
}
describe('Diff Risk Classifier', () => {
describe('Factory Function', () => {
it('should create RuVectorDiffRiskClassifier when enabled', async () => {
const classifier = await createDiffRiskClassifier(createConfig({ enabled: true }));
expect(classifier).toBeInstanceOf(RuVectorDiffRiskClassifier);
});
it('should create FallbackDiffRiskClassifier when disabled', async () => {
const classifier = await createDiffRiskClassifier(createConfig({ enabled: false }));
expect(classifier).toBeInstanceOf(FallbackDiffRiskClassifier);
});
});
describe('RuVectorDiffRiskClassifier', () => {
let classifier: RuVectorDiffRiskClassifier;
beforeEach(() => {
classifier = new RuVectorDiffRiskClassifier(createConfig());
});
afterEach(() => {
// Reset state to prevent leaks between tests
});
describe('classifyDiff', () => {
it('should classify a simple diff', async () => {
const context = createDiffContext();
const result = await classifier.classifyDiff(context);
expect(result).toHaveProperty('level');
expect(result).toHaveProperty('score');
expect(result).toHaveProperty('factors');
expect(result).toHaveProperty('highRiskFiles');
expect(result).toHaveProperty('recommendedTests');
expect(result.usedFallback).toBe(false);
});
it('should return score between 0 and 1', async () => {
const context = createDiffContext();
const result = await classifier.classifyDiff(context);
expect(result.score).toBeGreaterThanOrEqual(0);
expect(result.score).toBeLessThanOrEqual(1);
});
it('should return valid severity level', async () => {
const context = createDiffContext();
const result = await classifier.classifyDiff(context);
expect(['critical', 'high', 'medium', 'low', 'info']).toContain(result.level);
});
it('should identify high-risk files', async () => {
const context = createDiffContext({
files: [
createFileChange({ filePath: 'src/auth/token-service.ts', additions: 100 }),
createFileChange({ filePath: 'src/utils/helper.ts', additions: 5 }),
],
});
const result = await classifier.classifyDiff(context);
expect(Array.isArray(result.highRiskFiles)).toBe(true);
});
it('should recommend tests based on risk', async () => {
const context = createDiffContext({
files: [
createFileChange({ filePath: 'src/security/auth.ts', additions: 50 }),
],
});
const result = await classifier.classifyDiff(context);
expect(Array.isArray(result.recommendedTests)).toBe(true);
expect(result.recommendedTests.length).toBeGreaterThan(0);
for (const test of result.recommendedTests) {
expect(test).toHaveProperty('type');
expect(test).toHaveProperty('priority');
expect(test).toHaveProperty('reason');
}
});
it('should classify large changesets as higher risk', async () => {
const smallContext = createDiffContext({
files: [createFileChange({ additions: 10, deletions: 5 })],
});
const largeContext = createDiffContext({
files: [createFileChange({ additions: 500, deletions: 200 })],
});
const smallResult = await classifier.classifyDiff(smallContext);
const largeResult = await classifier.classifyDiff(largeContext);
expect(largeResult.score).toBeGreaterThanOrEqual(smallResult.score);
});
it('should classify security-related changes as higher risk', async () => {
const normalContext = createDiffContext({
files: [createFileChange({ filePath: 'src/utils/format.ts' })],
});
const securityContext = createDiffContext({
files: [createFileChange({ filePath: 'src/auth/password.ts' })],
});
const normalResult = await classifier.classifyDiff(normalContext);
const securityResult = await classifier.classifyDiff(securityContext);
expect(securityResult.score).toBeGreaterThanOrEqual(normalResult.score);
});
});
describe('rankFilesByRisk', () => {
it('should rank files by risk score', async () => {
const files: FileChange[] = [
createFileChange({ filePath: 'src/utils/helper.ts', additions: 5 }),
createFileChange({ filePath: 'src/auth/token.ts', additions: 50 }),
createFileChange({ filePath: 'src/services/user.ts', additions: 20 }),
];
const ranking = await classifier.rankFilesByRisk(files);
expect(ranking).toHaveLength(3);
// Should be sorted by risk score descending
for (let i = 1; i < ranking.length; i++) {
expect(ranking[i - 1].riskScore).toBeGreaterThanOrEqual(ranking[i].riskScore);
}
});
});
describe('requiresSecurityReview', () => {
it('should require security review for auth changes', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: 'src/auth/login.ts' })],
});
const result = await classifier.requiresSecurityReview(context);
expect(result).toBe(true);
});
it('should require security review for password changes', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: 'src/services/password-service.ts' })],
});
const result = await classifier.requiresSecurityReview(context);
expect(result).toBe(true);
});
it('should require security review for crypto changes', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: 'src/utils/encryption.ts' })],
});
const result = await classifier.requiresSecurityReview(context);
expect(result).toBe(true);
});
it('should not require security review for non-sensitive changes', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: 'src/components/button.tsx' })],
});
const result = await classifier.requiresSecurityReview(context);
expect(result).toBe(false);
});
});
describe('getRecommendedReviewers', () => {
it('should recommend security team for security changes', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: 'src/security/auth.ts' })],
});
const reviewers = await classifier.getRecommendedReviewers(context);
expect(reviewers).toContain('security-team');
});
it('should recommend API owner for API changes', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: 'src/api/public-api.ts' })],
});
const reviewers = await classifier.getRecommendedReviewers(context);
expect(reviewers).toContain('api-owner');
});
it('should recommend tech lead for core changes', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: 'src/kernel/bootstrap.ts' })],
});
const reviewers = await classifier.getRecommendedReviewers(context);
expect(reviewers).toContain('tech-lead');
});
});
describe('predictDefects', () => {
it('should predict defects for risky changes', async () => {
const context = createDiffContext({
files: [
createFileChange({ filePath: 'src/auth/complex.ts', additions: 150 }),
],
});
const predictions = await classifier.predictDefects(context);
expect(Array.isArray(predictions)).toBe(true);
for (const pred of predictions) {
expect(pred).toHaveProperty('filePath');
expect(pred).toHaveProperty('probability');
expect(pred).toHaveProperty('type');
}
});
it('should predict new-code-defects for large additions', async () => {
const context = createDiffContext({
files: [
createFileChange({ additions: 200, deletions: 10 }),
],
});
const predictions = await classifier.predictDefects(context);
const newCodeDefects = predictions.filter((p) => p.type === 'new-code-defect');
expect(newCodeDefects.length).toBeGreaterThan(0);
});
it('should predict regressions for large deletions', async () => {
const context = createDiffContext({
files: [
createFileChange({ additions: 10, deletions: 100 }),
],
});
const predictions = await classifier.predictDefects(context);
const regressions = predictions.filter((p) => p.type === 'regression');
expect(regressions.length).toBeGreaterThan(0);
});
});
});
describe('FallbackDiffRiskClassifier', () => {
let classifier: FallbackDiffRiskClassifier;
beforeEach(() => {
classifier = new FallbackDiffRiskClassifier();
});
it('should classify diffs using rules', async () => {
const context = createDiffContext();
const result = await classifier.classifyDiff(context);
expect(result.usedFallback).toBe(true);
expect(result).toHaveProperty('level');
expect(result).toHaveProperty('score');
});
it('should identify sensitive files', async () => {
const context = createDiffContext({
files: [createFileChange({ filePath: '.env.local' })],
});
const result = await classifier.classifyDiff(context);
expect(result.score).toBeGreaterThan(0.3);
});
});
describe('Integration', () => {
it('should fallback when RuVector disabled', async () => {
const classifier = await createDiffRiskClassifier(createConfig({ enabled: false }));
const result = await classifier.classifyDiff(createDiffContext());
expect(result.usedFallback).toBe(true);
});
it('should cache results when enabled', async () => {
const classifier = new RuVectorDiffRiskClassifier(
createConfig({ cacheEnabled: true, cacheTtl: 60000 })
);
const context = createDiffContext();
const result1 = await classifier.classifyDiff(context);
const result2 = await classifier.classifyDiff(context);
expect(result1.score).toBe(result2.score);
});
});
});