mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
Merged via squash.
Prepared head SHA: f77824419e
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
805 lines
21 KiB
TypeScript
805 lines
21 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../../config/config.js";
|
|
import { captureEnv } from "../../test-utils/env.js";
|
|
import { handleTelegramAction, readTelegramButtons } from "./telegram-actions.js";
|
|
|
|
const reactMessageTelegram = vi.fn(async () => ({ ok: true }));
|
|
const sendMessageTelegram = vi.fn(async () => ({
|
|
messageId: "789",
|
|
chatId: "123",
|
|
}));
|
|
const sendPollTelegram = vi.fn(async () => ({
|
|
messageId: "790",
|
|
chatId: "123",
|
|
pollId: "poll-1",
|
|
}));
|
|
const sendStickerTelegram = vi.fn(async () => ({
|
|
messageId: "456",
|
|
chatId: "123",
|
|
}));
|
|
const deleteMessageTelegram = vi.fn(async () => ({ ok: true }));
|
|
let envSnapshot: ReturnType<typeof captureEnv>;
|
|
|
|
vi.mock("../../telegram/send.js", () => ({
|
|
reactMessageTelegram: (...args: Parameters<typeof reactMessageTelegram>) =>
|
|
reactMessageTelegram(...args),
|
|
sendMessageTelegram: (...args: Parameters<typeof sendMessageTelegram>) =>
|
|
sendMessageTelegram(...args),
|
|
sendPollTelegram: (...args: Parameters<typeof sendPollTelegram>) => sendPollTelegram(...args),
|
|
sendStickerTelegram: (...args: Parameters<typeof sendStickerTelegram>) =>
|
|
sendStickerTelegram(...args),
|
|
deleteMessageTelegram: (...args: Parameters<typeof deleteMessageTelegram>) =>
|
|
deleteMessageTelegram(...args),
|
|
}));
|
|
|
|
describe("handleTelegramAction", () => {
|
|
const defaultReactionAction = {
|
|
action: "react",
|
|
chatId: "123",
|
|
messageId: "456",
|
|
emoji: "✅",
|
|
} as const;
|
|
|
|
function reactionConfig(reactionLevel: "minimal" | "extensive" | "off" | "ack"): OpenClawConfig {
|
|
return {
|
|
channels: { telegram: { botToken: "tok", reactionLevel } },
|
|
} as OpenClawConfig;
|
|
}
|
|
|
|
function telegramConfig(overrides?: Record<string, unknown>): OpenClawConfig {
|
|
return {
|
|
channels: {
|
|
telegram: {
|
|
botToken: "tok",
|
|
...overrides,
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
}
|
|
|
|
async function sendInlineButtonsMessage(params: {
|
|
to: string;
|
|
buttons: Array<Array<{ text: string; callback_data: string; style?: string }>>;
|
|
inlineButtons: "dm" | "group" | "all";
|
|
}) {
|
|
await handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to: params.to,
|
|
content: "Choose",
|
|
buttons: params.buttons,
|
|
},
|
|
telegramConfig({ capabilities: { inlineButtons: params.inlineButtons } }),
|
|
);
|
|
}
|
|
|
|
async function expectReactionAdded(reactionLevel: "minimal" | "extensive") {
|
|
await handleTelegramAction(defaultReactionAction, reactionConfig(reactionLevel));
|
|
expect(reactMessageTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
456,
|
|
"✅",
|
|
expect.objectContaining({ token: "tok", remove: false }),
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
envSnapshot = captureEnv(["TELEGRAM_BOT_TOKEN"]);
|
|
reactMessageTelegram.mockClear();
|
|
sendMessageTelegram.mockClear();
|
|
sendPollTelegram.mockClear();
|
|
sendStickerTelegram.mockClear();
|
|
deleteMessageTelegram.mockClear();
|
|
process.env.TELEGRAM_BOT_TOKEN = "tok";
|
|
});
|
|
|
|
afterEach(() => {
|
|
envSnapshot.restore();
|
|
});
|
|
|
|
it("adds reactions when reactionLevel is minimal", async () => {
|
|
await expectReactionAdded("minimal");
|
|
});
|
|
|
|
it("surfaces non-fatal reaction warnings", async () => {
|
|
reactMessageTelegram.mockResolvedValueOnce({
|
|
ok: false,
|
|
warning: "Reaction unavailable: ✅",
|
|
} as unknown as Awaited<ReturnType<typeof reactMessageTelegram>>);
|
|
const result = await handleTelegramAction(defaultReactionAction, reactionConfig("minimal"));
|
|
const textPayload = result.content.find((item) => item.type === "text");
|
|
expect(textPayload?.type).toBe("text");
|
|
const parsed = JSON.parse((textPayload as { type: "text"; text: string }).text) as {
|
|
ok: boolean;
|
|
warning?: string;
|
|
added?: string;
|
|
};
|
|
expect(parsed).toMatchObject({
|
|
ok: false,
|
|
warning: "Reaction unavailable: ✅",
|
|
added: "✅",
|
|
});
|
|
});
|
|
|
|
it("adds reactions when reactionLevel is extensive", async () => {
|
|
await expectReactionAdded("extensive");
|
|
});
|
|
|
|
it("accepts snake_case message_id for reactions", async () => {
|
|
await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
message_id: "456",
|
|
emoji: "✅",
|
|
},
|
|
reactionConfig("minimal"),
|
|
);
|
|
expect(reactMessageTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
456,
|
|
"✅",
|
|
expect.objectContaining({ token: "tok", remove: false }),
|
|
);
|
|
});
|
|
|
|
it("soft-fails when messageId is missing", async () => {
|
|
const cfg = {
|
|
channels: { telegram: { botToken: "tok", reactionLevel: "minimal" } },
|
|
} as OpenClawConfig;
|
|
const result = await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
emoji: "✅",
|
|
},
|
|
cfg,
|
|
);
|
|
expect(result.details).toMatchObject({
|
|
ok: false,
|
|
reason: "missing_message_id",
|
|
});
|
|
expect(reactMessageTelegram).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("removes reactions on empty emoji", async () => {
|
|
await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
messageId: "456",
|
|
emoji: "",
|
|
},
|
|
reactionConfig("minimal"),
|
|
);
|
|
expect(reactMessageTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
456,
|
|
"",
|
|
expect.objectContaining({ token: "tok", remove: false }),
|
|
);
|
|
});
|
|
|
|
it("rejects sticker actions when disabled by default", async () => {
|
|
const cfg = { channels: { telegram: { botToken: "tok" } } } as OpenClawConfig;
|
|
await expect(
|
|
handleTelegramAction(
|
|
{
|
|
action: "sendSticker",
|
|
to: "123",
|
|
fileId: "sticker",
|
|
},
|
|
cfg,
|
|
),
|
|
).rejects.toThrow(/sticker actions are disabled/i);
|
|
expect(sendStickerTelegram).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("sends stickers when enabled", async () => {
|
|
const cfg = {
|
|
channels: { telegram: { botToken: "tok", actions: { sticker: true } } },
|
|
} as OpenClawConfig;
|
|
await handleTelegramAction(
|
|
{
|
|
action: "sendSticker",
|
|
to: "123",
|
|
fileId: "sticker",
|
|
},
|
|
cfg,
|
|
);
|
|
expect(sendStickerTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
"sticker",
|
|
expect.objectContaining({ token: "tok" }),
|
|
);
|
|
});
|
|
|
|
it("removes reactions when remove flag set", async () => {
|
|
const cfg = reactionConfig("extensive");
|
|
await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
messageId: "456",
|
|
emoji: "✅",
|
|
remove: true,
|
|
},
|
|
cfg,
|
|
);
|
|
expect(reactMessageTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
456,
|
|
"✅",
|
|
expect.objectContaining({ token: "tok", remove: true }),
|
|
);
|
|
});
|
|
|
|
it.each(["off", "ack"] as const)(
|
|
"soft-fails reactions when reactionLevel is %s",
|
|
async (level) => {
|
|
const result = await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
messageId: "456",
|
|
emoji: "✅",
|
|
},
|
|
reactionConfig(level),
|
|
);
|
|
expect(result.details).toMatchObject({
|
|
ok: false,
|
|
reason: "disabled",
|
|
});
|
|
},
|
|
);
|
|
|
|
it("soft-fails when reactions are disabled via actions.reactions", async () => {
|
|
const cfg = {
|
|
channels: {
|
|
telegram: {
|
|
botToken: "tok",
|
|
reactionLevel: "minimal",
|
|
actions: { reactions: false },
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
const result = await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
messageId: "456",
|
|
emoji: "✅",
|
|
},
|
|
cfg,
|
|
);
|
|
expect(result.details).toMatchObject({
|
|
ok: false,
|
|
reason: "disabled",
|
|
});
|
|
});
|
|
|
|
it("sends a text message", async () => {
|
|
const result = await handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to: "@testchannel",
|
|
content: "Hello, Telegram!",
|
|
},
|
|
telegramConfig(),
|
|
);
|
|
expect(sendMessageTelegram).toHaveBeenCalledWith(
|
|
"@testchannel",
|
|
"Hello, Telegram!",
|
|
expect.objectContaining({ token: "tok", mediaUrl: undefined }),
|
|
);
|
|
expect(result.content).toContainEqual({
|
|
type: "text",
|
|
text: expect.stringContaining('"ok": true'),
|
|
});
|
|
});
|
|
|
|
it("sends a poll", async () => {
|
|
const result = await handleTelegramAction(
|
|
{
|
|
action: "poll",
|
|
to: "@testchannel",
|
|
question: "Ready?",
|
|
answers: ["Yes", "No"],
|
|
allowMultiselect: true,
|
|
durationSeconds: 60,
|
|
isAnonymous: false,
|
|
silent: true,
|
|
},
|
|
telegramConfig(),
|
|
);
|
|
expect(sendPollTelegram).toHaveBeenCalledWith(
|
|
"@testchannel",
|
|
{
|
|
question: "Ready?",
|
|
options: ["Yes", "No"],
|
|
maxSelections: 2,
|
|
durationSeconds: 60,
|
|
durationHours: undefined,
|
|
},
|
|
expect.objectContaining({
|
|
token: "tok",
|
|
isAnonymous: false,
|
|
silent: true,
|
|
}),
|
|
);
|
|
expect(result.details).toMatchObject({
|
|
ok: true,
|
|
messageId: "790",
|
|
chatId: "123",
|
|
pollId: "poll-1",
|
|
});
|
|
});
|
|
|
|
it("parses string booleans for poll flags", async () => {
|
|
await handleTelegramAction(
|
|
{
|
|
action: "poll",
|
|
to: "@testchannel",
|
|
question: "Ready?",
|
|
answers: ["Yes", "No"],
|
|
allowMultiselect: "true",
|
|
isAnonymous: "false",
|
|
silent: "true",
|
|
},
|
|
telegramConfig(),
|
|
);
|
|
expect(sendPollTelegram).toHaveBeenCalledWith(
|
|
"@testchannel",
|
|
expect.objectContaining({
|
|
question: "Ready?",
|
|
options: ["Yes", "No"],
|
|
maxSelections: 2,
|
|
}),
|
|
expect.objectContaining({
|
|
isAnonymous: false,
|
|
silent: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("forwards trusted mediaLocalRoots into sendMessageTelegram", async () => {
|
|
await handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to: "@testchannel",
|
|
content: "Hello with local media",
|
|
},
|
|
telegramConfig(),
|
|
{ mediaLocalRoots: ["/tmp/agent-root"] },
|
|
);
|
|
expect(sendMessageTelegram).toHaveBeenCalledWith(
|
|
"@testchannel",
|
|
"Hello with local media",
|
|
expect.objectContaining({ mediaLocalRoots: ["/tmp/agent-root"] }),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "media",
|
|
params: {
|
|
action: "sendMessage",
|
|
to: "123456",
|
|
content: "Check this image!",
|
|
mediaUrl: "https://example.com/image.jpg",
|
|
},
|
|
expectedTo: "123456",
|
|
expectedContent: "Check this image!",
|
|
expectedOptions: { mediaUrl: "https://example.com/image.jpg" },
|
|
},
|
|
{
|
|
name: "quoteText",
|
|
params: {
|
|
action: "sendMessage",
|
|
to: "123456",
|
|
content: "Replying now",
|
|
replyToMessageId: 144,
|
|
quoteText: "The text you want to quote",
|
|
},
|
|
expectedTo: "123456",
|
|
expectedContent: "Replying now",
|
|
expectedOptions: {
|
|
replyToMessageId: 144,
|
|
quoteText: "The text you want to quote",
|
|
},
|
|
},
|
|
{
|
|
name: "media-only",
|
|
params: {
|
|
action: "sendMessage",
|
|
to: "123456",
|
|
mediaUrl: "https://example.com/note.ogg",
|
|
},
|
|
expectedTo: "123456",
|
|
expectedContent: "",
|
|
expectedOptions: { mediaUrl: "https://example.com/note.ogg" },
|
|
},
|
|
] as const)("maps sendMessage params for $name", async (testCase) => {
|
|
await handleTelegramAction(testCase.params, telegramConfig());
|
|
expect(sendMessageTelegram).toHaveBeenCalledWith(
|
|
testCase.expectedTo,
|
|
testCase.expectedContent,
|
|
expect.objectContaining({
|
|
token: "tok",
|
|
...testCase.expectedOptions,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("requires content when no mediaUrl is provided", async () => {
|
|
await expect(
|
|
handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to: "123456",
|
|
},
|
|
telegramConfig(),
|
|
),
|
|
).rejects.toThrow(/content required/i);
|
|
});
|
|
|
|
it("respects sendMessage gating", async () => {
|
|
const cfg = {
|
|
channels: {
|
|
telegram: { botToken: "tok", actions: { sendMessage: false } },
|
|
},
|
|
} as OpenClawConfig;
|
|
await expect(
|
|
handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to: "@testchannel",
|
|
content: "Hello!",
|
|
},
|
|
cfg,
|
|
),
|
|
).rejects.toThrow(/Telegram sendMessage is disabled/);
|
|
});
|
|
|
|
it("respects poll gating", async () => {
|
|
const cfg = {
|
|
channels: {
|
|
telegram: { botToken: "tok", actions: { poll: false } },
|
|
},
|
|
} as OpenClawConfig;
|
|
await expect(
|
|
handleTelegramAction(
|
|
{
|
|
action: "poll",
|
|
to: "@testchannel",
|
|
question: "Lunch?",
|
|
answers: ["Pizza", "Sushi"],
|
|
},
|
|
cfg,
|
|
),
|
|
).rejects.toThrow(/Telegram polls are disabled/);
|
|
});
|
|
|
|
it("deletes a message", async () => {
|
|
const cfg = {
|
|
channels: { telegram: { botToken: "tok" } },
|
|
} as OpenClawConfig;
|
|
await handleTelegramAction(
|
|
{
|
|
action: "deleteMessage",
|
|
chatId: "123",
|
|
messageId: 456,
|
|
},
|
|
cfg,
|
|
);
|
|
expect(deleteMessageTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
456,
|
|
expect.objectContaining({ token: "tok" }),
|
|
);
|
|
});
|
|
|
|
it("respects deleteMessage gating", async () => {
|
|
const cfg = {
|
|
channels: {
|
|
telegram: { botToken: "tok", actions: { deleteMessage: false } },
|
|
},
|
|
} as OpenClawConfig;
|
|
await expect(
|
|
handleTelegramAction(
|
|
{
|
|
action: "deleteMessage",
|
|
chatId: "123",
|
|
messageId: 456,
|
|
},
|
|
cfg,
|
|
),
|
|
).rejects.toThrow(/Telegram deleteMessage is disabled/);
|
|
});
|
|
|
|
it("throws on missing bot token for sendMessage", async () => {
|
|
delete process.env.TELEGRAM_BOT_TOKEN;
|
|
const cfg = {} as OpenClawConfig;
|
|
await expect(
|
|
handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to: "@testchannel",
|
|
content: "Hello!",
|
|
},
|
|
cfg,
|
|
),
|
|
).rejects.toThrow(/Telegram bot token missing/);
|
|
});
|
|
|
|
it("allows inline buttons by default (allowlist)", async () => {
|
|
const cfg = {
|
|
channels: { telegram: { botToken: "tok" } },
|
|
} as OpenClawConfig;
|
|
await handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to: "@testchannel",
|
|
content: "Choose",
|
|
buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]],
|
|
},
|
|
cfg,
|
|
);
|
|
expect(sendMessageTelegram).toHaveBeenCalled();
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "scope is off",
|
|
to: "@testchannel",
|
|
inlineButtons: "off" as const,
|
|
expectedMessage: /inline buttons are disabled/i,
|
|
},
|
|
{
|
|
name: "scope is dm and target is group",
|
|
to: "-100123456",
|
|
inlineButtons: "dm" as const,
|
|
expectedMessage: /inline buttons are limited to DMs/i,
|
|
},
|
|
])("blocks inline buttons when $name", async ({ to, inlineButtons, expectedMessage }) => {
|
|
await expect(
|
|
handleTelegramAction(
|
|
{
|
|
action: "sendMessage",
|
|
to,
|
|
content: "Choose",
|
|
buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]],
|
|
},
|
|
telegramConfig({ capabilities: { inlineButtons } }),
|
|
),
|
|
).rejects.toThrow(expectedMessage);
|
|
});
|
|
|
|
it("allows inline buttons in DMs with tg: prefixed targets", async () => {
|
|
await sendInlineButtonsMessage({
|
|
to: "tg:5232990709",
|
|
buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]],
|
|
inlineButtons: "dm",
|
|
});
|
|
expect(sendMessageTelegram).toHaveBeenCalled();
|
|
});
|
|
|
|
it("allows inline buttons in groups with topic targets", async () => {
|
|
await sendInlineButtonsMessage({
|
|
to: "telegram:group:-1001234567890:topic:456",
|
|
buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]],
|
|
inlineButtons: "group",
|
|
});
|
|
expect(sendMessageTelegram).toHaveBeenCalled();
|
|
});
|
|
|
|
it("sends messages with inline keyboard buttons when enabled", async () => {
|
|
await sendInlineButtonsMessage({
|
|
to: "@testchannel",
|
|
buttons: [[{ text: " Option A ", callback_data: " cmd:a " }]],
|
|
inlineButtons: "all",
|
|
});
|
|
expect(sendMessageTelegram).toHaveBeenCalledWith(
|
|
"@testchannel",
|
|
"Choose",
|
|
expect.objectContaining({
|
|
buttons: [[{ text: "Option A", callback_data: "cmd:a" }]],
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("forwards optional button style", async () => {
|
|
await sendInlineButtonsMessage({
|
|
to: "@testchannel",
|
|
inlineButtons: "all",
|
|
buttons: [
|
|
[
|
|
{
|
|
text: "Option A",
|
|
callback_data: "cmd:a",
|
|
style: "primary",
|
|
},
|
|
],
|
|
],
|
|
});
|
|
expect(sendMessageTelegram).toHaveBeenCalledWith(
|
|
"@testchannel",
|
|
"Choose",
|
|
expect.objectContaining({
|
|
buttons: [
|
|
[
|
|
{
|
|
text: "Option A",
|
|
callback_data: "cmd:a",
|
|
style: "primary",
|
|
},
|
|
],
|
|
],
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("readTelegramButtons", () => {
|
|
it("returns trimmed button rows for valid input", () => {
|
|
const result = readTelegramButtons({
|
|
buttons: [[{ text: " Option A ", callback_data: " cmd:a " }]],
|
|
});
|
|
expect(result).toEqual([[{ text: "Option A", callback_data: "cmd:a" }]]);
|
|
});
|
|
|
|
it("normalizes optional style", () => {
|
|
const result = readTelegramButtons({
|
|
buttons: [
|
|
[
|
|
{
|
|
text: "Option A",
|
|
callback_data: "cmd:a",
|
|
style: " PRIMARY ",
|
|
},
|
|
],
|
|
],
|
|
});
|
|
expect(result).toEqual([
|
|
[
|
|
{
|
|
text: "Option A",
|
|
callback_data: "cmd:a",
|
|
style: "primary",
|
|
},
|
|
],
|
|
]);
|
|
});
|
|
|
|
it("rejects unsupported button style", () => {
|
|
expect(() =>
|
|
readTelegramButtons({
|
|
buttons: [[{ text: "Option A", callback_data: "cmd:a", style: "secondary" }]],
|
|
}),
|
|
).toThrow(/style must be one of danger, success, primary/i);
|
|
});
|
|
});
|
|
|
|
describe("handleTelegramAction per-account gating", () => {
|
|
function accountTelegramConfig(params: {
|
|
accounts: Record<
|
|
string,
|
|
{ botToken: string; actions?: { sticker?: boolean; reactions?: boolean } }
|
|
>;
|
|
topLevelBotToken?: string;
|
|
topLevelActions?: { reactions?: boolean };
|
|
}): OpenClawConfig {
|
|
return {
|
|
channels: {
|
|
telegram: {
|
|
...(params.topLevelBotToken ? { botToken: params.topLevelBotToken } : {}),
|
|
...(params.topLevelActions ? { actions: params.topLevelActions } : {}),
|
|
accounts: params.accounts,
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
}
|
|
|
|
async function expectAccountStickerSend(cfg: OpenClawConfig, accountId = "media") {
|
|
await handleTelegramAction(
|
|
{ action: "sendSticker", to: "123", fileId: "sticker-id", accountId },
|
|
cfg,
|
|
);
|
|
expect(sendStickerTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
"sticker-id",
|
|
expect.objectContaining({ token: "tok-media" }),
|
|
);
|
|
}
|
|
|
|
it("allows sticker when account config enables it", async () => {
|
|
const cfg = accountTelegramConfig({
|
|
accounts: {
|
|
media: { botToken: "tok-media", actions: { sticker: true } },
|
|
},
|
|
});
|
|
await expectAccountStickerSend(cfg);
|
|
});
|
|
|
|
it("blocks sticker when account omits it", async () => {
|
|
const cfg = {
|
|
channels: {
|
|
telegram: {
|
|
accounts: {
|
|
chat: { botToken: "tok-chat" },
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
await expect(
|
|
handleTelegramAction(
|
|
{ action: "sendSticker", to: "123", fileId: "sticker-id", accountId: "chat" },
|
|
cfg,
|
|
),
|
|
).rejects.toThrow(/sticker actions are disabled/i);
|
|
});
|
|
|
|
it("uses account-merged config, not top-level config", async () => {
|
|
// Top-level has no sticker enabled, but the account does
|
|
const cfg = accountTelegramConfig({
|
|
topLevelBotToken: "tok-base",
|
|
accounts: {
|
|
media: { botToken: "tok-media", actions: { sticker: true } },
|
|
},
|
|
});
|
|
await expectAccountStickerSend(cfg);
|
|
});
|
|
|
|
it("inherits top-level reaction gate when account overrides sticker only", async () => {
|
|
const cfg = accountTelegramConfig({
|
|
topLevelActions: { reactions: false },
|
|
accounts: {
|
|
media: { botToken: "tok-media", actions: { sticker: true } },
|
|
},
|
|
});
|
|
|
|
const result = await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
messageId: 1,
|
|
emoji: "👀",
|
|
accountId: "media",
|
|
},
|
|
cfg,
|
|
);
|
|
expect(result.details).toMatchObject({
|
|
ok: false,
|
|
reason: "disabled",
|
|
});
|
|
});
|
|
|
|
it("allows account to explicitly re-enable top-level disabled reaction gate", async () => {
|
|
const cfg = accountTelegramConfig({
|
|
topLevelActions: { reactions: false },
|
|
accounts: {
|
|
media: { botToken: "tok-media", actions: { sticker: true, reactions: true } },
|
|
},
|
|
});
|
|
|
|
await handleTelegramAction(
|
|
{
|
|
action: "react",
|
|
chatId: "123",
|
|
messageId: 1,
|
|
emoji: "👀",
|
|
accountId: "media",
|
|
},
|
|
cfg,
|
|
);
|
|
|
|
expect(reactMessageTelegram).toHaveBeenCalledWith(
|
|
"123",
|
|
1,
|
|
"👀",
|
|
expect.objectContaining({ token: "tok-media", accountId: "media" }),
|
|
);
|
|
});
|
|
});
|