




















@@ -6,6 +6,10 @@ function abortError(message: string): Error {
66return Object.assign(new Error(message), { name: "AbortError" });
77}
889+function timeoutError(message: string): Error {
10+return Object.assign(new Error(message), { name: "TimeoutError" });
11+}
12+913describe("executeWithApiKeyRotation", () => {
1014it("keeps transient retry disabled by default for single-key 500", async () => {
1115const execute = vi.fn(async () => {
@@ -154,11 +158,11 @@ describe("executeWithApiKeyRotation", () => {
154158expect(execute).toHaveBeenCalledTimes(2);
155159});
156160157-it("retries generic AbortError when the caller signal is not aborted", async () => {
161+it("retries timeout-named provider errors", async () => {
158162const sleep = vi.fn(async () => undefined);
159163const execute = vi
160164.fn<(apiKey: string) => Promise<string>>()
161-.mockRejectedValueOnce(abortError("This operation was aborted"))
165+.mockRejectedValueOnce(timeoutError("The operation was aborted due to timeout"))
162166.mockResolvedValueOnce("ok");
163167164168await expect(
@@ -173,6 +177,26 @@ describe("executeWithApiKeyRotation", () => {
173177expect(execute).toHaveBeenCalledTimes(2);
174178});
175179180+it("does not retry generic AbortError without timeout evidence", async () => {
181+const sleep = vi.fn(async () => undefined);
182+const execute = vi
183+.fn<(apiKey: string) => Promise<string>>()
184+.mockRejectedValueOnce(abortError("This operation was aborted"))
185+.mockResolvedValueOnce("ok");
186+187+await expect(
188+executeWithApiKeyRotation({
189+provider: "openai",
190+apiKeys: ["key-1"],
191+transientRetry: { attempts: 2, baseDelayMs: 0, maxDelayMs: 0, sleep },
192+ execute,
193+}),
194+).rejects.toThrow("This operation was aborted");
195+196+expect(execute).toHaveBeenCalledTimes(1);
197+expect(sleep).not.toHaveBeenCalled();
198+});
199+176200it.each([400, 401, 403, 404])("does not retry HTTP %i", async (status) => {
177201const sleep = vi.fn(async () => undefined);
178202const execute = vi.fn(async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。