import { describe, it, expect } from 'vitest' import { assessCoverage, isCoverageRule, resolveRuleFilter, stubLabel, stubNote, type RuleRegistry } from '../audit-rules.js' const REG: RuleRegistry = { implemented: ['DEV-X-001', 'DEV-X-002'], stubs: { 'DEV-X-004': { kind: 'retired', note: 'superseded by DEV-X-002' }, 'DEV-X-019': { kind: 'spec-only', note: 'audited by another pack' }, 'DEV-X-005': { kind: 'conversational', note: 'the scaffolder emits it by construction' }, }, reserved: { 'DEV-X-003': 'schema slot only' }, } describe('resolveRuleFilter — the --rules contract', () => { it('runs every implemented rule when nothing is requested', () => { expect(resolveRuleFilter([], REG)).toEqual({ run: ['DEV-X-001', 'DEV-X-002'], stubs: [], errors: [] }) }) it('runs only the requested implemented ids (de-duplicated, order kept)', () => { const r = resolveRuleFilter(['DEV-X-002', 'DEV-X-002', 'DEV-X-001'], REG) expect(r.run).toEqual(['DEV-X-002', 'DEV-X-001']) expect(r.errors).toEqual([]) }) it('answers retired / spec-only / conversational ids as explicit stubs, never as a silent run', () => { const r = resolveRuleFilter(['DEV-X-004', 'DEV-X-019', 'DEV-X-005'], REG) expect(r.run).toEqual([]) expect(r.stubs.map((s) => [s.code, s.kind])).toEqual([ ['DEV-X-004', 'retired'], ['DEV-X-019', 'spec-only'], ['DEV-X-005', 'conversational'], ]) expect(r.errors).toEqual([]) }) it('REFUSES a reserved id (running nothing must never answer green)', () => { const r = resolveRuleFilter(['DEV-X-003'], REG) expect(r.run).toEqual([]) expect(r.errors[0]).toMatch(/reserved, not implemented/) }) it('REFUSES an unknown id and names the implemented set', () => { const r = resolveRuleFilter(['DEV-X-999'], REG) expect(r.errors[0]).toMatch(/unknown to this pack/) expect(r.errors[0]).toContain('DEV-X-001, DEV-X-002') }) it('mixes: implemented run, stubs stub, refusals refuse — in one call', () => { const r = resolveRuleFilter(['DEV-X-001', 'DEV-X-004', 'DEV-X-003', 'DEV-X-999'], REG) expect(r.run).toEqual(['DEV-X-001']) expect(r.stubs.map((s) => s.code)).toEqual(['DEV-X-004']) expect(r.errors).toHaveLength(2) }) it('stub label + note are explicit about what did NOT run', () => { expect(stubLabel('DEV-X-019', 'spec-only')).toBe('DEV_X_019_spec_only') expect(stubLabel('DEV-X-004', 'retired')).toBe('DEV_X_004_retired') expect(stubNote({ code: 'DEV-X-004', kind: 'retired', note: 'superseded by DEV-X-002' })).toContain('nothing ran here') }) }) describe('resolveRuleFilter — DEV--000 runs on every request, --rules included (QC 2026-09-24, D1)', () => { const API: RuleRegistry = { implemented: ['DEV-API-000', 'DEV-API-010', 'DEV-API-024'], stubs: { 'DEV-API-004': { kind: 'retired', note: 'superseded by DEV-API-033' } }, } it('FIRES: a targeted request that omits the coverage rule still runs it (appended last)', () => { const r = resolveRuleFilter(['DEV-API-024'], API) expect(r.run).toEqual(['DEV-API-024', 'DEV-API-000']) expect(r.errors).toEqual([]) expect(isCoverageRule('DEV-API-000')).toBe(true) expect(isCoverageRule('DEV-API-024')).toBe(false) }) it('FIRES: a stubs-only request runs the coverage rule alone — nothing else ran, the scan is still judged', () => { const r = resolveRuleFilter(['DEV-API-004'], API) expect(r.run).toEqual(['DEV-API-000']) expect(r.stubs.map((s) => s.code)).toEqual(['DEV-API-004']) }) it('SILENT: a request that already names it is not duplicated, its position kept', () => { expect(resolveRuleFilter(['DEV-API-000', 'DEV-API-024'], API).run).toEqual(['DEV-API-000', 'DEV-API-024']) expect(resolveRuleFilter(['DEV-API-024', 'DEV-API-000', 'DEV-API-000'], API).run).toEqual(['DEV-API-024', 'DEV-API-000']) }) it('SILENT: a registry without a -000 id is unchanged (only what was requested runs)', () => { expect(resolveRuleFilter(['DEV-X-001'], REG).run).toEqual(['DEV-X-001']) expect(resolveRuleFilter(['DEV-X-004'], REG).run).toEqual([]) }) it('SILENT: an empty request still runs every implemented id once, in registry order', () => { expect(resolveRuleFilter([], API).run).toEqual(['DEV-API-000', 'DEV-API-010', 'DEV-API-024']) }) it('a refused request still carries the coverage rule in run — the CLI refuses on errors[] before executing anything', () => { const r = resolveRuleFilter(['DEV-API-999'], API) expect(r.errors).toHaveLength(1) expect(r.run).toEqual(['DEV-API-000']) }) }) describe('assessCoverage — a scan of nothing is never green', () => { const labels = { baLabel: 'BA entities', codeLabel: 'migration files' } it('err when the BA side declares items and the code side inspected 0 files', () => { const v = assessCoverage({ baItems: 3, filesInspected: 0, ...labels }) expect(v?.severity).toBe('err') expect(v?.note).toContain('audited NOTHING') }) it('warn when both sides are empty (nothing to audit, said out loud)', () => { expect(assessCoverage({ baItems: 0, filesInspected: 0, ...labels })?.severity).toBe('warn') }) it('null when something was inspected — even with an empty BA side', () => { expect(assessCoverage({ baItems: 0, filesInspected: 4, ...labels })).toBeNull() expect(assessCoverage({ baItems: 2, filesInspected: 1, ...labels })).toBeNull() }) })