import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { join } from 'node:path'; import { writeFile, mkdir, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { loadSuppressions, parseIgnoreFile, applySuppressions, formatSuppressionSummary, } from '../../scanners/lib/suppression.mjs'; // --- Helpers --- function makeFinding(id, scanner, severity = 'medium') { return { id, scanner, severity, title: `Finding ${id}`, description: `Description for ${id}`, file: null, line: null, evidence: null, category: null, recommendation: null, autoFixable: false, }; } // ======================================== // parseIgnoreFile // ======================================== describe('parseIgnoreFile', () => { it('parses exact finding IDs', () => { const result = parseIgnoreFile('CA-CML-001\nCA-SET-003'); assert.equal(result.length, 2); assert.equal(result[0].pattern, 'CA-CML-001'); assert.equal(result[1].pattern, 'CA-SET-003'); }); it('parses glob patterns', () => { const result = parseIgnoreFile('CA-GAP-*'); assert.equal(result.length, 1); assert.equal(result[0].pattern, 'CA-GAP-*'); }); it('skips comments and empty lines', () => { const content = `# This is a comment CA-CML-001 # Another comment CA-SET-002`; const result = parseIgnoreFile(content); assert.equal(result.length, 2); }); it('extracts inline comments', () => { const result = parseIgnoreFile('CA-HKV-003 # Known timeout in CI'); assert.equal(result.length, 1); assert.equal(result[0].pattern, 'CA-HKV-003'); assert.equal(result[0].comment, 'Known timeout in CI'); }); it('returns empty array for empty content', () => { const result = parseIgnoreFile(''); assert.deepEqual(result, []); }); it('returns empty array for comment-only content', () => { const result = parseIgnoreFile('# Just comments\n# Nothing else'); assert.deepEqual(result, []); }); }); // ======================================== // applySuppressions // ======================================== describe('applySuppressions', () => { it('filters exact match', () => { const findings = [ makeFinding('CA-CML-001', 'CML'), makeFinding('CA-CML-002', 'CML'), ]; const suppressions = [{ pattern: 'CA-CML-001', comment: '' }]; const { active, suppressed } = applySuppressions(findings, suppressions); assert.equal(active.length, 1); assert.equal(active[0].id, 'CA-CML-002'); assert.equal(suppressed.length, 1); assert.equal(suppressed[0].id, 'CA-CML-001'); }); it('filters glob pattern CA-SET-*', () => { const findings = [ makeFinding('CA-SET-001', 'SET'), makeFinding('CA-SET-002', 'SET'), makeFinding('CA-CML-001', 'CML'), ]; const suppressions = [{ pattern: 'CA-SET-*', comment: '' }]; const { active, suppressed } = applySuppressions(findings, suppressions); assert.equal(active.length, 1); assert.equal(active[0].id, 'CA-CML-001'); assert.equal(suppressed.length, 2); }); it('returns all active when no suppressions', () => { const findings = [makeFinding('CA-CML-001', 'CML')]; const { active, suppressed } = applySuppressions(findings, []); assert.equal(active.length, 1); assert.equal(suppressed.length, 0); }); it('returns all active when suppressions is null', () => { const findings = [makeFinding('CA-CML-001', 'CML')]; const { active, suppressed } = applySuppressions(findings, null); assert.equal(active.length, 1); assert.equal(suppressed.length, 0); }); it('handles empty findings list', () => { const suppressions = [{ pattern: 'CA-CML-*', comment: '' }]; const { active, suppressed } = applySuppressions([], suppressions); assert.equal(active.length, 0); assert.equal(suppressed.length, 0); }); it('applies multiple suppression patterns', () => { const findings = [ makeFinding('CA-CML-001', 'CML'), makeFinding('CA-SET-001', 'SET'), makeFinding('CA-GAP-001', 'GAP'), ]; const suppressions = [ { pattern: 'CA-CML-001', comment: '' }, { pattern: 'CA-GAP-*', comment: '' }, ]; const { active, suppressed } = applySuppressions(findings, suppressions); assert.equal(active.length, 1); assert.equal(active[0].id, 'CA-SET-001'); assert.equal(suppressed.length, 2); }); }); // ======================================== // formatSuppressionSummary // ======================================== describe('formatSuppressionSummary', () => { it('formats correct count and groups', () => { const suppressed = [ makeFinding('CA-GAP-001', 'GAP'), makeFinding('CA-GAP-002', 'GAP'), makeFinding('CA-HKV-003', 'HKV'), ]; const summary = formatSuppressionSummary(suppressed); assert.ok(summary.includes('3 finding(s) suppressed')); assert.ok(summary.includes('CA-GAP-*')); assert.ok(summary.includes('CA-HKV-*')); }); it('returns zero message for empty array', () => { assert.equal(formatSuppressionSummary([]), '0 findings suppressed'); }); it('returns zero message for null', () => { assert.equal(formatSuppressionSummary(null), '0 findings suppressed'); }); }); // ======================================== // loadSuppressions // ======================================== describe('loadSuppressions', () => { const tmpDir = join(tmpdir(), `config-audit-suppress-test-${Date.now()}`); it('returns empty when no .config-audit-ignore exists', async () => { const result = await loadSuppressions('/nonexistent/path'); assert.deepEqual(result.suppressions, []); assert.equal(result.source, 'none'); }); it('loads from project directory', async () => { await mkdir(tmpDir, { recursive: true }); await writeFile(join(tmpDir, '.config-audit-ignore'), 'CA-GAP-*\nCA-CML-001\n'); const result = await loadSuppressions(tmpDir); assert.equal(result.suppressions.length, 2); assert.equal(result.source, 'project'); await rm(tmpDir, { recursive: true, force: true }); }); });