import { afterEach, describe, expect, it, vi } from "vitest"; import { createTtlRequestCache } from "../../lib/ttl-request-cache"; afterEach(() => { vi.useRealTimers(); }); describe("createTtlRequestCache", () => { it("dedupes concurrent loaders into a single request", async () => { const cache = createTtlRequestCache(5_000); let calls = 0; const loader = vi.fn(async () => { calls += 1; await new Promise((r) => setTimeout(r, 20)); return "ok"; }); const [a, b] = await Promise.all([cache.get(loader), cache.get(loader)]); expect(a).toBe("ok"); expect(b).toBe("ok"); expect(calls).toBe(1); expect(loader).toHaveBeenCalledTimes(1); }); it("serves cached value within TTL without reloading", async () => { const cache = createTtlRequestCache(10_000); const loader = vi.fn(async () => 42); await cache.get(loader); await cache.get(loader); expect(loader).toHaveBeenCalledTimes(1); expect(cache.peek()).toBe(42); }); it("reloads after TTL expires", async () => { vi.useFakeTimers(); const cache = createTtlRequestCache(1_000); const loader = vi.fn(async () => Date.now()); await cache.get(loader); expect(loader).toHaveBeenCalledTimes(1); await vi.advanceTimersByTimeAsync(1_001); await cache.get(loader); expect(loader).toHaveBeenCalledTimes(2); }); it("does not cache rejected loaders", async () => { const cache = createTtlRequestCache(5_000); const loader = vi .fn() .mockRejectedValueOnce(new Error("boom")) .mockResolvedValueOnce("recovered"); await expect(cache.get(loader)).rejects.toThrow("boom"); await expect(cache.get(loader)).resolves.toBe("recovered"); expect(loader).toHaveBeenCalledTimes(2); }); it("invalidate drops the cached entry", async () => { const cache = createTtlRequestCache(5_000); const loader = vi.fn(async () => "v1"); await cache.get(loader); cache.invalidate(); expect(cache.peek()).toBeNull(); loader.mockResolvedValueOnce("v2"); await expect(cache.get(loader)).resolves.toBe("v2"); expect(loader).toHaveBeenCalledTimes(2); }); });