import { afterEach, describe, expect, it, vi } from "vitest"; import { SenderNameCache } from "../../src/feishu/senderCache.js"; describe("SenderNameCache", () => { afterEach(() => { vi.useRealTimers(); }); it("get returns null for missing key", () => { const cache = new SenderNameCache(); expect(cache.get("ou_user")).toBeNull(); }); it("set then get returns the name", () => { const cache = new SenderNameCache(); cache.set("ou_user", "Alice"); expect(cache.get("ou_user")).toBe("Alice"); }); it("expired entry returns null", () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-01-01T00:00:00Z")); const cache = new SenderNameCache({ ttlMs: 100 }); cache.set("ou_user", "Alice"); vi.advanceTimersByTime(101); expect(cache.get("ou_user")).toBeNull(); expect(cache.size()).toBe(0); }); it("getOrFetch uses cache on hit", async () => { const cache = new SenderNameCache(); const fetchFn = vi.fn(async () => "Fetched Alice"); cache.set("ou_user", "Alice"); await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice"); expect(fetchFn).not.toHaveBeenCalled(); }); it("getOrFetch calls fetchFn on miss", async () => { const cache = new SenderNameCache(); const fetchFn = vi.fn(async () => "Alice"); await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice"); expect(fetchFn).toHaveBeenCalledTimes(1); expect(fetchFn).toHaveBeenCalledWith("ou_user"); }); it("getOrFetch caches the result of fetchFn", async () => { const cache = new SenderNameCache(); const fetchFn = vi.fn(async () => "Alice"); await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice"); await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice"); expect(fetchFn).toHaveBeenCalledTimes(1); }); it("evicts the least recently used entry when full", () => { const cache = new SenderNameCache({ maxSize: 2 }); cache.set("ou_1", "Alice"); cache.set("ou_2", "Bob"); expect(cache.get("ou_1")).toBe("Alice"); cache.set("ou_3", "Carol"); expect(cache.get("ou_1")).toBe("Alice"); expect(cache.get("ou_2")).toBeNull(); expect(cache.get("ou_3")).toBe("Carol"); }); it("clear empties the cache", () => { const cache = new SenderNameCache(); cache.set("ou_user", "Alice"); cache.clear(); expect(cache.get("ou_user")).toBeNull(); expect(cache.size()).toBe(0); }); it("size returns correct count", () => { const cache = new SenderNameCache(); cache.set("ou_1", "Alice"); cache.set("ou_2", "Bob"); expect(cache.size()).toBe(2); }); it("getOrFetch with null fetchFn result does not cache null", async () => { const cache = new SenderNameCache(); const fetchFn = vi.fn(async () => null); await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBeNull(); await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBeNull(); expect(fetchFn).toHaveBeenCalledTimes(2); expect(cache.size()).toBe(0); }); });