diff --git a/src/infra/net/fetch-guard.ssrf.test.ts b/src/infra/net/fetch-guard.ssrf.test.ts index d66a59603e2..1b848e63241 100644 --- a/src/infra/net/fetch-guard.ssrf.test.ts +++ b/src/infra/net/fetch-guard.ssrf.test.ts @@ -1138,6 +1138,43 @@ describe("fetchWithSsrFGuard hardening", () => { await result.release(); }); + it("rejects timed-out fetches even when dispatcher close stalls", async () => { + agentCtor.mockImplementationOnce(function MockAgent(this: { close: () => Promise }) { + this.close = () => new Promise(() => {}); + }); + (globalThis as Record)[TEST_UNDICI_RUNTIME_DEPS_KEY] = { + Agent: agentCtor, + EnvHttpProxyAgent: envHttpProxyAgentCtor, + ProxyAgent: proxyAgentCtor, + fetch: vi.fn(async () => okResponse()), + }; + const fetchImpl = vi.fn( + (_input: RequestInfo | URL, init?: RequestInit) => + new Promise((_resolve, reject) => { + init?.signal?.addEventListener("abort", () => { + reject(init.signal?.reason ?? new Error("aborted")); + }); + }), + ); + + const fetchPromise = fetchWithSsrFGuard({ + url: "https://public.example/resource", + fetchImpl, + lookupFn: createPublicLookup(), + timeoutMs: 1, + }); + + const outcome = await Promise.race([ + fetchPromise.then( + () => "resolved", + (error: unknown) => (error instanceof Error ? error.name : "rejected"), + ), + new Promise((resolve) => setTimeout(() => resolve("hung"), 250)), + ]); + + expect(outcome).toBe("TimeoutError"); + }); + it("inherits the configured global stream timeout for guarded direct dispatchers", async () => { try { ensureGlobalUndiciStreamTimeouts({ timeoutMs: 1_900_000 });