/*
Unit tests for cache covering dht/cache. 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 cache suite's contract for test-file scaffold coverage.
*/
// @ai-invariant fixtures must not couple to kernel internals beyond the public surface under test
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
loadCache,
saveCache,
getCachedEntry,
setCachedEntry,
clearCachedEntry,
CACHE_FILENAME,
} from "./cache.ts";
import type { DHTSiteEntry } from "./types.ts";
function makeEntry(siteId: string): DHTSiteEntry {
return {
siteId,
owner: "did:web:example.com#v1",
workshopEndpoint: "10.0.0.1:7947",
mirrors: [],
registeredAt: "2025-01-01T00:00:00.000Z",
lastUpdated: "2025-01-01T00:00:00.000Z",
signature: "zFakeSignature123",
};
}
describe("dht/cache", () => {
let tmpDir: string;
beforeEach(async () => {
tmpDir = await mkdtemp(join(tmpdir(), "dht-cache-test-"));
});
afterEach(async () => {
await rm(tmpDir, { recursive: true, force: true });
});
it("loadCache returns empty cache when file does not exist", async () => {
const cache = await loadCache(tmpDir);
expect(cache.entries).toEqual({});
});
it("setCachedEntry and getCachedEntry round-trip", async () => {
const entry = makeEntry("test-bundle");
await setCachedEntry(tmpDir, "test-bundle", entry, 300000);
const cache = await loadCache(tmpDir);
const cached = getCachedEntry(cache, "test-bundle");
expect(cached).not.toBeNull();
expect(cached!.siteId).toBe("test-bundle");
expect(cached!.owner).toBe("did:web:example.com#v1");
});
it("getCachedEntry returns null for expired entries", async () => {
const entry = makeEntry("test-bundle");
// Set with 1ms TTL, then wait for expiry
await setCachedEntry(tmpDir, "test-bundle", entry, 1);
await new Promise((resolve) => setTimeout(resolve, 10));
const cache = await loadCache(tmpDir);
const cached = getCachedEntry(cache, "test-bundle");
expect(cached).toBeNull();
});
it("getCachedEntry returns null for missing site", async () => {
const cache = await loadCache(tmpDir);
const cached = getCachedEntry(cache, "nonexistent");
expect(cached).toBeNull();
});
it("clearCachedEntry removes entry", async () => {
const entry = makeEntry("test-bundle");
await setCachedEntry(tmpDir, "test-bundle", entry, 300000);
await clearCachedEntry(tmpDir, "test-bundle");
const cache = await loadCache(tmpDir);
expect(cache.entries["test-bundle"]).toBeUndefined();
});
it("saveCache and loadCache round-trip", async () => {
const cache = {
entries: {
"site-a": {
...makeEntry("site-a"),
cachedAt: "2025-01-01T00:00:00.000Z",
expiresAt: "2025-01-01T00:05:00.000Z",
},
},
};
await saveCache(tmpDir, cache);
const loaded = await loadCache(tmpDir);
expect(loaded.entries["site-a"]).toBeDefined();
expect(loaded.entries["site-a"]!.siteId).toBe("site-a");
});
it("CACHE_FILENAME is werkstatt.dht.cache.json", () => {
expect(CACHE_FILENAME).toBe("werkstatt.dht.cache.json");
});
});