650d34f111
- Restored the fetchWithRetry early return for Retry-After and quota hints larger than maxDelayMs. - Added coverage so oversized provider retry hints do not sleep and retry internally.
82 lines
2.5 KiB
TypeScript
82 lines
2.5 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { fetchWithRetry } from "@oh-my-pi/pi-utils/fetch-retry";
|
|
|
|
describe("fetchWithRetry", () => {
|
|
it("routes requests through the `fetch` override when provided", async () => {
|
|
const calls: Array<{ input: string | URL | Request; init: RequestInit | undefined }> = [];
|
|
const customFetch = async (input: string | URL | Request, init?: RequestInit) => {
|
|
calls.push({ input, init });
|
|
return new Response("ok", { status: 200 });
|
|
};
|
|
|
|
const response = await fetchWithRetry("https://example.invalid/x", {
|
|
method: "POST",
|
|
body: "hi",
|
|
fetch: customFetch,
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).toBe("ok");
|
|
expect(calls).toHaveLength(1);
|
|
expect(calls[0]?.input).toBe("https://example.invalid/x");
|
|
expect(calls[0]?.init).toMatchObject({ method: "POST", body: "hi" });
|
|
});
|
|
|
|
it("retries through the override on transient failures", async () => {
|
|
let attempt = 0;
|
|
const customFetch = async () => {
|
|
attempt += 1;
|
|
if (attempt === 1) return new Response("", { status: 503 });
|
|
return new Response("done", { status: 200 });
|
|
};
|
|
|
|
const response = await fetchWithRetry("https://example.invalid/y", {
|
|
fetch: customFetch,
|
|
defaultDelayMs: 1,
|
|
maxAttempts: 3,
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).toBe("done");
|
|
expect(attempt).toBe(2);
|
|
});
|
|
|
|
it("lets callers stop retries for deterministic response bodies", async () => {
|
|
let attempt = 0;
|
|
const customFetch = async () => {
|
|
attempt += 1;
|
|
return new Response("deterministic provider failure", { status: 500 });
|
|
};
|
|
|
|
const response = await fetchWithRetry("https://example.invalid/z", {
|
|
fetch: customFetch,
|
|
defaultDelayMs: 1,
|
|
maxAttempts: 3,
|
|
shouldRetryResponse: (_response, bodyText) => !bodyText.includes("deterministic"),
|
|
});
|
|
|
|
expect(response.status).toBe(500);
|
|
expect(await response.text()).toBe("deterministic provider failure");
|
|
expect(attempt).toBe(1);
|
|
});
|
|
|
|
it("returns retryable responses immediately when retry hints exceed the delay cap", async () => {
|
|
let attempt = 0;
|
|
const customFetch = async () => {
|
|
attempt += 1;
|
|
return new Response("slow down", { status: 429, headers: { "Retry-After": "3600" } });
|
|
};
|
|
|
|
const response = await fetchWithRetry("https://example.invalid/rate-limit", {
|
|
fetch: customFetch,
|
|
defaultDelayMs: 1,
|
|
maxAttempts: 3,
|
|
maxDelayMs: 10,
|
|
});
|
|
|
|
expect(response.status).toBe(429);
|
|
expect(await response.text()).toBe("slow down");
|
|
expect(attempt).toBe(1);
|
|
});
|
|
});
|