From a52db23c086dc0009d079dcb5c6a7c588c49d5ff Mon Sep 17 00:00:00 2001 From: spiceoogway Date: Fri, 30 Jan 2026 11:06:52 -0500 Subject: [PATCH] test: add coverage for conflict errors, abort signal, and backoff progression --- src/telegram/monitor.test.ts | 75 ++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/telegram/monitor.test.ts b/src/telegram/monitor.test.ts index d22a016a5..f2800bc4d 100644 --- a/src/telegram/monitor.test.ts +++ b/src/telegram/monitor.test.ts @@ -227,4 +227,79 @@ describe("monitorTelegramProvider (grammY)", () => { expect(sleepWithAbort).toHaveBeenCalled(); expect(runSpy).toHaveBeenCalledTimes(2); }); + + it("retries on getUpdates conflict errors (409)", async () => { + const conflictError = Object.assign(new Error("Conflict: terminated by other getUpdates"), { + error_code: 409, + description: "Conflict: terminated by other getUpdates request", + method: "getUpdates", + }); + runSpy + .mockImplementationOnce(() => ({ + task: () => Promise.reject(conflictError), + stop: vi.fn(), + })) + .mockImplementationOnce(() => ({ + task: () => Promise.resolve(), + stop: vi.fn(), + })); + + await monitorTelegramProvider({ token: "tok" }); + + expect(computeBackoff).toHaveBeenCalled(); + expect(sleepWithAbort).toHaveBeenCalled(); + expect(runSpy).toHaveBeenCalledTimes(2); + }); + + it("respects abort signal during retry backoff", async () => { + const abortController = new AbortController(); + const networkError = Object.assign(new Error("timeout"), { code: "ETIMEDOUT" }); + + // Mock sleepWithAbort to abort mid-sleep + sleepWithAbort.mockImplementationOnce(async () => { + abortController.abort(); + throw new Error("Aborted"); + }); + + runSpy.mockImplementationOnce(() => ({ + task: () => Promise.reject(networkError), + stop: vi.fn(), + })); + + await monitorTelegramProvider({ token: "tok", abortSignal: abortController.signal }); + + expect(runSpy).toHaveBeenCalledTimes(1); + expect(computeBackoff).toHaveBeenCalled(); + expect(sleepWithAbort).toHaveBeenCalled(); + }); + + it("uses exponential backoff for consecutive failures", async () => { + computeBackoff.mockReturnValueOnce(2000).mockReturnValueOnce(3600).mockReturnValueOnce(6480); + + runSpy + .mockImplementationOnce(() => ({ + task: () => Promise.reject(Object.assign(new Error("timeout"), { code: "ETIMEDOUT" })), + stop: vi.fn(), + })) + .mockImplementationOnce(() => ({ + task: () => Promise.reject(Object.assign(new Error("timeout"), { code: "ETIMEDOUT" })), + stop: vi.fn(), + })) + .mockImplementationOnce(() => ({ + task: () => Promise.reject(Object.assign(new Error("timeout"), { code: "ETIMEDOUT" })), + stop: vi.fn(), + })) + .mockImplementationOnce(() => ({ + task: () => Promise.resolve(), + stop: vi.fn(), + })); + + await monitorTelegramProvider({ token: "tok" }); + + // Verify backoff was called with increasing attempt numbers + expect(computeBackoff).toHaveBeenCalledTimes(4); // 3 failures + 1 normal stop + expect(computeBackoff).toHaveBeenCalledWith(expect.anything(), 1); + expect(computeBackoff).toHaveBeenCalledWith(expect.anything(), 2); + expect(computeBackoff).toHaveBeenCalledWith(expect.anything(), 3); + }); });