import { describe, expect, test } from 'bun:test'; import type { HealthCheckResult } from '../health-runner'; import { auditHealth } from './health'; import { computeVerdict } from './types'; describe('auditHealth', () => { test('a healthy module produces no finding; a module with no hook is unmeasured, never green', async () => { const results: HealthCheckResult[] = [ { moduleId: 'caddy', status: 'healthy', checks: [] }, { moduleId: 'iptables', status: 'no-checks', checks: [] }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ category: 'health', severity: 'unmeasured', code: 'health_unmeasured', subject: 'iptables', actionable: false, }); expect(result[0].message).toContain('no health_check hook'); expect(computeVerdict(result)).toBe('UNKNOWN'); }); test('a module whose checks all report skip is unmeasured and names the skipped checks', async () => { // The smoketest_bot_token incident, as an audit finding: the hook ran, // every meaningful check refused to guess, and the fleet must not read // READY off that. const results: HealthCheckResult[] = [ { moduleId: 'smoketest', status: 'no-checks', checks: [ { name: 'bot_token_valid', status: 'skip', message: 'smoketest_bot_token not yet provisioned', }, { name: 'webhook_reachable', status: 'skip', message: 'smoketest_bot_token not yet provisioned', }, ], }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ severity: 'unmeasured', code: 'health_unmeasured', subject: 'smoketest', actionable: true, remediation: 'celilo module health smoketest --debug', }); expect(result[0].message).toContain('all 2 health checks skipped'); expect(result[0].details).toContain('bot_token_valid'); expect(result[0].details).toContain('not yet provisioned'); }); test('skipped checks on an otherwise healthy module render unmeasured', async () => { const results: HealthCheckResult[] = [ { moduleId: 'caddy', status: 'healthy', checks: [ { name: 'config_valid', status: 'pass', message: 'config parses' }, { name: 'origin_reachable', status: 'skip', message: 'peer unreachable from control plane', }, ], }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ severity: 'unmeasured', code: 'health_checks_skipped', subject: 'caddy', }); expect(result[0].message).toContain('1 of 2'); expect(result[0].details).toContain('origin_reachable'); expect(computeVerdict(result)).toBe('UNKNOWN'); }); test('a degraded module with a skipped check yields both a drift and an unmeasured finding', async () => { const results: HealthCheckResult[] = [ { moduleId: 'iptables', status: 'degraded', checks: [ { name: 'persistence_drift', status: 'warn', message: '1 live rule(s) absent from rules.v4', }, { name: 'ssh_access', status: 'skip', message: 'fw01 unreachable' }, ], }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(2); expect(result.map((f) => f.severity).sort()).toEqual(['drift', 'unmeasured']); expect(computeVerdict(result)).toBe('UNKNOWN'); }); test('a waived module still emits unmeasured, annotated with who and why', async () => { // The waiver annotates the finding; it never replaces it. Severity // stays `unmeasured` so the fleet verdict stays UNKNOWN (D3). Zero // unmeasured findings after a waiver would mean the waiver re-greened. const results: HealthCheckResult[] = [ { moduleId: 'namecheap', status: 'no-checks', checks: [], waiver: { reason: 'API-only: a real check is hard and may not be worth forcing', by: 'peba', at: '2026-09-09', }, }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ severity: 'unmeasured', code: 'health_unmeasured', subject: 'namecheap', }); expect(result[0].message).toContain( 'WAIVED by peba (2026-09-09): API-only: a real check is hard and may not be worth forcing', ); expect(computeVerdict(result)).toBe('UNKNOWN'); }); test('a waiver does not green an all-skipped module either', async () => { const results: HealthCheckResult[] = [ { moduleId: 'homebridge', status: 'no-checks', checks: [{ name: 'bridge_reachable', status: 'skip', message: 'no operator time' }], waiver: { reason: 'deferred: should eventually have one, no operator time now', by: 'peba', at: '2026-09-09', }, }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ severity: 'unmeasured', code: 'health_unmeasured' }); expect(result[0].message).toContain('all 1 health checks skipped'); expect(result[0].message).toContain('WAIVED by peba'); expect(computeVerdict(result)).toBe('UNKNOWN'); }); test('a module without a waiver is not annotated', async () => { const results: HealthCheckResult[] = [ { moduleId: 'iptables', status: 'no-checks', checks: [] }, ]; const result = await auditHealth({ results }); expect(result[0].message).not.toContain('WAIVED'); }); test('drift finding for unhealthy module', async () => { const results: HealthCheckResult[] = [ { moduleId: 'lunacycle', status: 'unhealthy', checks: [ { name: 'service_running', status: 'fail', message: 'Service inactive' }, { name: 'api_reachable', status: 'pass', message: 'API responded' }, ], }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ category: 'health', severity: 'drift', code: 'health_unhealthy', subject: 'lunacycle', }); expect(result[0].details).toContain('service_running'); expect(result[0].details).not.toContain('api_reachable'); // pass entries excluded }); test('drift finding for degraded module', async () => { const results: HealthCheckResult[] = [ { moduleId: 'caddy', status: 'degraded', checks: [{ name: 'dns_resolution', status: 'warn', message: 'propagation pending' }], }, ]; const result = await auditHealth({ results }); expect(result[0].code).toBe('health_degraded'); }); test('error status surfaces with the error message', async () => { const results: HealthCheckResult[] = [ { moduleId: 'lunacycle', status: 'error', checks: [], error: 'Hook script not found', }, ]; const result = await auditHealth({ results }); expect(result[0].message).toContain('Hook script not found'); }); test('multiple unhealthy modules each produce a finding', async () => { const results: HealthCheckResult[] = [ { moduleId: 'a', status: 'unhealthy', checks: [{ name: 'x', status: 'fail', message: 'no' }], }, { moduleId: 'b', status: 'degraded', checks: [{ name: 'y', status: 'warn', message: 'meh' }], }, ]; const result = await auditHealth({ results }); expect(result).toHaveLength(2); expect(result.map((f) => f.subject).sort()).toEqual(['a', 'b']); }); test('firewall persistence drift reaches the audit as a drift finding', () => { // The delivery path for celilo#670's fix: the iptables module reports drift // as a `warn` health item, the runner turns any warn into `degraded`, and // this turns `degraded` into an audit finding. No core code knows what an // iptables rule is — which is the point (#941). const unpersisted = '-A POSTROUTING -s 10.9.9.0/24 -o eth1 -j MASQUERADE'; const results: HealthCheckResult[] = [ { moduleId: 'iptables', status: 'degraded', checks: [ { name: 'ssh_access', status: 'pass', message: 'SSH connected to fw01' }, { name: 'persistence_drift', status: 'warn', message: '1 live rule(s) absent from /etc/iptables/rules.v4 (lost on reboot)', details: `live only: nat ${unpersisted}`, }, ], }, ]; return auditHealth({ results }).then((findings) => { expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ category: 'health', severity: 'drift', subject: 'iptables', actionable: true, }); expect(findings[0].details).toContain('persistence_drift'); expect(findings[0].details).toContain('lost on reboot'); // The audit is a summary: it carries the COUNT, and points at the command // that names the rules. `module health` prints check.details verbatim // (cli/commands/module-health.ts:49), so every drifted rule is reachable. expect(findings[0].remediation).toBe('celilo module health iptables --debug'); }); }); });