/**
 * Unit tests for Semgrep scanner module
 *
 * Note: These tests run actual semgrep scans which can take 5-10 seconds each.
 * Tests have extended timeouts to accommodate this.
 */
import { describe, it, expect, beforeEach, afterEach, afterAll } from "vitest";
// Semgrep scans take ~6 seconds each, so we need longer timeouts
const SEMGREP_TEST_TIMEOUT = 30000;
import { mkdir, rm, writeFile, readFile } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { runSemgrep, checkSemgrepAvailable, generateSupabaseRules, } from "./semgrep.js";
const TEST_BASE = join(tmpdir(), "vaspera-semgrep-test-" + Date.now() + "-" + Math.random().toString(36).slice(2));
let testProjectPath;
beforeEach(async () => {
    testProjectPath = join(TEST_BASE, "project-" + Math.random().toString(36).slice(2));
    await mkdir(testProjectPath, { recursive: true });
});
afterEach(async () => {
    try {
        await rm(testProjectPath, { recursive: true, force: true });
    }
    catch {
        // Ignore cleanup errors
    }
});
afterAll(async () => {
    try {
        await rm(TEST_BASE, { recursive: true, force: true });
    }
    catch {
        // Ignore cleanup errors
    }
});
describe("checkSemgrepAvailable", () => {
    it("returns availability status", async () => {
        const result = await checkSemgrepAvailable();
        expect(result).toHaveProperty("available");
        expect(typeof result.available).toBe("boolean");
        if (result.available) {
            expect(result.version).toBeDefined();
        }
        else {
            expect(result.error).toBeDefined();
        }
    });
});
describe("runSemgrep", () => {
    it("returns scanner result structure", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        expect(result).toHaveProperty("scanner", "semgrep");
        expect(result).toHaveProperty("findings");
        expect(result).toHaveProperty("duration");
        expect(result).toHaveProperty("success");
        expect(Array.isArray(result.findings)).toBe(true);
    }, SEMGREP_TEST_TIMEOUT);
    it("returns error message when semgrep not available", async () => {
        const available = await checkSemgrepAvailable();
        if (!available.available) {
            await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
            const result = await runSemgrep(testProjectPath);
            expect(result.success).toBe(false);
            expect(result.error).toContain("not available");
        }
    }, SEMGREP_TEST_TIMEOUT);
    it("all findings have confidence 100", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.confidence).toBe(100);
        }
    }, SEMGREP_TEST_TIMEOUT);
    it("findings have proper structure", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.scanner).toBe("semgrep");
            expect(finding.ruleId).toMatch(/^semgrep:/);
            expect(finding.file).toBeDefined();
            expect(finding.line).toBeGreaterThan(0);
            expect(finding.message).toBeDefined();
            expect(["critical", "high", "medium", "low", "info"]).toContain(finding.severity);
        }
    }, SEMGREP_TEST_TIMEOUT);
    it("tracks duration", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        expect(result.duration).toBeGreaterThanOrEqual(0);
    }, SEMGREP_TEST_TIMEOUT);
    it("accepts custom configs", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath, {
            configs: ["p/javascript"],
        });
        expect(result.scanner).toBe("semgrep");
        if (result.success && result.rulesUsed) {
            expect(result.rulesUsed).toContain("p/javascript");
        }
    }, SEMGREP_TEST_TIMEOUT);
    it("uses project-local rules when available", async () => {
        // Create local rules directory
        const rulesDir = join(testProjectPath, ".vaspera", "semgrep-rules");
        await mkdir(rulesDir, { recursive: true });
        await writeFile(join(rulesDir, "custom.yaml"), `rules:
  - id: test-rule
    pattern: |
      console.log(...)
    message: "Console log detected"
    languages: [javascript, typescript]
    severity: INFO
`);
        await writeFile(join(testProjectPath, "index.ts"), `console.log("test");`);
        const result = await runSemgrep(testProjectPath);
        // If semgrep is available and ran successfully, custom rules should be loaded
        if (result.success) {
            expect(result.rulesUsed?.length || 0).toBeGreaterThanOrEqual(0);
        }
    }, SEMGREP_TEST_TIMEOUT);
    it("handles timeout option", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath, {
            timeout: 1000, // Very short timeout
        });
        // Should either complete or timeout gracefully
        expect(result.scanner).toBe("semgrep");
    }, SEMGREP_TEST_TIMEOUT);
});
describe("runSemgrep - OWASP detection", () => {
    it("detects potential SQL injection patterns", async () => {
        const available = await checkSemgrepAvailable();
        if (!available.available)
            return;
        await writeFile(join(testProjectPath, "db.ts"), `
        import { db } from './connection';

        export async function getUser(userId: string) {
          // Vulnerable to SQL injection
          const query = "SELECT * FROM users WHERE id = '" + userId + "'";
          return db.query(query);
        }
      `);
        const result = await runSemgrep(testProjectPath, {
            configs: ["p/owasp-top-ten"],
        });
        if (result.success && result.findings.length > 0) {
            // Should find SQL injection
            const sqlFindings = result.findings.filter((f) => f.message.toLowerCase().includes("sql") ||
                f.cweIds?.includes("CWE-89"));
            expect(sqlFindings.length).toBeGreaterThanOrEqual(0); // May or may not detect
        }
    }, SEMGREP_TEST_TIMEOUT);
    it("detects XSS patterns", async () => {
        const available = await checkSemgrepAvailable();
        if (!available.available)
            return;
        await writeFile(join(testProjectPath, "view.tsx"), `
        export function UserProfile({ userInput }: { userInput: string }) {
          return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
        }
      `);
        const result = await runSemgrep(testProjectPath, {
            configs: ["p/react"],
        });
        if (result.success) {
            // May detect dangerous React patterns
            expect(result.scanner).toBe("semgrep");
        }
    }, SEMGREP_TEST_TIMEOUT);
});
describe("generateSupabaseRules", () => {
    it("creates supabase.yaml rules file", async () => {
        const rulesPath = await generateSupabaseRules(testProjectPath);
        expect(rulesPath).toContain("supabase.yaml");
        expect(rulesPath).toContain(".vaspera");
        expect(rulesPath).toContain("semgrep-rules");
        const content = await readFile(rulesPath, "utf-8");
        expect(content).toContain("rules:");
        expect(content).toContain("supabase-rls-missing");
        expect(content).toContain("supabase-service-role-key");
        expect(content).toContain("supabase-no-auth-check");
    });
    it("creates rules directory if not exists", async () => {
        const rulesPath = await generateSupabaseRules(testProjectPath);
        const content = await readFile(rulesPath, "utf-8");
        expect(content).toBeDefined();
    });
    it("includes CWE references", async () => {
        const rulesPath = await generateSupabaseRules(testProjectPath);
        const content = await readFile(rulesPath, "utf-8");
        expect(content).toContain("CWE-284"); // Improper Access Control
        expect(content).toContain("CWE-862"); // Missing Authorization
    });
    it("includes severity levels", async () => {
        const rulesPath = await generateSupabaseRules(testProjectPath);
        const content = await readFile(rulesPath, "utf-8");
        expect(content).toContain("severity: ERROR");
        expect(content).toContain("severity: WARNING");
    });
    it("includes technology metadata", async () => {
        const rulesPath = await generateSupabaseRules(testProjectPath);
        const content = await readFile(rulesPath, "utf-8");
        expect(content).toContain("technology:");
        expect(content).toContain("supabase");
    });
});
describe("runSemgrep - severity mapping", () => {
    it("maps SARIF levels to vaspera severities", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        // All findings should have valid vaspera severity
        for (const finding of result.findings) {
            expect(["critical", "high", "medium", "low", "info"]).toContain(finding.severity);
        }
    }, SEMGREP_TEST_TIMEOUT);
});
describe("runSemgrep - CWE extraction", () => {
    it("extracts CWE IDs from rules", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        // CWE IDs should be in proper format if present
        for (const finding of result.findings) {
            if (finding.cweIds && finding.cweIds.length > 0) {
                for (const cwe of finding.cweIds) {
                    expect(cwe).toMatch(/^CWE-\d+$/);
                }
            }
        }
    }, SEMGREP_TEST_TIMEOUT);
});
describe("runSemgrep - metadata", () => {
    it("includes rule metadata in findings", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        // Findings may include metadata
        for (const finding of result.findings) {
            if (finding.metadata) {
                expect(typeof finding.metadata).toBe("object");
            }
        }
    }, SEMGREP_TEST_TIMEOUT);
    it("includes evidence (code snippet) when available", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runSemgrep(testProjectPath);
        // Some findings may include evidence
        for (const finding of result.findings) {
            if (finding.evidence) {
                expect(typeof finding.evidence).toBe("string");
            }
        }
    }, SEMGREP_TEST_TIMEOUT);
});
//# sourceMappingURL=semgrep.test.js.map