



















@@ -161,6 +161,36 @@ function requireValue<T>(value: T | null | undefined, label: string): T {
161161return value;
162162}
163163164+function requireRecord(value: unknown, label: string): Record<string, unknown> {
165+if (typeof value !== "object" || value === null || Array.isArray(value)) {
166+throw new Error(`expected ${label} to be an object`);
167+}
168+return value as Record<string, unknown>;
169+}
170+171+function expectRecordFields(
172+value: unknown,
173+expected: Record<string, unknown>,
174+label: string,
175+): Record<string, unknown> {
176+const record = requireRecord(value, label);
177+for (const [key, expectedValue] of Object.entries(expected)) {
178+expect(record[key], `${label}.${key}`).toEqual(expectedValue);
179+}
180+return record;
181+}
182+183+function getBotCtorOptions(callIndex = 0): Record<string, unknown> {
184+const call = requireValue(botCtorSpy.mock.calls[callIndex], `bot constructor call ${callIndex}`);
185+expect(call[0]).toBe("tok");
186+return requireRecord(call[1], `bot constructor options ${callIndex}`);
187+}
188+189+function expectBotClientFields(expected: Record<string, unknown>, callIndex = 0): void {
190+const options = getBotCtorOptions(callIndex);
191+expectRecordFields(options.client, expected, `bot constructor client ${callIndex}`);
192+}
193+164194describe("createTelegramBot", () => {
165195beforeAll(() => {
166196process.env.TZ = "UTC";
@@ -242,12 +272,7 @@ describe("createTelegramBot", () => {
242272},
243273});
244274createTelegramBot({ token: "tok" });
245-expect(botCtorSpy).toHaveBeenCalledWith(
246-"tok",
247-expect.objectContaining({
248-client: expect.objectContaining({ timeoutSeconds: 60 }),
249-}),
250-);
275+expectBotClientFields({ timeoutSeconds: 60 });
251276botCtorSpy.mockClear();
252277253278loadConfig.mockReturnValue({
@@ -263,12 +288,7 @@ describe("createTelegramBot", () => {
263288},
264289});
265290createTelegramBot({ token: "tok", accountId: "foo" });
266-expect(botCtorSpy).toHaveBeenCalledWith(
267-"tok",
268-expect.objectContaining({
269-client: expect.objectContaining({ timeoutSeconds: 61 }),
270-}),
271-);
291+expectBotClientFields({ timeoutSeconds: 61 });
272292});
273293274294it("keeps low timeoutSeconds above the outbound request guard", () => {
@@ -278,12 +298,7 @@ describe("createTelegramBot", () => {
278298},
279299});
280300createTelegramBot({ token: "tok" });
281-expect(botCtorSpy).toHaveBeenCalledWith(
282-"tok",
283-expect.objectContaining({
284-client: expect.objectContaining({ timeoutSeconds: 60 }),
285-}),
286-);
301+expectBotClientFields({ timeoutSeconds: 60 });
287302});
288303289304it("keeps polling client timeout above the outbound request guard", () => {
@@ -293,12 +308,7 @@ describe("createTelegramBot", () => {
293308},
294309});
295310createTelegramBot({ token: "tok", minimumClientTimeoutSeconds: 45 });
296-expect(botCtorSpy).toHaveBeenCalledWith(
297-"tok",
298-expect.objectContaining({
299-client: expect.objectContaining({ timeoutSeconds: 60 }),
300-}),
301-);
311+expectBotClientFields({ timeoutSeconds: 60 });
302312});
303313304314it("passes startup probe botInfo to grammY", () => {
@@ -319,12 +329,7 @@ describe("createTelegramBot", () => {
319329320330createTelegramBot({ token: "tok", botInfo });
321331322-expect(botCtorSpy).toHaveBeenCalledWith(
323-"tok",
324-expect.objectContaining({
325- botInfo,
326-}),
327-);
332+expect(getBotCtorOptions().botInfo).toBe(botInfo);
328333});
329334330335it("normalizes full Telegram bot endpoint apiRoot before passing it to grammY", () => {
@@ -340,12 +345,7 @@ describe("createTelegramBot", () => {
340345341346createTelegramBot({ token: "tok" });
342347343-expect(botCtorSpy).toHaveBeenCalledWith(
344-"tok",
345-expect.objectContaining({
346-client: expect.objectContaining({ apiRoot: "https://api.telegram.org" }),
347-}),
348-);
348+expectBotClientFields({ apiRoot: "https://api.telegram.org" });
349349});
350350351351it("sequentializes updates by chat and thread", () => {
@@ -949,7 +949,8 @@ describe("createTelegramBot", () => {
949949it("wraps inbound message with Telegram envelope", async () => {
950950await withEnvAsync({ TZ: "Europe/Vienna" }, async () => {
951951createTelegramBot({ token: "tok" });
952-expect(onSpy).toHaveBeenCalledWith("message", expect.any(Function));
952+const messageRegistration = onSpy.mock.calls.find(([event]) => event === "message");
953+expect(messageRegistration?.[1]).toBeTypeOf("function");
953954const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
954955955956const message = {
@@ -1041,9 +1042,7 @@ describe("createTelegramBot", () => {
10411042expect(pairingText, testCase.name).toContain(`Your Telegram user id: ${senderId}`);
10421043expect(pairingText, testCase.name).toContain("Pairing code:");
10431044expect(pairingText, testCase.name).toContain("openclaw pairing approve telegram");
1044-expect(sendMessageSpy.mock.calls[0]?.[2], testCase.name).toEqual(
1045-expect.objectContaining({ parse_mode: "HTML" }),
1046-);
1045+expectRecordFields(sendMessageSpy.mock.calls[0]?.[2], { parse_mode: "HTML" }, testCase.name);
10471046}
10481047});
10491048@@ -1123,8 +1122,10 @@ describe("createTelegramBot", () => {
11231122const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]);
11241123expect(pairingText).toContain("Pairing code:");
11251124expect(pairingText).toContain("<pre><code>");
1126-expect(sendMessageSpy.mock.calls[0]?.[2]).toEqual(
1127-expect.objectContaining({ parse_mode: "HTML" }),
1125+expectRecordFields(
1126+sendMessageSpy.mock.calls[0]?.[2],
1127+{ parse_mode: "HTML" },
1128+"pairing reply options",
11281129);
11291130expect(replySpy).not.toHaveBeenCalled();
11301131} finally {
@@ -1243,8 +1244,10 @@ describe("createTelegramBot", () => {
12431244const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]);
12441245expect(pairingText).toContain("Pairing code:");
12451246expect(pairingText).toContain("<pre><code>");
1246-expect(sendMessageSpy.mock.calls[0]?.[2]).toEqual(
1247-expect.objectContaining({ parse_mode: "HTML" }),
1247+expectRecordFields(
1248+sendMessageSpy.mock.calls[0]?.[2],
1249+{ parse_mode: "HTML" },
1250+"album pairing reply options",
12481251);
12491252expect(replySpy).not.toHaveBeenCalled();
12501253} finally {
@@ -2366,7 +2369,10 @@ describe("createTelegramBot", () => {
23662369});
2367237023682371expect(sendAnimationSpy).toHaveBeenCalledTimes(1);
2369-expect(sendAnimationSpy).toHaveBeenCalledWith("1234", expect.anything(), {
2372+const animationCall = requireValue(sendAnimationSpy.mock.calls[0], "animation send call");
2373+expect(animationCall[0]).toBe("1234");
2374+requireValue(animationCall[1], "animation payload");
2375+expect(animationCall[2]).toEqual({
23702376caption: "caption",
23712377parse_mode: "HTML",
23722378reply_to_message_id: undefined,
@@ -2826,14 +2832,16 @@ describe("createTelegramBot", () => {
2826283228272833expect(getChatSpy).toHaveBeenCalledOnce();
28282834expect(getChatSpy).toHaveBeenCalledWith(-1001234567890);
2829-expect(dispatchCall?.ctx).toEqual(
2830-expect.objectContaining({
2831- SessionKey: expect.stringContaining("telegram:group:-1001234567890:topic:1"),
2835+const dispatchCtx = expectRecordFields(
2836+dispatchCall?.ctx,
2837+{
28322838From: "telegram:group:-1001234567890:topic:1",
28332839MessageThreadId: 1,
28342840IsForum: true,
2835-}),
2841+},
2842+"forum dispatch context",
28362843);
2844+expect(String(dispatchCtx.SessionKey)).toContain("telegram:group:-1001234567890:topic:1");
28372845expect(sendChatActionSpy).toHaveBeenCalledWith(-1001234567890, "typing", {
28382846message_thread_id: 1,
28392847});
@@ -3264,10 +3272,13 @@ describe("createTelegramBot", () => {
32643272match: "",
32653273});
326632743267-expect(sendMessageSpy).toHaveBeenCalledWith(
3268-"-1001234567890",
3269-expect.any(String),
3270-expect.objectContaining({ message_thread_id: 99, reply_to_message_id: 42 }),
3275+const statusCall = requireValue(sendMessageSpy.mock.calls[0], "status reply call");
3276+expect(statusCall[0]).toBe("-1001234567890");
3277+expect(statusCall[1]).toBeTypeOf("string");
3278+expectRecordFields(
3279+statusCall[2],
3280+{ message_thread_id: 99, reply_to_message_id: 42 },
3281+"status reply options",
32713282);
32723283});
32733284it("reloads native command routing bindings between invocations without recreating the bot", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。