Squashed 'third_party/pi/' content from commit d981de122
git-subtree-dir: third_party/pi git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
73
packages/coding-agent/test/management-http.test.ts
Normal file
73
packages/coding-agent/test/management-http.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user