Files
boss/tests/telegram-gateway.test.ts

468 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, rm } from "node:fs/promises";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
let handleTelegramWebhookRequest: (typeof import("../src/lib/telegram-gateway"))["handleTelegramWebhookRequest"];
let completeTaskRoute: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-telegram-gateway-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [data, telegramGateway, taskCompleteRoute] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/telegram-gateway.ts"),
import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts"),
]);
readState = data.readState;
writeState = data.writeState;
handleTelegramWebhookRequest = telegramGateway.handleTelegramWebhookRequest;
completeTaskRoute = taskCompleteRoute.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await writeState(structuredClone(baseState));
const state = await readState();
state.telegramIntegration = {
enabled: true,
mode: "webhook",
botToken: "bot-token-demo",
botUsername: "boss_demo_bot",
dmPolicy: "allowlist",
allowFrom: ["123456"],
groupPolicy: "allowlist",
groups: [],
requireMentionInGroups: true,
defaultProjectId: "master-agent",
webhookSecret: "boss-telegram-secret",
lastConfiguredAt: "2026-04-19T10:00:00+08:00",
lastConfiguredBy: "krisolo",
processedUpdateIds: [],
};
await writeState(state);
});
test("Telegram webhook 会拒绝未通过 allowlist 的私聊消息", async () => {
await setup();
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1001,
message: {
message_id: 301,
date: 1_761_000_000,
chat: { id: 987654, type: "private" },
from: { id: 999999, is_bot: false, first_name: "Guest" },
text: "你好",
},
}),
}),
});
assert.equal(response.status, 403);
const payload = (await response.json()) as { ok: boolean; message: string };
assert.equal(payload.ok, false);
assert.equal(payload.message, "TELEGRAM_SENDER_FORBIDDEN");
});
test("Telegram webhook 对 allowlist 私聊会走主 Agent 快速回复并调用 sendMessage", async () => {
await setup();
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9001 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1002,
message: {
message_id: 302,
date: 1_761_000_001,
chat: { id: 123456, type: "private" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "hello",
},
}),
}),
});
assert.equal(response.status, 200);
const payload = (await response.json()) as { ok: boolean; delivery: string };
assert.equal(payload.ok, true);
assert.equal(payload.delivery, "sent");
assert.equal(outboundCalls.length, 1);
assert.equal((outboundCalls[0]?.body as { chat_id: number }).chat_id, 123456);
assert.match(String((outboundCalls[0]?.body as { text: string }).text), /主 Agent 可以开始协调/);
const state = await readState();
const project = state.projects.find((item) => item.id === "master-agent");
assert.ok(project?.messages.some((message) => message.senderLabel === "Telegram · Kris"));
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram webhook 对需要排队的消息会记录 externalReplyTarget任务完成后自动回推 Telegram", async () => {
await setup();
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9002 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const webhookResponse = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1003,
message: {
message_id: 303,
date: 1_761_000_002,
chat: { id: 123456, type: "private" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "请深入分析当前项目并给出迁移方案",
},
}),
}),
});
assert.equal(webhookResponse.status, 200);
const webhookPayload = (await webhookResponse.json()) as { ok: boolean; delivery: string; taskId?: string };
assert.equal(webhookPayload.ok, true);
assert.equal(webhookPayload.delivery, "queued");
assert.ok(webhookPayload.taskId);
const queuedState = await readState();
const task = queuedState.masterAgentTasks.find((item) => item.taskId === webhookPayload.taskId);
assert.ok(task, "expected queued task");
assert.equal(task?.externalReplyTarget?.provider, "telegram");
assert.equal(task?.externalReplyTarget?.chatId, "123456");
const completeResponse = await completeTaskRoute(
new NextRequest(`http://127.0.0.1:3000/api/v1/master-agent/tasks/${task?.taskId}/complete`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-boss-device-token": "boss-mac-studio-token",
},
body: JSON.stringify({
deviceId: "mac-studio",
status: "completed",
replyBody: "已经整理好迁移方案,稍后我会按模块推进。",
}),
}),
{ params: Promise.resolve({ taskId: task!.taskId }) },
);
assert.equal(completeResponse.status, 200);
assert.equal(outboundCalls.length, 2);
assert.match(String((outboundCalls[1]?.body as { text: string }).text), /已经整理好迁移方案/);
const completedState = await readState();
const completedTask = completedState.masterAgentTasks.find((item) => item.taskId === task?.taskId);
assert.equal(completedTask?.externalReplyTarget?.deliveredAt?.includes("T"), true);
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 群聊在 requireMentionInGroups 开启时,没有 @Bot 不允许进入主 Agent", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
};
await writeState(state);
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1004,
message: {
message_id: 304,
date: 1_761_000_003,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "请总结一下今天进展",
},
}),
}),
});
assert.equal(response.status, 400);
const payload = (await response.json()) as { ok: boolean; message: string };
assert.equal(payload.ok, false);
assert.equal(payload.message, "TELEGRAM_GROUP_MENTION_REQUIRED");
});
test("Telegram 群聊命中 @Bot 时会清洗 mention 后再进入主 Agent", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
};
await writeState(state);
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9003 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1005,
message: {
message_id: 305,
date: 1_761_000_004,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "@boss_demo_bot hello",
},
}),
}),
});
assert.equal(response.status, 200);
assert.equal(outboundCalls.length, 1);
const nextState = await readState();
const project = nextState.projects.find((item) => item.id === "master-agent");
const telegramMessage = project?.messages.find((item) => item.senderLabel === "Telegram · Boss 协作群 · Kris");
assert.equal(telegramMessage?.body, "hello");
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 群聊回复 Bot 上一条消息时,即使没有 @Bot 也允许进入主 Agent", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
};
await writeState(state);
const originalFetch = globalThis.fetch;
const outboundCalls: Array<{ url: string; body: unknown }> = [];
globalThis.fetch = (async (input, init) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
outboundCalls.push({
url,
body: JSON.parse(String(init?.body ?? "{}")),
});
return new Response(JSON.stringify({ ok: true, result: { message_id: 9004 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1006,
message: {
message_id: 306,
date: 1_761_000_005,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
reply_to_message: {
message_id: 299,
date: 1_761_000_000,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 777888, is_bot: true, username: "boss_demo_bot", first_name: "Boss" },
text: "上一条 bot 回复",
},
text: "继续展开这个方案",
},
}),
}),
});
assert.equal(response.status, 200);
assert.equal(outboundCalls.length, 1);
const nextState = await readState();
const project = nextState.projects.find((item) => item.id === "master-agent");
const telegramMessage = project?.messages.find((item) => item.body === "继续展开这个方案");
assert.equal(telegramMessage?.senderLabel, "Telegram · Boss 协作群 · Kris");
} finally {
globalThis.fetch = originalFetch;
}
});
test("Telegram 群聊可按 chat id 路由到指定 Boss 项目", async () => {
await setup();
const state = await readState();
state.telegramIntegration = {
...state.telegramIntegration!,
botUsername: "boss_demo_bot",
groupPolicy: "allowlist",
groups: ["-100200300"],
requireMentionInGroups: true,
defaultProjectId: "master-agent",
groupProjectRoutes: [
{
chatId: "-100200300",
projectId: "audit-collab",
label: "审计 Telegram 群",
},
],
};
await writeState(state);
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url === "https://api.telegram.org/botbot-token-demo/sendMessage") {
return new Response(JSON.stringify({ ok: true, result: { message_id: 9005 } }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch: ${url}`);
}) as typeof fetch;
try {
const response = await handleTelegramWebhookRequest({
request: new NextRequest("http://127.0.0.1:3000/api/v1/integrations/telegram/webhook", {
method: "POST",
headers: {
"content-type": "application/json",
"x-telegram-bot-api-secret-token": "boss-telegram-secret",
},
body: JSON.stringify({
update_id: 1007,
message: {
message_id: 307,
date: 1_761_000_006,
chat: { id: -100200300, type: "supergroup", title: "Boss 协作群" },
from: { id: 123456, is_bot: false, first_name: "Kris" },
text: "@boss_demo_bot 汇总审计群今天的风险",
},
}),
}),
});
assert.equal(response.status, 200);
const nextState = await readState();
const masterProject = nextState.projects.find((item) => item.id === "master-agent");
const auditProject = nextState.projects.find((item) => item.id === "audit-collab");
assert.equal(
masterProject?.messages.some((message) => message.body === "汇总审计群今天的风险"),
false,
);
assert.equal(
auditProject?.messages.some((message) => message.body === "汇总审计群今天的风险"),
true,
);
} finally {
globalThis.fetch = originalFetch;
}
});