import { describe, it, expect } from 'vitest'; import { coverageCardsFor, coverageGate } from './enforce-judge-gate.mjs'; // D4: чистые функции врезки охвата в гейт судьи. Мост (buildCoverageInput) инъектируется — // тест герметичен (без I/O). Живой путь main() инъектирует реальный buildCoverageInput. describe('enforce-judge-gate — врезка охвата (D4)', () => { it('coverageCardsFor парсит skills-json и зовёт мост → cards/ready/holes', () => { const content = '```skills-json\n["a","b"]\n```'; let seen = null; const impl = ({ recommendedSkills }) => { seen = recommendedSkills; return { cards: [{ skill: 'a', needs: [], produces: ['x'] }], ready: true, holes: [] }; }; const out = coverageCardsFor({ content, coverageImpl: impl }); expect(seen).toEqual(['a', 'b']); expect(out.cards).toEqual([{ skill: 'a', needs: [], produces: ['x'] }]); expect(out.ready).toBe(true); expect(out.holes).toEqual([]); }); it('нет skills-json → cards:[], ready:true; мост НЕ зовётся (backward-compat)', () => { const out = coverageCardsFor({ content: 'тело без навыков', coverageImpl: () => { throw new Error('не должен вызываться'); } }); expect(out.cards).toEqual([]); expect(out.ready).toBe(true); }); it('сбой моста → degraded:true (деградация не кирпичит)', () => { const out = coverageCardsFor({ content: '```skills-json\n["a"]\n```', coverageImpl: () => { throw new Error('boom'); } }); expect(out.degraded).toBe(true); expect(out.cards).toEqual([]); }); it('coverageGate: дыра (ready:false) → block с перечнем дыр', () => { const g = coverageGate({ ready: false, holes: [{ need: 'dast-report', neededBy: 'b' }] }); expect(g.block).toBe(true); expect(g.reason).toMatch(/dast-report/); }); it('coverageGate: нет дыр (ready:true) → не block', () => { expect(coverageGate({ ready: true, holes: [] }).block).toBe(false); }); it('coverageGate: degraded → не block (сбой охвата печать не стопорит)', () => { expect(coverageGate({ ready: false, holes: [], degraded: true }).block).toBe(false); }); });