88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { DEFAULT_RISK_THRESHOLD, evaluateRiskFloor, type ReviewSurface } from './risk-floor.js';
|
|
|
|
describe('evaluateRiskFloor', () => {
|
|
it('returns a no-review "none" verdict for an empty diff', () => {
|
|
const v = evaluateRiskFloor({ filesChanged: [] });
|
|
expect(v).toEqual({
|
|
needs_review: false,
|
|
score: 0,
|
|
surface: 'none',
|
|
reason: 'no files changed',
|
|
});
|
|
});
|
|
|
|
it('ignores empty/non-string entries', () => {
|
|
const v = evaluateRiskFloor({ filesChanged: ['', ' ' as unknown as string].filter(Boolean) });
|
|
// only the whitespace string survives the Boolean filter; it classifies to none
|
|
expect(v.surface).toBe('none');
|
|
expect(v.needs_review).toBe(false);
|
|
});
|
|
|
|
it.each<[string, string, ReviewSurface, boolean]>([
|
|
['auth', 'apps/api/src/auth/session.guard.ts', 'auth', true],
|
|
['data', 'packages/db/migrations/0007_add_users.sql', 'data', true],
|
|
['infra', '.woodpecker/deploy.yml', 'infra', true],
|
|
['build', 'packages/types/tsconfig.json', 'build', true],
|
|
['ui', 'apps/web/src/components/Button.tsx', 'ui', false],
|
|
['test', 'packages/macp/src/risk-floor.spec.ts', 'test', false],
|
|
['docs', 'docs/plans/agent-reflection-loop-PRD.md', 'docs', false],
|
|
['none', 'README', 'none', false],
|
|
])(
|
|
'classifies a single %s file → surface=%s needs_review=%s',
|
|
(_label, file, surface, needsReview) => {
|
|
const v = evaluateRiskFloor({ filesChanged: [file] });
|
|
expect(v.surface).toBe(surface);
|
|
expect(v.needs_review).toBe(needsReview);
|
|
expect(v.reason).toContain(
|
|
file === 'README' ? 'no sensitive surface' : surface === 'none' ? '' : surface,
|
|
);
|
|
},
|
|
);
|
|
|
|
it('lets the highest-risk surface dominate a mixed diff', () => {
|
|
const v = evaluateRiskFloor({
|
|
filesChanged: [
|
|
'docs/readme.md',
|
|
'apps/web/src/components/Nav.tsx',
|
|
'apps/api/src/auth/token.service.ts',
|
|
],
|
|
});
|
|
expect(v.surface).toBe('auth');
|
|
expect(v.score).toBe(1.0);
|
|
expect(v.needs_review).toBe(true);
|
|
expect(v.reason).toContain('token.service.ts');
|
|
expect(v.reason).not.toContain('readme.md');
|
|
});
|
|
|
|
it('names every file that ties at the dominant surface', () => {
|
|
const v = evaluateRiskFloor({
|
|
filesChanged: ['src/login.ts', 'src/permission-check.ts'],
|
|
});
|
|
expect(v.surface).toBe('auth');
|
|
expect(v.reason).toContain('src/login.ts');
|
|
expect(v.reason).toContain('src/permission-check.ts');
|
|
});
|
|
|
|
it('treats docs+test-only diffs as below the floor', () => {
|
|
const v = evaluateRiskFloor({
|
|
filesChanged: ['docs/guide.md', 'packages/x/src/x.test.ts'],
|
|
});
|
|
expect(v.needs_review).toBe(false);
|
|
expect(v.surface).toBe('test'); // higher weight than docs
|
|
});
|
|
|
|
it('honors a custom threshold', () => {
|
|
const docsOnly = { filesChanged: ['docs/guide.md'] };
|
|
expect(evaluateRiskFloor(docsOnly, 0.05).needs_review).toBe(true);
|
|
expect(evaluateRiskFloor(docsOnly, DEFAULT_RISK_THRESHOLD).needs_review).toBe(false);
|
|
});
|
|
|
|
it('is deterministic across call order', () => {
|
|
const a = evaluateRiskFloor({ filesChanged: ['a.md', 'auth/x.ts', 'b.tsx'] });
|
|
const b = evaluateRiskFloor({ filesChanged: ['b.tsx', 'a.md', 'auth/x.ts'] });
|
|
expect(a).toEqual(b);
|
|
});
|
|
});
|