/**
 * Tests for OPA/Rego Policy Evaluation
 */
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import { writeFile, unlink, mkdir } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { OPAClient, createOPAClient, createLocalOPAClient, createRemoteOPAClient, generateDefaultPolicy, generateSOC2Policy, policyResultToFindings, } from "./opa.js";
// Mock fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;
describe("OPAClient", () => {
    let tempDir;
    let testId;
    beforeEach(async () => {
        vi.clearAllMocks();
        // Use unique directory per test to avoid race conditions when tests run in parallel
        testId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
        tempDir = join(tmpdir(), `opa-test-${testId}`);
        await mkdir(tempDir, { recursive: true });
    });
    afterEach(async () => {
        // Cleanup temp files
        try {
            await unlink(join(tempDir, "policy.json"));
        }
        catch {
            // Ignore
        }
    });
    describe("local evaluation", () => {
        it("evaluates policy from local bundle", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    minScore: 70,
                    maxCriticalFindings: 0,
                },
                complianceChecks: [],
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 85,
                    findings: [],
                    frameworks: ["SOC2"],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(true);
            expect(result.evaluatedAt).toBeDefined();
            expect(result.policyVersion).toBe("1.0.0");
        });
        it("denies when score below minimum", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    minScore: 80,
                },
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 65,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.denialReasons).toContain("Certification score 65 below minimum 80");
        });
        it("denies when too many critical findings", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    maxCriticalFindings: 0,
                },
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 85,
                    findings: [
                        { id: "f1", severity: "critical", category: "vuln", confidence: 95 },
                    ],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.denialReasons?.[0]).toContain("critical findings exceed");
        });
        it("denies when too many high-severity findings", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    maxHighFindings: 2,
                },
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 75,
                    findings: [
                        { id: "f1", severity: "high", category: "vuln", confidence: 90 },
                        { id: "f2", severity: "high", category: "vuln", confidence: 85 },
                        { id: "f3", severity: "high", category: "vuln", confidence: 80 },
                    ],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.denialReasons?.[0]).toContain("high-severity findings exceed");
        });
        it("denies when required framework missing", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    requiredFrameworks: ["SOC2", "ISO27001"],
                },
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 90,
                    findings: [],
                    frameworks: ["SOC2"],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.denialReasons?.[0]).toContain("ISO27001");
        });
        it("denies findings in blocked categories", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    blockedCategories: ["sql-injection", "xss"],
                },
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 80,
                    findings: [
                        { id: "f1", severity: "high", category: "sql-injection", confidence: 95 },
                    ],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.denialReasons?.[0]).toContain("blocked category");
        });
        it("generates required remediations", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    requireRemediationFor: ["critical", "high"],
                },
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 80,
                    findings: [
                        { id: "f1", severity: "critical", category: "vuln", confidence: 95 },
                        { id: "f2", severity: "low", category: "style", confidence: 90 },
                    ],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.requiredRemediations).toBeDefined();
            expect(result.requiredRemediations?.[0]).toContain("f1");
        });
        it("generates warnings for warn categories", async () => {
            const policy = {
                version: "1.0.0",
                rules: {
                    warnOnCategories: ["dependency-vuln"],
                },
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 80,
                    findings: [
                        { id: "f1", severity: "medium", category: "dependency-vuln", confidence: 85 },
                    ],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(true);
            expect(result.warnings).toBeDefined();
            expect(result.warnings?.[0]).toContain("dependency-vuln");
        });
        it("throws when bundle path not found", async () => {
            const client = new OPAClient({
                mode: "local",
                bundlePath: "/nonexistent/path/policy.json",
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 80,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            await expect(client.evaluate(input)).rejects.toThrow("Policy bundle not found");
        });
        it("throws when bundle path not specified", async () => {
            const client = new OPAClient({
                mode: "local",
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 80,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            await expect(client.evaluate(input)).rejects.toThrow("Bundle path required");
        });
    });
    describe("compliance checks", () => {
        it("evaluates no_findings check", async () => {
            const policy = {
                version: "1.0.0",
                rules: {},
                complianceChecks: [
                    {
                        control: "SEC-001",
                        framework: "internal",
                        message: "No critical vulns",
                        blocking: true,
                        type: "no_findings",
                        category: "critical-vuln",
                    },
                ],
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 80,
                    findings: [
                        { id: "f1", severity: "high", category: "critical-vuln", confidence: 95 },
                    ],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.complianceFindings).toBeDefined();
            expect(result.complianceFindings?.[0].status).toBe("fail");
        });
        it("evaluates max_findings check", async () => {
            const policy = {
                version: "1.0.0",
                rules: {},
                complianceChecks: [
                    {
                        control: "SEC-002",
                        framework: "internal",
                        message: "Max 2 medium findings",
                        blocking: false,
                        type: "max_findings",
                        category: "medium-issue",
                        maxCount: 2,
                    },
                ],
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 80,
                    findings: [
                        { id: "f1", severity: "medium", category: "medium-issue", confidence: 85 },
                        { id: "f2", severity: "medium", category: "medium-issue", confidence: 80 },
                    ],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(true);
            expect(result.complianceFindings?.[0].status).toBe("pass");
        });
        it("evaluates min_score check", async () => {
            const policy = {
                version: "1.0.0",
                rules: {},
                complianceChecks: [
                    {
                        control: "SEC-003",
                        framework: "internal",
                        message: "Min score 80",
                        blocking: true,
                        type: "min_score",
                        minScore: 80,
                    },
                ],
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 75,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.complianceFindings?.[0].status).toBe("fail");
        });
        it("evaluates has_framework check", async () => {
            const policy = {
                version: "1.0.0",
                rules: {},
                complianceChecks: [
                    {
                        control: "SEC-004",
                        framework: "SOC2",
                        message: "SOC2 required",
                        blocking: true,
                        type: "has_framework",
                        requiredFramework: "SOC2",
                    },
                ],
            };
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify(policy));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 90,
                    findings: [],
                    frameworks: ["SOC2"],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(true);
            expect(result.complianceFindings?.[0].status).toBe("pass");
        });
    });
    describe("remote evaluation", () => {
        it("calls OPA server", async () => {
            mockFetch.mockResolvedValueOnce({
                ok: true,
                json: async () => ({
                    result: {
                        allow: true,
                        policy_version: "remote-1.0",
                    },
                }),
            });
            const client = new OPAClient({
                mode: "remote",
                serverUrl: "https://opa.example.com",
                decisionPath: "vaspera/certification/allow",
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 85,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(true);
            expect(result.policyVersion).toBe("remote-1.0");
            expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("opa.example.com"), expect.objectContaining({
                method: "POST",
            }));
        });
        it("throws when server URL not provided", async () => {
            const client = new OPAClient({
                mode: "remote",
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 85,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            await expect(client.evaluate(input)).rejects.toThrow("OPA server URL required");
        });
        it("handles server errors", async () => {
            mockFetch.mockResolvedValueOnce({
                ok: false,
                status: 500,
            });
            const client = new OPAClient({
                mode: "remote",
                serverUrl: "https://opa.example.com",
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 85,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            await expect(client.evaluate(input)).rejects.toThrow("OPA server returned 500");
        });
        it("handles timeout", async () => {
            mockFetch.mockImplementationOnce(() => new Promise((_, reject) => {
                const error = new Error("Aborted");
                error.name = "AbortError";
                reject(error);
            }));
            const client = new OPAClient({
                mode: "remote",
                serverUrl: "https://opa.example.com",
                timeout: 100,
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 85,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            await expect(client.evaluate(input)).rejects.toThrow("OPA evaluation timed out");
        });
        it("handles empty result", async () => {
            mockFetch.mockResolvedValueOnce({
                ok: true,
                json: async () => ({}),
            });
            const client = new OPAClient({
                mode: "remote",
                serverUrl: "https://opa.example.com",
            });
            const input = {
                certification: {
                    id: "cert-123",
                    target: "test-repo",
                    status: "completed",
                    score: 85,
                    findings: [],
                    frameworks: [],
                    startedAt: new Date().toISOString(),
                },
            };
            const result = await client.evaluate(input);
            expect(result.allow).toBe(false);
            expect(result.denialReasons).toContain("No policy result returned");
        });
    });
    describe("isAvailable", () => {
        it("returns true when remote server is healthy", async () => {
            mockFetch.mockResolvedValueOnce({
                ok: true,
            });
            const client = new OPAClient({
                mode: "remote",
                serverUrl: "https://opa.example.com",
            });
            const available = await client.isAvailable();
            expect(available).toBe(true);
        });
        it("returns false when remote server is down", async () => {
            mockFetch.mockRejectedValueOnce(new Error("Connection failed"));
            const client = new OPAClient({
                mode: "remote",
                serverUrl: "https://opa.example.com",
            });
            const available = await client.isAvailable();
            expect(available).toBe(false);
        });
        it("returns true when local bundle exists", async () => {
            const policyPath = join(tempDir, "policy.json");
            await writeFile(policyPath, JSON.stringify({ version: "1.0.0", rules: {} }));
            const client = new OPAClient({
                mode: "local",
                bundlePath: policyPath,
            });
            const available = await client.isAvailable();
            expect(available).toBe(true);
        });
        it("returns false when local bundle missing", async () => {
            const client = new OPAClient({
                mode: "local",
                bundlePath: "/nonexistent/policy.json",
            });
            const available = await client.isAvailable();
            expect(available).toBe(false);
        });
        it("returns false when no bundle path specified", async () => {
            const client = new OPAClient({
                mode: "local",
            });
            const available = await client.isAvailable();
            expect(available).toBe(false);
        });
    });
});
describe("Policy Templates", () => {
    describe("generateDefaultPolicy", () => {
        it("generates valid default policy", () => {
            const policy = generateDefaultPolicy();
            expect(policy.version).toBe("1.0.0");
            expect(policy.rules.minScore).toBe(70);
            expect(policy.rules.maxCriticalFindings).toBe(0);
            expect(policy.rules.maxHighFindings).toBe(3);
            expect(policy.complianceChecks).toBeDefined();
            expect(policy.complianceChecks?.length).toBeGreaterThan(0);
        });
    });
    describe("generateSOC2Policy", () => {
        it("generates valid SOC2 policy", () => {
            const policy = generateSOC2Policy();
            expect(policy.version).toContain("soc2");
            expect(policy.rules.minScore).toBe(85);
            expect(policy.rules.maxCriticalFindings).toBe(0);
            expect(policy.rules.maxHighFindings).toBe(0);
            expect(policy.rules.requiredFrameworks).toContain("SOC2");
        });
    });
});
describe("policyResultToFindings", () => {
    it("converts denial reasons to critical findings", () => {
        const result = {
            allow: false,
            denialReasons: ["Score too low", "Too many critical findings"],
            evaluatedAt: new Date().toISOString(),
            policyVersion: "1.0.0",
        };
        const findings = policyResultToFindings(result);
        expect(findings).toHaveLength(2);
        expect(findings[0].severity).toBe("critical");
        expect(findings[0].category).toBe("policy-violation");
    });
    it("converts warnings to low findings", () => {
        const result = {
            allow: true,
            warnings: ["Dependency warning"],
            evaluatedAt: new Date().toISOString(),
        };
        const findings = policyResultToFindings(result);
        expect(findings).toHaveLength(1);
        expect(findings[0].severity).toBe("low");
        expect(findings[0].category).toBe("policy-warning");
    });
    it("returns empty array for passing policy", () => {
        const result = {
            allow: true,
            evaluatedAt: new Date().toISOString(),
        };
        const findings = policyResultToFindings(result);
        expect(findings).toHaveLength(0);
    });
});
describe("Factory Functions", () => {
    it("createOPAClient creates OPAClient", () => {
        const client = createOPAClient({
            mode: "remote",
            serverUrl: "https://opa.example.com",
        });
        expect(client).toBeInstanceOf(OPAClient);
    });
    it("createLocalOPAClient creates local client", () => {
        const client = createLocalOPAClient("/path/to/bundle.json");
        expect(client).toBeInstanceOf(OPAClient);
    });
    it("createRemoteOPAClient creates remote client", () => {
        const client = createRemoteOPAClient("https://opa.example.com");
        expect(client).toBeInstanceOf(OPAClient);
    });
});
//# sourceMappingURL=opa.test.js.map