




























1-// Telegram tests cover sendchataction 401 backoff plugin behavior.
1+// Telegram tests cover sendchataction 401 and transient backoff plugin behavior.
22import { beforeAll, describe, expect, it, vi } from "vitest";
3344const mocks = vi.hoisted(() => ({
@@ -20,6 +20,13 @@ describe("createTelegramSendChatActionHandler", () => {
20202121const make401Error = () => new Error("401 Unauthorized");
2222const make500Error = () => new Error("500 Internal Server Error");
23+const makeNetworkError = () =>
24+Object.assign(new Error("read ECONNRESET"), { code: "ECONNRESET" });
25+const makeTelegramError = (
26+message: string,
27+error_code: number,
28+parameters?: { retry_after?: number },
29+) => Object.assign(new Error(message), { error_code, parameters });
23302431it("calls sendChatActionFn on success", async () => {
2532const fn = vi.fn().mockResolvedValue(true);
@@ -169,6 +176,96 @@ describe("createTelegramSendChatActionHandler", () => {
169176expect(handler.isSuspended()).toBe(false);
170177});
171178179+it.each([
180+["recoverable network", () => makeNetworkError(), 1000],
181+["Telegram 429", () => makeTelegramError("Too Many Requests", 429, { retry_after: 2 }), 2000],
182+["Telegram 5xx", () => makeTelegramError("Bad Gateway", 502), 1000],
183+])("cools down transient %s errors", async (_name, makeError, expectedCooldownMs) => {
184+let now = 10_000;
185+const fn = vi.fn().mockRejectedValueOnce(makeError()).mockResolvedValue(true);
186+const logger = vi.fn();
187+const handler = createTelegramSendChatActionHandler({
188+sendChatActionFn: fn,
189+ logger,
190+now: () => now,
191+});
192+193+await expect(handler.sendChatAction(123, "typing")).rejects.toThrow();
194+expect(logger.mock.calls.at(-1)).toEqual([
195+`sendChatAction transient error (1). Cooling down ${expectedCooldownMs}ms before retry.`,
196+]);
197+198+now += expectedCooldownMs - 1;
199+await expect(handler.sendChatAction(123, "typing")).rejects.toThrow(
200+"transient cooldown active",
201+);
202+expect(fn).toHaveBeenCalledTimes(1);
203+204+now += 1;
205+await handler.sendChatAction(123, "typing");
206+expect(fn).toHaveBeenCalledTimes(2);
207+});
208+209+it("rejects transient keepalive ticks until same-chat coalescing expires", async () => {
210+let now = 0;
211+const fn = vi
212+.fn()
213+.mockRejectedValueOnce(makeTelegramError("Bad Gateway", 502))
214+.mockResolvedValue(true);
215+const logger = vi.fn();
216+const handler = createTelegramSendChatActionHandler({
217+sendChatActionFn: fn,
218+ logger,
219+minIntervalMs: 4000,
220+now: () => now,
221+});
222+223+await expect(handler.sendChatAction(-100, "typing")).rejects.toThrow("Bad Gateway");
224+expect(logger.mock.calls.at(-1)).toEqual([
225+"sendChatAction transient error (1). Cooling down 4000ms before retry.",
226+]);
227+228+now = 3000;
229+await expect(handler.sendChatAction(-100, "typing")).rejects.toThrow(
230+"transient cooldown active",
231+);
232+expect(fn).toHaveBeenCalledTimes(1);
233+234+now = 4000;
235+await handler.sendChatAction(-100, "typing");
236+expect(fn).toHaveBeenCalledTimes(2);
237+});
238+239+it("resets transient counters on non-transient errors", async () => {
240+let now = 1000;
241+const fn = vi
242+.fn()
243+.mockRejectedValueOnce(makeTelegramError("Bad Gateway", 502))
244+.mockRejectedValueOnce(new Error("400 Bad Request"))
245+.mockRejectedValueOnce(makeTelegramError("Bad Gateway", 502));
246+const logger = vi.fn();
247+const handler = createTelegramSendChatActionHandler({
248+sendChatActionFn: fn,
249+ logger,
250+now: () => now,
251+});
252+253+await expect(handler.sendChatAction(123, "typing")).rejects.toThrow("Bad Gateway");
254+now = 2000;
255+await expect(handler.sendChatAction(123, "typing")).rejects.toThrow("400 Bad Request");
256+now = 3000;
257+await expect(handler.sendChatAction(123, "typing")).rejects.toThrow("Bad Gateway");
258+259+expect(
260+logger.mock.calls.filter(([message]) =>
261+String(message).startsWith("sendChatAction transient error"),
262+),
263+).toEqual([
264+["sendChatAction transient error (1). Cooling down 1000ms before retry."],
265+["sendChatAction transient error (1). Cooling down 1000ms before retry."],
266+]);
267+});
268+172269it("reset() clears suspension", async () => {
173270const fn = vi.fn().mockRejectedValue(make401Error());
174271const logger = vi.fn();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。