From 0586da70db07a0e6ee4db13782da00cfb9bfafd5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 12 May 2026 11:50:44 +0100 Subject: [PATCH] test: guard telegram bot core mock calls --- extensions/telegram/src/bot.test.ts | 82 ++++++++++++++++++----------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index b65d30476b7..9494998f739 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -4,6 +4,7 @@ import { clearPluginInteractiveHandlers, registerPluginInteractiveHandler, } from "openclaw/plugin-sdk/plugin-runtime"; +import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime"; import { loadSessionStore } from "openclaw/plugin-sdk/session-store-runtime"; import { mockPinnedHostnameResolution } from "openclaw/plugin-sdk/test-env"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -84,7 +85,7 @@ async function loadInboundContextContract() { type MockCallSource = { mock: { - calls: ArrayLike>; + calls: ReadonlyArray>; }; }; @@ -101,13 +102,22 @@ function requireArray(value: unknown, label: string): unknown[] { } function mockArg(source: MockCallSource, callIndex: number, argIndex: number, label: string) { - const call = source.mock.calls[callIndex]; + const call = source.mock.calls.at(callIndex); if (!call) { throw new Error(`expected mock call: ${label}`); } return call[argIndex]; } +function mockMsgContextArg( + source: MockCallSource, + callIndex: number, + argIndex: number, + label: string, +): MsgContext { + return mockArg(source, callIndex, argIndex, label) as MsgContext; +} + function execApprovalCall(index = 0) { return requireRecord( mockArg(resolveExecApprovalSpy as unknown as MockCallSource, index, 0, "exec approval call"), @@ -553,7 +563,7 @@ describe("createTelegramBot", () => { }); expect(editMessageReplyMarkupSpy).toHaveBeenCalledTimes(1); - const [chatId, messageId, replyMarkup] = editMessageReplyMarkupSpy.mock.calls[0] ?? []; + const [chatId, messageId, replyMarkup] = editMessageReplyMarkupSpy.mock.calls.at(0) ?? []; expect(chatId).toBe(1234); expect(messageId).toBe(21); expect(replyMarkup).toEqual({ reply_markup: { inline_keyboard: [] } }); @@ -988,7 +998,7 @@ describe("createTelegramBot", () => { expect(listCall.cfg).toBeTypeOf("object"); expect(listCall.agentIds).toEqual(["main"]); expect(editMessageTextSpy).toHaveBeenCalledTimes(1); - const [chatId, messageId, text, params] = editMessageTextSpy.mock.calls[0] ?? []; + const [chatId, messageId, text, params] = editMessageTextSpy.mock.calls.at(0) ?? []; expect(chatId).toBe(1234); expect(messageId).toBe(12); expect(String(text)).toContain(`${INFO_EMOJI} Commands (2/`); @@ -1143,10 +1153,10 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).toHaveBeenCalledTimes(1); - expect(editMessageTextSpy.mock.calls[0]?.[2]).toContain( + expect(editMessageTextSpy.mock.calls.at(0)?.[2]).toContain( `${CHECK_MARK_EMOJI} Model reset to default`, ); - expect(editMessageTextSpy.mock.calls[0]?.[2]).toContain( + expect(editMessageTextSpy.mock.calls.at(0)?.[2]).toContain( "Session selection cleared. Runtime unchanged. New replies use the agent's configured default.", ); @@ -1219,7 +1229,7 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).toHaveBeenCalledTimes(1); - const [, , , params] = editMessageTextSpy.mock.calls[0] ?? []; + const [, , , params] = editMessageTextSpy.mock.calls.at(0) ?? []; const inlineKeyboard = ( params as { reply_markup?: { @@ -1293,10 +1303,10 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).toHaveBeenCalledTimes(1); - expect(editMessageTextSpy.mock.calls[0]?.[2]).toContain( + expect(editMessageTextSpy.mock.calls.at(0)?.[2]).toContain( `${CHECK_MARK_EMOJI} Model reset to default`, ); - expect(editMessageTextSpy.mock.calls[0]?.[2]).toContain( + expect(editMessageTextSpy.mock.calls.at(0)?.[2]).toContain( "Session selection cleared. Runtime unchanged. New replies use the agent's configured default.", ); @@ -1368,7 +1378,7 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).toHaveBeenCalledTimes(1); - const editCall = editMessageTextSpy.mock.calls[0]; + const editCall = editMessageTextSpy.mock.calls.at(0); expect(editCall?.[0]).toBe(1234); expect(editCall?.[1]).toBe(17); expect(editCall?.[2]).toBe( @@ -1523,7 +1533,7 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); expect(editMessageTextSpy).toHaveBeenCalledTimes(1); - expect(editMessageTextSpy.mock.calls[0]?.[2]).toContain( + expect(editMessageTextSpy.mock.calls.at(0)?.[2]).toContain( 'Could not resolve model "shared-model".', ); expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-model-compact-2"); @@ -1568,7 +1578,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); const { expectChannelInboundContextContract: expectInboundContextContract } = await loadInboundContextContract(); const { escapeRegExp, formatEnvelopeTimestamp } = await loadEnvelopeTimestampHelpers(); @@ -1659,7 +1669,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); const [conversationContext] = requireArray( payload.UntrustedStructuredContext, "structured context", @@ -1762,7 +1772,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); const [conversationContext] = requireArray( payload.UntrustedStructuredContext, "structured context", @@ -1807,7 +1817,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.Body).toContain("[Reply chain - nearest first]"); expect(payload.Body).toContain("[1. Ada id:9001]"); expect(payload.Body).toContain('"summarize this"'); @@ -1845,7 +1855,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.Body).toContain("[Reply chain - nearest first]"); expect(payload.Body).toContain("[1. Ada id:9001]"); expect(payload.Body).not.toContain("PK"); @@ -1900,7 +1910,12 @@ describe("createTelegramBot", () => { } expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0] as { + const payload = mockMsgContextArg( + replySpy as unknown as MockCallSource, + 0, + 0, + "replySpy call", + ) as { MediaPath?: string; MediaPaths?: string[]; ReplyToBody?: string; @@ -1990,7 +2005,12 @@ describe("createTelegramBot", () => { } expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0] as { + const payload = mockMsgContextArg( + replySpy as unknown as MockCallSource, + 0, + 0, + "replySpy call", + ) as { ReplyChain?: Array<{ messageId?: string; body?: string; @@ -2178,7 +2198,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.Body).toContain("[Reply chain - nearest first]"); expect(payload.Body).toContain("[1. unknown sender]"); expect(payload.Body).toContain('"summarize this"'); @@ -2214,7 +2234,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.Body).toContain("[Reply chain - nearest first]"); expect(payload.Body).toContain("[1. Ada id:9002]"); expect(payload.Body).toContain('"summarize this"'); @@ -2262,7 +2282,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.ReplyToForwardedFrom).toBe("Bob Smith (@bobsmith)"); expect(payload.ReplyToForwardedFromType).toBe("user"); expect(payload.ReplyToForwardedFromId).toBe("999"); @@ -2325,7 +2345,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.ReplyToId).toBe("9003"); expect(payload.ReplyToBody).toBe("forwarded text"); expect(payload.ReplyToSender).toBe("Ada"); @@ -2365,7 +2385,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.WasMentioned).toBe(true); }); @@ -2837,7 +2857,7 @@ describe("createTelegramBot", () => { }); expect(replySpy).toHaveBeenCalledTimes(1); - const payload = replySpy.mock.calls[0][0]; + const payload = mockMsgContextArg(replySpy as unknown as MockCallSource, 0, 0, "replySpy call"); expect(payload.CommandTargetSessionKey).toBe("agent:main:main"); }); @@ -3019,7 +3039,7 @@ describe("createTelegramBot", () => { }); expect(enqueueSystemEventSpy).toHaveBeenCalledTimes(1); - expect(enqueueSystemEventSpy.mock.calls[0]?.[0]).toBe( + expect(enqueueSystemEventSpy.mock.calls.at(0)?.[0]).toBe( `Telegram reaction added: ${THUMBS_UP_EMOJI} by Ada (@ada_bot) on msg 42`, ); expect(String(systemEventOptions().contextKey)).toContain("telegram:reaction:add:1234:42:9"); @@ -3247,10 +3267,10 @@ describe("createTelegramBot", () => { }); expect(enqueueSystemEventSpy).toHaveBeenCalledTimes(1); - expect(enqueueSystemEventSpy.mock.calls[0]?.[0]).toBe( + expect(enqueueSystemEventSpy.mock.calls.at(0)?.[0]).toBe( `Telegram reaction added: ${PARTY_EMOJI} by Ada on msg 99`, ); - expect(enqueueSystemEventSpy.mock.calls[0]?.[1]).toBeTypeOf("object"); + expect(enqueueSystemEventSpy.mock.calls.at(0)?.[1]).toBeTypeOf("object"); }); it("skips reaction in own mode when message is not sent by bot", async () => { @@ -3444,7 +3464,7 @@ describe("createTelegramBot", () => { }); expect(enqueueSystemEventSpy).toHaveBeenCalledTimes(1); - expect(enqueueSystemEventSpy.mock.calls[0]?.[0]).toBe( + expect(enqueueSystemEventSpy.mock.calls.at(0)?.[0]).toBe( `Telegram reaction added: ${FIRE_EMOJI} by Bob (@bob_user) on msg 100`, ); expect(String(systemEventOptions().sessionKey)).toContain("telegram:group:5678:topic:1"); @@ -3480,7 +3500,7 @@ describe("createTelegramBot", () => { }); expect(enqueueSystemEventSpy).toHaveBeenCalledTimes(1); - expect(enqueueSystemEventSpy.mock.calls[0]?.[0]).toBe( + expect(enqueueSystemEventSpy.mock.calls.at(0)?.[0]).toBe( `Telegram reaction added: ${EYES_EMOJI} by Bob on msg 101`, ); expect(String(systemEventOptions().sessionKey)).toContain("telegram:group:5678:topic:1"); @@ -3515,13 +3535,13 @@ describe("createTelegramBot", () => { }); expect(enqueueSystemEventSpy).toHaveBeenCalledTimes(1); - expect(enqueueSystemEventSpy.mock.calls[0]?.[0]).toBe( + expect(enqueueSystemEventSpy.mock.calls.at(0)?.[0]).toBe( `Telegram reaction added: ${HEART_EMOJI} by Charlie on msg 200`, ); expect(String(systemEventOptions().sessionKey)).toContain("telegram:group:9999"); expect(String(systemEventOptions().contextKey)).toContain("telegram:reaction:add:9999:200:11"); // Verify session key does NOT contain :topic: - const eventOptions = enqueueSystemEventSpy.mock.calls[0]?.[1] as { + const eventOptions = enqueueSystemEventSpy.mock.calls.at(0)?.[1] as { sessionKey?: string; }; const sessionKey = eventOptions.sessionKey ?? "";