From c42a7aff372ad29f520cda59db3d925f2cc9c091 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Feb 2026 00:01:32 +0000 Subject: [PATCH] test(telegram): trim setup resets and table-drive edit fallback cases --- src/telegram/bot.test.ts | 128 ++++++++++++++++++------------------ src/telegram/send.test.ts | 132 ++++++++++++++++---------------------- 2 files changed, 121 insertions(+), 139 deletions(-) diff --git a/src/telegram/bot.test.ts b/src/telegram/bot.test.ts index 6c3776619..b4a49686c 100644 --- a/src/telegram/bot.test.ts +++ b/src/telegram/bot.test.ts @@ -154,8 +154,8 @@ describe("createTelegramBot", () => { }); it("blocks callback_query when inline buttons are allowlist-only and sender not authorized", async () => { - onSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + replySpy.mockClear(); createTelegramBot({ token: "tok", @@ -194,8 +194,8 @@ describe("createTelegramBot", () => { }); it("edits commands list for pagination callbacks", async () => { - onSpy.mockReset(); - listSkillCommandsForAgents.mockReset(); + onSpy.mockClear(); + listSkillCommandsForAgents.mockClear(); createTelegramBot({ token: "tok" }); const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as ( @@ -235,8 +235,8 @@ describe("createTelegramBot", () => { }); it("blocks pagination callbacks when allowlist rejects sender", async () => { - onSpy.mockReset(); - editMessageTextSpy.mockReset(); + onSpy.mockClear(); + editMessageTextSpy.mockClear(); createTelegramBot({ token: "tok", @@ -275,8 +275,8 @@ describe("createTelegramBot", () => { }); it("includes sender identity in group envelope headers", async () => { - onSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + replySpy.mockClear(); loadConfig.mockReturnValue({ agents: { @@ -326,9 +326,9 @@ describe("createTelegramBot", () => { }); it("uses quote text when a Telegram partial reply is received", async () => { - onSpy.mockReset(); - sendMessageSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + sendMessageSpy.mockClear(); + replySpy.mockClear(); createTelegramBot({ token: "tok" }); const handler = getOnHandler("message") as (ctx: Record) => Promise; @@ -361,9 +361,9 @@ describe("createTelegramBot", () => { }); it("handles quote-only replies without reply metadata", async () => { - onSpy.mockReset(); - sendMessageSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + sendMessageSpy.mockClear(); + replySpy.mockClear(); createTelegramBot({ token: "tok" }); const handler = getOnHandler("message") as (ctx: Record) => Promise; @@ -391,9 +391,9 @@ describe("createTelegramBot", () => { }); it("uses external_reply quote text for partial replies", async () => { - onSpy.mockReset(); - sendMessageSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + sendMessageSpy.mockClear(); + replySpy.mockClear(); createTelegramBot({ token: "tok" }); const handler = getOnHandler("message") as (ctx: Record) => Promise; @@ -426,8 +426,8 @@ describe("createTelegramBot", () => { }); it("accepts group replies to the bot without explicit mention when requireMention is enabled", async () => { - onSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + replySpy.mockClear(); loadConfig.mockReturnValue({ channels: { telegram: { groups: { "*": { requireMention: true } } }, @@ -458,8 +458,8 @@ describe("createTelegramBot", () => { }); it("inherits group allowlist + requireMention in topics", async () => { - onSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + replySpy.mockClear(); loadConfig.mockReturnValue({ channels: { telegram: { @@ -501,8 +501,8 @@ describe("createTelegramBot", () => { }); it("prefers topic allowFrom over group allowFrom", async () => { - onSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + replySpy.mockClear(); loadConfig.mockReturnValue({ channels: { telegram: { @@ -543,8 +543,8 @@ describe("createTelegramBot", () => { }); it("allows group messages for per-group groupPolicy open override (global groupPolicy allowlist)", async () => { - onSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + replySpy.mockClear(); loadConfig.mockReturnValue({ channels: { telegram: { @@ -578,8 +578,8 @@ describe("createTelegramBot", () => { }); it("blocks control commands from unauthorized senders in per-group open groups", async () => { - onSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + replySpy.mockClear(); loadConfig.mockReturnValue({ channels: { telegram: { @@ -612,10 +612,10 @@ describe("createTelegramBot", () => { expect(replySpy).not.toHaveBeenCalled(); }); it("sets command target session key for dm topic commands", async () => { - onSpy.mockReset(); - sendMessageSpy.mockReset(); - commandSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + sendMessageSpy.mockClear(); + commandSpy.mockClear(); + replySpy.mockClear(); replySpy.mockResolvedValue({ text: "response" }); loadConfig.mockReturnValue({ @@ -654,10 +654,10 @@ describe("createTelegramBot", () => { }); it("allows native DM commands for paired users", async () => { - onSpy.mockReset(); - sendMessageSpy.mockReset(); - commandSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + sendMessageSpy.mockClear(); + commandSpy.mockClear(); + replySpy.mockClear(); replySpy.mockResolvedValue({ text: "response" }); loadConfig.mockReturnValue({ @@ -698,10 +698,10 @@ describe("createTelegramBot", () => { }); it("blocks native DM commands for unpaired users", async () => { - onSpy.mockReset(); - sendMessageSpy.mockReset(); - commandSpy.mockReset(); - replySpy.mockReset(); + onSpy.mockClear(); + sendMessageSpy.mockClear(); + commandSpy.mockClear(); + replySpy.mockClear(); loadConfig.mockReturnValue({ commands: { native: true }, @@ -740,15 +740,15 @@ describe("createTelegramBot", () => { }); it("registers message_reaction handler", () => { - onSpy.mockReset(); + onSpy.mockClear(); createTelegramBot({ token: "tok" }); const reactionHandler = onSpy.mock.calls.find((call) => call[0] === "message_reaction"); expect(reactionHandler).toBeDefined(); }); it("enqueues system event for reaction", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); loadConfig.mockReturnValue({ channels: { @@ -783,8 +783,8 @@ describe("createTelegramBot", () => { }); it("skips reaction when reactionNotifications is off", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); wasSentByBot.mockReturnValue(true); loadConfig.mockReturnValue({ @@ -814,8 +814,8 @@ describe("createTelegramBot", () => { }); it("defaults reactionNotifications to own", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); wasSentByBot.mockReturnValue(true); loadConfig.mockReturnValue({ @@ -845,8 +845,8 @@ describe("createTelegramBot", () => { }); it("allows reaction in all mode regardless of message sender", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); wasSentByBot.mockReturnValue(false); loadConfig.mockReturnValue({ @@ -880,8 +880,8 @@ describe("createTelegramBot", () => { }); it("skips reaction in own mode when message is not sent by bot", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); wasSentByBot.mockReturnValue(false); loadConfig.mockReturnValue({ @@ -911,8 +911,8 @@ describe("createTelegramBot", () => { }); it("allows reaction in own mode when message is sent by bot", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); wasSentByBot.mockReturnValue(true); loadConfig.mockReturnValue({ @@ -942,8 +942,8 @@ describe("createTelegramBot", () => { }); it("skips reaction from bot users", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); wasSentByBot.mockReturnValue(true); loadConfig.mockReturnValue({ @@ -973,8 +973,8 @@ describe("createTelegramBot", () => { }); it("skips reaction removal (only processes added reactions)", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); loadConfig.mockReturnValue({ channels: { @@ -1003,8 +1003,8 @@ describe("createTelegramBot", () => { }); it("enqueues one event per added emoji reaction", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); loadConfig.mockReturnValue({ channels: { @@ -1041,8 +1041,8 @@ describe("createTelegramBot", () => { }); it("routes forum group reactions to the general topic (thread id not available on reactions)", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); loadConfig.mockReturnValue({ channels: { @@ -1080,8 +1080,8 @@ describe("createTelegramBot", () => { }); it("uses correct session key for forum group reactions in general topic", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); loadConfig.mockReturnValue({ channels: { @@ -1118,8 +1118,8 @@ describe("createTelegramBot", () => { }); it("uses correct session key for regular group reactions without topic", async () => { - onSpy.mockReset(); - enqueueSystemEventSpy.mockReset(); + onSpy.mockClear(); + enqueueSystemEventSpy.mockClear(); loadConfig.mockReturnValue({ channels: { diff --git a/src/telegram/send.test.ts b/src/telegram/send.test.ts index ed839212d..250f38050 100644 --- a/src/telegram/send.test.ts +++ b/src/telegram/send.test.ts @@ -1271,88 +1271,70 @@ describe("shared send behaviors", () => { }); describe("editMessageTelegram", () => { - it("handles button payload + parse fallback behavior", async () => { - const cases: Array<{ - name: string; - setup: () => { - text: string; - buttons: Parameters[0]; - }; - expectedCalls: number; - firstExpectNoReplyMarkup?: boolean; - firstExpectReplyMarkup?: Record; - secondExpectReplyMarkup?: Record; - }> = [ - { - name: "buttons undefined keeps existing keyboard", - setup: () => { - botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } }); - return { text: "hi", buttons: undefined }; - }, - expectedCalls: 1, - firstExpectNoReplyMarkup: true, - }, - { - name: "buttons empty clears keyboard", - setup: () => { - botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } }); - return { text: "hi", buttons: [] }; - }, - expectedCalls: 1, - firstExpectReplyMarkup: { inline_keyboard: [] }, - }, - { - name: "parse error fallback preserves cleared keyboard", - setup: () => { - botApi.editMessageText - .mockRejectedValueOnce(new Error("400: Bad Request: can't parse entities")) - .mockResolvedValueOnce({ message_id: 1, chat: { id: "123" } }); - return { text: " html", buttons: [] }; - }, - expectedCalls: 2, - firstExpectReplyMarkup: { inline_keyboard: [] }, - secondExpectReplyMarkup: { inline_keyboard: [] }, - }, - ]; + it.each([ + { + name: "buttons undefined keeps existing keyboard", + text: "hi", + buttons: undefined as Parameters[0], + expectedCalls: 1, + firstExpectNoReplyMarkup: true, + parseFallback: false, + }, + { + name: "buttons empty clears keyboard", + text: "hi", + buttons: [] as Parameters[0], + expectedCalls: 1, + firstExpectReplyMarkup: { inline_keyboard: [] } as Record, + parseFallback: false, + }, + { + name: "parse error fallback preserves cleared keyboard", + text: " html", + buttons: [] as Parameters[0], + expectedCalls: 2, + firstExpectReplyMarkup: { inline_keyboard: [] } as Record, + secondExpectReplyMarkup: { inline_keyboard: [] } as Record, + parseFallback: true, + }, + ])("$name", async (testCase) => { + if (testCase.parseFallback) { + botApi.editMessageText + .mockRejectedValueOnce(new Error("400: Bad Request: can't parse entities")) + .mockResolvedValueOnce({ message_id: 1, chat: { id: "123" } }); + } else { + botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } }); + } - for (const testCase of cases) { - botApi.editMessageText.mockReset(); - botCtorSpy.mockReset(); - const input = testCase.setup(); + await editMessageTelegram("123", 1, testCase.text, { + token: "tok", + cfg: {}, + buttons: testCase.buttons ? testCase.buttons.map((row) => [...row]) : testCase.buttons, + }); - await editMessageTelegram("123", 1, input.text, { - token: "tok", - cfg: {}, - buttons: input.buttons ? input.buttons.map((row) => [...row]) : input.buttons, - }); + expect(botCtorSpy, testCase.name).toHaveBeenCalledTimes(1); + expect(botCtorSpy.mock.calls[0]?.[0], testCase.name).toBe("tok"); + expect(botApi.editMessageText, testCase.name).toHaveBeenCalledTimes(testCase.expectedCalls); - expect(botCtorSpy, testCase.name).toHaveBeenCalledTimes(1); - expect(botCtorSpy.mock.calls[0]?.[0], testCase.name).toBe("tok"); - expect(botApi.editMessageText, testCase.name).toHaveBeenCalledTimes(testCase.expectedCalls); + const firstParams = (botApi.editMessageText.mock.calls[0] ?? [])[3] as Record; + expect(firstParams, testCase.name).toEqual(expect.objectContaining({ parse_mode: "HTML" })); + if ("firstExpectNoReplyMarkup" in testCase && testCase.firstExpectNoReplyMarkup) { + expect(firstParams, testCase.name).not.toHaveProperty("reply_markup"); + } + if ("firstExpectReplyMarkup" in testCase && testCase.firstExpectReplyMarkup) { + expect(firstParams, testCase.name).toEqual( + expect.objectContaining({ reply_markup: testCase.firstExpectReplyMarkup }), + ); + } - const firstParams = (botApi.editMessageText.mock.calls[0] ?? [])[3] as Record< + if ("secondExpectReplyMarkup" in testCase && testCase.secondExpectReplyMarkup) { + const secondParams = (botApi.editMessageText.mock.calls[1] ?? [])[3] as Record< string, unknown >; - expect(firstParams, testCase.name).toEqual(expect.objectContaining({ parse_mode: "HTML" })); - if ("firstExpectNoReplyMarkup" in testCase && testCase.firstExpectNoReplyMarkup) { - expect(firstParams, testCase.name).not.toHaveProperty("reply_markup"); - } - if ("firstExpectReplyMarkup" in testCase && testCase.firstExpectReplyMarkup) { - expect(firstParams, testCase.name).toEqual( - expect.objectContaining({ reply_markup: testCase.firstExpectReplyMarkup }), - ); - } - - if ("secondExpectReplyMarkup" in testCase && testCase.secondExpectReplyMarkup) { - const secondParams = (botApi.editMessageText.mock.calls[1] ?? [])[3] as Record< - string, - unknown - >; - expect(secondParams, testCase.name).toEqual( - expect.objectContaining({ reply_markup: testCase.secondExpectReplyMarkup }), - ); - } + expect(secondParams, testCase.name).toEqual( + expect.objectContaining({ reply_markup: testCase.secondExpectReplyMarkup }), + ); } });