Files
triggerdotdev--trigger.dev/apps/webapp/test/billingLimitReconcileTick.test.ts
2026-07-13 13:32:57 +08:00

109 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { runBillingLimitReconcileTick } from "~/v3/services/billingLimit/runBillingLimitReconcileTick.server";
import type { PendingBillingLimitResolve } from "~/v3/services/billingLimit/billingLimitPendingResolve.types";
describe("runBillingLimitReconcileTick", () => {
const pending: PendingBillingLimitResolve = {
organizationId: "org_pending",
resumeMode: "queue",
resolvedAt: "2026-06-17T12:00:00.000Z",
};
it("runs pending resolves before collecting orgs and excludes still-pending orgs", async () => {
const order: string[] = [];
await runBillingLimitReconcileTick({
getPendingResolves: async () => ({ orgs: [pending] }),
runPendingResolves: async (pendingResolves) => {
order.push(`pending:${pendingResolves.map((row) => row.organizationId).join(",")}`);
return new Set(["org_pending"]);
},
collectOrgs: async (options) => {
order.push(`collect:${[...(options?.excludeOrgIds ?? [])].join(",")}`);
return {
targets: [{ organizationId: "org_active", targetState: "grace" }],
queuedOrgIds: ["org_active"],
};
},
reconcileTarget: async (target) => {
order.push(`reconcile:${target.organizationId}:${target.targetState}`);
},
clearProcessedQueue: async (queuedOrgIds, processedOrgIds) => {
order.push(`clear:${queuedOrgIds.join(",")}:${processedOrgIds.join(",")}`);
},
bustCaches: () => {},
enqueueConverge: async () => undefined,
});
expect(order).toEqual([
"pending:org_pending",
"collect:org_pending",
"reconcile:org_active:grace",
"clear:org_active:org_active",
]);
});
it("reconciles collected targets when no pending resolves remain", async () => {
const reconciled: Array<{ organizationId: string; targetState: string }> = [];
await runBillingLimitReconcileTick({
getPendingResolves: async () => ({ orgs: [] }),
runPendingResolves: async () => new Set(),
collectOrgs: async () => ({
targets: [
{ organizationId: "org_grace", targetState: "grace" },
{ organizationId: "org_ok", targetState: "ok" },
],
queuedOrgIds: ["org_grace", "org_ok"],
}),
reconcileTarget: async (target, deps) => {
reconciled.push(target);
await deps.enqueueConverge(target.organizationId, target.targetState);
},
clearProcessedQueue: async () => undefined,
bustCaches: () => {},
enqueueConverge: async (organizationId, targetState) => {
reconciled.push({ organizationId, targetState: `enqueued:${targetState}` });
},
});
expect(reconciled).toEqual([
{ organizationId: "org_grace", targetState: "grace" },
{ organizationId: "org_grace", targetState: "enqueued:grace" },
{ organizationId: "org_ok", targetState: "ok" },
{ organizationId: "org_ok", targetState: "enqueued:ok" },
]);
});
it("continues reconciling other targets and only clears successfully processed orgs", async () => {
const reconciled: string[] = [];
let clearedProcessedOrgIds: string[] = [];
await runBillingLimitReconcileTick({
getPendingResolves: async () => ({ orgs: [] }),
runPendingResolves: async () => new Set(),
collectOrgs: async () => ({
targets: [
{ organizationId: "org_fail", targetState: "grace" },
{ organizationId: "org_ok", targetState: "ok" },
],
queuedOrgIds: ["org_fail", "org_ok"],
}),
reconcileTarget: async (target) => {
if (target.organizationId === "org_fail") {
throw new Error("reconcile failed");
}
reconciled.push(target.organizationId);
},
clearProcessedQueue: async (_queuedOrgIds, processedOrgIds) => {
clearedProcessedOrgIds = processedOrgIds;
},
bustCaches: () => {},
enqueueConverge: async () => undefined,
});
expect(reconciled).toEqual(["org_ok"]);
expect(clearedProcessedOrgIds).toEqual(["org_ok"]);
});
});