From 057233953ed243aa3ccdc402d2d5b158274e0431 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 21 Feb 2026 23:58:03 +0000 Subject: [PATCH] test(retry): table-drive retryAfter timer cases --- src/infra/retry.test.ts | 70 ++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/infra/retry.test.ts b/src/infra/retry.test.ts index ed4b43fea..d4d66dcb7 100644 --- a/src/infra/retry.test.ts +++ b/src/infra/retry.test.ts @@ -2,6 +2,31 @@ import { describe, expect, it, vi } from "vitest"; import { retryAsync } from "./retry.js"; describe("retryAsync", () => { + async function runRetryAfterCase(options: { + maxDelayMs: number; + retryAfterMs: number; + expectedDelayMs: number; + }) { + vi.useFakeTimers(); + try { + const fn = vi.fn().mockRejectedValueOnce(new Error("boom")).mockResolvedValueOnce("ok"); + const delays: number[] = []; + const promise = retryAsync(fn, { + attempts: 2, + minDelayMs: 0, + maxDelayMs: options.maxDelayMs, + jitter: 0, + retryAfterMs: () => options.retryAfterMs, + onRetry: (info) => delays.push(info.delayMs), + }); + await vi.runAllTimersAsync(); + await expect(promise).resolves.toBe("ok"); + expect(delays[0]).toBe(options.expectedDelayMs); + } finally { + vi.useRealTimers(); + } + } + it("returns on first success", async () => { const fn = vi.fn().mockResolvedValue("ok"); const result = await retryAsync(fn, 3, 10); @@ -49,39 +74,20 @@ describe("retryAsync", () => { expect(fn).toHaveBeenCalledTimes(1); }); - it("uses retryAfterMs when provided", async () => { - vi.useFakeTimers(); - const fn = vi.fn().mockRejectedValueOnce(new Error("boom")).mockResolvedValueOnce("ok"); - const delays: number[] = []; - const promise = retryAsync(fn, { - attempts: 2, - minDelayMs: 0, + it.each([ + { + name: "uses retryAfterMs when provided", maxDelayMs: 1000, - jitter: 0, - retryAfterMs: () => 500, - onRetry: (info) => delays.push(info.delayMs), - }); - await vi.runAllTimersAsync(); - await expect(promise).resolves.toBe("ok"); - expect(delays[0]).toBe(500); - vi.useRealTimers(); - }); - - it("clamps retryAfterMs to maxDelayMs", async () => { - vi.useFakeTimers(); - const fn = vi.fn().mockRejectedValueOnce(new Error("boom")).mockResolvedValueOnce("ok"); - const delays: number[] = []; - const promise = retryAsync(fn, { - attempts: 2, - minDelayMs: 0, + retryAfterMs: 500, + expectedDelayMs: 500, + }, + { + name: "clamps retryAfterMs to maxDelayMs", maxDelayMs: 100, - jitter: 0, - retryAfterMs: () => 500, - onRetry: (info) => delays.push(info.delayMs), - }); - await vi.runAllTimersAsync(); - await expect(promise).resolves.toBe("ok"); - expect(delays[0]).toBe(100); - vi.useRealTimers(); + retryAfterMs: 500, + expectedDelayMs: 100, + }, + ])("$name", async ({ maxDelayMs, retryAfterMs, expectedDelayMs }) => { + await runRetryAfterCase({ maxDelayMs, retryAfterMs, expectedDelayMs }); }); });