import { describe, expect, it } from "vitest"; import { MemCache } from "../../src/lib/memCache"; describe("MemCache", () => { it("TTL 내에는 캐시된 값을 반환하고 fetcher를 다시 돌리지 않는다", async () => { const cache = new MemCache(60_000); let calls = 0; const fetcher = async () => { calls += 1; return 42; }; expect(await cache.getOrFetch("k", fetcher)).toBe(42); expect(await cache.getOrFetch("k", fetcher)).toBe(42); expect(calls).toBe(1); }); /** * negative 캐싱 회귀 — truthy 검사로 히트를 판정하면 캐시된 null이 미스와 * 구분되지 않아 fetcher가 매번 다시 돈다(상품 상세 조회의 Firestore read 증폭). */ it("캐시된 null도 히트로 취급해 fetcher를 다시 돌리지 않는다", async () => { const cache = new MemCache(60_000); let calls = 0; const fetcher = async () => { calls += 1; return null; }; expect(await cache.getOrFetch("missing", fetcher)).toBeNull(); expect(await cache.getOrFetch("missing", fetcher)).toBeNull(); expect(calls).toBe(1); }); it("0과 빈 문자열도 히트로 취급한다", async () => { const zero = new MemCache(60_000); const empty = new MemCache(60_000); let zeroCalls = 0; let emptyCalls = 0; await zero.getOrFetch("z", async () => { zeroCalls += 1; return 0; }); await zero.getOrFetch("z", async () => { zeroCalls += 1; return 0; }); await empty.getOrFetch("e", async () => { emptyCalls += 1; return ""; }); await empty.getOrFetch("e", async () => { emptyCalls += 1; return ""; }); expect(zeroCalls).toBe(1); expect(emptyCalls).toBe(1); }); it("TTL이 지나면 다시 조회한다", async () => { const cache = new MemCache(-1); // 즉시 만료 let calls = 0; const fetcher = async () => { calls += 1; return 1; }; await cache.getOrFetch("k", fetcher); await cache.getOrFetch("k", fetcher); expect(calls).toBe(2); }); it("delete는 해당 키만, clear는 전부 버린다", async () => { const cache = new MemCache(60_000); await cache.getOrFetch("a", async () => 1); await cache.getOrFetch("b", async () => 2); cache.delete("a"); expect(cache.get("a")).toBeNull(); expect(cache.get("b")).toBe(2); cache.clear(); expect(cache.get("b")).toBeNull(); }); it("동일 키 동시 호출은 fetcher를 한 번만 실행한다", async () => { const cache = new MemCache(60_000); let calls = 0; const fetcher = async () => { calls += 1; await new Promise((r) => setTimeout(r, 10)); return 7; }; const [a, b, c] = await Promise.all([ cache.getOrFetch("k", fetcher), cache.getOrFetch("k", fetcher), cache.getOrFetch("k", fetcher), ]); expect([a, b, c]).toEqual([7, 7, 7]); expect(calls).toBe(1); }); });