// sarif.test.mjs — Tests for SARIF 2.1.0 output formatter import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { toSARIF } from '../../scanners/lib/sarif-formatter.mjs'; // --------------------------------------------------------------------------- // Fixture: minimal scan-orchestrator envelope // --------------------------------------------------------------------------- const EMPTY_ENVELOPE = { meta: { target: '/tmp/test', timestamp: '2026-04-10T12:00:00.000Z' }, scanners: {}, aggregate: { total_findings: 0, counts: { critical: 0, high: 0, medium: 0, low: 0, info: 0 } }, }; const ENVELOPE_WITH_FINDINGS = { meta: { target: '/tmp/test', timestamp: '2026-04-10T12:00:00.000Z' }, scanners: { unicode: { scanner: 'unicode', status: 'ok', findings: [ { id: 'DS-UNI-001', scanner: 'UNI', severity: 'critical', title: 'Invisible Unicode characters detected', description: 'File contains zero-width joiners that may hide malicious intent.', file: 'src/hook.mjs', line: 42, owasp: 'LLM01', recommendation: 'Remove invisible characters.', }, { id: 'DS-UNI-002', scanner: 'UNI', severity: 'medium', title: 'Homoglyph characters detected', description: 'Cyrillic characters mixed with Latin.', file: 'src/config.mjs', line: null, owasp: 'LLM01', recommendation: 'Replace with ASCII equivalents.', }, ], counts: { critical: 1, high: 0, medium: 1, low: 0, info: 0 }, }, entropy: { scanner: 'entropy', status: 'ok', findings: [ { id: 'DS-ENT-001', scanner: 'ENT', severity: 'high', title: 'High-entropy string detected', description: 'Possible hardcoded API key.', file: '.env.example', line: 5, owasp: 'LLM03', recommendation: 'Move secrets to environment variables.', }, ], counts: { critical: 0, high: 1, medium: 0, low: 0, info: 0 }, }, }, aggregate: { total_findings: 3 }, }; // --------------------------------------------------------------------------- // SARIF structure tests // --------------------------------------------------------------------------- describe('sarif-formatter: structure', () => { it('produces valid SARIF 2.1.0 shell', () => { const sarif = toSARIF(EMPTY_ENVELOPE); assert.equal(sarif.version, '2.1.0'); assert.ok(sarif.$schema.includes('sarif-schema-2.1.0')); assert.ok(Array.isArray(sarif.runs)); assert.equal(sarif.runs.length, 1); }); it('has tool driver info', () => { const sarif = toSARIF(EMPTY_ENVELOPE); const driver = sarif.runs[0].tool.driver; assert.equal(driver.name, 'llm-security'); assert.ok(driver.version); assert.ok(driver.informationUri); }); it('empty findings produce empty results array', () => { const sarif = toSARIF(EMPTY_ENVELOPE); assert.deepEqual(sarif.runs[0].results, []); assert.deepEqual(sarif.runs[0].tool.driver.rules, []); }); }); // --------------------------------------------------------------------------- // Findings conversion tests // --------------------------------------------------------------------------- describe('sarif-formatter: findings', () => { it('converts all findings to results', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); assert.equal(sarif.runs[0].results.length, 3); }); it('maps critical/high severity to error level', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); const critResult = sarif.runs[0].results[0]; // critical const highResult = sarif.runs[0].results[2]; // high assert.equal(critResult.level, 'error'); assert.equal(highResult.level, 'error'); }); it('maps medium severity to warning level', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); const medResult = sarif.runs[0].results[1]; // medium assert.equal(medResult.level, 'warning'); }); it('includes file as artifact location URI', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); const result = sarif.runs[0].results[0]; assert.equal(result.locations[0].physicalLocation.artifactLocation.uri, 'src/hook.mjs'); }); it('includes line number as region startLine', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); const result = sarif.runs[0].results[0]; assert.equal(result.locations[0].physicalLocation.region.startLine, 42); }); it('omits region when line is null', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); const result = sarif.runs[0].results[1]; // line: null assert.ok(result.locations[0].physicalLocation.artifactLocation); assert.equal(result.locations[0].physicalLocation.region, undefined); }); it('includes OWASP tags in properties', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); const result = sarif.runs[0].results[0]; assert.deepEqual(result.properties.tags, ['LLM01']); }); it('generates unique rules from findings', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); const rules = sarif.runs[0].tool.driver.rules; assert.equal(rules.length, 3); // 3 unique title+scanner combos assert.ok(rules[0].id.startsWith('UNI/')); assert.ok(rules[2].id.startsWith('ENT/')); }); it('results reference correct rule index', () => { const sarif = toSARIF(ENVELOPE_WITH_FINDINGS); for (const result of sarif.runs[0].results) { assert.ok(typeof result.ruleIndex === 'number'); assert.ok(result.ruleIndex >= 0); assert.ok(result.ruleIndex < sarif.runs[0].tool.driver.rules.length); } }); it('accepts custom version', () => { const sarif = toSARIF(EMPTY_ENVELOPE, '7.0.0'); assert.equal(sarif.runs[0].tool.driver.version, '7.0.0'); }); });