import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; import { MemoryCache } from "./memory-cache"; describe("MemoryCache", () => { let cache: MemoryCache; beforeEach(() => { cache = new MemoryCache(); }); afterEach(() => { cache.clear(); }); test("should store and retrieve values", async () => { await cache.set("key1", "value1"); const value = await cache.get("key1"); expect(value).toBe("value1"); }); test("should return undefined for non-existent keys", async () => { const value = await cache.get("non-existent"); expect(value).toBeUndefined(); }); test("should respect TTL and expire items", async () => { vi.useFakeTimers(); try { await cache.set("expiring", "value", 100); // 100ms TTL expect(await cache.get("expiring")).toBe("value"); // Advance time past TTL vi.advanceTimersByTime(101); expect(await cache.get("expiring")).toBeUndefined(); } finally { vi.useRealTimers(); } }); test("should use default TTL when not specified", async () => { const cache = new MemoryCache(); vi.useFakeTimers(); try { await cache.set("key", "value"); // Uses default TTL (Infinity) expect(await cache.get("key")).toBe("value"); // Advance time significantly - should still be there since TTL is Infinity vi.advanceTimersByTime(100000); expect(await cache.get("key")).toBe("value"); } finally { vi.useRealTimers(); } }); test("should check if a key exists", async () => { await cache.set("exists", "value"); expect(await cache.has("exists")).toBe(true); expect(await cache.has("does-not-exist")).toBe(false); }); test("should delete keys", async () => { await cache.set("toDelete", "value"); expect(await cache.get("toDelete")).toBe("value"); await cache.delete("toDelete"); expect(await cache.get("toDelete")).toBeUndefined(); }); test("should clear all keys", async () => { await cache.set("key1", "value1"); await cache.set("key2", "value2"); await cache.clear(); expect(await cache.get("key1")).toBeUndefined(); expect(await cache.get("key2")).toBeUndefined(); }); test("should handle complex values", async () => { const complexValue = { nested: { array: [1, 2, 3], boolean: true, }, date: new Date().toISOString(), }; await cache.set("complex", complexValue); expect(await cache.get("complex")).toEqual(complexValue); }); test("should get all valid entries", async () => { await cache.set("key1", "value1"); await cache.set("key2", "value2"); await cache.set("key3", { nested: "value" }); const allEntries = await cache.getAll(); expect(allEntries.size).toBe(3); expect(allEntries.get("key1")).toBe("value1"); expect(allEntries.get("key2")).toBe("value2"); expect(allEntries.get("key3")).toEqual({ nested: "value" }); }); test("should return empty map when cache is empty", async () => { const allEntries = await cache.getAll(); expect(allEntries.size).toBe(0); }); test("should exclude expired entries from getAll", async () => { vi.useFakeTimers(); try { await cache.set("valid", "value1"); await cache.set("expiring", "value2", 100); // 100ms TTL // Before expiration let allEntries = await cache.getAll(); expect(allEntries.size).toBe(2); expect(allEntries.get("valid")).toBe("value1"); expect(allEntries.get("expiring")).toBe("value2"); // After expiration vi.advanceTimersByTime(101); allEntries = await cache.getAll(); expect(allEntries.size).toBe(1); expect(allEntries.get("valid")).toBe("value1"); expect(allEntries.has("expiring")).toBe(false); } finally { vi.useRealTimers(); } }); test("cleanup interval should remove expired items", async () => { vi.useFakeTimers(); try { // Create cache with cleanup interval const cleanupCache = new MemoryCache({ cleanupIntervalMs: 200, }); // Set item with short TTL await cleanupCache.set("expire-me", "value", 100); // Advance time past TTL but before cleanup vi.advanceTimersByTime(101); // Item should still be in store but get() will return undefined expect(await cleanupCache.get("expire-me")).toBeUndefined(); // Advance time to trigger cleanup vi.advanceTimersByTime(100); // The sweep should have removed the item from the store // We'll verify this is working by checking internal implementation const hasKey = (cleanupCache as any).store.has("expire-me"); expect(hasKey).toBe(false); } finally { vi.useRealTimers(); } }); });