/**
 * Audit Trail Tests
 *
 * Tests for tamper-evident audit trail with hash chains and Sigstore signing.
 */
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtemp, rm, readFile } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { appendHistoryEntry, calculateEntryHash, exportAuditTrail, getHistoryStats, } from "../history/store.js";
import { verifyHistoryIntegrity, verifyEntry, formatVerificationResultAsMarkdown, } from "../history/verify.js";
describe("Audit Trail", () => {
    let tempDir;
    beforeEach(async () => {
        tempDir = await mkdtemp(join(tmpdir(), "audit-trail-test-"));
    });
    afterEach(async () => {
        await rm(tempDir, { recursive: true, force: true });
    });
    describe("Hash Chain", () => {
        it("creates genesis entry with correct hash chain", async () => {
            const entry = await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security", "dependency"],
                certificationId: "cert-001",
            });
            expect(entry.integrity).toBeDefined();
            expect(entry.integrity?.hash).toBeDefined();
            expect(entry.integrity?.previousHash).toBe("0000000000000000000000000000000000000000000000000000000000000000");
        });
        it("chains entries correctly", async () => {
            const entry1 = await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const entry2 = await appendHistoryEntry(tempDir, {
                type: "scan_completed",
                scanners: ["semgrep"],
                findingsCount: 5,
                bySeverity: { critical: 1, high: 2, medium: 2, low: 0, info: 0 },
                durationMs: 1000,
                certificationId: "cert-001",
            });
            expect(entry2.integrity?.previousHash).toBe(entry1.integrity?.hash);
        });
        it("calculates consistent hashes", async () => {
            const entry = await appendHistoryEntry(tempDir, {
                type: "finding_submitted",
                certificationId: "cert-001",
                agent: "security",
                findingId: "finding-001",
                severity: "high",
                category: "sql-injection",
                file: "src/api.ts",
            });
            const recalculatedHash = calculateEntryHash(entry);
            expect(recalculatedHash).toBe(entry.integrity?.hash);
        });
        it("detects tampering", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            await appendHistoryEntry(tempDir, {
                type: "scan_completed",
                scanners: ["semgrep"],
                findingsCount: 5,
                bySeverity: { critical: 1, high: 2, medium: 2, low: 0, info: 0 },
                durationMs: 1000,
                certificationId: "cert-001",
            });
            // Verify chain is valid
            const result = await verifyHistoryIntegrity(tempDir);
            expect(result.verified).toBe(true);
            expect(result.chainIntegrity).toBe(true);
            expect(result.entriesFailed).toBe(0);
        });
    });
    describe("Entry Verification", () => {
        it("verifies valid entry", async () => {
            const entry = await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const result = verifyEntry(entry, "0000000000000000000000000000000000000000000000000000000000000000");
            expect(result.hashValid).toBe(true);
            expect(result.chainValid).toBe(true);
        });
        it("detects hash mismatch", async () => {
            const entry = await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            // Tamper with the entry
            const tamperedEntry = {
                ...entry,
                integrity: {
                    ...entry.integrity,
                    hash: "tampered-hash",
                },
            };
            const result = verifyEntry(tamperedEntry, "0000000000000000000000000000000000000000000000000000000000000000");
            expect(result.hashValid).toBe(false);
            expect(result.failureReason).toContain("Hash mismatch");
        });
        it("detects chain break", async () => {
            const entry = await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const result = verifyEntry(entry, "wrong-previous-hash");
            expect(result.chainValid).toBe(false);
            expect(result.failureReason).toContain("Chain break");
        });
    });
    describe("Audit Export", () => {
        it("exports to JSON format", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            await appendHistoryEntry(tempDir, {
                type: "scan_completed",
                scanners: ["semgrep"],
                findingsCount: 3,
                bySeverity: { critical: 0, high: 1, medium: 2, low: 0, info: 0 },
                durationMs: 500,
                certificationId: "cert-001",
            });
            const result = await exportAuditTrail(tempDir, { format: "json" });
            expect(result.entryCount).toBe(2);
            expect(result.content).toBeDefined();
            expect(result.chainIntegrity.entriesWithIntegrity).toBe(2);
            const parsed = JSON.parse(result.content);
            expect(parsed.entries).toHaveLength(2);
            expect(parsed.chainIntegrity.genesisHash).toBeDefined();
        });
        it("exports to JSONL format", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const result = await exportAuditTrail(tempDir, { format: "jsonl" });
            expect(result.content).toBeDefined();
            const lines = result.content.trim().split("\n");
            expect(lines).toHaveLength(1);
            const parsed = JSON.parse(lines[0]);
            expect(parsed.type).toBe("certification_started");
        });
        it("exports to CSV format", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const result = await exportAuditTrail(tempDir, { format: "csv" });
            expect(result.content).toBeDefined();
            const lines = result.content.trim().split("\n");
            expect(lines.length).toBeGreaterThanOrEqual(2);
            expect(lines[0]).toContain("id");
            expect(lines[0]).toContain("timestamp");
            expect(lines[0]).toContain("hash");
        });
        it("writes to file when outputPath provided", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const outputPath = join(tempDir, "audit-export.json");
            const result = await exportAuditTrail(tempDir, {
                format: "json",
                outputPath,
            });
            expect(result.outputPath).toBe(outputPath);
            expect(result.content).toBeUndefined();
            const fileContent = await readFile(outputPath, "utf-8");
            const parsed = JSON.parse(fileContent);
            expect(parsed.entries).toHaveLength(1);
        });
        it("filters by date range", async () => {
            const now = new Date();
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const result = await exportAuditTrail(tempDir, {
                startDate: now.toISOString(),
                endDate: new Date(now.getTime() + 1000).toISOString(),
            });
            expect(result.entryCount).toBe(1);
        });
        it("filters by entry type", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            await appendHistoryEntry(tempDir, {
                type: "scan_completed",
                scanners: ["semgrep"],
                findingsCount: 3,
                bySeverity: { critical: 0, high: 1, medium: 2, low: 0, info: 0 },
                durationMs: 500,
                certificationId: "cert-001",
            });
            const result = await exportAuditTrail(tempDir, {
                types: ["scan_completed"],
            });
            expect(result.entryCount).toBe(1);
            const parsed = JSON.parse(result.content);
            expect(parsed.entries[0].type).toBe("scan_completed");
        });
        it("excludes integrity proofs when requested", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const result = await exportAuditTrail(tempDir, {
                includeIntegrity: false,
            });
            const parsed = JSON.parse(result.content);
            expect(parsed.entries[0].integrity).toBeUndefined();
        });
    });
    describe("History Stats", () => {
        it("returns stats for existing history", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            await appendHistoryEntry(tempDir, {
                type: "scan_completed",
                scanners: ["semgrep"],
                findingsCount: 3,
                bySeverity: { critical: 0, high: 1, medium: 2, low: 0, info: 0 },
                durationMs: 500,
                certificationId: "cert-001",
            });
            const stats = await getHistoryStats(tempDir);
            expect(stats.exists).toBe(true);
            expect(stats.entries).toBe(2);
            expect(stats.sizeBytes).toBeGreaterThan(0);
            expect(stats.oldestEntry).toBeDefined();
            expect(stats.newestEntry).toBeDefined();
        });
        it("returns empty stats for non-existent history", async () => {
            const emptyDir = await mkdtemp(join(tmpdir(), "empty-"));
            try {
                const stats = await getHistoryStats(emptyDir);
                expect(stats.exists).toBe(false);
                expect(stats.entries).toBe(0);
                expect(stats.sizeBytes).toBe(0);
            }
            finally {
                await rm(emptyDir, { recursive: true, force: true });
            }
        });
    });
    describe("Actor Identity", () => {
        it("records actor in entry", async () => {
            const entry = await appendHistoryEntry(tempDir, {
                type: "finding_fixed",
                findingId: "finding-001",
                severity: "high",
                fixMethod: "manual",
            }, {
                actor: {
                    type: "user",
                    id: "user-123",
                    email: "developer@example.com",
                    name: "Developer",
                },
            });
            expect(entry.actor).toBeDefined();
            expect(entry.actor?.type).toBe("user");
            expect(entry.actor?.email).toBe("developer@example.com");
        });
    });
    describe("Verification Formatting", () => {
        it("formats successful verification as markdown", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            const result = await verifyHistoryIntegrity(tempDir);
            const markdown = formatVerificationResultAsMarkdown(result);
            expect(markdown).toContain("# Audit Trail Integrity Verification");
            expect(markdown).toContain("VERIFIED");
            expect(markdown).toContain("Attestation");
        });
        it("formats failed verification as markdown", async () => {
            await appendHistoryEntry(tempDir, {
                type: "certification_started",
                agents: ["security"],
                certificationId: "cert-001",
            });
            // Create a mock failed result
            const failedResult = {
                projectPath: tempDir,
                verifiedAt: new Date().toISOString(),
                verified: false,
                totalEntries: 2,
                entriesVerified: 2,
                entriesPassed: 1,
                entriesFailed: 1,
                chainIntegrity: false,
                failures: [
                    {
                        entryId: "test-id",
                        timestamp: new Date().toISOString(),
                        hashValid: false,
                        chainValid: true,
                        failureReason: "Hash mismatch",
                    },
                ],
            };
            const markdown = formatVerificationResultAsMarkdown(failedResult);
            expect(markdown).toContain("FAILED");
            expect(markdown).toContain("Warning");
            expect(markdown).toContain("COMPROMISED");
        });
    });
});
//# sourceMappingURL=audit-trail.test.js.map