import { describe, expect, it, vi } from "vitest"; import type { PrismaClient } from "@prisma/client"; import { writeAudit } from "../../src/audit.js"; /// Minimal mock: only the surface `writeAudit` touches. Cast through unknown /// so we don't have to implement the rest of the PrismaClient surface — this /// is a unit test, not a DB test. interface AuditEntryMock { create: ReturnType; } function mockPrisma(): PrismaClient & { auditEntry: AuditEntryMock } { return { auditEntry: { create: vi.fn() } } as unknown as PrismaClient & { auditEntry: AuditEntryMock; }; } describe("writeAudit (A-8 audit write path)", () => { it("creates a row with all fields set", async () => { const prisma = mockPrisma(); prisma.auditEntry.create.mockResolvedValue({ id: "aud-1" }); await writeAudit(prisma, { runId: "run-1", projectId: "proj-1", actorUserId: "user-1", action: "run.triggered", metadata: { source: "feishu" }, }); expect(prisma.auditEntry.create).toHaveBeenCalledTimes(1); expect(prisma.auditEntry.create).toHaveBeenCalledWith({ data: { runId: "run-1", projectId: "proj-1", actorUserId: "user-1", action: "run.triggered", metadata: { source: "feishu" }, }, }); }); it("creates a row with only required fields (action + metadata)", async () => { const prisma = mockPrisma(); prisma.auditEntry.create.mockResolvedValue({ id: "aud-2" }); await writeAudit(prisma, { action: "permission.denied", metadata: { reason: "no-edit-role" }, }); expect(prisma.auditEntry.create).toHaveBeenCalledTimes(1); expect(prisma.auditEntry.create).toHaveBeenCalledWith({ data: { action: "permission.denied", metadata: { reason: "no-edit-role" }, }, }); }); it("swallows a prisma error (best-effort, must not throw)", async () => { const prisma = mockPrisma(); prisma.auditEntry.create.mockRejectedValue(new Error("db down")); // Must not throw — audit is observability, not control. await expect( writeAudit(prisma, { action: "run.triggered", metadata: {}, }), ).resolves.toBeUndefined(); expect(prisma.auditEntry.create).toHaveBeenCalledTimes(1); }); });