/**
 * Tests for PII Redaction
 */
import { describe, it, expect } from "vitest";
import { redactPII, containsPII, analyzePII, createPIIPattern, getDefaultPatternNames, getPatternsByType, generateRedactionSummary, createRedactionHook, } from "./redaction.js";
describe("redactPII", () => {
    describe("email detection", () => {
        it("redacts email addresses", () => {
            const result = redactPII("Contact: john.doe@company.com");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toBe("Contact: [REDACTED:EMAIL]");
            expect(result.redactions.length).toBe(1);
            expect(result.redactions[0].type).toBe("email");
        });
        it("redacts multiple emails", () => {
            const result = redactPII("Send to alice@example.org and bob@example.org");
            expect(result.redactions.length).toBe(2);
            expect(result.redacted).toContain("[REDACTED:EMAIL]");
        });
        it("excludes test domains", () => {
            // example.com, test.com, and localhost are excluded by the validator
            const result = redactPII("Contact: test@example.com");
            // example.com is excluded from detection
            expect(result.hadPII).toBe(false);
            // But real domains are detected
            const realResult = redactPII("Contact: user@company.com");
            expect(realResult.hadPII).toBe(true);
        });
    });
    describe("phone number detection", () => {
        it("redacts US phone numbers", () => {
            const result = redactPII("Call me at 555-123-4567");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toBe("Call me at [REDACTED:PHONE]");
            expect(result.redactions[0].type).toBe("phone");
        });
        it("redacts phone with parentheses", () => {
            const result = redactPII("Phone: (555) 123-4567");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toContain("[REDACTED:PHONE]");
        });
        it("redacts international format", () => {
            const result = redactPII("Call +1-555-123-4567");
            expect(result.hadPII).toBe(true);
        });
    });
    describe("SSN detection", () => {
        it("redacts SSN format", () => {
            const result = redactPII("SSN: 123-45-6789");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toBe("SSN: [REDACTED:SSN]");
            expect(result.redactions[0].type).toBe("ssn");
        });
        it("redacts SSN without dashes", () => {
            const result = redactPII("SSN: 123456789");
            expect(result.hadPII).toBe(true);
        });
        it("rejects invalid SSN (666 prefix)", () => {
            const result = redactPII("Invalid: 666-12-3456");
            // Should not be detected as SSN (666 is invalid prefix)
            expect(result.redactions.filter((r) => r.type === "ssn").length).toBe(0);
        });
    });
    describe("credit card detection", () => {
        it("detects sensitive numeric patterns", () => {
            // Credit card-like numbers get detected as PII
            const result = redactPII("Card: 4111111111111111");
            expect(result.hadPII).toBe(true);
            // Sensitive patterns are redacted (phone or CC pattern may match)
            expect(result.redacted).toContain("[REDACTED:");
        });
        it("validates Luhn checksum for credit cards", () => {
            // Use a number that clearly fails Luhn and won't match other patterns
            // 4000000000000002 - fails Luhn, should not be detected as CC
            const result = redactPII("Not a card: 4000000000000002");
            // Should not be detected as credit card specifically (fails Luhn)
            expect(result.redactions.filter((r) => r.type === "credit-card").length).toBe(0);
        });
    });
    describe("IP address detection", () => {
        it("redacts public IP addresses", () => {
            const result = redactPII("Server: 8.8.8.8");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toBe("Server: [REDACTED:IP]");
        });
        it("excludes localhost", () => {
            const result = redactPII("Localhost: 127.0.0.1");
            expect(result.redactions.filter((r) => r.type === "ip-address").length).toBe(0);
        });
        it("excludes private ranges", () => {
            const result = redactPII("Internal: 192.168.1.1");
            expect(result.redactions.filter((r) => r.type === "ip-address").length).toBe(0);
        });
    });
    describe("API key detection", () => {
        it("redacts GitHub tokens", () => {
            const result = redactPII("Token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toContain("[REDACTED:GH_TOKEN]");
        });
        it("redacts OpenAI API keys", () => {
            const result = redactPII("Key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toContain("[REDACTED:OPENAI_KEY]");
        });
        it("redacts AWS access keys", () => {
            const result = redactPII("AWS: AKIAIOSFODNN7EXAMPLE");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toContain("[REDACTED:AWS_KEY]");
        });
        it("redacts Slack tokens", () => {
            const result = redactPII("Slack: xoxb-123456789012-1234567890123-abcdefghijklmnop");
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toContain("[REDACTED:SLACK_TOKEN]");
        });
    });
    describe("JWT detection", () => {
        it("redacts JWT tokens", () => {
            const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";
            const result = redactPII(`Bearer ${jwt}`);
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toContain("[REDACTED:JWT]");
        });
    });
    describe("password detection", () => {
        it("redacts password fields", () => {
            const result = redactPII('password: "supersecret123"');
            expect(result.hadPII).toBe(true);
            expect(result.redacted).toContain("[REDACTED:PASSWORD]");
        });
        it("redacts pwd fields", () => {
            const result = redactPII("pwd=mypassword");
            expect(result.hadPII).toBe(true);
        });
    });
    describe("UUID detection", () => {
        it("redacts UUIDs", () => {
            const result = redactPII("ID: 550e8400-e29b-41d4-a716-446655440000");
            expect(result.hadPII).toBe(true);
            // UUID should be redacted (may have overlapping patterns)
            expect(result.redacted).toContain("[REDACTED:");
            expect(result.redactions.some((r) => r.type === "uuid")).toBe(true);
        });
    });
});
describe("containsPII", () => {
    it("returns true when PII is present", () => {
        expect(containsPII("Email: test@example.org")).toBe(true);
    });
    it("returns false when no PII is present", () => {
        expect(containsPII("Hello, world!")).toBe(false);
    });
});
describe("analyzePII", () => {
    it("returns statistics by type", () => {
        const result = analyzePII("Contact: john@example.org, phone: 555-123-4567, SSN: 123-45-6789");
        expect(result.totalPII).toBeGreaterThanOrEqual(3);
        expect(result.byType.email).toBe(1);
        expect(result.byType.phone).toBe(1);
        expect(result.byType.ssn).toBe(1);
    });
    it("tracks matched patterns", () => {
        const result = analyzePII("Token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        expect(result.patterns).toContain("github-token");
    });
});
describe("createPIIPattern", () => {
    it("creates custom pattern", () => {
        const pattern = createPIIPattern("custom-id", "custom", /CUST-\d{6}/g, {
            placeholder: "[REDACTED:CUSTOM_ID]",
        });
        expect(pattern.name).toBe("custom-id");
        expect(pattern.type).toBe("custom");
        expect(pattern.pattern.test("CUST-123456")).toBe(true);
    });
    it("custom patterns work with redactPII", () => {
        const customPattern = createPIIPattern("employee-id", "custom", /EMP-\d{5}/g, {
            placeholder: "[REDACTED:EMP]",
        });
        const result = redactPII("Employee: EMP-12345", {
            customPatterns: [customPattern],
        });
        expect(result.hadPII).toBe(true);
        expect(result.redacted).toContain("[REDACTED:EMP]");
    });
});
describe("getDefaultPatternNames", () => {
    it("returns list of default pattern names", () => {
        const names = getDefaultPatternNames();
        expect(names).toContain("email");
        expect(names).toContain("phone-us");
        expect(names).toContain("ssn");
        expect(names).toContain("credit-card");
        expect(names).toContain("jwt");
    });
});
describe("getPatternsByType", () => {
    it("returns patterns for a type", () => {
        const phonePatterns = getPatternsByType("phone");
        expect(phonePatterns.length).toBeGreaterThan(0);
        expect(phonePatterns.every((p) => p.type === "phone")).toBe(true);
    });
});
describe("generateRedactionSummary", () => {
    it("generates summary from results", () => {
        const results = [
            redactPII("Email: a@b.com, Phone: 555-123-4567"),
            redactPII("SSN: 123-45-6789"),
        ];
        const summary = generateRedactionSummary(results);
        expect(summary).toContain("Redacted");
        expect(summary).toContain("email");
        expect(summary).toContain("ssn");
    });
    it("returns 'No PII detected' for clean content", () => {
        const results = [redactPII("Hello, world!")];
        const summary = generateRedactionSummary(results);
        expect(summary).toBe("No PII detected");
    });
});
describe("createRedactionHook", () => {
    it("creates hook for TranscriptLogger", () => {
        const hook = createRedactionHook();
        const result = hook("Contact: john@example.org");
        expect(result.content).toContain("[REDACTED:EMAIL]");
        expect(result.redactionLog).toBeDefined();
        expect(result.redactionLog?.length).toBe(1);
    });
    it("returns original content when no PII", () => {
        const hook = createRedactionHook();
        const result = hook("Hello, world!");
        expect(result.content).toBe("Hello, world!");
        expect(result.redactionLog).toBeUndefined();
    });
    it("respects options", () => {
        const hook = createRedactionHook({ excludePatterns: ["email"] });
        const result = hook("Email: john@example.org");
        // Email should not be redacted when excluded
        expect(result.content).not.toContain("[REDACTED");
    });
});
describe("edge cases", () => {
    it("handles empty string", () => {
        const result = redactPII("");
        expect(result.hadPII).toBe(false);
        expect(result.redacted).toBe("");
    });
    it("handles string with only whitespace", () => {
        const result = redactPII("   \n\t  ");
        expect(result.hadPII).toBe(false);
    });
    it("handles multiple PII in same string", () => {
        const result = redactPII("Name: John, Email: john@test.org, Phone: 555-123-4567, SSN: 123-45-6789");
        expect(result.redactions.length).toBeGreaterThanOrEqual(3);
    });
    it("respects maxRedactions limit", () => {
        const manyEmails = Array.from({ length: 100 }, (_, i) => `email${i}@example.org`).join(", ");
        const result = redactPII(manyEmails, { maxRedactions: 10 });
        expect(result.redactions.length).toBeLessThanOrEqual(10);
    });
    it("hashes originals when requested", () => {
        const result = redactPII("Email: test@example.org", { hashOriginals: true });
        expect(result.redactions[0].contentHash).toBeDefined();
        expect(result.redactions[0].contentHash?.length).toBe(16);
    });
});
//# sourceMappingURL=redaction.test.js.map