From 81ece77f8c012d7131e4b5cbd9a236f7a33c55cf Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sun, 3 May 2026 20:07:24 +0530 Subject: [PATCH] test(telegram): cover polling after webhook cleanup timeout --- extensions/telegram/src/monitor.test.ts | 37 ++++++------------- .../telegram/src/polling-session.test.ts | 24 ++++++++++++ 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/extensions/telegram/src/monitor.test.ts b/extensions/telegram/src/monitor.test.ts index 757540a833d..a44f856468d 100644 --- a/extensions/telegram/src/monitor.test.ts +++ b/extensions/telegram/src/monitor.test.ts @@ -495,50 +495,35 @@ describe("monitorTelegramProvider (grammY)", () => { expect(order).toEqual(["deleteWebhook", "run"]); }); - it("retries recoverable deleteWebhook failures before polling", async () => { + it("starts polling after recoverable deleteWebhook failures", async () => { const abort = new AbortController(); const cleanupError = makeRecoverableFetchError(); api.deleteWebhook.mockReset(); - api.getWebhookInfo.mockReset().mockResolvedValueOnce({ url: "https://example.test/hook" }); - api.deleteWebhook.mockRejectedValueOnce(cleanupError).mockResolvedValueOnce(true); - mockRunOnceAndAbort(abort); - - await monitorTelegramProvider({ token: "tok", abortSignal: abort.signal }); - - expect(api.deleteWebhook).toHaveBeenCalledTimes(2); - expect(api.getWebhookInfo).toHaveBeenCalledTimes(1); - expectRecoverableRetryState(1); - }); - - it("continues polling when deleteWebhook transiently fails but webhook is already absent", async () => { - const abort = new AbortController(); - const cleanupError = makeRecoverableFetchError(); - api.deleteWebhook.mockReset(); - api.getWebhookInfo.mockReset().mockResolvedValueOnce({ url: "" }); + api.getWebhookInfo.mockReset(); api.deleteWebhook.mockRejectedValueOnce(cleanupError); mockRunOnceAndAbort(abort); await monitorTelegramProvider({ token: "tok", abortSignal: abort.signal }); expect(api.deleteWebhook).toHaveBeenCalledTimes(1); - expect(api.getWebhookInfo).toHaveBeenCalledTimes(1); - expect(runSpy).toHaveBeenCalledTimes(1); - expect(sleepWithAbort).not.toHaveBeenCalled(); + expect(api.getWebhookInfo).not.toHaveBeenCalled(); + expectRecoverableRetryState(1); }); - it("retries cleanup when deleteWebhook and webhook confirmation both transiently fail", async () => { + it("does not run webhook confirmation when deleteWebhook transiently fails", async () => { const abort = new AbortController(); const cleanupError = makeRecoverableFetchError(); api.deleteWebhook.mockReset(); - api.getWebhookInfo.mockReset().mockRejectedValueOnce(makeRecoverableFetchError()); - api.deleteWebhook.mockRejectedValueOnce(cleanupError).mockResolvedValueOnce(true); + api.getWebhookInfo.mockReset(); + api.deleteWebhook.mockRejectedValueOnce(cleanupError); mockRunOnceAndAbort(abort); await monitorTelegramProvider({ token: "tok", abortSignal: abort.signal }); - expect(api.deleteWebhook).toHaveBeenCalledTimes(2); - expect(api.getWebhookInfo).toHaveBeenCalledTimes(1); - expectRecoverableRetryState(1); + expect(api.deleteWebhook).toHaveBeenCalledTimes(1); + expect(api.getWebhookInfo).not.toHaveBeenCalled(); + expect(runSpy).toHaveBeenCalledTimes(1); + expect(sleepWithAbort).not.toHaveBeenCalled(); }); it("retries setup-time recoverable errors before starting polling", async () => { diff --git a/extensions/telegram/src/polling-session.test.ts b/extensions/telegram/src/polling-session.test.ts index e86923c5ac2..553fb750080 100644 --- a/extensions/telegram/src/polling-session.test.ts +++ b/extensions/telegram/src/polling-session.test.ts @@ -572,6 +572,30 @@ describe("TelegramPollingSession", () => { expect(createTelegramTransport).toHaveBeenCalledTimes(1); }); + it("starts polling when webhook cleanup times out during startup", async () => { + const abort = new AbortController(); + const cleanupError = new Error("Telegram deleteWebhook timed out after 15000ms"); + const bot = makeBot(); + bot.api.deleteWebhook.mockRejectedValueOnce(cleanupError); + createTelegramBotMock.mockReturnValueOnce(bot); + runMock.mockReturnValueOnce({ + task: async () => { + abort.abort(); + }, + stop: vi.fn(async () => undefined), + isRunning: () => false, + }); + + const session = createPollingSession({ + abortSignal: abort.signal, + }); + + await session.runUntilAbort(); + + expect(bot.api.deleteWebhook).toHaveBeenCalledTimes(1); + expect(runMock).toHaveBeenCalledTimes(1); + }); + it("does not trigger stall restart shortly after a getUpdates error", async () => { const abort = new AbortController(); const botStop = vi.fn(async () => undefined);