Adds createAuditErasureHook in core-audit/src/hooks/. The factory returns a CollectionAfterDeleteHook that calls auditLog.eraseSubject() when a document is deleted. Defaults to "pseudonymize" mode; coerces numeric ids to string; skips undefined/null/object ids. Barrel at hooks/index.ts. 6 unit tests cover all guard branches. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { createAuditErasureHook } from "./audit-erasure-hook";
|
|
import type { IAuditLog } from "../audit-log.interface";
|
|
|
|
function makeAuditLog(): IAuditLog {
|
|
return {
|
|
record: vi.fn().mockResolvedValue(undefined),
|
|
eraseSubject: vi.fn().mockResolvedValue(undefined),
|
|
};
|
|
}
|
|
|
|
/** Minimal CollectionAfterDeleteHook args shape (only `doc` matters here). */
|
|
function hookArgs(id: unknown) {
|
|
return {
|
|
doc: { id },
|
|
req: {} as never,
|
|
id: String(id),
|
|
collection: {} as never,
|
|
context: {},
|
|
};
|
|
}
|
|
|
|
describe("createAuditErasureHook", () => {
|
|
it("defaults to 'pseudonymize' mode", async () => {
|
|
const auditLog = makeAuditLog();
|
|
const hook = createAuditErasureHook({ auditLog });
|
|
await hook(hookArgs("user_1") as never);
|
|
expect(auditLog.eraseSubject).toHaveBeenCalledWith("user_1", "pseudonymize");
|
|
});
|
|
|
|
it("respects explicit mode='delete'", async () => {
|
|
const auditLog = makeAuditLog();
|
|
const hook = createAuditErasureHook({ auditLog, mode: "delete" });
|
|
await hook(hookArgs("user_2") as never);
|
|
expect(auditLog.eraseSubject).toHaveBeenCalledWith("user_2", "delete");
|
|
});
|
|
|
|
it("coerces numeric id to string", async () => {
|
|
const auditLog = makeAuditLog();
|
|
const hook = createAuditErasureHook({ auditLog });
|
|
await hook(hookArgs(42) as never);
|
|
expect(auditLog.eraseSubject).toHaveBeenCalledWith("42", "pseudonymize");
|
|
});
|
|
|
|
it("skips when doc.id is undefined", async () => {
|
|
const auditLog = makeAuditLog();
|
|
const hook = createAuditErasureHook({ auditLog });
|
|
await hook(hookArgs(undefined) as never);
|
|
expect(auditLog.eraseSubject).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("skips when doc.id is null", async () => {
|
|
const auditLog = makeAuditLog();
|
|
const hook = createAuditErasureHook({ auditLog });
|
|
await hook(hookArgs(null) as never);
|
|
expect(auditLog.eraseSubject).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("skips when doc.id is an object", async () => {
|
|
const auditLog = makeAuditLog();
|
|
const hook = createAuditErasureHook({ auditLog });
|
|
await hook(hookArgs({ nested: true }) as never);
|
|
expect(auditLog.eraseSubject).not.toHaveBeenCalled();
|
|
});
|
|
});
|