import { describe, it, expect, vi } from "vitest"; vi.mock("../src/logger.js", () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, })); function mockKV() { const store = new Map>(); return { store, get: async (scope: string, key: string): Promise => (store.get(scope)?.get(key) as T) ?? null, set: async (scope: string, key: string, data: T): Promise => { if (!store.has(scope)) store.set(scope, new Map()); store.get(scope)!.set(key, data); return data; }, delete: async (scope: string, key: string) => { store.get(scope)?.delete(key); }, list: async (scope: string): Promise => { const m = store.get(scope); return m ? (Array.from(m.values()) as T[]) : []; }, }; } describe("access-tracker", () => { it("getAccessLog returns empty log for unknown id", async () => { const { getAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); const log = await getAccessLog(kv as never, "mem_xyz"); expect(log).toEqual({ memoryId: "mem_xyz", count: 0, lastAt: "", recent: [] }); }); it("recordAccess increments count and lastAt", async () => { const { recordAccess, getAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); await recordAccess(kv as never, "mem_a", 1_000_000); await recordAccess(kv as never, "mem_a", 2_000_000); await recordAccess(kv as never, "mem_a", 3_000_000); const log = await getAccessLog(kv as never, "mem_a"); expect(log.count).toBe(3); expect(log.recent).toEqual([1_000_000, 2_000_000, 3_000_000]); expect(log.lastAt).toBe(new Date(3_000_000).toISOString()); }); it("recent[] is bounded to last 20 entries", async () => { const { recordAccess, getAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); for (let i = 1; i <= 50; i++) { await recordAccess(kv as never, "mem_b", i * 1000); } const log = await getAccessLog(kv as never, "mem_b"); expect(log.count).toBe(50); expect(log.recent.length).toBe(20); // Should be the LAST 20: 31_000..50_000 expect(log.recent[0]).toBe(31_000); expect(log.recent[19]).toBe(50_000); }); it("recordAccessBatch deduplicates and writes once per id", async () => { const { recordAccessBatch, getAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); await recordAccessBatch( kv as never, ["mem_a", "mem_b", "mem_a", "mem_b", "mem_c"], 5_000_000, ); expect((await getAccessLog(kv as never, "mem_a")).count).toBe(1); expect((await getAccessLog(kv as never, "mem_b")).count).toBe(1); expect((await getAccessLog(kv as never, "mem_c")).count).toBe(1); }); it("recordAccess swallows kv.set errors (must not break reads)", async () => { const { recordAccess } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); kv.set = (async () => { throw new Error("boom"); }) as never; await expect(recordAccess(kv as never, "mem_a")).resolves.toBeUndefined(); }); it("concurrent recordAccess calls do not lose increments (keyed mutex)", async () => { const { recordAccess, getAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); await Promise.all( Array.from({ length: 25 }, (_, i) => recordAccess(kv as never, "mem_race", i * 100), ), ); const log = await getAccessLog(kv as never, "mem_race"); expect(log.count).toBe(25); }); it("recordAccessBatch: a single failing id does not block siblings", async () => { const { recordAccessBatch, getAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); const realSet = kv.set.bind(kv); kv.set = (async (scope: string, key: string, val: unknown) => { if (key === "mem_slow") throw new Error("write failed"); return realSet(scope, key, val); }) as never; await recordAccessBatch( kv as never, ["mem_slow", "mem_fast_a", "mem_fast_b"], 1_000_000, ); expect((await getAccessLog(kv as never, "mem_fast_a")).count).toBe(1); expect((await getAccessLog(kv as never, "mem_fast_b")).count).toBe(1); expect((await getAccessLog(kv as never, "mem_slow")).count).toBe(0); }); it("ignores empty / falsy memory ids", async () => { const { recordAccess, recordAccessBatch } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); await recordAccess(kv as never, ""); await recordAccessBatch(kv as never, ["", "mem_x", ""]); expect(kv.store.get("mem:access")?.has("")).toBeFalsy(); expect(kv.store.get("mem:access")?.get("mem_x")).toBeTruthy(); }); it("deleteAccessLog removes the target entry and leaves siblings intact", async () => { const { recordAccess, deleteAccessLog, getAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); await recordAccess(kv as never, "mem_a"); await recordAccess(kv as never, "mem_b"); await deleteAccessLog(kv as never, "mem_a"); expect(kv.store.get("mem:access")?.has("mem_a")).toBe(false); expect((await getAccessLog(kv as never, "mem_b")).count).toBe(1); }); it("deleteAccessLog is a no-op for unknown ids and empty ids", async () => { const { deleteAccessLog, recordAccess } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); await recordAccess(kv as never, "mem_keep"); await deleteAccessLog(kv as never, ""); await deleteAccessLog(kv as never, "mem_unknown"); expect(kv.store.get("mem:access")?.has("mem_keep")).toBe(true); }); it("deleteAccessLog swallows kv.delete errors", async () => { const { deleteAccessLog } = await import( "../src/functions/access-tracker.js" ); const kv = mockKV(); kv.delete = (async () => { throw new Error("boom"); }) as never; await expect( deleteAccessLog(kv as never, "mem_x"), ).resolves.toBeUndefined(); }); }); describe("normalizeAccessLog", () => { it("returns a well-formed empty log for nullish / non-object input", async () => { const { normalizeAccessLog } = await import( "../src/functions/access-tracker.js" ); const log = normalizeAccessLog(null); expect(log).toEqual({ memoryId: "", count: 0, lastAt: "", recent: [], }); expect(normalizeAccessLog(undefined).count).toBe(0); expect(normalizeAccessLog("garbage").count).toBe(0); }); it("coerces count to a non-negative integer", async () => { const { normalizeAccessLog } = await import( "../src/functions/access-tracker.js" ); expect(normalizeAccessLog({ count: -5 }).count).toBe(0); expect(normalizeAccessLog({ count: 3.7 }).count).toBe(3); expect(normalizeAccessLog({ count: NaN }).count).toBe(0); expect(normalizeAccessLog({ count: "123" }).count).toBe(0); }); it("preserves large lifetime counts (NOT capped at ring buffer size)", async () => { const { normalizeAccessLog } = await import( "../src/functions/access-tracker.js" ); const log = normalizeAccessLog({ memoryId: "m", count: 500, recent: [1] }); expect(log.count).toBe(500); }); it("truncates recent[] to the last 20 entries and drops non-finite values", async () => { const { normalizeAccessLog } = await import( "../src/functions/access-tracker.js" ); const input = Array.from({ length: 40 }, (_, i) => i * 1000); const withGarbage = [...input, NaN, Infinity, "bad" as unknown as number]; const log = normalizeAccessLog({ recent: withGarbage }); expect(log.recent.length).toBe(20); expect(log.recent[0]).toBe(20_000); expect(log.recent[19]).toBe(39_000); }); it("count is at least recent.length when count < recent.length", async () => { const { normalizeAccessLog } = await import( "../src/functions/access-tracker.js" ); const log = normalizeAccessLog({ count: 2, recent: [1, 2, 3, 4, 5], }); expect(log.count).toBeGreaterThanOrEqual(5); }); it("fills in memoryId only when field is a string", async () => { const { normalizeAccessLog } = await import( "../src/functions/access-tracker.js" ); expect(normalizeAccessLog({ memoryId: "mem_x" }).memoryId).toBe("mem_x"); expect(normalizeAccessLog({ memoryId: 42 }).memoryId).toBe(""); }); });