mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-08-20 14:14:00 +00:00
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { fetchWithRetry } from "../src/utils/management-http.ts";
|
|
|
|
afterEach(() => vi.restoreAllMocks());
|
|
|
|
describe("fetchWithRetry", () => {
|
|
it("retries a transient transport failure once", async () => {
|
|
const fetchMock = vi
|
|
.spyOn(globalThis, "fetch")
|
|
.mockRejectedValueOnce(new Error("fetch failed"))
|
|
.mockRejectedValueOnce(new Error("fetch failed"))
|
|
.mockResolvedValueOnce(Response.json({ ok: true }));
|
|
|
|
const response = await fetchWithRetry("https://example.test");
|
|
expect(response.ok).toBe(true);
|
|
expect(fetchMock).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it("shares the timeout budget across attempts", async () => {
|
|
const signals: AbortSignal[] = [];
|
|
const fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(async (_input, init) => {
|
|
if (init?.signal) signals.push(init.signal);
|
|
if (signals.length === 1) throw new Error("fetch failed");
|
|
return Response.json({ ok: true });
|
|
});
|
|
|
|
const response = await fetchWithRetry("https://example.test", undefined, { timeoutMs: 1000 });
|
|
expect(response.ok).toBe(true);
|
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
expect(signals[0]).toBe(signals[1]);
|
|
});
|
|
|
|
it("retries an attempt timeout", async () => {
|
|
const controllers: AbortController[] = [];
|
|
vi.spyOn(AbortSignal, "timeout").mockImplementation(() => {
|
|
const controller = new AbortController();
|
|
controllers.push(controller);
|
|
return controller.signal;
|
|
});
|
|
const fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(async (_input, init) => {
|
|
if (fetchMock.mock.calls.length === 1) {
|
|
controllers[0].abort();
|
|
init?.signal?.throwIfAborted();
|
|
}
|
|
return Response.json({ ok: true });
|
|
});
|
|
|
|
await fetchWithRetry("https://example.test", undefined, { attemptTimeoutMs: 4000 });
|
|
|
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
expect(controllers).toHaveLength(2);
|
|
});
|
|
|
|
it("retries transient HTTP responses and returns the successful response", async () => {
|
|
const fetchMock = vi
|
|
.spyOn(globalThis, "fetch")
|
|
.mockResolvedValueOnce(new Response("busy", { status: 503 }))
|
|
.mockResolvedValueOnce(Response.json({ ok: true }));
|
|
|
|
const response = await fetchWithRetry("https://example.test");
|
|
expect(response.ok).toBe(true);
|
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("does not retry caller cancellation", async () => {
|
|
const controller = new AbortController();
|
|
controller.abort();
|
|
const fetchMock = vi.spyOn(globalThis, "fetch");
|
|
|
|
await expect(fetchWithRetry("https://example.test", { signal: controller.signal })).rejects.toThrow();
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|