


























1-import { beforeEach, describe, expect, it } from "vitest";
2-import { clearAccountThrottlersForTest, getOrCreateAccountThrottler } from "./account-throttler.js";
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+import {
3+clearAccountThrottlersForTest,
4+createTelegramAccountThrottler,
5+getOrCreateAccountThrottler,
6+} from "./account-throttler.js";
7+8+type TelegramPreviousCall = Parameters<ReturnType<typeof createTelegramAccountThrottler>>[0];
9+10+function deferred<T>() {
11+let resolve: (value: T) => void;
12+const promise = new Promise<T>((innerResolve) => {
13+resolve = innerResolve;
14+});
15+return { promise, resolve: resolve! };
16+}
317418describe("getOrCreateAccountThrottler", () => {
519beforeEach(() => {
@@ -14,4 +28,94 @@ describe("getOrCreateAccountThrottler", () => {
1428expect(second).toBe(first);
1529expect(other).not.toBe(first);
1630});
31+32+it("round-robins group topic requests before entering the Telegram throttler", async () => {
33+const firstGate = deferred<void>();
34+const entered: string[] = [];
35+const throttler = createTelegramAccountThrottler(
36+() => async (prev, method, payload, signal) => prev(method, payload, signal),
37+);
38+const prev = vi.fn(async (_method: string, payload: unknown) => {
39+const request = payload as { message_thread_id?: number; text?: string };
40+entered.push(`${request.message_thread_id}:${request.text}`);
41+if (entered.length === 1) {
42+await firstGate.promise;
43+}
44+return { ok: true, result: request.text ?? "" };
45+}) as unknown as TelegramPreviousCall;
46+47+const first = throttler(
48+prev,
49+"sendMessage",
50+{ chat_id: -100123, message_thread_id: 10, text: "first" },
51+undefined,
52+);
53+await vi.waitFor(() => expect(entered).toEqual(["10:first"]));
54+55+const secondSameTopic = throttler(
56+prev,
57+"sendMessage",
58+{ chat_id: -100123, message_thread_id: 10, text: "second" },
59+undefined,
60+);
61+const otherTopic = throttler(
62+prev,
63+"sendMessage",
64+{ chat_id: -100123, message_thread_id: 20, text: "other" },
65+undefined,
66+);
67+await Promise.resolve();
68+69+expect(entered).toEqual(["10:first"]);
70+firstGate.resolve();
71+await vi.waitFor(() => expect(entered.length).toBeGreaterThanOrEqual(2));
72+expect(entered[1]).toBe("20:other");
73+await Promise.all([first, secondSameTopic, otherTopic]);
74+75+expect(entered).toEqual(["10:first", "20:other", "10:second"]);
76+});
77+78+it("uses edited message ids as lanes when Telegram omits topic ids", async () => {
79+const firstGate = deferred<void>();
80+const entered: string[] = [];
81+const throttler = createTelegramAccountThrottler(
82+() => async (prev, method, payload, signal) => prev(method, payload, signal),
83+);
84+const prev = vi.fn(async (_method: string, payload: unknown) => {
85+const request = payload as { message_id?: number; text?: string };
86+entered.push(`${request.message_id}:${request.text}`);
87+if (entered.length === 1) {
88+await firstGate.promise;
89+}
90+return { ok: true, result: request.text ?? "" };
91+}) as unknown as TelegramPreviousCall;
92+93+const first = throttler(
94+prev,
95+"editMessageText",
96+{ chat_id: -100123, message_id: 101, text: "first-edit" },
97+undefined,
98+);
99+await vi.waitFor(() => expect(entered).toEqual(["101:first-edit"]));
100+101+const secondSameMessage = throttler(
102+prev,
103+"editMessageText",
104+{ chat_id: -100123, message_id: 101, text: "second-edit" },
105+undefined,
106+);
107+const otherMessage = throttler(
108+prev,
109+"editMessageText",
110+{ chat_id: -100123, message_id: 202, text: "other-edit" },
111+undefined,
112+);
113+114+firstGate.resolve();
115+await vi.waitFor(() => expect(entered.length).toBeGreaterThanOrEqual(2));
116+expect(entered[1]).toBe("202:other-edit");
117+await Promise.all([first, secondSameMessage, otherMessage]);
118+119+expect(entered).toEqual(["101:first-edit", "202:other-edit", "101:second-edit"]);
120+});
17121});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。