/**
 * Unit tests for exploit-chain analyzer module
 */
import { describe, it, expect } from "vitest";
import { analyzeExploitChains, exploitChainsToFindings, getChainSummary, } from "./exploit-chain.js";
// Helper to create mock findings
function createMockFinding(id, category, severity = "medium", confidence = 80) {
    return {
        id,
        severity,
        category,
        description: `Mock finding: ${category}`,
        evidence: `File: src/test.ts:10`,
        confidence,
        verifications: [],
        created_at: new Date().toISOString(),
        scanner_source: "semgrep", // Use a valid scanner type
    };
}
describe("analyzeExploitChains", () => {
    it("returns ExploitChainResult structure", async () => {
        const findings = [
            createMockFinding("f1", "injection"),
            createMockFinding("f2", "authentication"),
        ];
        const result = await analyzeExploitChains(findings);
        expect(result).toHaveProperty("totalFindings");
        expect(result).toHaveProperty("chainsDetected");
        expect(result).toHaveProperty("chains");
        expect(result).toHaveProperty("riskScore");
        expect(result).toHaveProperty("recommendations");
        expect(Array.isArray(result.chains)).toBe(true);
        expect(Array.isArray(result.recommendations)).toBe(true);
    });
    it("returns empty chains when less than 2 findings", async () => {
        const findings = [createMockFinding("f1", "injection")];
        const result = await analyzeExploitChains(findings);
        expect(result.chainsDetected).toBe(0);
        expect(result.chains).toHaveLength(0);
        expect(result.recommendations).toContain("Insufficient findings to detect chains. Run more scanners.");
    });
    it("returns zero chains when no findings provided", async () => {
        const result = await analyzeExploitChains([]);
        expect(result.chainsDetected).toBe(0);
        expect(result.totalFindings).toBe(0);
    });
});
describe("analyzeExploitChains - chain detection rules", () => {
    it("detects info disclosure to RCE chain", async () => {
        const findings = [
            createMockFinding("f1", "sensitive-disclosure", "medium"),
            createMockFinding("f2", "code-injection", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const infoRceChain = result.chains.find(c => c.name.toLowerCase().includes("information disclosure") ||
                c.name.toLowerCase().includes("rce"));
            expect(infoRceChain).toBeDefined();
        }
    });
    it("detects SSRF to internal access chain", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const ssrfChain = result.chains.find(c => c.name.toLowerCase().includes("ssrf") ||
                c.name.toLowerCase().includes("internal"));
            expect(ssrfChain).toBeDefined();
        }
    });
    it("detects XSS to session hijacking chain", async () => {
        const findings = [
            createMockFinding("f1", "xss", "medium"),
            createMockFinding("f2", "session-management", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const xssChain = result.chains.find(c => c.name.toLowerCase().includes("xss") ||
                c.name.toLowerCase().includes("session"));
            expect(xssChain).toBeDefined();
        }
    });
    it("detects auth bypass to data exfiltration chain", async () => {
        const findings = [
            createMockFinding("f1", "authentication", "high"),
            createMockFinding("f2", "sensitive-disclosure", "medium"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const authChain = result.chains.find(c => c.name.toLowerCase().includes("authentication") ||
                c.name.toLowerCase().includes("data"));
            expect(authChain).toBeDefined();
        }
    });
    it("detects SQL injection to database takeover chain", async () => {
        const findings = [
            createMockFinding("f1", "sql-injection", "high"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const sqliChain = result.chains.find(c => c.name.toLowerCase().includes("sql") ||
                c.name.toLowerCase().includes("database"));
            expect(sqliChain).toBeDefined();
        }
    });
    it("detects path traversal to secret exposure chain", async () => {
        const findings = [
            createMockFinding("f1", "path-traversal", "high"),
            createMockFinding("f2", "secret-management", "critical"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const lfiChain = result.chains.find(c => c.name.toLowerCase().includes("path") ||
                c.name.toLowerCase().includes("secret"));
            expect(lfiChain).toBeDefined();
        }
    });
});
describe("analyzeExploitChains - severity calculation", () => {
    it("escalates chain severity appropriately", async () => {
        const findings = [
            createMockFinding("f1", "sensitive-disclosure", "medium"),
            createMockFinding("f2", "code-injection", "medium"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            // Info → RCE should escalate to critical
            const criticalChain = result.chains.find(c => c.totalSeverity === "critical");
            expect(criticalChain).toBeDefined();
        }
    });
    it("calculates confidence based on finding confidences", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium", 90),
            createMockFinding("f2", "authentication", "high", 80),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                expect(chain.confidence).toBeGreaterThan(0);
                expect(chain.confidence).toBeLessThanOrEqual(100);
            }
        }
    });
});
describe("analyzeExploitChains - chain properties", () => {
    it("chains have unique IDs", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
            createMockFinding("f3", "sensitive-disclosure", "medium"),
            createMockFinding("f4", "code-injection", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const ids = result.chains.map(c => c.id);
            const uniqueIds = new Set(ids);
            expect(uniqueIds.size).toBe(ids.length);
        }
    });
    it("chains have steps with entry and target roles", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                expect(chain.steps.length).toBeGreaterThanOrEqual(2);
                const entryStep = chain.steps.find(s => s.role === "entry");
                const targetStep = chain.steps.find(s => s.role === "target");
                expect(entryStep).toBeDefined();
                expect(targetStep).toBeDefined();
            }
        }
    });
    it("chains have MITRE ATT&CK IDs", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                expect(Array.isArray(chain.mitreAttackIds)).toBe(true);
                expect(chain.mitreAttackIds.length).toBeGreaterThan(0);
            }
        }
    });
    it("chains have difficulty rating", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                expect(["trivial", "easy", "moderate", "hard", "expert"]).toContain(chain.difficulty);
            }
        }
    });
    it("chains have attack scenario description", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                expect(typeof chain.attackScenario).toBe("string");
                expect(chain.attackScenario.length).toBeGreaterThan(0);
            }
        }
    });
});
describe("analyzeExploitChains - risk score", () => {
    it("calculates risk score between 0 and 100", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        expect(result.riskScore).toBeGreaterThanOrEqual(0);
        expect(result.riskScore).toBeLessThanOrEqual(100);
    });
    it("returns zero risk score when no chains detected", async () => {
        const findings = [createMockFinding("f1", "code-quality")];
        const result = await analyzeExploitChains(findings);
        expect(result.riskScore).toBe(0);
    });
});
describe("analyzeExploitChains - recommendations", () => {
    it("generates recommendations for critical chains", async () => {
        const findings = [
            createMockFinding("f1", "sensitive-disclosure", "medium"),
            createMockFinding("f2", "code-injection", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const criticalChains = result.chains.filter(c => c.totalSeverity === "critical");
            if (criticalChains.length > 0) {
                const criticalRec = result.recommendations.find(r => r.toLowerCase().includes("critical"));
                expect(criticalRec).toBeDefined();
            }
        }
    });
    it("generates recommendations for easy chains", async () => {
        const findings = [
            createMockFinding("f1", "xss", "medium"),
            createMockFinding("f2", "session-management", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            const easyChains = result.chains.filter(c => c.difficulty === "easy" || c.difficulty === "trivial");
            if (easyChains.length > 0) {
                const easyRec = result.recommendations.find(r => r.toLowerCase().includes("easy"));
                expect(easyRec).toBeDefined();
            }
        }
    });
});
describe("exploitChainsToFindings", () => {
    it("converts chains to Finding array", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        const chainFindings = exploitChainsToFindings(result);
        expect(Array.isArray(chainFindings)).toBe(true);
        expect(chainFindings.length).toBe(result.chainsDetected);
    });
    it("findings have correct properties", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        const chainFindings = exploitChainsToFindings(result);
        for (const finding of chainFindings) {
            expect(finding).toHaveProperty("id");
            expect(finding).toHaveProperty("severity");
            expect(finding).toHaveProperty("category", "exploit-chain");
            expect(finding).toHaveProperty("description");
            expect(finding).toHaveProperty("evidence");
            expect(finding).toHaveProperty("confidence");
            expect(finding).toHaveProperty("verifications");
            expect(finding).toHaveProperty("created_at");
            expect(finding).toHaveProperty("scanner_source", "exploit-chain-analyzer");
        }
    });
    it("findings have chain metadata", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        const chainFindings = exploitChainsToFindings(result);
        for (const finding of chainFindings) {
            expect(finding.metadata).toBeDefined();
            expect(finding.metadata?.stepsCount).toBeGreaterThanOrEqual(2);
            expect(Array.isArray(finding.metadata?.chainedFindingIds)).toBe(true);
        }
    });
});
describe("getChainSummary", () => {
    it("returns readable summary string", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        const summary = getChainSummary(result);
        expect(typeof summary).toBe("string");
        expect(summary.length).toBeGreaterThan(0);
    });
    it("returns appropriate message when no chains detected", async () => {
        const findings = [createMockFinding("f1", "code-quality")];
        const result = await analyzeExploitChains(findings);
        const summary = getChainSummary(result);
        expect(summary).toContain("No exploit chains detected");
    });
    it("includes chain count in summary", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        const summary = getChainSummary(result);
        if (result.chainsDetected > 0) {
            expect(summary).toContain(result.chainsDetected.toString());
        }
    });
    it("includes risk score in summary", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        const summary = getChainSummary(result);
        if (result.chainsDetected > 0) {
            expect(summary.toLowerCase()).toContain("risk");
        }
    });
    it("mentions critical chains when present", async () => {
        const findings = [
            createMockFinding("f1", "sensitive-disclosure", "medium"),
            createMockFinding("f2", "code-injection", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        const summary = getChainSummary(result);
        const criticalCount = result.chains.filter(c => c.totalSeverity === "critical").length;
        if (criticalCount > 0) {
            expect(summary.toLowerCase()).toContain("critical");
        }
    });
});
describe("exploit chain step properties", () => {
    it("entry steps have enables property", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                const entryStep = chain.steps.find(s => s.role === "entry");
                expect(entryStep?.enables).toBeDefined();
            }
        }
    });
    it("target steps have prerequisite property", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                const targetStep = chain.steps.find(s => s.role === "target");
                expect(targetStep?.prerequisite).toBeDefined();
            }
        }
    });
    it("steps have techniques array", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                for (const step of chain.steps) {
                    expect(Array.isArray(step.techniques)).toBe(true);
                    expect(step.techniques.length).toBeGreaterThan(0);
                }
            }
        }
    });
    it("steps contain original finding reference", async () => {
        const findings = [
            createMockFinding("f1", "ssrf", "medium"),
            createMockFinding("f2", "authentication", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 0) {
            for (const chain of result.chains) {
                for (const step of chain.steps) {
                    expect(step.finding).toBeDefined();
                    expect(step.findingId).toBeDefined();
                    expect(step.findingId).toBe(step.finding.id);
                }
            }
        }
    });
});
describe("exploit chain sorting", () => {
    it("chains are sorted by severity (critical first)", async () => {
        const findings = [
            createMockFinding("f1", "xss", "medium"),
            createMockFinding("f2", "session-management", "high"),
            createMockFinding("f3", "sensitive-disclosure", "medium"),
            createMockFinding("f4", "code-injection", "high"),
        ];
        const result = await analyzeExploitChains(findings);
        if (result.chainsDetected > 1) {
            const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
            for (let i = 1; i < result.chains.length; i++) {
                const prevSev = severityOrder[result.chains[i - 1].totalSeverity];
                const currSev = severityOrder[result.chains[i].totalSeverity];
                expect(prevSev).toBeLessThanOrEqual(currSev);
            }
        }
    });
});
describe("analyzeExploitChains - limits", () => {
    it("limits chains to prevent noise", async () => {
        // Create many findings that could generate many chains
        const findings = [];
        const categories = [
            "ssrf", "authentication", "sensitive-disclosure", "code-injection",
            "xss", "session-management", "path-traversal", "secret-management"
        ];
        for (let i = 0; i < 20; i++) {
            findings.push(createMockFinding(`f${i}`, categories[i % categories.length], "medium"));
        }
        const result = await analyzeExploitChains(findings);
        // Should be capped at 20 chains
        expect(result.chainsDetected).toBeLessThanOrEqual(20);
    });
});
//# sourceMappingURL=exploit-chain.test.js.map