import { describe, expect, test } from "bun:test"; import { Bulkhead, CircuitBreaker, ResilienceError, durationMs, resilientCall, } from "../src/index.ts"; describe("resilience primitives", () => { test("parses durations and validates bad configuration", () => { expect(durationMs("1.5s")).toBe(1_500); expect(durationMs("2m")).toBe(120_000); expect(() => durationMs("soon" as never)).toThrow("Invalid duration"); }); test("retries with exponential backoff and reports attempts", async () => { const waits: number[] = []; let calls = 0; const value = await resilientCall({ retries: 2, retryDelay: 1, backoff: "exponential", onRetry: (_error, _attempt, wait) => waits.push(wait), run: async (_signal, attempt) => { calls += 1; if (attempt < 3) throw new Error("temporary"); return "ready"; }, }); expect(value).toBe("ready"); expect(calls).toBe(3); expect(waits).toEqual([1, 2]); }); test("times out cooperative operations and supports fallback", async () => { const value = await resilientCall({ timeout: "5ms", fallback: (error) => (error as ResilienceError).code, run: (signal) => new Promise((_resolve, reject) => signal.addEventListener("abort", () => reject(signal.reason)), ), }); expect(value).toBe("WRN-RESILIENCE-TIMEOUT"); }); test("times out integrations that ignore cancellation", async () => { await expect( resilientCall({ timeout: "2ms", run: () => new Promise(() => {}) }), ).rejects.toMatchObject({ code: "WRN-RESILIENCE-TIMEOUT" }); }); test("opens a circuit and exposes health", async () => { const breaker = new CircuitBreaker({ failures: 2, resetAfter: "1h" }); for (let index = 0; index < 2; index += 1) { await expect( breaker.execute(async () => { throw new Error("down"); }), ).rejects.toThrow("down"); } expect(breaker.snapshot().state).toBe("open"); await expect(breaker.execute(async () => "nope")).rejects.toMatchObject({ code: "WRN-RESILIENCE-CIRCUIT-OPEN", }); }); test("retains circuit state for a reused declarative configuration", async () => { const circuitBreaker = { failures: 1, resetAfter: "1h" } as const; await expect( resilientCall({ circuitBreaker, run: async () => { throw new Error("down"); }, }), ).rejects.toThrow("down"); await expect( resilientCall({ circuitBreaker, run: async () => "unreachable" }), ).rejects.toMatchObject({ code: "WRN-RESILIENCE-CIRCUIT-OPEN" }); }); test("bulkhead bounds concurrency and queue depth", async () => { const bulkhead = new Bulkhead({ concurrency: 1, queue: 1 }); let release!: () => void; const first = bulkhead.execute( () => new Promise((resolve) => { release = resolve; }), ); const second = bulkhead.execute(async () => "second"); await expect(bulkhead.execute(async () => "third")).rejects.toMatchObject({ code: "WRN-RESILIENCE-BULKHEAD-FULL", }); expect(bulkhead.snapshot).toEqual({ active: 1, queued: 1, capacity: 1 }); release(); await first; expect(await second).toBe("second"); }); });