import { describe, expect, test } from 'bun:test'; import type { AuditResult } from '../../module/packaging/audit'; import { auditModuleIntegrity } from './module-integrity'; import { computeVerdict } from './types'; const clean = (moduleId: string): AuditResult => ({ success: true, moduleId, violations: [], moduleVersion: '1.0.0', baselineVersion: '1.0.0', }); describe('auditModuleIntegrity', () => { test('a clean module produces nothing', () => { expect(auditModuleIntegrity({ results: [clean('caddy')] })).toEqual([]); }); test('a check that could not run is unmeasured, and the verdict is UNKNOWN', () => { const findings = auditModuleIntegrity({ results: [ { success: false, moduleId: 'caddy', violations: [], error: "No integrity data found for module 'caddy'.", }, ], }); expect(findings.map((f) => f.severity)).toEqual(['unmeasured']); expect(computeVerdict(findings)).toBe('UNKNOWN'); }); test('a stale baseline is unmeasured, and it SUPPRESSES the file findings it explains', () => { // The 72-violation shape. One frozen row made every legitimately-changed // file read as [MODIFIED], and printing all of them is what made `module // verify` unreadable. The baseline is the finding; the files are its // consequence. const findings = auditModuleIntegrity({ results: [ { ...clean('wireguard'), success: false, baselineVersion: '0.7.0', moduleVersion: '0.8.0', violations: [ { type: 'stale-baseline', path: 'checksums.json', message: 'Baseline describes 0.7.0, module records 0.8.0.', }, { type: 'modified', path: 'manifest.yml', message: 'Checksum mismatch: manifest.yml' }, { type: 'extra', path: 'scripts/new.ts', message: 'Unexpected file: scripts/new.ts' }, ], }, ], }); expect(findings).toHaveLength(1); expect(findings[0]?.code).toBe('module_integrity_stale_baseline'); expect(findings[0]?.severity).toBe('unmeasured'); expect(findings[0]?.remediation).toBe( 'no automatic remediation: the baseline predates version stamping', ); }); test('file drift under a TRUSTWORTHY baseline is drift, and lists every file', () => { const findings = auditModuleIntegrity({ results: [ { ...clean('wireguard'), success: false, violations: [ { type: 'modified', path: 'manifest.yml', message: 'Checksum mismatch: manifest.yml' }, { type: 'extra', path: 'scripts/new.ts', message: 'Unexpected file: scripts/new.ts' }, ], }, ], }); expect(findings).toHaveLength(1); expect(findings[0]?.severity).toBe('drift'); // Never truncated to the first item. celilo#951's real finding would have // arrived at position 19 of 19 and never been printed. expect(findings[0]?.details).toContain('manifest.yml'); expect(findings[0]?.details).toContain('scripts/new.ts'); }); test('host-plane results carry through with the right severity', () => { const findings = auditModuleIntegrity({ results: [ { ...clean('wireguard'), hostPlane: { findings: [ { hostname: 'a', state: 'converged' }, { hostname: 'b', state: 'drift', detail: 'would change 2 things' }, { hostname: 'c', state: 'unmeasured', detail: 'did not answer' }, ], }, }, ], }); expect(findings.map((f) => [f.code, f.severity])).toEqual([ ['module_host_drifted', 'drift'], ['module_host_unmeasured', 'unmeasured'], ]); // An unreachable host must not let the run read as READY. expect(computeVerdict(findings)).toBe('UNKNOWN'); }); test('every remediation is a runnable celilo command', () => { const findings = auditModuleIntegrity({ results: [ { success: false, moduleId: 'a', violations: [], error: 'gone' }, { ...clean('b'), success: false, violations: [{ type: 'modified', path: 'x', message: 'x' }], }, ], }); expect(findings.length).toBeGreaterThan(0); for (const f of findings) { expect(`${f.code}: ${f.remediation}`).toMatch(/: celilo /); expect(f.actionable).toBe(true); } }); test('a stale baseline says there is no command, it does not prescribe one (celilo#1308)', () => { const findings = auditModuleIntegrity({ results: [ { ...clean('c'), success: false, violations: [ { type: 'stale-baseline', path: 'x', message: 'baseline is v1, manifest v2' }, ], }, ], }); expect(findings).toHaveLength(1); expect(findings[0].code).toBe('module_integrity_stale_baseline'); // `module update` takes a source path, not the module id this finding // carries, and no other verb restamps a baseline. Name the gap. expect(findings[0].remediation).not.toMatch(/celilo /); expect(findings[0].actionable).toBe(false); }); });