Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
+80
@@ -0,0 +1,80 @@
|
||||
// @vitest-environment node
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Subscription-product mutations (the path an in-app plan upgrade runs)
|
||||
// must purge the cache cross-replica. Assert each wrapper routes through the
|
||||
// publishing helper `clearTenantSubscriptionCache` rather than a bare,
|
||||
// local-only `clearCacheKey`.
|
||||
//
|
||||
// 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<typeof vi.fn> };
|
||||
|
||||
function getMocks(): CacheMocks {
|
||||
const store = globalThis as typeof globalThis & { __tenantSubProductDbCacheMocks__?: CacheMocks };
|
||||
store.__tenantSubProductDbCacheMocks__ ??= { clearTenantSubscriptionCache: vi.fn() };
|
||||
return store.__tenantSubProductDbCacheMocks__;
|
||||
}
|
||||
|
||||
vi.mock("@toolctrl/db/subscriptions.server", () => ({
|
||||
addTenantSubscriptionProduct: vi.fn(),
|
||||
cancelTenantSubscriptionProduct: vi.fn(),
|
||||
updateTenantSubscriptionProduct: vi.fn(),
|
||||
}));
|
||||
vi.mock("~/utils/services/.server/subscriptionService", () => ({
|
||||
clearTenantSubscriptionCache: getMocks().clearTenantSubscriptionCache,
|
||||
}));
|
||||
|
||||
import {
|
||||
addTenantSubscriptionProduct,
|
||||
cancelTenantSubscriptionProduct,
|
||||
updateTenantSubscriptionProduct,
|
||||
} from "./tenantSubscriptionProducts.db.server";
|
||||
import {
|
||||
addTenantSubscriptionProduct as persistAdd,
|
||||
cancelTenantSubscriptionProduct as persistCancel,
|
||||
updateTenantSubscriptionProduct as persistUpdate,
|
||||
} from "@toolctrl/db/subscriptions.server";
|
||||
|
||||
const mocks = getMocks();
|
||||
|
||||
describe("tenantSubscriptionProducts db wrapper — cross-replica cache invalidation", () => {
|
||||
it("addTenantSubscriptionProduct publishes a cross-replica invalidation for the tenant", async () => {
|
||||
mocks.clearTenantSubscriptionCache.mockClear();
|
||||
vi.mocked(persistAdd).mockResolvedValueOnce({
|
||||
tenantSubscription: { tenantId: "tenant-a" },
|
||||
} as Awaited<ReturnType<typeof persistAdd>>);
|
||||
|
||||
await addTenantSubscriptionProduct({} as Parameters<typeof addTenantSubscriptionProduct>[0]);
|
||||
|
||||
expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledWith("tenant-a");
|
||||
});
|
||||
|
||||
it("updateTenantSubscriptionProduct publishes a cross-replica invalidation for the tenant", async () => {
|
||||
mocks.clearTenantSubscriptionCache.mockClear();
|
||||
vi.mocked(persistUpdate).mockResolvedValueOnce({
|
||||
tenantSubscription: { tenantId: "tenant-b" },
|
||||
} as Awaited<ReturnType<typeof persistUpdate>>);
|
||||
|
||||
await updateTenantSubscriptionProduct("p1", "tenant-b", {} as Parameters<typeof updateTenantSubscriptionProduct>[2]);
|
||||
|
||||
expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledWith("tenant-b");
|
||||
});
|
||||
|
||||
it("cancelTenantSubscriptionProduct publishes cross-replica before and after the persist", async () => {
|
||||
mocks.clearTenantSubscriptionCache.mockClear();
|
||||
vi.mocked(persistCancel).mockResolvedValueOnce({
|
||||
tenantId: "tenant-c",
|
||||
} as Awaited<ReturnType<typeof persistCancel>>);
|
||||
|
||||
await cancelTenantSubscriptionProduct("p1", "tenant-c", {} as Parameters<typeof cancelTenantSubscriptionProduct>[2]);
|
||||
|
||||
// Pre-invalidate on the passed tenantId + re-invalidate on the persisted
|
||||
// record's tenantId — both publish cross-replica.
|
||||
expect(mocks.clearTenantSubscriptionCache).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.clearTenantSubscriptionCache).toHaveBeenNthCalledWith(1, "tenant-c");
|
||||
expect(mocks.clearTenantSubscriptionCache).toHaveBeenNthCalledWith(2, "tenant-c");
|
||||
});
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
addTenantSubscriptionProduct as persistTenantSubscriptionProduct,
|
||||
cancelTenantSubscriptionProduct as persistTenantSubscriptionProductCancellation,
|
||||
updateTenantSubscriptionProduct as persistTenantSubscriptionProductUpdate,
|
||||
type TenantSubscriptionProductCreateInput,
|
||||
type TenantSubscriptionProductUpdateInput,
|
||||
type TenantSubscriptionProductCancelInput,
|
||||
} from "@toolctrl/db/subscriptions.server";
|
||||
import { clearTenantSubscriptionCache } from "~/utils/services/.server/subscriptionService";
|
||||
|
||||
export {
|
||||
adminGetTenantSubscriptionProductById,
|
||||
getAllTenantSubscriptionProducts,
|
||||
getTenantSubscriptionProduct,
|
||||
getTenantSubscriptionProductById,
|
||||
getTenantSubscriptionProductByStripeSubscriptionId,
|
||||
type TenantSubscriptionProductPage,
|
||||
type TenantSubscriptionProductWithDetails,
|
||||
type TenantSubscriptionProductWithTenant,
|
||||
} from "@toolctrl/db/subscriptions.server";
|
||||
|
||||
export async function addTenantSubscriptionProduct(data: TenantSubscriptionProductCreateInput) {
|
||||
const item = await persistTenantSubscriptionProduct(data);
|
||||
// Cross-replica purge: clearTenantSubscriptionCache publishes on the
|
||||
// subscription pub/sub channel so peer web replicas drop their L1 too.
|
||||
clearTenantSubscriptionCache(item.tenantSubscription.tenantId);
|
||||
return item;
|
||||
}
|
||||
|
||||
export async function updateTenantSubscriptionProduct(id: string, tenantId: string, data: TenantSubscriptionProductUpdateInput) {
|
||||
const item = await persistTenantSubscriptionProductUpdate({ id, tenantId }, data);
|
||||
clearTenantSubscriptionCache(item.tenantSubscription.tenantId);
|
||||
return item;
|
||||
}
|
||||
|
||||
export async function cancelTenantSubscriptionProduct(id: string, tenantId: string, data: TenantSubscriptionProductCancelInput) {
|
||||
// Pre-invalidate the affected tenant only — no need to wipe every tenant's
|
||||
// subscription cache for a single tenant cancellation.
|
||||
clearTenantSubscriptionCache(tenantId);
|
||||
const item = await persistTenantSubscriptionProductCancellation({ id, tenantId }, data);
|
||||
// Re-invalidate against the persisted record's tenantId in case it differs;
|
||||
// publishes cross-replica so no peer keeps a stale entry.
|
||||
clearTenantSubscriptionCache(item.tenantId);
|
||||
}
|
||||
Reference in new issue
Block a user