import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner"; import { MemoryAuditAdapter } from "../src/adapters/memory/memory.js"; import { AuditEventType, AuditSeverity } from "../src/constants/index.js"; import { AuditLogger } from "../src/core/audit-logger/audit-logger.js"; describe("AuditLogger integration", () => { let adapter: MemoryAuditAdapter; let logger: AuditLogger; beforeEach(() => { adapter = new MemoryAuditAdapter(); logger = new AuditLogger({ storage: adapter, secretKey: "integration-secret-key", enableChainIntegrity: true, }); }); it("full lifecycle: log, query, verify, export", { timeout: 30000, }, async () => { // Log several events await logger.log({ type: AuditEventType.AUTH_LOGIN, severity: AuditSeverity.INFO, actor: { userId: "alice" }, }); await logger.log({ type: AuditEventType.DATA_READ, severity: AuditSeverity.INFO, actor: { userId: "alice" }, target: { type: "report", id: "r1" }, }); await logger.log({ type: AuditEventType.AUTH_LOGOUT, severity: AuditSeverity.INFO, actor: { userId: "alice" }, }); await logger.log({ type: AuditEventType.AUTH_LOGIN, severity: AuditSeverity.INFO, actor: { userId: "bob" }, }); // Query all events const all = await logger.query({}); expect(all.length).toBe(4); // Query by user const aliceEvents = await logger.query({ userId: "alice" }); expect(aliceEvents.length).toBe(3); // Verify chain integrity const ordered = await logger.query({ order: "asc" }); const verifyResult = await logger.verifyChainIntegrity(ordered); expect(verifyResult.valid).toBe(true); // GDPR export const gdprExport = await logger.exportGDPRData("alice"); expect(gdprExport.userId).toBe("alice"); expect(gdprExport.summary.totalEvents).toBe(3); expect(gdprExport.summary.eventTypes[AuditEventType.AUTH_LOGIN]).toBe( 1 ); }); it("chain links correctly across multiple events", { timeout: 30000, }, async () => { let tick = 0; const chainLogger = new AuditLogger({ storage: adapter, secretKey: "integration-secret-key", enableChainIntegrity: true, clock: () => new Date("2024-01-01").getTime() + tick++ * 1000, }); const events = []; for (let i = 0; i < 5; i++) { events.push( await chainLogger.log({ type: AuditEventType.DATA_READ, severity: AuditSeverity.INFO, actor: { userId: `user-${i}` }, }) ); } // Verify each event links to previous for (let i = 1; i < events.length; i++) { expect(events[i]?.previousHash).toBe(events[i - 1]?.hash); } const stored = await chainLogger.query({ order: "asc" }); const chainEvents = stored.slice(-5); // last 5 we logged const result = await chainLogger.verifyChainIntegrity(chainEvents); expect(result.valid).toBe(true); }); it("retention policy lifecycle", { timeout: 30000 }, async () => { const now = new Date("2024-12-31").getTime(); const retentionLogger = new AuditLogger({ storage: adapter, secretKey: "secret", enableChainIntegrity: false, retentionPolicy: { retentionDays: 1, archiveBeforeDelete: true, archiveLocation: "s3://archive", }, clock: () => now, }); // Add old event await adapter.save({ id: "old-event", type: AuditEventType.AUTH_LOGIN, severity: AuditSeverity.INFO, actor: {}, timestamp: new Date("2024-01-01"), hash: "h", }); const result = await retentionLogger.applyRetentionPolicy(); expect(result.eventsArchived).toBe(1); expect(result.eventsDeleted).toBe(0); // already moved to archive expect(adapter.snapshot().length).toBe(0); expect(adapter.archivedSnapshot().length).toBe(1); }); it("multi-tenant chain isolation", { timeout: 30000 }, async () => { // Each tenant gets its own chain: getLatestEvent(tenantId) scopes the lookup await logger.log({ type: AuditEventType.AUTH_LOGIN, severity: AuditSeverity.INFO, actor: { userId: "u1" }, tenantId: "t1", }); await logger.log({ type: AuditEventType.AUTH_LOGIN, severity: AuditSeverity.INFO, actor: { userId: "u2" }, tenantId: "t2", }); await logger.log({ type: AuditEventType.DATA_READ, severity: AuditSeverity.INFO, actor: { userId: "u1" }, tenantId: "t1", }); const t1Events = await logger.query({ tenantId: "t1", order: "asc" }); const t2Events = await logger.query({ tenantId: "t2", order: "asc" }); // First t1 event has no predecessor → no previousHash expect(t1Events[0]?.previousHash).toBeUndefined(); // Second t1 event links to first t1 event expect(t1Events[1]?.previousHash).toBe(t1Events[0]?.hash); // t2 only has one event, so no previousHash expect(t2Events[0]?.previousHash).toBeUndefined(); }); it("tamper detection in integration", { timeout: 30000 }, async () => { await logger.log({ type: AuditEventType.AUTH_LOGIN, severity: AuditSeverity.INFO, actor: { userId: "u1" }, }); await logger.log({ type: AuditEventType.DATA_READ, severity: AuditSeverity.INFO, actor: { userId: "u1" }, }); const events = await logger.query({ order: "asc" }); // Tamper with first event const tampered = [...events]; tampered[0] = { ...events[0]!, actor: { userId: "attacker" } }; const result = await logger.verifyChainIntegrity(tampered); expect(result.valid).toBe(false); expect(result.brokenAt).toBe(0); }); });