feat(diagnostics): correlate staff operations errors and audit records
CI / check (push) Successful in 56s
CI / deploy (push) Successful in 19s
CI / publish-container (push) Successful in 1m15s

This commit is contained in:
Simo committed 2026-09-11 00:34:49 +02:00
1 parent 440ce4e556
commit a2954e4408
14 files changed
+473 -110

No files matched your search

+59
View File
@@ -49,6 +49,12 @@ vi.mock("@/lib/db", () => ({
vi.mock("@/env", () => ({ env: {} }));
import {
createStore,
runWithStore,
setContextUserId,
} from "@/lib/foundation/request-context";
import type { IpAddress, UserId } from "@/lib/foundation/types";
import { getAuditLogs, logAudit } from "./audit";
beforeEach(() => {
@@ -56,6 +62,25 @@ beforeEach(() => {
});
describe("logAudit", () => {
it("stores the same trusted operation ID across asynchronous audit writes", async () => {
const store = createStore("test" as IpAddress);
await runWithStore(store, async () => {
setContextUserId(42 as UserId);
await Promise.resolve();
await logAudit({
userId: 42,
action: "update",
target: "user",
after: { name: "Alice" },
});
});
expect(JSON.parse(insertValues.mock.calls[0][0].details)).toEqual({
operationId: store.requestId,
userId: 42,
});
await logAudit({ userId: 42, action: "outside", target: "user" });
expect(JSON.parse(insertValues.mock.calls[1][0].details)).toEqual({});
});
it("creates an audit entry", async () => {
insertValues.mockResolvedValue({ id: 1 });
await logAudit({
@@ -189,6 +214,40 @@ describe("getAuditLogs", () => {
});
describe("audit response privacy", () => {
it("exposes only a validated operation ID, never arbitrary stored details", async () => {
selectRows.mockResolvedValue([
{
id: 1,
userId: 42,
diff: null,
before: null,
after: null,
operationDetails: JSON.stringify({
operationId: "op-123",
token: "private-token",
ip: "private-ip",
}),
},
{
id: 2,
userId: 42,
diff: null,
before: null,
after: null,
operationDetails: JSON.stringify({
operationId: "../../other?token=private",
}),
},
]);
selectCount.mockResolvedValue([{ value: 2 }]);
selectUsers.mockResolvedValue([]);
const result = await getAuditLogs();
expect(result.rows[0].operationId).toBe("op-123");
expect(result.rows[1].operationId).toBeNull();
expect(JSON.stringify(result.rows)).not.toContain("private-token");
expect(JSON.stringify(result.rows)).not.toContain("private-ip");
expect(result.rows[0]).not.toHaveProperty("operationDetails");
});
it("does not serialize legacy snapshot secrets to the client", async () => {
selectRows.mockResolvedValue([
{