fix(telegram): retry transient 5xx download errors

This commit is contained in:
Jerry-Xin 2026-03-14 21:10:24 +08:00
parent e2c4993be7
commit b54f823a03
2 changed files with 38 additions and 3 deletions

View File

@ -239,6 +239,31 @@ describe("resolveMedia getFile retry", () => {
expect(fetchRemoteMedia).toHaveBeenCalledTimes(1);
});
it("retries fetchRemoteMedia on transient HTTP 5xx error and succeeds", async () => {
const getFile = vi.fn().mockResolvedValue({ file_path: "voice/file_0.oga" });
fetchRemoteMedia
.mockRejectedValueOnce(new MockMediaFetchError("http_error", "HTTP 502 Bad Gateway"))
.mockResolvedValueOnce({
buffer: Buffer.from("audio"),
contentType: "audio/ogg",
fileName: "file_0.oga",
});
saveMediaBuffer.mockResolvedValueOnce({
path: "/tmp/file_0.oga",
contentType: "audio/ogg",
});
const promise = resolveMedia(makeCtx("voice", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
await flushRetryTimers();
const result = await promise;
expect(getFile).toHaveBeenCalledTimes(1);
expect(fetchRemoteMedia).toHaveBeenCalledTimes(2);
expect(result).toEqual(
expect.objectContaining({ path: "/tmp/file_0.oga", placeholder: "<media:audio>" }),
);
});
it("does not retry fetchRemoteMedia on max_bytes policy violation", async () => {
const getFile = vi.fn().mockResolvedValue({ file_path: "video/large.mp4" });
fetchRemoteMedia.mockRejectedValueOnce(

View File

@ -66,9 +66,19 @@ function isRetryableGetFileError(err: unknown): boolean {
*/
function isRetryableDownloadError(err: unknown): boolean {
if (err instanceof MediaFetchError) {
// Only retry transient fetch failures (network issues, timeouts, connection drops).
// Do not retry HTTP errors (4xx, 5xx) or policy violations (max_bytes).
return err.code === "fetch_failed";
// Retry transient fetch failures (network issues, timeouts, connection drops).
if (err.code === "fetch_failed") {
return true;
}
// Retry transient HTTP 5xx server errors; do not retry 4xx or policy violations.
if (err.code === "http_error") {
const match = /HTTP (\d{3})/.exec(err.message);
if (match) {
const status = Number(match[1]);
return status >= 500;
}
}
return false;
}
// For non-MediaFetchError, check if it looks like a transient network error.
const msg = formatErrorMessage(err).toLowerCase();