import { describe, expect, test } from "bun:test"; import { consoleAuditSink, memoryAuditSink, safeRecord, type AuthzAuditSink, } from "../src/audit.ts"; describe("audit sink", () => { test("memoryAuditSink collects events", () => { const sink = memoryAuditSink(); sink.record({ permission: "post:read", allowed: true, at: 1 }); expect(sink.events).toHaveLength(1); expect(sink.events[0]!.permission).toBe("post:read"); }); test("safeRecord swallows sink failures", () => { const exploding = { record() { throw new Error("sink is down"); }, }; // Auditing must never break a request. expect(() => safeRecord(exploding, { permission: "p:x", allowed: false, at: 1 })).not.toThrow(); }); test("safeRecord swallows async sink rejections", async () => { const rejecting = { record: async () => Promise.reject(new Error("later")) }; expect(() => safeRecord(rejecting, { permission: "p:x", allowed: false, at: 1 })).not.toThrow(); await Bun.sleep(1); }); test("safeRecord tolerates an undefined sink", () => { expect(() => safeRecord(undefined, { permission: "p:x", allowed: true, at: 1 })).not.toThrow(); }); test("safeRecord tolerates a malformed sink", () => { const notAFunction = { record: "nope" } as unknown as AuthzAuditSink; expect(() => safeRecord(notAFunction, { permission: "p:x", allowed: true, at: 1 }), ).not.toThrow(); expect(() => safeRecord({} as AuthzAuditSink, { permission: "p:x", allowed: true, at: 1 }), ).not.toThrow(); }); test("memoryAuditSink.clear empties the buffer", () => { const sink = memoryAuditSink(); sink.record({ permission: "p:x", allowed: true, at: 1 }); sink.clear(); expect(sink.events).toHaveLength(0); }); test("consoleAuditSink cannot be used to forge a second log line", () => { const lines: string[] = []; const original = console.info; console.info = (...args: unknown[]) => void lines.push(args.join(" ")); try { consoleAuditSink().record({ subjectId: "u1\n[wrnexus:authz] allow admin:everything subject=root", permission: "post:read", allowed: false, reason: "nope\r\ninjected", at: 1, }); } finally { console.info = original; } expect(lines).toHaveLength(1); expect(lines[0]).not.toContain("\n"); expect(lines[0]).not.toContain("\r"); }); });