// diff-engine-exact-pass.test.mjs — Regression for #22 (LOW, v7.8.3). // // The moved-fallback ran per-current inside the main loop with no global // exact pass first, so an earlier unmatched current finding greedily consumed // (baseline.matched = true) a baseline candidate that a LATER current finding // needed for a byte-exact match. With duplicate fingerprints this mislabeled // unchanged findings as new/moved. The fix runs a global EXACT-match pass // first, then the moved-fallback on the remainder. import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { diffFindings } from '../../scanners/lib/diff-engine.mjs'; /** Build a finding sharing the duplicate fingerprint at a given line. */ function fpFinding(line) { return { fingerprint: 'dupfp0123456789a', scanner: 'ENT', severity: 'high', title: 'High-entropy string detected', file: 'src/config.mjs', line, evidence: 'AAAA... (fake)', owasp: 'LLM03', recommendation: 'Rotate the credential.', }; } describe('diff-engine: global exact pass before moved-fallback (#22)', () => { it('an earlier current finding does not steal the exact match of a later one', () => { // Baseline: ONE finding at line 100. // Current: TWO findings with the same fingerprint, at lines 50 and 100. // The line-100 current is byte-exact vs the baseline and must be unchanged; // the line-50 current has no remaining candidate and must be new. const baseline = [fpFinding(100)]; const current = [fpFinding(50), fpFinding(100)]; const diff = diffFindings(baseline, current); assert.equal( diff.unchanged.length, 1, `expected the exact line-100 match to be unchanged, got unchanged=${diff.unchanged.length} ` + `moved=${diff.moved.length} new=${diff.new.length}`, ); assert.equal(diff.unchanged[0].line, 100, 'the unchanged finding should be the line-100 one'); assert.equal(diff.moved.length, 0, 'nothing should be labeled moved'); assert.equal(diff.new.length, 1, 'the line-50 finding should be new'); assert.equal(diff.new[0].line, 50); assert.equal(diff.resolved.length, 0, 'the baseline finding was matched, nothing resolved'); }); it('moved-fallback still applies when no exact match exists', () => { const baseline = [fpFinding(100)]; const current = [fpFinding(500)]; // same fingerprint, drifted far const diff = diffFindings(baseline, current); assert.equal(diff.moved.length, 1, 'a lone drifted finding should be moved'); assert.equal(diff.moved[0].previous_line, 100); assert.equal(diff.unchanged.length, 0); assert.equal(diff.new.length, 0); assert.equal(diff.resolved.length, 0); }); it('two exact duplicates both match two exact baseline duplicates', () => { const baseline = [fpFinding(50), fpFinding(100)]; const current = [fpFinding(100), fpFinding(50)]; // order flipped const diff = diffFindings(baseline, current); assert.equal(diff.unchanged.length, 2, 'both exact duplicates should be unchanged'); assert.equal(diff.moved.length, 0); assert.equal(diff.new.length, 0); assert.equal(diff.resolved.length, 0); }); });