mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-30 21:30:21 +00:00
telegram: retry media fetch with IPv4 fallback on connect errors (#30554)
* telegram: retry fetch once with IPv4 fallback on connect errors * test(telegram): format fetch fallback test * style(telegram): apply oxfmt for fetch test * fix(telegram): retry ipv4 fallback per request * test: harden telegram ipv4 fallback coverage (#30554) --------- Co-authored-by: root <root@vultr.guest> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -217,4 +217,95 @@ describe("resolveTelegramFetch", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("retries once with ipv4 fallback when fetch fails with network timeout/unreachable", async () => {
|
||||
const timeoutErr = Object.assign(new Error("connect ETIMEDOUT 149.154.166.110:443"), {
|
||||
code: "ETIMEDOUT",
|
||||
});
|
||||
const unreachableErr = Object.assign(
|
||||
new Error("connect ENETUNREACH 2001:67c:4e8:f004::9:443"),
|
||||
{
|
||||
code: "ENETUNREACH",
|
||||
},
|
||||
);
|
||||
const fetchError = Object.assign(new TypeError("fetch failed"), {
|
||||
cause: Object.assign(new Error("aggregate"), {
|
||||
errors: [timeoutErr, unreachableErr],
|
||||
}),
|
||||
});
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(fetchError)
|
||||
.mockResolvedValueOnce({ ok: true } as Response);
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
const resolved = resolveTelegramFetch();
|
||||
if (!resolved) {
|
||||
throw new Error("expected resolved fetch");
|
||||
}
|
||||
|
||||
await resolved("https://api.telegram.org/file/botx/photos/file_1.jpg");
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(2);
|
||||
expect(EnvHttpProxyAgentCtor).toHaveBeenNthCalledWith(1, {
|
||||
connect: {
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
},
|
||||
});
|
||||
expect(EnvHttpProxyAgentCtor).toHaveBeenNthCalledWith(2, {
|
||||
connect: {
|
||||
autoSelectFamily: false,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("retries with ipv4 fallback once per request, not once per process", async () => {
|
||||
const timeoutErr = Object.assign(new Error("connect ETIMEDOUT 149.154.166.110:443"), {
|
||||
code: "ETIMEDOUT",
|
||||
});
|
||||
const fetchError = Object.assign(new TypeError("fetch failed"), {
|
||||
cause: timeoutErr,
|
||||
});
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(fetchError)
|
||||
.mockResolvedValueOnce({ ok: true } as Response)
|
||||
.mockRejectedValueOnce(fetchError)
|
||||
.mockResolvedValueOnce({ ok: true } as Response);
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
const resolved = resolveTelegramFetch();
|
||||
if (!resolved) {
|
||||
throw new Error("expected resolved fetch");
|
||||
}
|
||||
|
||||
await resolved("https://api.telegram.org/file/botx/photos/file_1.jpg");
|
||||
await resolved("https://api.telegram.org/file/botx/photos/file_2.jpg");
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it("does not retry when fetch fails without fallback network error codes", async () => {
|
||||
const fetchError = Object.assign(new TypeError("fetch failed"), {
|
||||
cause: Object.assign(new Error("connect ECONNRESET"), {
|
||||
code: "ECONNRESET",
|
||||
}),
|
||||
});
|
||||
const fetchMock = vi.fn().mockRejectedValue(fetchError);
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
const resolved = resolveTelegramFetch();
|
||||
if (!resolved) {
|
||||
throw new Error("expected resolved fetch");
|
||||
}
|
||||
|
||||
await expect(resolved("https://api.telegram.org/file/botx/photos/file_3.jpg")).rejects.toThrow(
|
||||
"fetch failed",
|
||||
);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user