From 4f8a2ed2ce16ef69c7bea64697fb6884bf7f9962 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 15:19:10 +0000 Subject: [PATCH] refactor(test): dedupe telegram dispatch scaffolding --- src/telegram/bot-message-dispatch.test.ts | 231 ++++++---------------- 1 file changed, 65 insertions(+), 166 deletions(-) diff --git a/src/telegram/bot-message-dispatch.test.ts b/src/telegram/bot-message-dispatch.test.ts index cd63fffa9..035ea83f1 100644 --- a/src/telegram/bot-message-dispatch.test.ts +++ b/src/telegram/bot-message-dispatch.test.ts @@ -30,6 +30,8 @@ vi.mock("./sticker-cache.js", () => ({ import { dispatchTelegramMessage } from "./bot-message-dispatch.js"; describe("dispatchTelegramMessage draft streaming", () => { + type TelegramMessageContext = Parameters[0]["context"]; + beforeEach(() => { createTelegramDraftStream.mockReset(); dispatchReplyWithBufferedBlockDispatcher.mockReset(); @@ -37,25 +39,18 @@ describe("dispatchTelegramMessage draft streaming", () => { editMessageTelegram.mockReset(); }); - it("streams drafts in private threads and forwards thread id", async () => { - const draftStream = { + function createDraftStream(messageId?: number) { + return { update: vi.fn(), flush: vi.fn().mockResolvedValue(undefined), - messageId: vi.fn().mockReturnValue(undefined), + messageId: vi.fn().mockReturnValue(messageId), clear: vi.fn().mockResolvedValue(undefined), stop: vi.fn(), }; - createTelegramDraftStream.mockReturnValue(draftStream); - dispatchReplyWithBufferedBlockDispatcher.mockImplementation( - async ({ dispatcherOptions, replyOptions }) => { - await replyOptions?.onPartialReply?.({ text: "Hello" }); - await dispatcherOptions.deliver({ text: "Hello" }, { kind: "final" }); - return { queuedFinal: true }; - }, - ); - deliverReplies.mockResolvedValue({ delivered: true }); + } - const context = { + function createContext(overrides?: Partial): TelegramMessageContext { + const base = { ctxPayload: {}, primaryCtx: { message: { chat: { id: 123, type: "private" } } }, msg: { @@ -78,28 +73,72 @@ describe("dispatchTelegramMessage draft streaming", () => { ackReactionPromise: null, reactionApi: null, removeAckAfterReply: false, - }; + } as unknown as TelegramMessageContext; - const bot = { api: { sendMessage: vi.fn(), editMessageText: vi.fn() } } as unknown as Bot; - const runtime = { + return { + ...base, + ...overrides, + // Merge nested fields when overrides provide partial objects. + primaryCtx: { + ...(base.primaryCtx as object), + ...(overrides?.primaryCtx ? (overrides.primaryCtx as object) : null), + } as TelegramMessageContext["primaryCtx"], + msg: { + ...(base.msg as object), + ...(overrides?.msg ? (overrides.msg as object) : null), + } as TelegramMessageContext["msg"], + route: { + ...(base.route as object), + ...(overrides?.route ? (overrides.route as object) : null), + } as TelegramMessageContext["route"], + }; + } + + function createBot(): Bot { + return { api: { sendMessage: vi.fn(), editMessageText: vi.fn() } } as unknown as Bot; + } + + function createRuntime(): Parameters[0]["runtime"] { + return { log: vi.fn(), error: vi.fn(), exit: () => { throw new Error("exit"); }, }; + } + async function dispatchWithContext(params: { + context: TelegramMessageContext; + telegramCfg?: Parameters[0]["telegramCfg"]; + }) { await dispatchTelegramMessage({ - context, - bot, + context: params.context, + bot: createBot(), cfg: {}, - runtime, + runtime: createRuntime(), replyToMode: "first", streamMode: "partial", textLimit: 4096, - telegramCfg: {}, + telegramCfg: params.telegramCfg ?? {}, opts: { token: "token" }, }); + } + + it("streams drafts in private threads and forwards thread id", async () => { + const draftStream = createDraftStream(); + createTelegramDraftStream.mockReturnValue(draftStream); + dispatchReplyWithBufferedBlockDispatcher.mockImplementation( + async ({ dispatcherOptions, replyOptions }) => { + await replyOptions?.onPartialReply?.({ text: "Hello" }); + await dispatcherOptions.deliver({ text: "Hello" }, { kind: "final" }); + return { queuedFinal: true }; + }, + ); + deliverReplies.mockResolvedValue({ delivered: true }); + + const context = createContext(); + await dispatchWithContext({ context }); expect(createTelegramDraftStream).toHaveBeenCalledWith( expect.objectContaining({ @@ -131,49 +170,9 @@ describe("dispatchTelegramMessage draft streaming", () => { }); deliverReplies.mockResolvedValue({ delivered: true }); - const context = { - ctxPayload: {}, - primaryCtx: { message: { chat: { id: 123, type: "private" } } }, - msg: { - chat: { id: 123, type: "private" }, - message_id: 456, - message_thread_id: 777, - }, - chatId: 123, - isGroup: false, - resolvedThreadId: undefined, - replyThreadId: 777, - threadSpec: { id: 777, scope: "dm" }, - historyKey: undefined, - historyLimit: 0, - groupHistories: new Map(), - route: { agentId: "default", accountId: "default" }, - skillFilter: undefined, - sendTyping: vi.fn(), - sendRecordVoice: vi.fn(), - ackReactionPromise: null, - reactionApi: null, - removeAckAfterReply: false, - }; - const bot = { api: { sendMessage: vi.fn(), editMessageText: vi.fn() } } as unknown as Bot; - const runtime = { - log: vi.fn(), - error: vi.fn(), - exit: () => { - throw new Error("exit"); - }, - }; - - await dispatchTelegramMessage({ - context, - bot, - cfg: {}, - runtime, - replyToMode: "first", - streamMode: "partial", - textLimit: 4096, + await dispatchWithContext({ + context: createContext(), telegramCfg: { blockStreaming: true }, - opts: { token: "token" }, }); expect(createTelegramDraftStream).not.toHaveBeenCalled(); @@ -188,13 +187,7 @@ describe("dispatchTelegramMessage draft streaming", () => { }); it("finalizes text-only replies by editing the preview message in place", async () => { - const draftStream = { - update: vi.fn(), - flush: vi.fn().mockResolvedValue(undefined), - messageId: vi.fn().mockReturnValue(999), - clear: vi.fn().mockResolvedValue(undefined), - stop: vi.fn(), - }; + const draftStream = createDraftStream(999); createTelegramDraftStream.mockReturnValue(draftStream); dispatchReplyWithBufferedBlockDispatcher.mockImplementation( async ({ dispatcherOptions, replyOptions }) => { @@ -206,51 +199,7 @@ describe("dispatchTelegramMessage draft streaming", () => { deliverReplies.mockResolvedValue({ delivered: true }); editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" }); - const context = { - ctxPayload: {}, - primaryCtx: { message: { chat: { id: 123, type: "private" } } }, - msg: { - chat: { id: 123, type: "private" }, - message_id: 456, - message_thread_id: 777, - }, - chatId: 123, - isGroup: false, - resolvedThreadId: undefined, - replyThreadId: 777, - threadSpec: { id: 777, scope: "dm" }, - historyKey: undefined, - historyLimit: 0, - groupHistories: new Map(), - route: { agentId: "default", accountId: "default" }, - skillFilter: undefined, - sendTyping: vi.fn(), - sendRecordVoice: vi.fn(), - ackReactionPromise: null, - reactionApi: null, - removeAckAfterReply: false, - }; - - const bot = { api: { sendMessage: vi.fn(), editMessageText: vi.fn() } } as unknown as Bot; - const runtime = { - log: vi.fn(), - error: vi.fn(), - exit: () => { - throw new Error("exit"); - }, - }; - - await dispatchTelegramMessage({ - context, - bot, - cfg: {}, - runtime, - replyToMode: "first", - streamMode: "partial", - textLimit: 4096, - telegramCfg: {}, - opts: { token: "token" }, - }); + await dispatchWithContext({ context: createContext() }); expect(editMessageTelegram).toHaveBeenCalledWith(123, 999, "Hello final", expect.any(Object)); expect(deliverReplies).not.toHaveBeenCalled(); @@ -259,13 +208,7 @@ describe("dispatchTelegramMessage draft streaming", () => { }); it("falls back to normal delivery when preview final is too long to edit", async () => { - const draftStream = { - update: vi.fn(), - flush: vi.fn().mockResolvedValue(undefined), - messageId: vi.fn().mockReturnValue(999), - clear: vi.fn().mockResolvedValue(undefined), - stop: vi.fn(), - }; + const draftStream = createDraftStream(999); createTelegramDraftStream.mockReturnValue(draftStream); const longText = "x".repeat(5000); dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => { @@ -275,51 +218,7 @@ describe("dispatchTelegramMessage draft streaming", () => { deliverReplies.mockResolvedValue({ delivered: true }); editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" }); - const context = { - ctxPayload: {}, - primaryCtx: { message: { chat: { id: 123, type: "private" } } }, - msg: { - chat: { id: 123, type: "private" }, - message_id: 456, - message_thread_id: 777, - }, - chatId: 123, - isGroup: false, - resolvedThreadId: undefined, - replyThreadId: 777, - threadSpec: { id: 777, scope: "dm" }, - historyKey: undefined, - historyLimit: 0, - groupHistories: new Map(), - route: { agentId: "default", accountId: "default" }, - skillFilter: undefined, - sendTyping: vi.fn(), - sendRecordVoice: vi.fn(), - ackReactionPromise: null, - reactionApi: null, - removeAckAfterReply: false, - }; - - const bot = { api: { sendMessage: vi.fn(), editMessageText: vi.fn() } } as unknown as Bot; - const runtime = { - log: vi.fn(), - error: vi.fn(), - exit: () => { - throw new Error("exit"); - }, - }; - - await dispatchTelegramMessage({ - context, - bot, - cfg: {}, - runtime, - replyToMode: "first", - streamMode: "partial", - textLimit: 4096, - telegramCfg: {}, - opts: { token: "token" }, - }); + await dispatchWithContext({ context: createContext() }); expect(editMessageTelegram).not.toHaveBeenCalled(); expect(deliverReplies).toHaveBeenCalledWith(