lib/client-request-deadline.test.ts
Rama main · réplica de lectura del repositorio original.
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { withClientRequestDeadline } from "./client-request-deadline";
beforeEach(() => {
vi.useFakeTimers();
vi.spyOn(AbortSignal, "any").mockImplementation(() => { throw new Error("Unsupported API"); });
});
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); });
it("completes without AbortSignal.any and removes caller listeners", async () => {
const caller = new AbortController();
const add = vi.spyOn(caller.signal, "addEventListener");
const remove = vi.spyOn(caller.signal, "removeEventListener");
await expect(withClientRequestDeadline(async () => "ready", 30_000, caller.signal)).resolves.toBe("ready");
expect(AbortSignal.any).not.toHaveBeenCalled();
expect(remove).toHaveBeenCalledWith("abort", add.mock.calls[0][1]);
expect(vi.getTimerCount()).toBe(0);
});
it("forwards caller cancellation and its reason without leaving listeners", async () => {
const caller = new AbortController();
const remove = vi.spyOn(caller.signal, "removeEventListener");
let transport!: AbortSignal;
const pending = withClientRequestDeadline(signal => {
transport = signal;
return new Promise(() => {});
}, 30_000, caller.signal);
const reason = new Error("Caller cancelled");
caller.abort(reason);
await expect(pending).rejects.toBe(reason);
expect(transport.reason).toBe(reason);
expect(remove).toHaveBeenCalledTimes(1);
expect(vi.getTimerCount()).toBe(0);
});
it("never starts work for a pre-aborted caller", async () => {
const caller = new AbortController();
caller.abort();
const work = vi.fn();
await expect(withClientRequestDeadline(work, 30_000, caller.signal)).rejects.toMatchObject({ name: "AbortError" });
expect(work).not.toHaveBeenCalled();
expect(vi.getTimerCount()).toBe(0);
});
it("times out the transport and cleans up even without AbortSignal.any", async () => {
const caller = new AbortController();
const remove = vi.spyOn(caller.signal, "removeEventListener");
let transport!: AbortSignal;
const pending = withClientRequestDeadline(signal => {
transport = signal;
return new Promise(() => {});
}, 30_000, caller.signal);
const rejected = expect(pending).rejects.toMatchObject({ name: "TimeoutError" });
await vi.advanceTimersByTimeAsync(30_000);
await rejected;
expect(transport.aborted).toBe(true);
expect(remove).toHaveBeenCalledTimes(1);
expect(vi.getTimerCount()).toBe(0);
});
it("keeps cancellation available when no transfer deadline is configured", async () => {
const caller = new AbortController();
const pending = withClientRequestDeadline(() => new Promise(() => {}), null, caller.signal);
await vi.advanceTimersByTimeAsync(90_000);
expect(vi.getTimerCount()).toBe(0);
caller.abort();
await expect(pending).rejects.toMatchObject({ name: "AbortError" });
});
it("cleans up after work rejects", async () => {
const caller = new AbortController();
const remove = vi.spyOn(caller.signal, "removeEventListener");
const error = new Error("Read failed");
await expect(withClientRequestDeadline(async () => { throw error; }, 30_000, caller.signal)).rejects.toBe(error);
expect(remove).toHaveBeenCalledTimes(1);
expect(vi.getTimerCount()).toBe(0);
});