openclaw/src/telegram/bot.create-telegram-bot.sends-replies-without-native-reply-threading.test.ts
2026-02-14 17:13:24 +00:00

225 lines
6.6 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
getOnHandler,
getLoadConfigMock,
onSpy,
replySpy,
sendMessageSpy,
} from "./bot.create-telegram-bot.test-harness.js";
import { createTelegramBot } from "./bot.js";
const loadConfig = getLoadConfigMock();
describe("createTelegramBot", () => {
// groupPolicy tests
it("sends replies without native reply threading", async () => {
onSpy.mockReset();
sendMessageSpy.mockReset();
replySpy.mockReset();
replySpy.mockResolvedValue({ text: "a".repeat(4500) });
createTelegramBot({ token: "tok" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
await handler({
message: {
chat: { id: 5, type: "private" },
text: "hi",
date: 1736380800,
message_id: 101,
},
me: { username: "openclaw_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).toBeUndefined();
}
});
it("honors replyToMode=first for threaded replies", async () => {
onSpy.mockReset();
sendMessageSpy.mockReset();
replySpy.mockReset();
replySpy.mockResolvedValue({
text: "a".repeat(4500),
replyToId: "101",
});
createTelegramBot({ token: "tok", replyToMode: "first" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
await handler({
message: {
chat: { id: 5, type: "private" },
text: "hi",
date: 1736380800,
message_id: 101,
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(sendMessageSpy.mock.calls.length).toBeGreaterThan(1);
const [first, ...rest] = sendMessageSpy.mock.calls;
expect(first?.[2]?.reply_to_message_id).toBe(101);
for (const call of rest) {
expect(call[2]?.reply_to_message_id).toBeUndefined();
}
});
it("prefixes final replies with responsePrefix", async () => {
onSpy.mockReset();
sendMessageSpy.mockReset();
replySpy.mockReset();
replySpy.mockResolvedValue({ text: "final reply" });
loadConfig.mockReturnValue({
channels: {
telegram: { dmPolicy: "open", allowFrom: ["*"] },
},
messages: { responsePrefix: "PFX" },
});
createTelegramBot({ token: "tok" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
await handler({
message: {
chat: { id: 5, type: "private" },
text: "hi",
date: 1736380800,
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy.mock.calls[0][1]).toBe("PFX final reply");
});
it("honors replyToMode=all for threaded replies", async () => {
onSpy.mockReset();
sendMessageSpy.mockReset();
replySpy.mockReset();
replySpy.mockResolvedValue({
text: "a".repeat(4500),
replyToId: "101",
});
createTelegramBot({ token: "tok", replyToMode: "all" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
await handler({
message: {
chat: { id: 5, type: "private" },
text: "hi",
date: 1736380800,
message_id: 101,
},
me: { username: "openclaw_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);
}
});
it("blocks group messages when telegram.groups is set without a wildcard", async () => {
onSpy.mockReset();
replySpy.mockReset();
loadConfig.mockReturnValue({
channels: {
telegram: {
groups: {
"123": { requireMention: false },
},
},
},
});
createTelegramBot({ token: "tok" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
await handler({
message: {
chat: { id: 456, type: "group", title: "Ops" },
text: "@openclaw_bot hello",
date: 1736380800,
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(replySpy).not.toHaveBeenCalled();
});
it("skips group messages without mention when requireMention is enabled", async () => {
onSpy.mockReset();
replySpy.mockReset();
loadConfig.mockReturnValue({
channels: {
telegram: { groups: { "*": { requireMention: true } } },
},
});
createTelegramBot({ token: "tok" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
await handler({
message: {
chat: { id: 123, type: "group", title: "Dev Chat" },
text: "hello",
date: 1736380800,
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(replySpy).not.toHaveBeenCalled();
});
it("honors routed group activation from session store", async () => {
onSpy.mockReset();
replySpy.mockReset();
const storeDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-"));
const storePath = path.join(storeDir, "sessions.json");
fs.writeFileSync(
storePath,
JSON.stringify({
"agent:ops:telegram:group:123": { groupActivation: "always" },
}),
"utf-8",
);
loadConfig.mockReturnValue({
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
bindings: [
{
agentId: "ops",
match: {
channel: "telegram",
peer: { kind: "group", id: "123" },
},
},
],
session: { store: storePath },
});
createTelegramBot({ token: "tok" });
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
await handler({
message: {
chat: { id: 123, type: "group", title: "Routing" },
text: "hello",
date: 1736380800,
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(replySpy).toHaveBeenCalledTimes(1);
});
});