import { describe, expect, test } from 'bun:test'; import type { DriftFinding, SystemAuditReport } from '../../services/audit/types'; import { COMMAND_LOG_MAX_LINES, initState, reducer } from './audit-state'; function finding(over: Partial): DriftFinding { return { category: 'module_configs', severity: 'drift', code: 'cfg_unset', subject: 'caddy', message: 'config "acme_ca" is unset', remediation: 'celilo module config set caddy acme_ca ', ...over, }; } function report(findings: DriftFinding[]): SystemAuditReport { return { version: 1, verdict: findings.length > 0 ? 'DRIFT' : 'READY', generatedAt: '2026-04-25T12:00:00.000Z', findings, }; } describe('modal lifecycle', () => { test('open-remediate stores findingId + command', () => { const s = initState(report([finding({ code: 'a' })])); const next = reducer(s, { type: 'open-remediate', findingId: 'caddy/a', command: 'celilo module config set caddy acme_ca ', }); expect(next.modal).toEqual({ kind: 'remediate', findingId: 'caddy/a', command: 'celilo module config set caddy acme_ca ', }); }); test('set-modal-command updates the command in the modal', () => { const s = initState(report([finding({ code: 'a' })])); let st = reducer(s, { type: 'open-remediate', findingId: 'caddy/a', command: 'celilo x', }); st = reducer(st, { type: 'set-modal-command', command: 'celilo y' }); expect(st.modal).toMatchObject({ kind: 'remediate', command: 'celilo y' }); }); test('set-modal-command is a no-op when no remediate modal is open', () => { const s = initState(report([finding({ code: 'a' })])); const next = reducer(s, { type: 'set-modal-command', command: 'noop' }); expect(next.modal).toBeNull(); }); test('open-reaudit-prompt switches modal', () => { const s = initState(report([finding({ code: 'a' })])); const opened = reducer(s, { type: 'open-remediate', findingId: 'caddy/a', command: 'celilo x', }); const next = reducer(opened, { type: 'open-reaudit-prompt' }); expect(next.modal).toEqual({ kind: 'reaudit-prompt' }); }); test('close-modal clears the modal', () => { const s = initState(report([finding({ code: 'a' })])); const opened = reducer(s, { type: 'open-remediate', findingId: 'caddy/a', command: 'celilo x', }); const next = reducer(opened, { type: 'close-modal' }); expect(next.modal).toBeNull(); }); }); describe('command log lifecycle', () => { test('log-start appends a running entry', () => { const s = initState(report([])); const next = reducer(s, { type: 'log-start', startedAt: 1000, cmd: 'celilo x' }); expect(next.commandLog).toHaveLength(1); expect(next.commandLog[0]).toEqual({ startedAt: 1000, cmd: 'celilo x', lines: [], exitCode: null, }); }); test('log-line appends a line to the matching entry', () => { let st = initState(report([])); st = reducer(st, { type: 'log-start', startedAt: 1000, cmd: 'celilo x' }); st = reducer(st, { type: 'log-line', startedAt: 1000, stream: 'stdout', text: 'hello' }); st = reducer(st, { type: 'log-line', startedAt: 1000, stream: 'stderr', text: 'oh no' }); expect(st.commandLog[0].lines).toEqual([ { stream: 'stdout', text: 'hello' }, { stream: 'stderr', text: 'oh no' }, ]); }); test('log-finish sets exit code on matching entry', () => { let st = initState(report([])); st = reducer(st, { type: 'log-start', startedAt: 1000, cmd: 'celilo x' }); st = reducer(st, { type: 'log-finish', startedAt: 1000, exitCode: 0 }); expect(st.commandLog[0].exitCode).toBe(0); }); test('log entries with mismatched startedAt are not affected', () => { let st = initState(report([])); st = reducer(st, { type: 'log-start', startedAt: 1000, cmd: 'a' }); st = reducer(st, { type: 'log-start', startedAt: 2000, cmd: 'b' }); st = reducer(st, { type: 'log-line', startedAt: 1000, stream: 'stdout', text: 'hi' }); expect(st.commandLog[0].lines).toHaveLength(1); expect(st.commandLog[1].lines).toHaveLength(0); }); test('log evicts oldest entries when total lines exceed cap', () => { let st = initState(report([])); // First entry — fills almost the whole cap st = reducer(st, { type: 'log-start', startedAt: 1, cmd: 'big' }); for (let i = 0; i < COMMAND_LOG_MAX_LINES - 5; i++) { st = reducer(st, { type: 'log-line', startedAt: 1, stream: 'stdout', text: `line ${i}` }); } // Second entry — pushes us over st = reducer(st, { type: 'log-start', startedAt: 2, cmd: 'small' }); for (let i = 0; i < 50; i++) { st = reducer(st, { type: 'log-line', startedAt: 2, stream: 'stdout', text: `t ${i}` }); } // Oldest (entry 1) should have been evicted expect(st.commandLog).toHaveLength(1); expect(st.commandLog[0].cmd).toBe('small'); }); });