/* Unit tests for sqlite cache layer covering SqliteCacheLayer. It also guards the documented public contract and regression-sensitive edge cases. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. Fixtures stay inline and minimal so each case states its own setup. RFC-1311: Compass header sweep — declares the sqlite suite's contract for test-file scaffold coverage. */ // @ai-invariant fixtures must not couple to kernel internals beyond the public surface under test import { test, expect, describe, beforeEach, afterEach } from "vitest"; import { mkdtempSync, rmSync } from "node:fs"; import { join } from "node:path"; import { SqliteCacheLayer } from "../sqlite-cache-layer.ts"; let tmpDir: string; let dbPath: string; beforeEach(() => { tmpDir = mkdtempSync(join(process.cwd(), "tmp-sqlite-cache-")); dbPath = join(tmpDir, "test-cache.db"); }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); describe("SqliteCacheLayer", () => { test("available is true", async () => { const layer = await SqliteCacheLayer.create(dbPath); expect(layer.available).toBe(true); layer.close(); }); test("get returns null for missing key", async () => { const layer = await SqliteCacheLayer.create(dbPath); expect(await layer.get("ns", "missing")).toBeNull(); await layer.close(); }); test("set and get round-trip", async () => { const layer = await SqliteCacheLayer.create(dbPath); await layer.set("ns", "key1", { foo: "bar" }, 12345, "hash123"); const entry = await layer.get("ns", "key1"); expect(entry).not.toBeNull(); expect(entry!.data).toEqual({ foo: "bar" }); expect(entry!.mtime).toBe(12345); expect(entry!.contentHash).toBe("hash123"); await layer.close(); }); test("set overwrites existing entry", async () => { const layer = await SqliteCacheLayer.create(dbPath); await layer.set("ns", "key1", { v: 1 }, 100, "hash1"); await layer.set("ns", "key1", { v: 2 }, 200, "hash2"); const entry = await layer.get("ns", "key1"); expect(entry!.data).toEqual({ v: 2 }); expect(entry!.mtime).toBe(200); await layer.close(); }); test("clear with namespace removes only that namespace", async () => { const layer = await SqliteCacheLayer.create(dbPath); await layer.set("ns-a", "key1", "data-a", 100, "hash"); await layer.set("ns-b", "key1", "data-b", 100, "hash"); await layer.clear("ns-a"); expect(await layer.get("ns-a", "key1")).toBeNull(); expect(await layer.get("ns-b", "key1")).not.toBeNull(); await layer.close(); }); test("clear without namespace removes all", async () => { const layer = await SqliteCacheLayer.create(dbPath); await layer.set("ns-a", "key1", "data-a", 100, "hash"); await layer.set("ns-b", "key1", "data-b", 100, "hash"); await layer.clear(); expect(await layer.get("ns-a", "key1")).toBeNull(); expect(await layer.get("ns-b", "key1")).toBeNull(); await layer.close(); }); test("status returns available with namespaces", async () => { const layer = await SqliteCacheLayer.create(dbPath); await layer.set("ns-a", "key1", "data", 100, "hash"); await layer.set("ns-a", "key2", "data", 100, "hash"); const status = await layer.status(); expect(status.available).toBe(true); expect(status.dbPath).toBe(dbPath); expect(status.namespaces).toHaveLength(1); expect(status.namespaces[0]!.name).toBe("ns-a"); expect(status.namespaces[0]!.entries).toBe(2); await layer.close(); }); test("hit ratio is computed from stats", async () => { const layer = await SqliteCacheLayer.create(dbPath); await layer.set("ns", "key1", "data", 100, "hash"); await layer.get("ns", "key1"); await layer.get("ns", "key1"); await layer.get("ns", "missing"); const status = await layer.status(); const ns = status.namespaces.find((n) => n.name === "ns"); expect(ns).toBeDefined(); expect(ns!.hitRatio).toBeGreaterThan(0); await layer.close(); }); test("close does not throw", async () => { const layer = await SqliteCacheLayer.create(dbPath); await expect(layer.close()).resolves.toBeUndefined(); }); test("list returns empty for no entries", async () => { const layer = await SqliteCacheLayer.create(dbPath); expect(await layer.list()).toEqual([]); await layer.close(); }); });