import { describe, expect, test } from 'bun:test'; import { auditSecretsDecryptable } from './secrets-decryptable'; describe('auditSecretsDecryptable', () => { test('no findings when every secret decrypts', async () => { const result = await auditSecretsDecryptable({ results: [ { scope: 'module', subject: 'caddy', name: 'acme_token', error: null }, { scope: 'system', subject: 'system', name: 'master', error: null }, ], }); expect(result).toEqual([]); }); test('per-secret findings when some decrypt and others fail', async () => { const result = await auditSecretsDecryptable({ results: [ { scope: 'module', subject: 'caddy', name: 'good', error: null }, { scope: 'module', subject: 'iptables', name: 'bad', error: 'Unsupported state or unable to authenticate data', }, ], }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ category: 'secrets_decryptable', severity: 'blocked', subject: 'iptables', actionable: false, }); expect(result[0].code).toContain('module'); expect(result[0].remediation).toContain('celilo module secret set iptables bad'); }); test('collapses to "master_key_mismatch" when every secret fails identically', async () => { const result = await auditSecretsDecryptable({ results: [ { scope: 'module', subject: 'caddy', name: 'a', error: 'bad auth tag' }, { scope: 'module', subject: 'iptables', name: 'b', error: 'bad auth tag' }, { scope: 'system', subject: 'system', name: 'c', error: 'bad auth tag' }, ], }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ category: 'secrets_decryptable', severity: 'blocked', code: 'master_key_mismatch', subject: 'system', actionable: false, }); expect(result[0].message).toContain('All 3 secrets fail to decrypt'); }); test('does NOT collapse when failures have different errors', async () => { const result = await auditSecretsDecryptable({ results: [ { scope: 'module', subject: 'caddy', name: 'a', error: 'bad auth tag' }, { scope: 'system', subject: 'system', name: 'b', error: 'corrupted' }, ], }); expect(result).toHaveLength(2); expect(result.every((f) => f.code !== 'master_key_mismatch')).toBe(true); }); test('different scopes produce different remediation text', async () => { // Distinct error messages so the "all-fail-same-reason" collapse // doesn't trigger — we want per-scope findings here. const result = await auditSecretsDecryptable({ results: [ { scope: 'module', subject: 'caddy', name: 'm', error: 'bad tag' }, { scope: 'system', subject: 'system', name: 's', error: 'bad iv' }, ], }); const moduleFinding = result.find((f) => f.subject === 'caddy'); const systemFinding = result.find((f) => f.subject === 'system'); expect(moduleFinding?.remediation).toContain('celilo module secret set'); expect(systemFinding?.remediation).toContain('celilo system secret set'); }); });