import { describe, expect, it } from 'vitest' import { overall, validateReport } from '@extant2000/evidence-record' import { codeOnly, toReport, compareImplementations, extractFunction, fingerprint, formatDrift, } from '../src/index.js' const A = ` // header comment function evaluateSendRules(c) { if (!c.postalAddress) return { ok: false } return { ok: true } } module.exports = { evaluateSendRules } ` // Same behaviour, different comments and formatting — must NOT be drift. const B = ` /* different doc */ function evaluateSendRules(c) { // a comment the other copy does not have if (!c.postalAddress) return { ok: false } return { ok: true } // trailing note } ` // A real behavioural change — the postal-address check is gone. const C = ` function evaluateSendRules(c) { return { ok: true } } ` describe('extractFunction', () => { it('extracts a function by brace matching', () => { expect(extractFunction(A, 'evaluateSendRules')).toContain('postalAddress') }) it('handles nested braces without stopping early', () => { const src = 'function f() { if (x) { g({ a: 1 }) } return 2 }' expect(extractFunction(src, 'f')).toBe(src) }) it('finds async and exported declarations', () => { expect(extractFunction('async function f() { return 1 }', 'f')).toBeTruthy() expect(extractFunction('export function f() { return 1 }', 'f')).toBeTruthy() }) it('returns null when the function is absent', () => { expect(extractFunction(A, 'nope')).toBeNull() }) }) describe('codeOnly', () => { it('ignores comments and whitespace so docs may differ', () => { expect(codeOnly(extractFunction(A, 'evaluateSendRules')!)) .toBe(codeOnly(extractFunction(B, 'evaluateSendRules')!)) }) it('does NOT ignore a behavioural change', () => { expect(codeOnly(extractFunction(A, 'evaluateSendRules')!)) .not.toBe(codeOnly(extractFunction(C, 'evaluateSendRules')!)) }) }) describe('compareImplementations', () => { it('reports identical copies as in sync', () => { const r = compareImplementations( [{ label: 'a', source: A }, { label: 'b', source: B }], 'evaluateSendRules', ) expect(r.inSync).toBe(true) expect(r.groups).toHaveLength(1) expect(r.vacuous).toBe(false) }) it('catches the one-sided fix — the failure this exists for', () => { // A CAN-SPAM guard removed in one repo only. const r = compareImplementations( [{ label: 'hosting', source: A }, { label: 'crm', source: C }], 'evaluateSendRules', ) expect(r.inSync).toBe(false) expect(r.groups).toHaveLength(2) expect(formatDrift(r)).toContain('has drifted across') }) it('groups three copies as 2-vs-1 and names which side is odd', () => { const r = compareImplementations( [{ label: 'a', source: A }, { label: 'b', source: B }, { label: 'c', source: C }], 'evaluateSendRules', ) // Majority group first. expect(r.groups[0]!.labels).toEqual(['a', 'b']) expect(r.groups[1]!.labels).toEqual(['c']) }) it('treats a MISSING function as not-found, never as passing', () => { // Renamed or deleted is not "no drift" — a guard that cannot find its // subject is not passing. const r = compareImplementations( [{ label: 'a', source: A }, { label: 'gone', source: 'function other() {}' }], 'evaluateSendRules', ) expect(r.missing).toEqual(['gone']) expect(r.inSync).toBe(false) expect(formatDrift(r)).toContain('could not be found') }) it('flags a single copy as NOT ASSESSED, not in sync', () => { // One copy cannot disagree with itself; calling that a pass would let a // deleted sibling read as clean. const r = compareImplementations([{ label: 'a', source: A }], 'evaluateSendRules') expect(r.vacuous).toBe(true) const text = formatDrift(r) expect(text).toContain('NOT ASSESSED') expect(text).toContain('One copy cannot disagree with itself') // The word it replaced needed a glossary. expect(text).not.toContain('VACUOUS') }) it('an empty input set is vacuous', () => { expect(compareImplementations([], 'f').vacuous).toBe(true) }) }) describe('fingerprint', () => { it('is stable and differs on changed code', () => { expect(fingerprint('abc')).toBe(fingerprint('abc')) expect(fingerprint('abc')).not.toBe(fingerprint('abd')) }) }) describe('conformance with the evidence-record standard', () => { const A = 'function evaluateSendRules(c) {\n if (c.unsub) return false\n return true\n}' const B = 'function evaluateSendRules(c) {\n return true\n}' it('cites the file of every copy that disagrees', () => { const r = compareImplementations([ { label: 'crm', path: 'service-a/lib/send.js', source: A }, { label: 'hosting', path: 'service-b/lib/send.js', source: B }, ], 'evaluateSendRules') const text = formatDrift(r, { evidence: 'full' }) expect(text).toContain('service-a/lib/send.js') expect(text).toContain('service-b/lib/send.js') expect(overall(toReport(r))).toBe('fail') }) it('cites the copies behind an in-sync pass', () => { // A pass that cannot name what it compared is an asserted result. const r = compareImplementations([ { label: 'crm', path: 'a/send.js', source: A }, { label: 'hosting', path: 'b/send.js', source: A }, ], 'evaluateSendRules') expect(overall(toReport(r))).toBe('pass') expect(formatDrift(r, { evidence: 'full' })).toContain('a/send.js') }) it('every conclusion carries a citation', () => { const r = compareImplementations([ { label: 'crm', path: 'a/send.js', source: A }, { label: 'hosting', path: 'b/send.js', source: B }, ], 'evaluateSendRules') expect(validateReport(toReport(r))).toEqual([]) }) }) describe('a missing subject is a finding, not an absence of one', () => { it('stays assessed when one copy is present and one is missing', () => { // `vacuous` counts LOCATED copies, so this pair is vacuous by that // definition. Reporting it as NOT ASSESSED would hide a deleted or // renamed sibling behind "nothing was compared" — the exact failure this // library exists to catch. const r = compareImplementations( [ { label: 'a', path: 'a/send.js', source: 'function evaluateSendRules(c) { return true }' }, { label: 'gone', path: 'b/send.js', source: 'function other() {}' }, ], 'evaluateSendRules', ) expect(r.vacuous).toBe(true) expect(overall(toReport(r))).toBe('fail') expect(formatDrift(r)).toContain('could not be found') expect(formatDrift(r)).not.toContain('NOT ASSESSED') }) })