import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("~/utils/cache.server", () => { const map = new Map(); return { cache: map, cachified: vi.fn(), clearCacheKey: vi.fn((key: string) => map.delete(key)), clearCacheKeyPrefix: vi.fn((prefix: string) => { for (const k of map.keys()) if (k.startsWith(prefix)) map.delete(k); }), __test_map: map, }; }); vi.mock("~/shared-store.server", async () => { const { MemoryAdapter, createSharedStore } = await import("@toolctrl/shared-store"); const adapter = new MemoryAdapter(); const store = createSharedStore({ namespace: "web-test", adapter }); return { getSharedStore: () => store, __setSharedStoreFactoryForTests: vi.fn(), __resetSharedStoreForTests: vi.fn(), }; }); import { publishTenantInvalidation, subscribeTenantCache, __resetTenantCacheForTests, } from "./tenant-cache.server"; import * as cacheServer from "~/utils/cache.server"; beforeEach(() => { __resetTenantCacheForTests(); (cacheServer as unknown as { __test_map: Map }).__test_map.clear(); vi.mocked(cacheServer.clearCacheKey).mockClear(); vi.mocked(cacheServer.clearCacheKeyPrefix).mockClear(); delete process.env.CACHE_INVALIDATION_ENABLED; }); afterEach(() => { __resetTenantCacheForTests(); }); describe("tenant cache (two-tier)", () => { it("publishTenantInvalidation clears local cachified keys for tenant id", async () => { await publishTenantInvalidation("tenant-7"); expect(vi.mocked(cacheServer.clearCacheKey)).toHaveBeenCalledWith("tenant:tenant-7"); expect(vi.mocked(cacheServer.clearCacheKey)).toHaveBeenCalledWith("tenantSimple:tenant-7"); expect(vi.mocked(cacheServer.clearCacheKey)).toHaveBeenCalledWith("tenantIdOrSlug:tenant-7"); }); it("remote invalidation purges all 3 cachified prefixes for the affected tenant", async () => { await subscribeTenantCache(); await publishTenantInvalidation("tenant-9"); await new Promise((r) => setTimeout(r, 10)); // local + remote both fire clearCacheKey — assert tenant-9 keys were dropped expect(vi.mocked(cacheServer.clearCacheKey).mock.calls.filter((c) => c[0] === "tenant:tenant-9").length).toBeGreaterThanOrEqual(1); }); it("subscribeTenantCache is idempotent", async () => { const a = await subscribeTenantCache(); const b = await subscribeTenantCache(); expect(a).toBe(b); }); });