diff --git a/src/infra/retry.test.ts b/src/infra/retry.test.ts index dfba7cabd6b..0eafafa6536 100644 --- a/src/infra/retry.test.ts +++ b/src/infra/retry.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import { retryAsync } from "./retry.js"; +import { resolveRetryConfig, retryAsync } from "./retry.js"; async function runRetryAfterCase(params: { minDelayMs: number; @@ -48,22 +48,34 @@ describe("retryAsync", () => { }); it("stops when shouldRetry returns false", async () => { - const fn = vi.fn().mockRejectedValue(new Error("boom")); - await expect(retryAsync(fn, { attempts: 3, shouldRetry: () => false })).rejects.toThrow("boom"); + const err = new Error("boom"); + const fn = vi.fn().mockRejectedValue(err); + const shouldRetry = vi.fn(() => false); + await expect(retryAsync(fn, { attempts: 3, shouldRetry })).rejects.toThrow("boom"); expect(fn).toHaveBeenCalledTimes(1); + expect(shouldRetry).toHaveBeenCalledWith(err, 1); }); - it("calls onRetry before retrying", async () => { - const fn = vi.fn().mockRejectedValueOnce(new Error("boom")).mockResolvedValueOnce("ok"); + it("calls onRetry with retry metadata before retrying", async () => { + const err = new Error("boom"); + const fn = vi.fn().mockRejectedValueOnce(err).mockResolvedValueOnce("ok"); const onRetry = vi.fn(); const res = await retryAsync(fn, { attempts: 2, minDelayMs: 0, maxDelayMs: 0, + label: "telegram", onRetry, }); expect(res).toBe("ok"); - expect(onRetry).toHaveBeenCalledWith(expect.objectContaining({ attempt: 1, maxAttempts: 2 })); + expect(onRetry).toHaveBeenCalledWith( + expect.objectContaining({ + attempt: 1, + maxAttempts: 2, + err, + label: "telegram", + }), + ); }); it("clamps attempts to at least 1", async () => { @@ -89,3 +101,30 @@ describe("retryAsync", () => { expect(delays[0]).toBe(250); }); }); + +describe("resolveRetryConfig", () => { + it.each([ + { + name: "rounds attempts and delays", + overrides: { attempts: 2.6, minDelayMs: 10.4, maxDelayMs: 99.8, jitter: 0.4 }, + expected: { attempts: 3, minDelayMs: 10, maxDelayMs: 100, jitter: 0.4 }, + }, + { + name: "clamps attempts to at least one and maxDelayMs to minDelayMs", + overrides: { attempts: 0, minDelayMs: 250, maxDelayMs: 100, jitter: -1 }, + expected: { attempts: 1, minDelayMs: 250, maxDelayMs: 250, jitter: 0 }, + }, + { + name: "falls back for non-finite overrides and caps jitter at one", + overrides: { + attempts: Number.NaN, + minDelayMs: Number.POSITIVE_INFINITY, + maxDelayMs: Number.NaN, + jitter: 2, + }, + expected: { attempts: 3, minDelayMs: 300, maxDelayMs: 30000, jitter: 1 }, + }, + ])("$name", ({ overrides, expected }) => { + expect(resolveRetryConfig(undefined, overrides)).toEqual(expected); + }); +});