























@@ -267,6 +267,35 @@ describe("provider operation deadlines", () => {
267267expect(fetchFn).toHaveBeenCalledTimes(2);
268268expect(sleep).toHaveBeenCalledWith(0, undefined);
269269});
270+271+it("recomputes remaining download timeout before retry attempts", async () => {
272+vi.useFakeTimers();
273+vi.setSystemTime(1_000);
274+const sleep = vi.fn(async () => undefined);
275+const fetchFn = vi.fn<typeof fetch>(async () => {
276+vi.setSystemTime(2_001);
277+throw Object.assign(new Error("socket hang up"), { code: "ECONNRESET" });
278+});
279+const deadline = createProviderOperationDeadline({
280+label: "video download",
281+timeoutMs: 1_000,
282+});
283+284+await expect(
285+fetchProviderDownloadResponse({
286+url: "https://cdn.example.com/video.mp4",
287+init: { method: "GET" },
288+timeoutMs: () => resolveProviderOperationTimeoutMs({ deadline, defaultTimeoutMs: 5_000 }),
289+ fetchFn,
290+provider: "test-video",
291+requestFailedMessage: "download failed",
292+retry: { attempts: 2, baseDelayMs: 0, maxDelayMs: 0, sleep },
293+}),
294+).rejects.toThrow("video download timed out after 1000ms");
295+296+expect(fetchFn).toHaveBeenCalledTimes(1);
297+expect(sleep).toHaveBeenCalledWith(0, undefined);
298+});
270299});
271300272301describe("resolveProviderHttpRequestConfig", () => {
@@ -546,6 +575,39 @@ describe("fetchWithTimeoutGuarded", () => {
546575expect(sleep).toHaveBeenCalledWith(0, undefined);
547576});
548577578+it("retries read JSON POST transient HTTP responses", async () => {
579+fetchWithSsrFGuardMock.mockReset();
580+const firstRelease = vi.fn(async () => undefined);
581+const secondRelease = vi.fn(async () => undefined);
582+const sleep = vi.fn(async () => undefined);
583+fetchWithSsrFGuardMock
584+.mockResolvedValueOnce({
585+response: new Response("busy", { status: 503, statusText: "Service Unavailable" }),
586+finalUrl: "https://api.example.com",
587+release: firstRelease,
588+})
589+.mockResolvedValueOnce({
590+response: new Response(null, { status: 200 }),
591+finalUrl: "https://api.example.com",
592+release: secondRelease,
593+});
594+595+const result = await postJsonRequest({
596+url: "https://api.example.com/v1/analyze",
597+headers: new Headers(),
598+body: { media: "base64" },
599+fetchFn: fetch,
600+retryStage: "read",
601+retry: { attempts: 2, baseDelayMs: 0, maxDelayMs: 0, sleep },
602+});
603+604+expect(result.response.status).toBe(200);
605+expect(fetchWithSsrFGuardMock).toHaveBeenCalledTimes(2);
606+expect(firstRelease).toHaveBeenCalledOnce();
607+expect(secondRelease).not.toHaveBeenCalled();
608+expect(sleep).toHaveBeenCalledWith(0, undefined);
609+});
610+549611it("forwards explicit pinDns overrides to transcription requests", async () => {
550612fetchWithSsrFGuardMock.mockResolvedValue({
551613response: new Response(null, { status: 200 }),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。