import { describe, expect, it, vi } from "vitest"; import * as replyModule from "../auto-reply/reply.js"; import { createTelegramBot } from "./bot.js"; const useSpy = vi.fn(); const onSpy = vi.fn(); const stopSpy = vi.fn(); const sendChatActionSpy = vi.fn(); const sendMessageSpy = vi.fn(async () => ({ message_id: 77 })); type ApiStub = { config: { use: (arg: unknown) => void }; sendChatAction: typeof sendChatActionSpy; sendMessage: typeof sendMessageSpy; }; const apiStub: ApiStub = { config: { use: useSpy }, sendChatAction: sendChatActionSpy, sendMessage: sendMessageSpy, }; vi.mock("grammy", () => ({ Bot: class { api = apiStub; on = onSpy; stop = stopSpy; constructor(public token: string) {} }, InputFile: class {}, webhookCallback: vi.fn(), })); const throttlerSpy = vi.fn(() => "throttler"); vi.mock("@grammyjs/transformer-throttler", () => ({ apiThrottler: () => throttlerSpy(), })); vi.mock("../auto-reply/reply.js", () => { const replySpy = vi.fn(async (_ctx, opts) => { await opts?.onReplyStart?.(); return undefined; }); return { getReplyFromConfig: replySpy, __replySpy: replySpy }; }); describe("createTelegramBot", () => { it("installs grammY throttler", () => { createTelegramBot({ token: "tok" }); expect(throttlerSpy).toHaveBeenCalledTimes(1); expect(useSpy).toHaveBeenCalledWith("throttler"); }); it("wraps inbound message with Telegram envelope", async () => { const originalTz = process.env.TZ; process.env.TZ = "Europe/Vienna"; try { onSpy.mockReset(); const replySpy = replyModule.__replySpy as unknown as ReturnType< typeof vi.fn >; replySpy.mockReset(); createTelegramBot({ token: "tok" }); expect(onSpy).toHaveBeenCalledWith("message", expect.any(Function)); const handler = onSpy.mock.calls[0][1] as ( ctx: Record, ) => Promise; const message = { chat: { id: 1234, type: "private" }, text: "hello world", date: 1736380800, // 2025-01-09T00:00:00Z from: { first_name: "Ada", last_name: "Lovelace", username: "ada_bot", }, }; await handler({ message, me: { username: "clawdis_bot" }, getFile: async () => ({ download: async () => new Uint8Array() }), }); expect(replySpy).toHaveBeenCalledTimes(1); const payload = replySpy.mock.calls[0][0]; expect(payload.Body).toMatch( /^\[Telegram Ada Lovelace \(@ada_bot\) id:1234 2025-01-09T01:00\+01:00\{Europe\/Vienna\}\]/, ); expect(payload.Body).toContain("hello world"); } finally { process.env.TZ = originalTz; } }); it("triggers typing cue via onReplyStart", async () => { onSpy.mockReset(); sendChatActionSpy.mockReset(); createTelegramBot({ token: "tok" }); const handler = onSpy.mock.calls[0][1] as ( ctx: Record, ) => Promise; await handler({ message: { chat: { id: 42, type: "private" }, text: "hi" }, me: { username: "clawdis_bot" }, getFile: async () => ({ download: async () => new Uint8Array() }), }); expect(sendChatActionSpy).toHaveBeenCalledWith(42, "typing"); }); it("includes reply-to context when a Telegram reply is received", async () => { onSpy.mockReset(); sendMessageSpy.mockReset(); const replySpy = replyModule.__replySpy as unknown as ReturnType< typeof vi.fn >; replySpy.mockReset(); createTelegramBot({ token: "tok" }); const handler = onSpy.mock.calls[0][1] as ( ctx: Record, ) => Promise; await handler({ message: { chat: { id: 7, type: "private" }, text: "Sure, see below", date: 1736380800, reply_to_message: { message_id: 9001, text: "Can you summarize this?", from: { first_name: "Ada" }, }, }, me: { username: "clawdis_bot" }, getFile: async () => ({ download: async () => new Uint8Array() }), }); expect(replySpy).toHaveBeenCalledTimes(1); const payload = replySpy.mock.calls[0][0]; expect(payload.Body).toContain("[Replying to Ada]"); expect(payload.Body).toContain("Can you summarize this?"); expect(payload.ReplyToId).toBe("9001"); expect(payload.ReplyToBody).toBe("Can you summarize this?"); expect(payload.ReplyToSender).toBe("Ada"); }); it("sends replies as native replies without chaining", async () => { onSpy.mockReset(); sendMessageSpy.mockReset(); const replySpy = replyModule.__replySpy as unknown as ReturnType< typeof vi.fn >; replySpy.mockReset(); replySpy.mockResolvedValue({ text: "a".repeat(4500) }); createTelegramBot({ token: "tok" }); const handler = onSpy.mock.calls[0][1] as ( ctx: Record, ) => Promise; await handler({ message: { chat: { id: 5, type: "private" }, text: "hi", date: 1736380800, message_id: 101, }, me: { username: "clawdis_bot" }, getFile: async () => ({ download: async () => new Uint8Array() }), }); expect(sendMessageSpy.mock.calls.length).toBeGreaterThan(1); for (const call of sendMessageSpy.mock.calls) { expect(call[2]?.reply_to_message_id).toBe(101); } }); });