import { afterEach, describe, expect, it } from "vitest"; import { TestSqliteDatabase } from "./test-support.js"; import { AuditLogRepository } from "../../../database/repositories/audit-log-repository.js"; describe("AuditLogRepository", () => { let adapter: TestSqliteDatabase | null = null; afterEach(async () => { if (adapter) { await adapter.close(); adapter = null; } }); async function createRepository( onWrite?: () => void | Promise, ): Promise { adapter = new TestSqliteDatabase(); const context = await adapter.connect(); await adapter.exec(` INSERT INTO users (id, username, password_hash) VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash'); `); return new AuditLogRepository(context, onWrite); } it("creates, filters, pages, and lists actions", async () => { const repo = await createRepository(); await repo.create({ userId: "user-1", username: "alice", action: "create_host", resourceType: "host", resourceId: "1", success: true, timestamp: "2026-06-27T00:00:00.000Z", }); await repo.create({ userId: "user-2", username: "bob", action: "delete_host", resourceType: "host", resourceId: "2", success: false, timestamp: "2026-06-27T01:00:00.000Z", }); const page = await repo.listPage({ filters: { resourceType: "host", success: false, startDate: "2026-06-27T00:30:00.000Z", }, limit: 10, offset: 0, }); expect(page.total).toBe(1); expect(page.logs[0]).toMatchObject({ userId: "user-2", action: "delete_host", success: false, }); expect(await repo.listDistinctActions()).toEqual([ "create_host", "delete_host", ]); }); it("deletes logs by user id and only runs write hook for deleted rows", async () => { let writeCount = 0; const repo = await createRepository(() => { writeCount += 1; }); await repo.create({ userId: "user-1", username: "alice", action: "login", resourceType: "auth", success: true, }); await repo.create({ userId: "user-2", username: "bob", action: "login", resourceType: "auth", success: true, }); expect(await repo.deleteByUserId("missing")).toBe(0); expect(writeCount).toBe(2); expect(await repo.deleteByUserId("user-1")).toBe(1); expect(writeCount).toBe(3); expect( ( await repo.listPage({ filters: {}, limit: 10, offset: 0, }) ).logs.map((log) => log.userId), ).toEqual(["user-2"]); }); it("keeps entries when their user is deleted, detaching instead of removing", async () => { const repo = await createRepository(); await repo.create({ userId: "user-1", username: "alice", action: "delete_host", resourceType: "host", resourceId: "9", success: true, timestamp: "2026-07-01T00:00:00.000Z", }); await repo.create({ userId: "user-2", username: "bob", action: "create_host", resourceType: "host", resourceId: "8", success: true, timestamp: "2026-07-02T00:00:00.000Z", }); expect(await repo.anonymizeByUserId("user-1")).toBe(1); const { logs } = await repo.listPage({ filters: {}, limit: 10, offset: 0 }); expect(logs).toHaveLength(2); const detached = logs.find((log) => log.action === "delete_host"); // The account is gone; the entry and its actor name are not. expect(detached?.userId).toBeNull(); expect(detached?.username).toBe("alice"); expect(logs.find((log) => log.action === "create_host")?.userId).toBe( "user-2", ); }); it("reports nothing to detach for a user with no entries", async () => { const repo = await createRepository(); expect(await repo.anonymizeByUserId("user-2")).toBe(0); }); });