/**
 * Unit tests for agent-privacy module
 */
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { tmpdir } from "os";
import { join } from "path";
import { mkdir, rm, writeFile } from "fs/promises";
import { runPrivacyAnalysis, privacyToFindings } from "./agent-privacy.js";
describe("runPrivacyAnalysis", () => {
    let testDir;
    beforeEach(async () => {
        testDir = join(tmpdir(), `vaspera-privacy-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
        await mkdir(testDir, { recursive: true });
    });
    afterEach(async () => {
        await rm(testDir, { recursive: true, force: true });
    });
    it("returns empty result when no traces exist", async () => {
        const result = await runPrivacyAnalysis(testDir);
        expect(result.totalTraces).toBe(0);
        expect(result.tracesAnalyzed).toBe(0);
        expect(result.piiDetected).toBe(0);
        expect(result.findings).toHaveLength(0);
        expect(result.recommendations).toContain("No traces found. Ensure tool usage logs are being captured.");
    });
    it("returns empty result for non-existent directory", async () => {
        const result = await runPrivacyAnalysis("/nonexistent/dir/path");
        expect(result.totalTraces).toBe(0);
        expect(result.findings).toHaveLength(0);
    });
    it("detects email addresses in traces", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "user-lookup",
                input: "Find user john.doe@company.com",
                output: "User found: jane.smith@corporate.org",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.piiDetected).toBeGreaterThanOrEqual(2);
        expect(result.byType.email).toBeGreaterThanOrEqual(2);
        expect(result.findings.some((f) => f.type === "email")).toBe(true);
        expect(result.findings.some((f) => f.location === "input")).toBe(true);
        expect(result.findings.some((f) => f.location === "output")).toBe(true);
    });
    it("detects phone numbers in traces", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "contact-service",
                input: "Call customer at 555-123-4567",
                output: "Number registered: (800) 555-1234",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.piiDetected).toBeGreaterThanOrEqual(2);
        expect(result.byType.phone).toBeGreaterThanOrEqual(2);
        expect(result.findings.some((f) => f.type === "phone")).toBe(true);
    });
    it("detects SSN with critical severity", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "identity-verify",
                input: "Verify SSN 123-45-6789",
                output: "Verified",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.piiDetected).toBeGreaterThanOrEqual(1);
        expect(result.byType.ssn).toBeGreaterThanOrEqual(1);
        const ssnFinding = result.findings.find((f) => f.type === "ssn");
        expect(ssnFinding).toBeDefined();
        expect(ssnFinding?.severity).toBe("critical");
        expect(ssnFinding?.gdprRelevant).toBe(true);
        expect(ssnFinding?.ccpaRelevant).toBe(true);
    });
    it("detects credit card numbers with Luhn validation", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "payment-processor",
                input: "Process card 4532015112830366", // Valid Visa test number
                output: "Payment processed",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.byType["credit-card"]).toBeGreaterThanOrEqual(1);
        const ccFinding = result.findings.find((f) => f.type === "credit-card");
        expect(ccFinding).toBeDefined();
        expect(ccFinding?.severity).toBe("critical");
    });
    it("does not detect invalid credit card numbers", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "payment-processor",
                input: "Process card 4111111111111112", // Invalid Luhn
                output: "Payment failed",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.byType["credit-card"] || 0).toBe(0);
    });
    it("detects IP addresses (excluding private ranges)", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "geo-lookup",
                input: "Lookup IP 8.8.8.8",
                output: "Location: Mountain View, CA",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.byType["ip-address"]).toBeGreaterThanOrEqual(1);
    });
    it("excludes private IP ranges from detection", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "internal-service",
                input: "Connect to 192.168.1.100",
                output: "Connected to 10.0.0.1",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.byType["ip-address"] || 0).toBe(0);
    });
    it("detects medical information", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "patient-records",
                input: "Get patient symptoms",
                output: "Diagnosis: Common cold. Prescription for medication.",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.byType.medical).toBeGreaterThanOrEqual(1);
        const medFinding = result.findings.find((f) => f.type === "medical");
        expect(medFinding?.severity).toBe("high");
    });
    it("aggregates findings by tool", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "user-service",
                input: "user@test.org",
                output: "Found",
            },
            {
                timestamp: new Date().toISOString(),
                tool: "user-service",
                input: "admin@company.com",
                output: "Found",
            },
            {
                timestamp: new Date().toISOString(),
                tool: "contact-api",
                input: "555-111-2222",
                output: "Valid",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.byTool["user-service"]).toBeGreaterThanOrEqual(2);
        expect(result.byTool["contact-api"]).toBeGreaterThanOrEqual(1);
    });
    it("handles JSON array trace files", async () => {
        const traces = JSON.stringify([
            {
                timestamp: new Date().toISOString(),
                tool: "test-tool",
                input: "test@real.com",
                output: "OK",
            },
        ]);
        await writeFile(join(testDir, "traces.json"), traces);
        const result = await runPrivacyAnalysis(testDir);
        // Array format won't parse line by line, but shouldn't crash
        expect(result.totalTraces).toBeGreaterThanOrEqual(0);
    });
    it("generates recommendations based on findings", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "checkout",
                input: "Process SSN 234-56-7890",
                output: "Card 4532015112830366 charged",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.recommendations.some((r) => r.includes("Sensitive financial/identity data"))).toBe(true);
    });
    it("generates GDPR recommendation when relevant PII found", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "user-api",
                input: "Get user data",
                output: "Email: person@domain.com",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.recommendations.some((r) => r.includes("GDPR"))).toBe(true);
    });
    it("detects potential cross-tenant data bleed", async () => {
        // Create traces with significant overlap between sessions
        const sharedData = Array.from({ length: 15 }, (_, i) => `shared_token_${i}_abcdef1234`);
        const traces = [
            ...sharedData.map((d) => ({
                timestamp: new Date().toISOString(),
                tool: "data-service",
                input: "request",
                output: d,
                sessionId: "session-a",
            })),
            ...sharedData.map((d) => ({
                timestamp: new Date().toISOString(),
                tool: "data-service",
                input: "request",
                output: d,
                sessionId: "session-b",
            })),
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir, { detectCrossTenant: true });
        const crossTenantFinding = result.findings.find((f) => f.description.includes("cross-tenant"));
        expect(crossTenantFinding).toBeDefined();
        expect(crossTenantFinding?.severity).toBe("high");
    });
    it("skips cross-tenant detection when disabled", async () => {
        const sharedData = Array.from({ length: 15 }, (_, i) => `token_${i}_xyz123456`);
        const traces = [
            ...sharedData.map((d) => ({
                timestamp: new Date().toISOString(),
                tool: "api",
                input: "req",
                output: d,
                sessionId: "sess-1",
            })),
            ...sharedData.map((d) => ({
                timestamp: new Date().toISOString(),
                tool: "api",
                input: "req",
                output: d,
                sessionId: "sess-2",
            })),
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir, { detectCrossTenant: false });
        expect(result.findings.some((f) => f.description.includes("cross-tenant"))).toBe(false);
    });
    it("excludes example.com emails from detection", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "test",
                input: "test@example.com",
                output: "OK",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        expect(result.byType.email || 0).toBe(0);
    });
    it("provides redacted samples in findings", async () => {
        const traces = [
            {
                timestamp: new Date().toISOString(),
                tool: "user-service",
                input: "john.doe@company.com",
                output: "Found",
            },
        ];
        await writeFile(join(testDir, "traces.jsonl"), traces.map((t) => JSON.stringify(t)).join("\n"));
        const result = await runPrivacyAnalysis(testDir);
        const emailFinding = result.findings.find((f) => f.type === "email");
        expect(emailFinding?.redactedSample).toMatch(/^j\*\*\*@company\.com$/);
    });
});
describe("privacyToFindings", () => {
    it("converts privacy findings to certification findings", () => {
        const privacyResult = {
            totalTraces: 10,
            tracesAnalyzed: 10,
            piiDetected: 2,
            findings: [
                {
                    type: "email",
                    location: "input",
                    tool: "user-api",
                    severity: "medium",
                    description: "EMAIL detected in input",
                    evidence: 'Found in tool "user-api" input',
                    redactedSample: "j***@company.com",
                    gdprRelevant: true,
                    ccpaRelevant: true,
                },
                {
                    type: "ssn",
                    location: "output",
                    tool: "verify-api",
                    severity: "critical",
                    description: "SSN detected in output",
                    evidence: 'Found in tool "verify-api" output',
                    redactedSample: "***-**-6789",
                    gdprRelevant: true,
                    ccpaRelevant: true,
                },
            ],
            byType: { email: 1, ssn: 1 },
            byTool: { "user-api": 1, "verify-api": 1 },
            recommendations: [],
        };
        const certFindings = privacyToFindings(privacyResult);
        expect(certFindings).toHaveLength(2);
        expect(certFindings[0].id).toBe("prv-001");
        expect(certFindings[1].id).toBe("prv-002");
        expect(certFindings[0].category).toBe("pii-exposure");
        expect(certFindings[0].severity).toBe("medium");
        expect(certFindings[1].severity).toBe("critical");
        expect(certFindings[0].confidence).toBe(90);
        expect(certFindings[0].evidence).toContain("j***@company.com");
    });
    it("returns empty array for no findings", () => {
        const privacyResult = {
            totalTraces: 5,
            tracesAnalyzed: 5,
            piiDetected: 0,
            findings: [],
            byType: {},
            byTool: {},
            recommendations: [],
        };
        const certFindings = privacyToFindings(privacyResult);
        expect(certFindings).toHaveLength(0);
    });
    it("generates sequential IDs", () => {
        const privacyResult = {
            totalTraces: 10,
            tracesAnalyzed: 10,
            piiDetected: 3,
            findings: Array.from({ length: 3 }, (_, i) => ({
                type: "email",
                location: "input",
                tool: `tool-${i}`,
                severity: "medium",
                description: `Finding ${i}`,
                evidence: `Evidence ${i}`,
                gdprRelevant: true,
                ccpaRelevant: true,
            })),
            byType: { email: 3 },
            byTool: { "tool-0": 1, "tool-1": 1, "tool-2": 1 },
            recommendations: [],
        };
        const certFindings = privacyToFindings(privacyResult);
        expect(certFindings.map((f) => f.id)).toEqual(["prv-001", "prv-002", "prv-003"]);
    });
});
//# sourceMappingURL=agent-privacy.test.js.map