/**
 * Unit tests for race-condition 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 { runRaceConditionAnalysis } from "./race-condition.js";
const TEST_BASE = join(tmpdir(), "vaspera-race-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("runRaceConditionAnalysis", () => {
    it("returns proper ScannerResult structure", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        expect(result).toHaveProperty("scanner", "race-condition");
        expect(result).toHaveProperty("findings");
        expect(result).toHaveProperty("duration");
        expect(result).toHaveProperty("success");
        expect(Array.isArray(result.findings)).toBe(true);
    });
    it("returns empty findings when no supported languages detected", async () => {
        // Create only an HTML file (not a supported language)
        await writeFile(join(testProjectPath, "index.html"), `<html></html>`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        expect(result.success).toBe(true);
        expect(result.metadata?.message).toContain("No supported languages");
    });
    it("tracks duration", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        expect(result.duration).toBeGreaterThanOrEqual(0);
    });
    it("handles non-existent directories gracefully", async () => {
        const result = await runRaceConditionAnalysis("/nonexistent/path/that/does/not/exist");
        expect(result.scanner).toBe("race-condition");
        expect(result).toHaveProperty("success");
    });
    it("accepts timeout option", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `export const x = 1;`);
        const result = await runRaceConditionAnalysis(testProjectPath, { timeout: 5000 });
        expect(result.scanner).toBe("race-condition");
    });
});
describe("runRaceConditionAnalysis - language detection", () => {
    it("detects Go projects", async () => {
        await writeFile(join(testProjectPath, "main.go"), `package main

func main() {
  println("hello")
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        const languages = result.metadata?.languages;
        expect(languages?.go).toBe(true);
    });
    it("detects JavaScript projects", async () => {
        await writeFile(join(testProjectPath, "index.js"), `console.log("hello");`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        const languages = result.metadata?.languages;
        expect(languages?.javascript).toBe(true);
    });
    it("detects TypeScript projects", async () => {
        await writeFile(join(testProjectPath, "index.ts"), `const x: number = 1;`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        const languages = result.metadata?.languages;
        expect(languages?.javascript).toBe(true);
    });
    it("detects Python projects", async () => {
        await writeFile(join(testProjectPath, "main.py"), `print("hello")`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        const languages = result.metadata?.languages;
        expect(languages?.python).toBe(true);
    });
    it("detects Java projects", async () => {
        await writeFile(join(testProjectPath, "Main.java"), `public class Main {
  public static void main(String[] args) {
    System.out.println("hello");
  }
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        const languages = result.metadata?.languages;
        expect(languages?.java).toBe(true);
    });
});
describe("runRaceConditionAnalysis - Node.js pattern detection", () => {
    it("detects TOCTOU with fs.exists", async () => {
        await writeFile(join(testProjectPath, "toctou.js"), `const fs = require('fs');

if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const toctouFinding = result.findings.find(f => f.ruleId === "race:toctou-fs");
            expect(toctouFinding).toBeDefined();
            expect(toctouFinding?.severity).toBe("high");
            expect(toctouFinding?.cweIds).toContain("CWE-367");
        }
    });
    it("detects shared state in async context", async () => {
        await writeFile(join(testProjectPath, "shared.js"), `let counter = global.counter;

async function increment() {
  counter++;
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const sharedStateFinding = result.findings.find(f => f.ruleId === "race:shared-state-async");
            expect(sharedStateFinding).toBeDefined();
            expect(sharedStateFinding?.cweIds).toContain("CWE-362");
        }
    });
    it("detects global mutation in timers", async () => {
        await writeFile(join(testProjectPath, "timer.js"), `globalThis.counter = 0;

setInterval(() => {
  globalThis.counter++;
}, 1000);
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const timerFinding = result.findings.find(f => f.ruleId === "race:global-mutation-timer");
            expect(timerFinding).toBeDefined();
        }
    });
    it("detects check-then-mutate patterns", async () => {
        await writeFile(join(testProjectPath, "checkmutate.js"), `const items = [];

function addItem(item) {
  if (items.length < 10) {
    items.push(item);
  }
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const checkMutateFinding = result.findings.find(f => f.ruleId === "race:check-then-mutate");
            expect(checkMutateFinding).toBeDefined();
        }
    });
});
describe("runRaceConditionAnalysis - Python pattern detection", () => {
    it("detects TOCTOU with os.path.exists", async () => {
        await writeFile(join(testProjectPath, "toctou.py"), `import os

if os.path.exists('/tmp/file'):
    with open('/tmp/file', 'w') as f:
        f.write('data')
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const toctouFinding = result.findings.find(f => f.ruleId === "race:toctou-python-file");
            expect(toctouFinding).toBeDefined();
            expect(toctouFinding?.cweIds).toContain("CWE-367");
        }
    });
    it("detects threading with shared state", async () => {
        await writeFile(join(testProjectPath, "threading.py"), `import threading

lock = threading.Lock()

def worker():
    global counter
    counter = counter + 1
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const threadingFinding = result.findings.find(f => f.ruleId === "race:threading-shared-state");
            expect(threadingFinding).toBeDefined();
        }
    });
});
describe("runRaceConditionAnalysis - Go pattern detection", () => {
    it("detects goroutine shared write", async () => {
        await writeFile(join(testProjectPath, "goroutine.go"), `package main

var counter int

func main() {
    go func() {
        counter++
    }()
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const goroutineFinding = result.findings.find(f => f.ruleId === "race:goroutine-shared-write");
            expect(goroutineFinding).toBeDefined();
            expect(goroutineFinding?.severity).toBe("high");
        }
    });
    it("detects lazy init without sync.Once", async () => {
        await writeFile(join(testProjectPath, "lazyinit.go"), `package main

var instance *Service

func GetInstance() *Service {
    if instance == nil {
        instance = &Service{}
    }
    return instance
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const lazyInitFinding = result.findings.find(f => f.ruleId === "race:lazy-init");
            expect(lazyInitFinding).toBeDefined();
        }
    });
    it("detects concurrent map access", async () => {
        await writeFile(join(testProjectPath, "concurrentmap.go"), `package main

var cache map[string]string

func main() {
    cache = make(map[string]string)
    go func() {
        cache["key"] = "value"
    }()
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const mapFinding = result.findings.find(f => f.ruleId === "race:concurrent-map");
            expect(mapFinding).toBeDefined();
        }
    });
    it("detects closure variable capture in loop", async () => {
        await writeFile(join(testProjectPath, "closure.go"), `package main

func main() {
    for i, v := range items {
        go func() {
            process(i, v)
        }()
    }
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const closureFinding = result.findings.find(f => f.ruleId === "race:closure-variable");
            expect(closureFinding).toBeDefined();
        }
    });
});
describe("runRaceConditionAnalysis - Java pattern detection", () => {
    it("detects double-checked locking issues", async () => {
        await writeFile(join(testProjectPath, "Singleton.java"), `public class Singleton {
    private static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const dclFinding = result.findings.find(f => f.ruleId === "race:double-checked-locking");
            expect(dclFinding).toBeDefined();
        }
    });
    it("detects HashMap in concurrent context", async () => {
        await writeFile(join(testProjectPath, "ThreadMap.java"), `import java.util.HashMap;

public class ThreadMap {
    private HashMap<String, String> cache = new HashMap<>();

    public void process() {
        Thread t = new Thread(() -> {
            cache.put("key", "value");
        });
    }
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.success && result.findings.length > 0) {
            const mapFinding = result.findings.find(f => f.ruleId === "race:concurrent-hashmap");
            expect(mapFinding).toBeDefined();
        }
    });
});
describe("runRaceConditionAnalysis - finding properties", () => {
    it("findings have scanner property set to race-condition", async () => {
        await writeFile(join(testProjectPath, "toctou.js"), `const fs = require('fs');
if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.scanner).toBe("race-condition");
        }
    });
    it("findings have valid severity levels", async () => {
        await writeFile(join(testProjectPath, "toctou.js"), `const fs = require('fs');
if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(["critical", "high", "medium", "low", "info"]).toContain(finding.severity);
        }
    });
    it("findings have category race-condition", async () => {
        await writeFile(join(testProjectPath, "toctou.js"), `const fs = require('fs');
if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.category).toBe("race-condition");
        }
    });
    it("findings have CWE IDs in correct format", async () => {
        await writeFile(join(testProjectPath, "toctou.js"), `const fs = require('fs');
if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(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+$/);
                }
            }
        }
    });
    it("findings have ruleId starting with race:", async () => {
        await writeFile(join(testProjectPath, "toctou.js"), `const fs = require('fs');
if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        for (const finding of result.findings) {
            expect(finding.ruleId).toMatch(/^race:/);
        }
    });
});
describe("runRaceConditionAnalysis - edge cases", () => {
    it("skips test files", async () => {
        await writeFile(join(testProjectPath, "app.test.js"), `const fs = require('fs');
if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        // Test files should be skipped
        const testFileFindings = result.findings.filter(f => f.file.includes(".test."));
        expect(testFileFindings).toHaveLength(0);
    });
    it("skips spec files", async () => {
        await writeFile(join(testProjectPath, "app.spec.js"), `const fs = require('fs');
if (fs.existsSync('/tmp/file')) {
  fs.writeFileSync('/tmp/file', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        // Spec files should be skipped
        const specFileFindings = result.findings.filter(f => f.file.includes(".spec."));
        expect(specFileFindings).toHaveLength(0);
    });
    it("deduplicates findings", async () => {
        await writeFile(join(testProjectPath, "multi.js"), `const fs = require('fs');

if (fs.existsSync('/tmp/file1')) {
  fs.writeFileSync('/tmp/file1', 'data');
}

if (fs.existsSync('/tmp/file2')) {
  fs.writeFileSync('/tmp/file2', 'data');
}
`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        if (result.findings.length > 0) {
            // Each finding should have unique file+line+ruleId
            const keys = result.findings.map(f => `${f.file}:${f.line}:${f.ruleId}`);
            const uniqueKeys = new Set(keys);
            expect(uniqueKeys.size).toBe(keys.length);
        }
    });
});
describe("runRaceConditionAnalysis - metadata", () => {
    it("includes languages in metadata", async () => {
        await writeFile(join(testProjectPath, "main.go"), `package main`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        const languages = result.metadata?.languages;
        expect(result.metadata?.languages).toBeDefined();
        expect(languages?.go).toBe(true);
    });
    it("includes toolsUsed in metadata", async () => {
        await writeFile(join(testProjectPath, "index.js"), `const x = 1;`);
        const result = await runRaceConditionAnalysis(testProjectPath);
        const toolsUsed = result.metadata?.toolsUsed;
        expect(result.metadata?.toolsUsed).toBeDefined();
        expect(toolsUsed?.patternAnalysis).toBe(true);
    });
});
//# sourceMappingURL=race-condition.test.js.map