import { describe, it, expect } from 'vitest'; import { readFileSync } from 'node:fs'; import { buildCoverageInput } from './coverage-wiring.mjs'; // Синтетический fs: реальный словарь + поддельные контракты в памяти. function fakeFsWith(contracts) { const files = Object.keys(contracts); return { readdirSync: () => files, readFileSync: (p) => { const s = String(p); if (s.endsWith('capability-vocabulary.json')) return readFileSync('docs/registry/capability-vocabulary.json', 'utf8'); const name = files.find((f) => s.endsWith(f)); return JSON.stringify(contracts[name]); }, }; } const C = (skill, needs, produces) => ({ skill, kind: 'own', needs, produces, constraints: [], 'preview-form': 'none', defaults: [], 'key-decisions': [], 'acceptance-criteria': [], }); describe('coverage-wiring — мост охвата (D3)', () => { it('полная цепочка (данность→producer→consumer) → ready:true, без дыр', () => { const fs = fakeFsWith({ 'a.contract.json': C('a', ['running-portal'], ['dast-report']), 'b.contract.json': C('b', ['dast-report'], ['go-live-verdict']), }); const out = buildCoverageInput({ recommendedSkills: ['a', 'b'], fsImpl: fs }); expect(out.ready).toBe(true); expect(out.holes).toEqual([]); expect(out.cards.map((c) => c.skill).sort()).toEqual(['a', 'b']); }); it('набор с непокрытой нуждой → ready:false, дыра видна', () => { const fs = fakeFsWith({ 'b.contract.json': C('b', ['dast-report'], ['go-live-verdict']) }); const out = buildCoverageInput({ recommendedSkills: ['b'], fsImpl: fs }); expect(out.ready).toBe(false); expect(out.holes.length).toBeGreaterThan(0); expect(out.holes.some((h) => h.need === 'dast-report')).toBe(true); }); it('cards несут {skill, needs, produces}', () => { const fs = fakeFsWith({ 'a.contract.json': C('a', ['running-portal'], ['dast-report']) }); const out = buildCoverageInput({ recommendedSkills: ['a'], fsImpl: fs }); expect(out.cards).toEqual([{ skill: 'a', needs: ['running-portal'], produces: ['dast-report'] }]); }); it('неизвестный рекомендованный навык не попадает в cards и не роняет мост', () => { const fs = fakeFsWith({ 'a.contract.json': C('a', ['running-portal'], ['dast-report']) }); const out = buildCoverageInput({ recommendedSkills: ['a', 'nonexistent'], fsImpl: fs }); expect(out.cards.map((c) => c.skill)).toEqual(['a']); }); it('D5: замок словаря активен на живом пути — неизвестный токен в errors', () => { const fs = fakeFsWith({ 'bad.contract.json': C('bad', ['ghost-token-xyz'], ['running-portal']) }); const out = buildCoverageInput({ recommendedSkills: ['bad'], fsImpl: fs }); const err = out.errors.find((e) => e.skill === 'bad'); expect(err).toBeTruthy(); expect(err.errors.join(' ')).toMatch(/ghost-token-xyz/); }); });