import { describe, expect, test } from "bun:test"; import { RPC_ERROR_CODES, failure, retryingTransport, success, type Transport, } from "../src/index.ts"; function failingTransport(): { transport: Transport; calls: () => number } { let count = 0; return { transport: { async call() { count++; return failure(RPC_ERROR_CODES.transport, "down"); }, }, calls: () => count, }; } describe("retryingTransport", () => { test("retries only declared idempotent calls", async () => { const retryable = failingTransport(); const write = failingTransport(); const options = { retries: 2, backoffMs: 0 }; await retryingTransport(retryable.transport, options).call( { app: "billing", service: "invoice", procedure: "get" }, {}, { idempotent: true }, ); await retryingTransport(write.transport, options).call( { app: "billing", service: "invoice", procedure: "create" }, {}, { idempotent: false }, ); expect(retryable.calls()).toBe(3); expect(write.calls()).toBe(1); }); test("opens a circuit after repeated exhausted failures and recovers after cooldown", async () => { let clock = 0; let calls = 0; const base: Transport = { async call() { calls++; return calls < 3 ? failure(RPC_ERROR_CODES.transport, "down") : success("ok"); }, }; const transport = retryingTransport(base, { retries: 0, circuitFailureThreshold: 2, circuitCooldownMs: 10, now: () => clock, }); const target = { app: "billing", service: "invoice", procedure: "get" }; await transport.call(target, {}, { idempotent: true }); await transport.call(target, {}, { idempotent: true }); expect(await transport.call(target, {}, { idempotent: true })).toMatchObject({ ok: false, code: RPC_ERROR_CODES.transport, }); expect(calls).toBe(2); clock = 11; expect(await transport.call(target, {}, { idempotent: true })).toEqual(success("ok")); }); });