import { describe, expect, test } from 'bun:test'; import { moduleAlertKey, moduleCheckAlertKey } from './keys'; import { type ReconcileAction, type ReconcileInput, reconcile } from './reconcile'; const NOW = new Date('2026-07-28T03:00:00Z'); const GRACE_MS = 60_000; const MODULE = 'caddy'; const MODULE_KEY = moduleAlertKey(MODULE); const DISK = moduleCheckAlertKey(MODULE, 'disk-space'); const CERT = moduleCheckAlertKey(MODULE, 'cert-validity'); function input(over: Partial = {}): ReconcileInput { return { liveAlerts: [], outcome: 'success', failingKeys: [], monitorLevelKey: MODULE_KEY, monitorSeverity: 'critical', now: NOW, graceMs: GRACE_MS, ...over, }; } const failing = (key: string, message = 'bad') => ({ key, severity: 'critical', message }) as const; const keysOf = (actions: ReconcileAction[], type: ReconcileAction['type']) => actions.filter((a) => a.type === type).map((a) => a.key); describe('successful run', () => { test('a newly failing key is created with a grace window', () => { const actions = reconcile(input({ failingKeys: [failing(DISK)] })); expect(actions).toHaveLength(1); const [action] = actions; expect(action.type).toBe('create'); if (action.type !== 'create') throw new Error('expected create'); expect(action.key).toBe(DISK); expect(action.graceUntil).toEqual(new Date(NOW.getTime() + GRACE_MS)); }); test('a still-failing key refreshes rather than duplicating', () => { const actions = reconcile( input({ liveAlerts: [{ id: 'a1', key: DISK }], failingKeys: [failing(DISK, '/var 96% used')], }), ); expect(actions).toEqual([ { type: 'refresh', alertId: 'a1', key: DISK, severity: 'critical', message: '/var 96% used', details: undefined, }, ]); }); test('a key that stopped being reported resolves', () => { const actions = reconcile(input({ liveAlerts: [{ id: 'a1', key: DISK }], failingKeys: [] })); expect(actions).toEqual([{ type: 'resolve', alertId: 'a1', key: DISK }]); }); test('resolves only what disappeared, keeping what persists', () => { const actions = reconcile( input({ liveAlerts: [ { id: 'a1', key: DISK }, { id: 'a2', key: CERT }, ], failingKeys: [failing(DISK)], }), ); expect(keysOf(actions, 'refresh')).toEqual([DISK]); expect(keysOf(actions, 'resolve')).toEqual([CERT]); }); // A module upgrade renaming a check is indistinguishable from one problem // clearing and another appearing — which is correct, and beats a zombie // alert firing forever for a key nobody reports any more. test('a renamed check resolves the old key and fires the new one', () => { const renamed = moduleCheckAlertKey(MODULE, 'disk-usage'); const actions = reconcile( input({ liveAlerts: [{ id: 'a1', key: DISK }], failingKeys: [failing(renamed)] }), ); expect(keysOf(actions, 'resolve')).toEqual([DISK]); expect(keysOf(actions, 'create')).toEqual([renamed]); }); // The hook ran, so "could not run" is no longer true. It is never in the // failing set, so set difference retires it without a special case. test('a successful run resolves a standing monitor-level alert', () => { const actions = reconcile( input({ liveAlerts: [{ id: 'm1', key: MODULE_KEY }], failingKeys: [failing(DISK)] }), ); expect(keysOf(actions, 'resolve')).toEqual([MODULE_KEY]); expect(keysOf(actions, 'create')).toEqual([DISK]); }); test('a clean run against no live alerts does nothing', () => { expect(reconcile(input())).toEqual([]); }); }); describe('errored run — the false-all-clear guard', () => { const errored = (liveAlerts: ReconcileInput['liveAlerts']) => reconcile( input({ outcome: 'error', liveAlerts, // A caller that leaks an errored run's empty item list into failingKeys // must still not cause a resolve. The branch ignores this field. failingKeys: [], errorMessage: 'ssh: connect to host caddy.dmz port 22: timed out', }), ); // THE case. Disk has been failing since yesterday; now the box drops off the // network. A naive set-difference sends "✅ RESOLVED — disk-space" at 3am for // a problem that just got much worse, and fires nothing to replace it. test('an unreachable system does NOT resolve a firing alert', () => { const actions = errored([{ id: 'a1', key: DISK }]); expect(keysOf(actions, 'resolve')).toEqual([]); expect(actions.some((a) => a.type === 'resolve')).toBe(false); }); test('it fires the monitor-level alert carrying the real error', () => { const actions = errored([{ id: 'a1', key: DISK }]); const created = actions.filter((a) => a.type === 'create'); expect(created).toHaveLength(1); expect(created[0].key).toBe(MODULE_KEY); expect(created[0].type === 'create' && created[0].message).toContain('timed out'); }); test('existing item alerts are frozen — not refreshed either', () => { const actions = errored([ { id: 'a1', key: DISK }, { id: 'a2', key: CERT }, ]); const touched = actions.filter((a) => a.key === DISK || a.key === CERT); expect(touched).toEqual([]); }); test('a repeated failure refreshes the monitor-level alert instead of duplicating', () => { const actions = errored([ { id: 'm1', key: MODULE_KEY }, { id: 'a1', key: DISK }, ]); expect(actions).toHaveLength(1); expect(actions[0].type).toBe('refresh'); expect(actions[0].key).toBe(MODULE_KEY); }); test('falls back to a message when the caller supplies no error text', () => { const actions = reconcile(input({ outcome: 'error', errorMessage: undefined })); expect(actions[0].type === 'create' && actions[0].message).toBe('Monitor run failed'); }); }); describe('recovery after an outage', () => { // The frozen alerts are only ever cleaned up by a run that actually executed. test('a later successful run resolves what is genuinely gone', () => { const afterOutage = reconcile( input({ liveAlerts: [ { id: 'm1', key: MODULE_KEY }, { id: 'a1', key: DISK }, ], failingKeys: [], }), ); expect(keysOf(afterOutage, 'resolve').sort()).toEqual([MODULE_KEY, DISK].sort()); }); test('a later successful run keeps what is still broken', () => { const afterOutage = reconcile( input({ liveAlerts: [ { id: 'm1', key: MODULE_KEY }, { id: 'a1', key: DISK }, ], failingKeys: [failing(DISK)], }), ); expect(keysOf(afterOutage, 'resolve')).toEqual([MODULE_KEY]); expect(keysOf(afterOutage, 'refresh')).toEqual([DISK]); }); });