// @vitest-environment node import { describe, expect, it } from "vitest"; import type { TFunction } from "i18next"; import type { AuditExportFilters, AuditExportRow, AuditIntegritySummary } from "~/routes/admin/audit-export.helpers.server"; import { buildAuditPdfLabels, buildAuditPdfReportInput, renderAuditExportPdfResponse, toAuditPdfEvent, toAuditPdfIntegrity, } from "./pdfRender"; function makeRow(over: Partial = {}): AuditExportRow { return { id: "audit_1", createdAt: new Date("2026-05-17T00:00:00.000Z"), tenantId: "tenant_1", actorType: "user", actorId: "user_1", actorEmail: null, actorIp: null, actorUserAgent: null, action: "widget.created", targetType: "widget", targetId: "widget_1", requestId: null, sourceModule: "crm", outcome: "success", dataSensitivity: "internal", regulatoryScope: ["soc2"], metadata: null, previousHash: null, eventHash: "h1", ...over, }; } const filters: AuditExportFilters = { from: new Date("2026-05-01T00:00:00.000Z"), to: new Date("2026-05-17T00:00:00.000Z"), regulatoryScope: ["soc2"], actorType: null, outcome: null, tenantId: "tenant_1", }; const integrity: AuditIntegritySummary = { ok: true, inspected: 1, firstBreakId: null, reason: null }; describe("toAuditPdfEvent / toAuditPdfIntegrity", () => { it("maps an AuditExportRow into AuditPdfEvent unchanged", () => { const row = makeRow(); expect(toAuditPdfEvent(row)).toMatchObject({ id: row.id, createdAt: row.createdAt, tenantId: row.tenantId, action: row.action, eventHash: row.eventHash, }); }); it("maps integrity 1:1", () => { expect(toAuditPdfIntegrity(integrity)).toEqual(integrity); }); }); describe("buildAuditPdfReportInput", () => { it("combines filters + rows + integrity into the renderer input shape", () => { const rows = [makeRow()]; const input = buildAuditPdfReportInput({ tenantId: "tenant_1", filters, integrity, rows, generatedBy: "ops-bot" }); expect(input.tenantId).toBe("tenant_1"); expect(input.from).toBe("2026-05-01T00:00:00.000Z"); expect(input.to).toBe("2026-05-17T00:00:00.000Z"); expect(input.filterSummary).toContain("regulatoryScope=soc2"); expect(input.events).toHaveLength(1); expect(input.integrity.ok).toBe(true); expect(input.generatedBy).toBe("ops-bot"); }); it("passes localized label overrides to the renderer input", () => { const t = ((key: string, args?: Record) => { if (key === "settings:auditExport.pdf.events") { return `translated events ${args?.count}`; } if (key === "settings:auditExport.pdf.integrity.ok") { return `translated ok ${args?.inspected}`; } if (key === "settings:auditExport.pdf.integrity.broken") { return `translated broken ${args?.firstBreakId}`; } return `translated:${key}`; }) as TFunction; const input = buildAuditPdfReportInput({ tenantId: "tenant_1", filters, integrity, rows: [makeRow()], labels: buildAuditPdfLabels(t), }); expect(input.labels?.tenant).toBe("translated:settings:auditExport.pdf.tenant"); expect(input.labels?.events?.(3)).toBe("translated events 3"); expect(input.labels?.integrityOk?.(1)).toBe("translated ok 1"); expect(input.labels?.integrityBroken?.({ inspected: 1, firstBreakId: "audit_1", reason: "hash" })).toBe("translated broken audit_1"); expect(input.labels?.columns?.createdAt).toBe("translated:settings:auditExport.pdf.columns.createdAt"); }); }); describe("renderAuditExportPdfResponse", () => { it("returns a Response with PDF content-type and chain-status header", async () => { const rows = [makeRow()]; const response = await renderAuditExportPdfResponse( { tenantId: "tenant_1", filters, integrity, rows }, { filenamePrefix: "audit-export-admin" } ); expect(response.headers.get("Content-Type")).toBe("application/pdf"); expect(response.headers.get("X-Audit-Chain-Status")).toBe("ok"); const disposition = response.headers.get("Content-Disposition") ?? ""; expect(disposition).toContain("audit-export-admin"); expect(disposition).toContain("tenant_1"); const bytes = new Uint8Array(await response.arrayBuffer()); expect(bytes.length).toBeGreaterThan(0); expect(Buffer.from(bytes.slice(0, 4)).toString("utf8")).toBe("%PDF"); }); it("marks the chain status as broken when integrity.ok=false", async () => { const rows = [makeRow()]; const response = await renderAuditExportPdfResponse( { tenantId: "tenant_1", filters, integrity: { ok: false, inspected: 1, firstBreakId: "audit_1", reason: "hash-mismatch" }, rows, }, { filenamePrefix: "audit-export-admin" } ); expect(response.headers.get("X-Audit-Chain-Status")).toBe("broken"); }); it("falls back to 'platform' in the filename when tenantId is null", async () => { const rows: AuditExportRow[] = []; const response = await renderAuditExportPdfResponse( { tenantId: null, filters: { ...filters, tenantId: null }, integrity: { ok: true, inspected: 0, firstBreakId: null, reason: null }, rows, }, { filenamePrefix: "audit-export-admin" } ); expect(response.headers.get("Content-Disposition") ?? "").toContain("audit-export-admin-platform-"); }); });