import { beforeEach, describe, expect, mock, test } from "bun:test"; import { ApiVersion, ApiVersionClass, AppEnv, type TrackResponseV3, } from "@autumn/shared"; import { RedisUnavailableError } from "@/external/redis/utils/errors.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; const mockState = { queueCalls: [] as Record[], runTrackV2Calls: [] as Record[], runTrackV3Calls: [] as Record[], v3Error: null as unknown, queuedResponse: { customer_id: "cus_123", value: 2, balance: null, } as TrackResponseV3 | null, }; mock.module("@/internal/balances/track/runTrackV2.js", () => ({ runTrackV2: async (args: Record) => { mockState.runTrackV2Calls.push(args); return { ok: true }; }, })); mock.module("@/internal/balances/track/v3/runTrackV3.js", () => ({ runTrackV3: async (args: Record) => { mockState.runTrackV3Calls.push(args); if (mockState.v3Error) throw mockState.v3Error; return { ok: true }; }, })); mock.module("@/internal/balances/track/utils/queueTrack.js", () => ({ queueTrack: async (args: Record) => { mockState.queueCalls.push(args); return mockState.queuedResponse; }, })); mock.module("@/external/redis/initUtils/redisV2Availability.js", () => ({ shouldUseRedisV2: () => true, })); import { runTrackWithRollout } from "@/internal/balances/track/runTrackWithRollout.js"; const ctx = { org: { id: "org_123" }, env: AppEnv.Sandbox, apiVersion: new ApiVersionClass(ApiVersion.V2_1), logger: { warn: () => undefined, }, rolloutSnapshot: { rolloutId: "v2-cache", enabled: true, percent: 100, previousPercent: 0, changedAt: 1, customerBucket: 10, }, } as unknown as AutumnContext; const body = { customer_id: "cus_123", feature_id: "messages", value: 2, idempotency_key: "idem_123", }; describe("track queue fallback", () => { beforeEach(() => { mockState.queueCalls = []; mockState.runTrackV2Calls = []; mockState.runTrackV3Calls = []; mockState.v3Error = null; mockState.queuedResponse = { customer_id: "cus_123", value: 2, balance: null, }; }); test("queues track when rollout path hits a retryable Redis failure", async () => { mockState.v3Error = new RedisUnavailableError({ source: "runTrackV3", reason: "not_ready", }); const response = await runTrackWithRollout({ ctx, body, featureDeductions: [], }); expect(mockState.runTrackV3Calls).toHaveLength(1); expect(mockState.runTrackV2Calls).toHaveLength(0); expect(mockState.queueCalls).toHaveLength(1); expect(mockState.queueCalls[0]).toMatchObject({ body: { customer_id: "cus_123", feature_id: "messages", }, }); if (!mockState.queuedResponse) throw new Error("expected queued response"); expect(response).toEqual(mockState.queuedResponse); }); test("throws retryable Redis failure when queue fallback is unavailable", async () => { const error = new RedisUnavailableError({ source: "runTrackV3", reason: "timeout", }); mockState.v3Error = error; mockState.queuedResponse = null; await expect( runTrackWithRollout({ ctx, body, featureDeductions: [], }), ).rejects.toBe(error); expect(mockState.queueCalls).toHaveLength(1); }); });