/**
 * Unit tests for memory-safety 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 { runMemorySafetyAnalysis, checkCppcheckAvailable, checkCargoGeigerAvailable, detectUnsafeLanguages, } from "./memory-safety.js";
const TEST_BASE = join(tmpdir(), "vaspera-memory-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("checkCppcheckAvailable", () => {
    it("returns boolean", async () => {
        const result = await checkCppcheckAvailable();
        expect(typeof result).toBe("boolean");
    });
});
describe("checkCargoGeigerAvailable", () => {
    it("returns boolean", async () => {
        const result = await checkCargoGeigerAvailable();
        expect(typeof result).toBe("boolean");
    });
});
describe("detectUnsafeLanguages", () => {
    it("returns correct LanguageDetection structure", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await detectUnsafeLanguages(testProjectPath);
        expect(result).toHaveProperty("cpp");
        expect(result).toHaveProperty("rust");
        expect(result).toHaveProperty("nodeNative");
        expect(typeof result.cpp).toBe("boolean");
        expect(typeof result.rust).toBe("boolean");
        expect(typeof result.nodeNative).toBe("boolean");
    });
    it("detects C files", async () => {
        await writeFile(join(testProjectPath, "main.c"), `#include <stdio.h>
int main() { return 0; }
`);
        const result = await detectUnsafeLanguages(testProjectPath);
        expect(result.cpp).toBe(true);
    });
    it("detects C++ files", async () => {
        await writeFile(join(testProjectPath, "main.cpp"), `#include <iostream>
int main() { return 0; }
`);
        const result = await detectUnsafeLanguages(testProjectPath);
        expect(result.cpp).toBe(true);
    });
    it("detects header files", async () => {
        await writeFile(join(testProjectPath, "header.h"), `#ifndef HEADER_H
#define HEADER_H
#endif
`);
        const result = await detectUnsafeLanguages(testProjectPath);
        expect(result.cpp).toBe(true);
    });
    it("detects Rust projects", async () => {
        await writeFile(join(testProjectPath, "Cargo.toml"), `[package]
name = "my-project"
version = "0.1.0"
`);
        const result = await detectUnsafeLanguages(testProjectPath);
        expect(result.rust).toBe(true);
    });
    it("detects Node.js native addons", async () => {
        await writeFile(join(testProjectPath, "binding.gyp"), `{"targets": []}`);
        const result = await detectUnsafeLanguages(testProjectPath);
        expect(result.nodeNative).toBe(true);
    });
    it("returns all false for TypeScript-only project", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await detectUnsafeLanguages(testProjectPath);
        expect(result.cpp).toBe(false);
        expect(result.rust).toBe(false);
        expect(result.nodeNative).toBe(false);
    });
});
describe("runMemorySafetyAnalysis", () => {
    it("returns proper ScannerResult structure", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        expect(result).toHaveProperty("scanner", "memory-safety");
        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 C/C++/Rust", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        expect(result.success).toBe(true);
        expect(result.findings).toHaveLength(0);
        expect(result.metadata?.message).toContain("No C/C++/Rust");
    });
    it("tracks duration", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        expect(result.duration).toBeGreaterThanOrEqual(0);
    });
    it("handles non-existent directories gracefully", async () => {
        const result = await runMemorySafetyAnalysis("/nonexistent/path/that/does/not/exist");
        expect(result.scanner).toBe("memory-safety");
        expect(result).toHaveProperty("success");
    });
    it("accepts timeout option", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runMemorySafetyAnalysis(testProjectPath, { timeout: 5000 });
        expect(result.scanner).toBe("memory-safety");
    });
});
describe("runMemorySafetyAnalysis - C/C++ pattern detection", () => {
    it("detects unsafe strcpy usage", async () => {
        await writeFile(join(testProjectPath, "unsafe.c"), `#include <string.h>
void copy(char *dest, char *src) {
  strcpy(dest, src);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const strcpyFinding = result.findings.find(f => f.ruleId === "memory:unsafe-strcpy");
            expect(strcpyFinding).toBeDefined();
            expect(strcpyFinding?.severity).toBe("high");
            expect(strcpyFinding?.cweIds).toContain("CWE-120");
        }
    });
    it("detects unsafe sprintf usage", async () => {
        await writeFile(join(testProjectPath, "format.c"), `#include <stdio.h>
void format(char *buf, int x) {
  sprintf(buf, "%d", x);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const sprintfFinding = result.findings.find(f => f.ruleId === "memory:unsafe-sprintf");
            expect(sprintfFinding).toBeDefined();
            expect(sprintfFinding?.severity).toBe("high");
            expect(sprintfFinding?.cweIds).toContain("CWE-134");
        }
    });
    it("detects banned gets() function", async () => {
        await writeFile(join(testProjectPath, "banned.c"), `#include <stdio.h>
void read_input(char *buf) {
  gets(buf);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const getsFinding = result.findings.find(f => f.ruleId === "memory:banned-gets");
            expect(getsFinding).toBeDefined();
            expect(getsFinding?.severity).toBe("critical");
            expect(getsFinding?.cweIds).toContain("CWE-242");
        }
    });
    it("detects unsafe strcat usage", async () => {
        await writeFile(join(testProjectPath, "concat.c"), `#include <string.h>
void concat(char *dest, char *src) {
  strcat(dest, src);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const strcatFinding = result.findings.find(f => f.ruleId === "memory:unsafe-strcat");
            expect(strcatFinding).toBeDefined();
            expect(strcatFinding?.severity).toBe("high");
        }
    });
    it("detects unsafe alloca usage", async () => {
        await writeFile(join(testProjectPath, "alloca.c"), `#include <alloca.h>
void allocate(int size) {
  char *buf = alloca(size);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const allocaFinding = result.findings.find(f => f.ruleId === "memory:unsafe-alloca");
            expect(allocaFinding).toBeDefined();
            expect(allocaFinding?.severity).toBe("high");
        }
    });
});
describe("runMemorySafetyAnalysis - Rust pattern detection", () => {
    it("detects unsafe transmute", async () => {
        await writeFile(join(testProjectPath, "Cargo.toml"), `[package]
name = "test"
version = "0.1.0"
`);
        await mkdir(join(testProjectPath, "src"), { recursive: true });
        await writeFile(join(testProjectPath, "src", "lib.rs"), `unsafe {
  let x: u32 = std::mem::transmute(y);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const transmuteFinding = result.findings.find(f => f.ruleId === "memory:rust-transmute");
            expect(transmuteFinding).toBeDefined();
            expect(transmuteFinding?.severity).toBe("high");
        }
    });
    it("detects FFI boundaries", async () => {
        await writeFile(join(testProjectPath, "Cargo.toml"), `[package]
name = "test"
version = "0.1.0"
`);
        await mkdir(join(testProjectPath, "src"), { recursive: true });
        await writeFile(join(testProjectPath, "src", "lib.rs"), `#[no_mangle]
pub extern "C" fn my_function() {
  // FFI function
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const ffiFinding = result.findings.find(f => f.ruleId === "memory:rust-ffi-boundary");
            expect(ffiFinding).toBeDefined();
            expect(ffiFinding?.severity).toBe("info");
        }
    });
});
describe("runMemorySafetyAnalysis - finding properties", () => {
    it("findings have scanner property set to memory-safety", async () => {
        await writeFile(join(testProjectPath, "unsafe.c"), `#include <string.h>
void copy(char *dest, char *src) {
  strcpy(dest, src);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.scanner).toBe("memory-safety");
        }
    });
    it("findings have valid severity levels", async () => {
        await writeFile(join(testProjectPath, "unsafe.c"), `#include <string.h>
void copy(char *dest, char *src) {
  strcpy(dest, src);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(["critical", "high", "medium", "low", "info"]).toContain(finding.severity);
        }
    });
    it("findings have category memory-safety", async () => {
        await writeFile(join(testProjectPath, "unsafe.c"), `#include <string.h>
void copy(char *dest, char *src) {
  strcpy(dest, src);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.category).toBe("memory-safety");
        }
    });
    it("findings have CWE IDs in correct format", async () => {
        await writeFile(join(testProjectPath, "unsafe.c"), `#include <string.h>
void copy(char *dest, char *src) {
  strcpy(dest, src);
}
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        for (const finding of result.findings) {
            if (finding.cweIds && finding.cweIds.length > 0) {
                for (const cwe of finding.cweIds) {
                    expect(cwe).toMatch(/^CWE-\d+$/);
                }
            }
        }
    });
});
describe("runMemorySafetyAnalysis - deduplication", () => {
    it("deduplicates findings by file+line+ruleId", async () => {
        // Create a file with the same pattern multiple times on different lines
        await writeFile(join(testProjectPath, "multi.c"), `#include <string.h>
void copy1(char *dest, char *src) { strcpy(dest, src); }
void copy2(char *dest, char *src) { strcpy(dest, src); }
`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        if (result.findings.length > 0) {
            // Each finding should be on a different line
            const lineNumbers = result.findings
                .filter(f => f.ruleId === "memory:unsafe-strcpy")
                .map(f => f.line);
            const uniqueLines = new Set(lineNumbers);
            expect(uniqueLines.size).toBe(lineNumbers.length);
        }
    });
});
describe("runMemorySafetyAnalysis - metadata", () => {
    it("includes languages in metadata", async () => {
        await writeFile(join(testProjectPath, "main.c"), `int main() { return 0; }`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        expect(result.metadata?.languages).toBeDefined();
        const languages = result.metadata?.languages;
        expect(languages?.cpp).toBe(true);
    });
    it("includes tools used in metadata", async () => {
        await writeFile(join(testProjectPath, "main.c"), `int main() { return 0; }`);
        const result = await runMemorySafetyAnalysis(testProjectPath);
        expect(result.metadata?.toolsUsed).toBeDefined();
        const toolsUsed = result.metadata?.toolsUsed;
        expect(toolsUsed?.patternAnalysis).toBe(true);
    });
});
//# sourceMappingURL=memory-safety.test.js.map