import { describe, it, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; import { finding, scannerResult, envelope, resetCounter } from '../../scanners/lib/output.mjs'; describe('resetCounter', () => { it('resets finding ID counter', () => { resetCounter(); const f1 = finding({ scanner: 'TST', severity: 'info', title: 'a', description: 'b' }); assert.strictEqual(f1.id, 'CA-TST-001'); resetCounter(); const f2 = finding({ scanner: 'TST', severity: 'info', title: 'c', description: 'd' }); assert.strictEqual(f2.id, 'CA-TST-001'); }); }); describe('finding', () => { beforeEach(() => { resetCounter(); }); it('generates correct ID format', () => { const f = finding({ scanner: 'CML', severity: 'high', title: 't', description: 'd' }); assert.match(f.id, /^CA-CML-\d{3}$/); }); it('auto-increments IDs', () => { const f1 = finding({ scanner: 'CML', severity: 'info', title: 'a', description: 'b' }); const f2 = finding({ scanner: 'CML', severity: 'info', title: 'c', description: 'd' }); assert.strictEqual(f1.id, 'CA-CML-001'); assert.strictEqual(f2.id, 'CA-CML-002'); }); it('includes all required fields', () => { const f = finding({ scanner: 'SET', severity: 'critical', title: 'Test', description: 'Desc', file: '/foo.json', line: 10, evidence: 'x=1', category: 'Structure', recommendation: 'Fix it', autoFixable: true, }); assert.strictEqual(f.scanner, 'SET'); assert.strictEqual(f.severity, 'critical'); assert.strictEqual(f.title, 'Test'); assert.strictEqual(f.description, 'Desc'); assert.strictEqual(f.file, '/foo.json'); assert.strictEqual(f.line, 10); assert.strictEqual(f.evidence, 'x=1'); assert.strictEqual(f.category, 'Structure'); assert.strictEqual(f.recommendation, 'Fix it'); assert.strictEqual(f.autoFixable, true); }); it('defaults nullable fields to null', () => { const f = finding({ scanner: 'TST', severity: 'info', title: 't', description: 'd' }); assert.strictEqual(f.file, null); assert.strictEqual(f.line, null); assert.strictEqual(f.evidence, null); assert.strictEqual(f.category, null); assert.strictEqual(f.recommendation, null); assert.strictEqual(f.autoFixable, false); }); }); describe('scannerResult', () => { beforeEach(() => { resetCounter(); }); it('counts severity correctly', () => { const findings = [ finding({ scanner: 'TST', severity: 'critical', title: 'a', description: 'b' }), finding({ scanner: 'TST', severity: 'high', title: 'c', description: 'd' }), finding({ scanner: 'TST', severity: 'high', title: 'e', description: 'f' }), finding({ scanner: 'TST', severity: 'info', title: 'g', description: 'h' }), ]; const r = scannerResult('TST', 'ok', findings, 5, 100); assert.strictEqual(r.counts.critical, 1); assert.strictEqual(r.counts.high, 2); assert.strictEqual(r.counts.medium, 0); assert.strictEqual(r.counts.low, 0); assert.strictEqual(r.counts.info, 1); }); it('includes error message when provided', () => { const r = scannerResult('TST', 'error', [], 0, 50, 'boom'); assert.strictEqual(r.error, 'boom'); }); it('omits error when not provided', () => { const r = scannerResult('TST', 'ok', [], 3, 100); assert.strictEqual(r.error, undefined); }); it('returns correct structure', () => { const r = scannerResult('CML', 'ok', [], 2, 42); assert.strictEqual(r.scanner, 'CML'); assert.strictEqual(r.status, 'ok'); assert.strictEqual(r.files_scanned, 2); assert.strictEqual(r.duration_ms, 42); assert.deepStrictEqual(r.findings, []); }); }); describe('envelope', () => { beforeEach(() => { resetCounter(); }); it('aggregates across scanners', () => { const r1 = scannerResult('A', 'ok', [ finding({ scanner: 'A', severity: 'high', title: 'x', description: 'y' }), ], 1, 10); resetCounter(); const r2 = scannerResult('B', 'ok', [ finding({ scanner: 'B', severity: 'critical', title: 'a', description: 'b' }), finding({ scanner: 'B', severity: 'low', title: 'c', description: 'd' }), ], 2, 20); const env = envelope('/target', [r1, r2], 50); assert.strictEqual(env.aggregate.total_findings, 3); assert.strictEqual(env.aggregate.counts.critical, 1); assert.strictEqual(env.aggregate.counts.high, 1); assert.strictEqual(env.aggregate.counts.low, 1); assert.strictEqual(env.aggregate.scanners_ok, 2); }); it('counts scanner statuses', () => { const r1 = scannerResult('A', 'ok', [], 1, 10); const r2 = scannerResult('B', 'skipped', [], 0, 5); const r3 = scannerResult('C', 'error', [], 0, 3, 'fail'); const env = envelope('/t', [r1, r2, r3], 30); assert.strictEqual(env.aggregate.scanners_ok, 1); assert.strictEqual(env.aggregate.scanners_skipped, 1); assert.strictEqual(env.aggregate.scanners_error, 1); }); it('includes meta with version and tool', () => { const env = envelope('/t', [], 0); assert.strictEqual(env.meta.version, '2.2.0'); assert.strictEqual(env.meta.tool, 'config-audit'); assert.strictEqual(env.meta.target, '/t'); assert.ok(env.meta.timestamp); }); it('calculates verdict correctly', () => { const env = envelope('/t', [], 0); assert.strictEqual(env.aggregate.verdict, 'PASS'); }); });