/** * Blanket invariants over the whole scanner set (M-BUG-28). * * Deliberately NOT per-scanner: a per-scanner assertion goes green on a partial * conversion, which is the class #51/#57/#58 kept reproducing. Both directions * are asserted — no finding without a declared code, and no declared code * without a call site (an orphan declaration is as wrong as a missing one). */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { readFile, readdir } from 'node:fs/promises'; import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { runAllScanners } from '../../scanners/scan-orchestrator.mjs'; import { FINDING_CODES, allFindingIds } from '../../scanners/lib/finding-codes.mjs'; import { GAP_CHECKS } from '../../scanners/feature-gap-scanner.mjs'; import { withHermeticHome } from '../helpers/hermetic-home.mjs'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const FIXTURES = resolve(__dirname, '../fixtures'); const SCANNERS_DIR = resolve(__dirname, '../../scanners'); // Fixtures chosen to spread across scanners: broken/conflicting config fires the // validators, healthy config fires the gap dimensions. const SWEEP = [ 'broken-project', 'conflict-project', 'healthy-project', 'minimal-project', 'fixable-project', 'large-cascade', ]; describe('every emitted finding carries a declared check code', () => { it('holds across all orchestrated scanners and a spread of fixtures', async () => { const declaredIds = allFindingIds(); const offenders = []; const scannerErrors = []; for (const fixture of SWEEP) { const env = await withHermeticHome(async () => runAllScanners(resolve(FIXTURES, fixture), { filterFixtures: false, suppress: false }) ); for (const r of env.scanners || []) { if (r.status === 'error') scannerErrors.push(`${fixture}/${r.scanner}: ${r.error}`); for (const f of r.findings || []) { if (!declaredIds.has(f.id)) offenders.push(`${fixture}/${r.scanner}: ${f.id} — ${f.title}`); } } } // A scanner that throws returns status:'error' with zero findings, so an // unconverted call site would otherwise hide as "no findings". assert.deepEqual(scannerErrors, [], 'a scanner failed during the sweep'); assert.deepEqual(offenders, [], 'a finding carries an ID outside the registry'); }); }); describe('every declared code is claimed by a call site', () => { it('finds no orphan declarations', async () => { const files = (await readdir(SCANNERS_DIR)).filter((f) => f.endsWith('.mjs')); const sources = await Promise.all( files.map((f) => readFile(resolve(SCANNERS_DIR, f), 'utf-8')) ); const blob = sources.join('\n'); const gapIds = new Set(GAP_CHECKS.map((g) => g.id)); const orphans = []; for (const [scanner, table] of Object.entries(FINDING_CODES)) { for (const key of Object.keys(table)) { // GAP dimension codes are claimed via `code: gap.id` over GAP_CHECKS. if (scanner === 'GAP' && gapIds.has(key)) continue; if (!blob.includes(`code: '${key}'`)) orphans.push(`${scanner}.${key}`); } } assert.deepEqual(orphans, [], 'a declared code is emitted by nothing'); }); });