/**
 * Unit tests for binary-analysis scanner module
 */
import { describe, it, expect, beforeEach, afterEach, afterAll } from "vitest";
import { mkdir, rm, writeFile } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { runBinaryAnalysis, checkBinaryAnalysisAvailable, detectNativeModules, } from "./binary-analysis.js";
const TEST_BASE = join(tmpdir(), "vaspera-binary-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("checkBinaryAnalysisAvailable", () => {
    it("returns proper structure with boolean flags", async () => {
        const result = await checkBinaryAnalysisAvailable();
        expect(result).toHaveProperty("checksec");
        expect(result).toHaveProperty("nm");
        expect(result).toHaveProperty("objdump");
        expect(result).toHaveProperty("file");
        expect(typeof result.checksec).toBe("boolean");
        expect(typeof result.nm).toBe("boolean");
        expect(typeof result.objdump).toBe("boolean");
        expect(typeof result.file).toBe("boolean");
    });
});
describe("detectNativeModules", () => {
    it("returns empty array for project without native modules", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const targets = await detectNativeModules(testProjectPath);
        expect(Array.isArray(targets)).toBe(true);
        expect(targets).toHaveLength(0);
    });
    it("detects binding.gyp files", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "sources": ["addon.cc"]
        }]
      }`);
        const targets = await detectNativeModules(testProjectPath);
        expect(targets.length).toBeGreaterThan(0);
        const bindingGyp = targets.find(t => t.type === "binding-gyp");
        expect(bindingGyp).toBeDefined();
        expect(bindingGyp?.name).toBe("binding.gyp");
    });
    it("detects Rust FFI projects with cdylib", async () => {
        await writeFile(join(testProjectPath, "Cargo.toml"), `[package]
name = "my-lib"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]
`);
        const targets = await detectNativeModules(testProjectPath);
        const rustFfi = targets.find(t => t.type === "rust-ffi");
        expect(rustFfi).toBeDefined();
    });
    it("detects Go CGO usage", async () => {
        await writeFile(join(testProjectPath, "main.go"), `package main

import "C"

func main() {
  // CGO code here
}
`);
        const targets = await detectNativeModules(testProjectPath);
        const goCgo = targets.find(t => t.type === "go-cgo");
        expect(goCgo).toBeDefined();
    });
    it("ignores node_modules/.pnpm and .cache directories", async () => {
        await mkdir(join(testProjectPath, "node_modules", ".pnpm", "pkg"), { recursive: true });
        await writeFile(join(testProjectPath, "node_modules", ".pnpm", "pkg", "binding.gyp"), `{"targets": []}`);
        const targets = await detectNativeModules(testProjectPath);
        const pnpmTarget = targets.find(t => t.path.includes(".pnpm"));
        expect(pnpmTarget).toBeUndefined();
    });
});
describe("runBinaryAnalysis", () => {
    it("returns proper ScannerResult structure", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runBinaryAnalysis(testProjectPath);
        expect(result).toHaveProperty("scanner", "binary-analysis");
        expect(result).toHaveProperty("findings");
        expect(result).toHaveProperty("duration");
        expect(result).toHaveProperty("success");
        expect(Array.isArray(result.findings)).toBe(true);
    });
    it("returns empty findings for projects without native modules", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runBinaryAnalysis(testProjectPath);
        expect(result.success).toBe(true);
        expect(result.findings).toHaveLength(0);
        expect(result.metadata?.message).toContain("No native modules");
    });
    it("tracks duration", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runBinaryAnalysis(testProjectPath);
        expect(result.duration).toBeGreaterThanOrEqual(0);
    });
    it("handles non-existent directories gracefully", async () => {
        const result = await runBinaryAnalysis("/nonexistent/path/that/does/not/exist");
        expect(result.scanner).toBe("binary-analysis");
        // Should either succeed with 0 findings or fail gracefully
        expect(result).toHaveProperty("success");
    });
    it("accepts timeout option", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runBinaryAnalysis(testProjectPath, { timeout: 5000 });
        expect(result.scanner).toBe("binary-analysis");
    });
});
describe("runBinaryAnalysis - binding.gyp analysis", () => {
    it("detects -fno-stack-protector flag", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "cflags": ["-fno-stack-protector"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const stackProtectorFinding = result.findings.find(f => f.ruleId === "binary:disabled-stack-protector");
            expect(stackProtectorFinding).toBeDefined();
            expect(stackProtectorFinding?.severity).toBe("critical");
        }
    });
    it("detects -z execstack flag", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "ldflags": ["-z execstack"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const execStackFinding = result.findings.find(f => f.ruleId === "binary:executable-stack-flag");
            expect(execStackFinding).toBeDefined();
            expect(execStackFinding?.severity).toBe("critical");
        }
    });
    it("detects -z norelro flag", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "ldflags": ["-z norelro"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const relroFinding = result.findings.find(f => f.ruleId === "binary:disabled-relro");
            expect(relroFinding).toBeDefined();
            expect(relroFinding?.severity).toBe("high");
        }
    });
    it("detects disabled FORTIFY_SOURCE", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "defines": ["-D_FORTIFY_SOURCE=0"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const fortifyFinding = result.findings.find(f => f.ruleId === "binary:disabled-fortify");
            expect(fortifyFinding).toBeDefined();
            expect(fortifyFinding?.severity).toBe("high");
        }
    });
});
describe("runBinaryAnalysis - finding properties", () => {
    it("findings have correct scanner property", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "cflags": ["-fno-stack-protector"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.scanner).toBe("binary-analysis");
        }
    });
    it("findings have ruleId starting with binary:", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "cflags": ["-fno-stack-protector"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.ruleId).toMatch(/^binary:/);
        }
    });
    it("findings have valid severity levels", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "cflags": ["-fno-stack-protector"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(["critical", "high", "medium", "low", "info"]).toContain(finding.severity);
        }
    });
    it("findings have category binary-security", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "cflags": ["-fno-stack-protector"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.category).toBe("binary-security");
        }
    });
    it("findings have confidence 100", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{
        "targets": [{
          "target_name": "addon",
          "cflags": ["-fno-stack-protector"],
          "sources": ["addon.cc"]
        }]
      }`);
        const result = await runBinaryAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.confidence).toBe(100);
        }
    });
});
describe("runBinaryAnalysis - Rust and Go detection", () => {
    it("reports Rust FFI detection as info", async () => {
        await writeFile(join(testProjectPath, "Cargo.toml"), `[package]
name = "my-lib"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]
`);
        const result = await runBinaryAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const rustFinding = result.findings.find(f => f.ruleId === "binary:rust-ffi-detected");
            expect(rustFinding).toBeDefined();
            expect(rustFinding?.severity).toBe("info");
        }
    });
    it("reports Go CGO detection as info", async () => {
        await writeFile(join(testProjectPath, "main.go"), `package main

import "C"

func main() {}
`);
        const result = await runBinaryAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const goFinding = result.findings.find(f => f.ruleId === "binary:go-cgo-detected");
            expect(goFinding).toBeDefined();
            expect(goFinding?.severity).toBe("info");
        }
    });
});
//# sourceMappingURL=binary-analysis.test.js.map