/**
 * Unit tests for certification cache 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 { hashFile, computeProjectHash, generateFileHashes, loadCache, saveCache, createCache, compareWithCache, getFilesToAudit, canUseIncrementalAudit, } from "./cache.js";
const TEST_BASE = join(tmpdir(), "vaspera-cache-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("hashFile", () => {
    it("computes consistent SHA-256 hash", async () => {
        const filePath = join(testProjectPath, "test.ts");
        await writeFile(filePath, "const x = 1;");
        const hash1 = await hashFile(filePath);
        const hash2 = await hashFile(filePath);
        expect(hash1).toBe(hash2);
        expect(hash1).toHaveLength(64); // SHA-256 hex length
    });
    it("produces different hashes for different content", async () => {
        const file1 = join(testProjectPath, "file1.ts");
        const file2 = join(testProjectPath, "file2.ts");
        await writeFile(file1, "const a = 1;");
        await writeFile(file2, "const b = 2;");
        const hash1 = await hashFile(file1);
        const hash2 = await hashFile(file2);
        expect(hash1).not.toBe(hash2);
    });
});
describe("computeProjectHash", () => {
    it("computes consistent hash from file hashes", () => {
        const files = [
            { path: "a.ts", hash: "abc123", lastChecked: "2024-01-01T00:00:00Z", size: 100 },
            { path: "b.ts", hash: "def456", lastChecked: "2024-01-01T00:00:00Z", size: 200 },
        ];
        const hash1 = computeProjectHash(files);
        const hash2 = computeProjectHash(files);
        expect(hash1).toBe(hash2);
        expect(hash1).toHaveLength(64);
    });
    it("produces same hash regardless of input order", () => {
        const files1 = [
            { path: "a.ts", hash: "abc123", lastChecked: "2024-01-01T00:00:00Z", size: 100 },
            { path: "b.ts", hash: "def456", lastChecked: "2024-01-01T00:00:00Z", size: 200 },
        ];
        const files2 = [
            { path: "b.ts", hash: "def456", lastChecked: "2024-01-01T00:00:00Z", size: 200 },
            { path: "a.ts", hash: "abc123", lastChecked: "2024-01-01T00:00:00Z", size: 100 },
        ];
        expect(computeProjectHash(files1)).toBe(computeProjectHash(files2));
    });
    it("produces different hash when file hashes differ", () => {
        const files1 = [
            { path: "a.ts", hash: "abc123", lastChecked: "2024-01-01T00:00:00Z", size: 100 },
        ];
        const files2 = [
            { path: "a.ts", hash: "xyz789", lastChecked: "2024-01-01T00:00:00Z", size: 100 },
        ];
        expect(computeProjectHash(files1)).not.toBe(computeProjectHash(files2));
    });
});
describe("generateFileHashes", () => {
    it("generates hashes for TypeScript files", async () => {
        await writeFile(join(testProjectPath, "index.ts"), "export const x = 1;");
        await writeFile(join(testProjectPath, "utils.ts"), "export const y = 2;");
        const hashes = await generateFileHashes(testProjectPath);
        expect(hashes.length).toBe(2);
        expect(hashes.map((h) => h.path).sort()).toEqual(["index.ts", "utils.ts"]);
        expect(hashes.every((h) => h.hash.length === 64)).toBe(true);
        expect(hashes.every((h) => h.size > 0)).toBe(true);
    });
    it("excludes node_modules", async () => {
        await mkdir(join(testProjectPath, "node_modules", "pkg"), { recursive: true });
        await writeFile(join(testProjectPath, "node_modules", "pkg", "index.js"), "module.exports = {};");
        await writeFile(join(testProjectPath, "index.ts"), "import pkg from 'pkg';");
        const hashes = await generateFileHashes(testProjectPath);
        expect(hashes.length).toBe(1);
        expect(hashes[0].path).toBe("index.ts");
    });
    it("includes nested files in src directory", async () => {
        await mkdir(join(testProjectPath, "src", "utils"), { recursive: true });
        await writeFile(join(testProjectPath, "src", "index.ts"), "export * from './utils';");
        await writeFile(join(testProjectPath, "src", "utils", "helper.ts"), "export const help = () => {};");
        const hashes = await generateFileHashes(testProjectPath);
        expect(hashes.length).toBe(2);
        expect(hashes.map((h) => h.path).sort()).toEqual([
            "src/index.ts",
            "src/utils/helper.ts",
        ]);
    });
    it("includes JSON files", async () => {
        await writeFile(join(testProjectPath, "package.json"), '{"name": "test"}');
        const hashes = await generateFileHashes(testProjectPath);
        expect(hashes.some((h) => h.path === "package.json")).toBe(true);
    });
});
describe("saveCache and loadCache", () => {
    it("saves and loads cache correctly", async () => {
        const certDir = join(testProjectPath, ".vaspera", "certifications", "cert-test-123");
        await mkdir(certDir, { recursive: true });
        const cache = {
            version: 1,
            projectHash: "abc123",
            createdAt: "2024-01-01T00:00:00Z",
            updatedAt: "2024-01-01T00:00:00Z",
            files: [
                { path: "index.ts", hash: "def456", lastChecked: "2024-01-01T00:00:00Z", size: 100 },
            ],
        };
        await saveCache(certDir, cache);
        const loaded = await loadCache(certDir);
        expect(loaded).toEqual(cache);
    });
    it("returns null for non-existent cache", async () => {
        const loaded = await loadCache(join(testProjectPath, "nonexistent"));
        expect(loaded).toBeNull();
    });
    it("returns null for invalid cache version", async () => {
        const certDir = join(testProjectPath, ".vaspera", "certifications", "cert-test-123");
        await mkdir(certDir, { recursive: true });
        const invalidCache = {
            version: 2, // Invalid version
            projectHash: "abc123",
            createdAt: "2024-01-01T00:00:00Z",
            updatedAt: "2024-01-01T00:00:00Z",
            files: [],
        };
        await writeFile(join(certDir, "cache.json"), JSON.stringify(invalidCache));
        const loaded = await loadCache(certDir);
        expect(loaded).toBeNull();
    });
});
describe("createCache", () => {
    it("creates cache from project files", async () => {
        await writeFile(join(testProjectPath, "index.ts"), "export const x = 1;");
        await writeFile(join(testProjectPath, "utils.ts"), "export const y = 2;");
        const cache = await createCache(testProjectPath);
        expect(cache.version).toBe(1);
        expect(cache.files.length).toBe(2);
        expect(cache.projectHash).toHaveLength(64);
        expect(cache.createdAt).toBeDefined();
        expect(cache.updatedAt).toBeDefined();
    });
});
describe("compareWithCache", () => {
    it("detects unchanged files", async () => {
        await writeFile(join(testProjectPath, "index.ts"), "export const x = 1;");
        const cache = await createCache(testProjectPath);
        const comparison = await compareWithCache(testProjectPath, cache);
        expect(comparison.cacheValid).toBe(true);
        expect(comparison.unchanged).toEqual(["index.ts"]);
        expect(comparison.changed).toHaveLength(0);
        expect(comparison.added).toHaveLength(0);
        expect(comparison.removed).toHaveLength(0);
    });
    it("detects changed files", async () => {
        await writeFile(join(testProjectPath, "index.ts"), "export const x = 1;");
        const cache = await createCache(testProjectPath);
        // Modify the file
        await writeFile(join(testProjectPath, "index.ts"), "export const x = 2;");
        const comparison = await compareWithCache(testProjectPath, cache);
        expect(comparison.cacheValid).toBe(false);
        expect(comparison.changed).toEqual(["index.ts"]);
        expect(comparison.unchanged).toHaveLength(0);
    });
    it("detects added files", async () => {
        await writeFile(join(testProjectPath, "index.ts"), "export const x = 1;");
        const cache = await createCache(testProjectPath);
        // Add a new file
        await writeFile(join(testProjectPath, "utils.ts"), "export const y = 2;");
        const comparison = await compareWithCache(testProjectPath, cache);
        expect(comparison.cacheValid).toBe(false);
        expect(comparison.added).toEqual(["utils.ts"]);
        expect(comparison.unchanged).toEqual(["index.ts"]);
    });
    it("detects removed files", async () => {
        await writeFile(join(testProjectPath, "index.ts"), "export const x = 1;");
        await writeFile(join(testProjectPath, "utils.ts"), "export const y = 2;");
        const cache = await createCache(testProjectPath);
        // Remove a file
        await rm(join(testProjectPath, "utils.ts"));
        const comparison = await compareWithCache(testProjectPath, cache);
        expect(comparison.cacheValid).toBe(false);
        expect(comparison.removed).toEqual(["utils.ts"]);
        expect(comparison.unchanged).toEqual(["index.ts"]);
    });
});
describe("getFilesToAudit", () => {
    it("returns changed and added files", () => {
        const comparison = {
            changed: ["modified.ts"],
            added: ["new.ts"],
            removed: ["deleted.ts"],
            unchanged: ["same.ts"],
            cacheValid: false,
        };
        const toAudit = getFilesToAudit(comparison);
        expect(toAudit.sort()).toEqual(["modified.ts", "new.ts"]);
    });
    it("returns empty array when nothing changed", () => {
        const comparison = {
            changed: [],
            added: [],
            removed: [],
            unchanged: ["file.ts"],
            cacheValid: true,
        };
        const toAudit = getFilesToAudit(comparison);
        expect(toAudit).toHaveLength(0);
    });
});
describe("canUseIncrementalAudit", () => {
    it("returns true when valid cache exists", async () => {
        const certDir = join(testProjectPath, ".vaspera", "certifications", "cert-test-123");
        await mkdir(certDir, { recursive: true });
        const cache = {
            version: 1,
            projectHash: "abc123",
            createdAt: "2024-01-01T00:00:00Z",
            updatedAt: "2024-01-01T00:00:00Z",
            files: [
                { path: "index.ts", hash: "def456", lastChecked: "2024-01-01T00:00:00Z", size: 100 },
            ],
        };
        await saveCache(certDir, cache);
        const canUse = await canUseIncrementalAudit(certDir);
        expect(canUse).toBe(true);
    });
    it("returns false when no cache exists", async () => {
        const certDir = join(testProjectPath, ".vaspera", "certifications", "cert-test-123");
        await mkdir(certDir, { recursive: true });
        const canUse = await canUseIncrementalAudit(certDir);
        expect(canUse).toBe(false);
    });
    it("returns false when cache has no files", async () => {
        const certDir = join(testProjectPath, ".vaspera", "certifications", "cert-test-123");
        await mkdir(certDir, { recursive: true });
        const cache = {
            version: 1,
            projectHash: "abc123",
            createdAt: "2024-01-01T00:00:00Z",
            updatedAt: "2024-01-01T00:00:00Z",
            files: [],
        };
        await saveCache(certDir, cache);
        const canUse = await canUseIncrementalAudit(certDir);
        expect(canUse).toBe(false);
    });
});
//# sourceMappingURL=cache.test.js.map