// @vitest-environment node import { describe, expect, it, vi } from "vitest"; // Stripe-driven subscription mutations must purge the cache cross-replica, // not just in-process — assert these wrappers route through the publishing // helper `clearTenantSubscriptionCache` (which fans out on pub/sub). // // The mock fn is parked on `globalThis` so the hoisted `vi.mock` factory can // reach it without a top-level reference (vitest 0.29 has no `vi.hoisted`). type CacheMocks = { clearTenantSubscriptionCache: ReturnType }; function getMocks(): CacheMocks { const store = globalThis as typeof globalThis & { __tenantSubsDbCacheMocks__?: CacheMocks }; store.__tenantSubsDbCacheMocks__ ??= { clearTenantSubscriptionCache: vi.fn() }; return store.__tenantSubsDbCacheMocks__; } vi.mock("@toolctrl/db/subscriptions.server", () => ({ createTenantSubscription: vi.fn(), getTenantSubscription: vi.fn(), getTenantSubscriptions: vi.fn(), updateTenantSubscriptionCustomerId: vi.fn(), })); vi.mock("~/utils/services/.server/subscriptionService", () => ({ clearTenantSubscriptionCache: getMocks().clearTenantSubscriptionCache, })); import { createTenantSubscription, updateTenantSubscriptionCustomerId } from "./tenantSubscriptions.db.server"; import { createTenantSubscription as persistTenantSubscription, updateTenantSubscriptionCustomerId as persistTenantSubscriptionCustomerId, } from "@toolctrl/db/subscriptions.server"; const mocks = getMocks(); describe("tenantSubscriptions db wrapper — cross-replica cache invalidation", () => { it("createTenantSubscription publishes a cross-replica invalidation for the tenant", async () => { mocks.clearTenantSubscriptionCache.mockClear(); vi.mocked(persistTenantSubscription).mockResolvedValueOnce({ tenantId: "tenant-a", } as Awaited>); await createTenantSubscription("tenant-a", "cus_x"); expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledTimes(1); expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledWith("tenant-a"); }); it("updateTenantSubscriptionCustomerId publishes a cross-replica invalidation for the tenant", async () => { mocks.clearTenantSubscriptionCache.mockClear(); vi.mocked(persistTenantSubscriptionCustomerId).mockResolvedValueOnce( {} as Awaited>, ); await updateTenantSubscriptionCustomerId("tenant-b", { stripeCustomerId: "cus_y" }); expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledTimes(1); expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledWith("tenant-b"); }); });