Files
openclaw/extensions/telegram/src/channel-actions.test.ts
2026-04-03 11:20:49 -05:00

309 lines
8.4 KiB
TypeScript

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { telegramMessageActions, telegramMessageActionRuntime } from "./channel-actions.js";
const handleTelegramActionMock = vi.hoisted(() => vi.fn());
const originalHandleTelegramAction = telegramMessageActionRuntime.handleTelegramAction;
describe("telegramMessageActions", () => {
beforeEach(() => {
handleTelegramActionMock.mockReset().mockResolvedValue({
ok: true,
content: [],
details: {},
});
telegramMessageActionRuntime.handleTelegramAction = (...args) =>
handleTelegramActionMock(...args);
});
afterEach(() => {
telegramMessageActionRuntime.handleTelegramAction = originalHandleTelegramAction;
});
it("allows interactive-only sends", async () => {
await telegramMessageActions.handleAction!({
action: "send",
params: {
to: "123456",
interactive: {
blocks: [
{
type: "buttons",
buttons: [{ label: "Approve", value: "approve", style: "success" }],
},
],
},
},
cfg: {} as never,
accountId: "default",
mediaLocalRoots: [],
} as never);
expect(handleTelegramActionMock).toHaveBeenCalledWith(
expect.objectContaining({
action: "sendMessage",
to: "123456",
interactive: {
blocks: [
{
type: "buttons",
buttons: [{ label: "Approve", value: "approve", style: "success" }],
},
],
},
accountId: "default",
}),
expect.anything(),
expect.objectContaining({
mediaLocalRoots: [],
}),
);
});
it("computes poll/topic action availability from config gates", () => {
const cases = [
{
name: "configured telegram enables poll",
cfg: { channels: { telegram: { botToken: "tok" } } } as OpenClawConfig,
expectPoll: true,
expectTopicEdit: true,
},
{
name: "sendMessage disabled hides poll",
cfg: {
channels: {
telegram: {
botToken: "tok",
actions: { sendMessage: false },
},
},
} as OpenClawConfig,
expectPoll: false,
expectTopicEdit: true,
},
{
name: "poll gate disabled hides poll",
cfg: {
channels: {
telegram: {
botToken: "tok",
actions: { poll: false },
},
},
} as OpenClawConfig,
expectPoll: false,
expectTopicEdit: true,
},
{
name: "split account gates do not expose poll",
cfg: {
channels: {
telegram: {
accounts: {
senderOnly: {
botToken: "tok-send",
actions: {
sendMessage: true,
poll: false,
},
},
pollOnly: {
botToken: "tok-poll",
actions: {
sendMessage: false,
poll: true,
},
},
},
},
},
} as OpenClawConfig,
expectPoll: false,
expectTopicEdit: true,
},
] as const;
for (const testCase of cases) {
const actions =
telegramMessageActions.describeMessageTool?.({
cfg: testCase.cfg,
})?.actions ?? [];
if (testCase.expectPoll) {
expect(actions, testCase.name).toContain("poll");
} else {
expect(actions, testCase.name).not.toContain("poll");
}
if (testCase.expectTopicEdit) {
expect(actions, testCase.name).toContain("topic-edit");
} else {
expect(actions, testCase.name).not.toContain("topic-edit");
}
}
});
it("lists sticker actions only when enabled by config", () => {
const cases = [
{
name: "default config",
cfg: { channels: { telegram: { botToken: "tok" } } } as OpenClawConfig,
expectSticker: false,
},
{
name: "per-account sticker enabled",
cfg: {
channels: {
telegram: {
accounts: {
media: { botToken: "tok", actions: { sticker: true } },
},
},
},
} as OpenClawConfig,
expectSticker: true,
},
{
name: "all accounts omit sticker",
cfg: {
channels: {
telegram: {
accounts: {
a: { botToken: "tok1" },
b: { botToken: "tok2" },
},
},
},
} as OpenClawConfig,
expectSticker: false,
},
] as const;
for (const testCase of cases) {
const actions =
telegramMessageActions.describeMessageTool?.({
cfg: testCase.cfg,
})?.actions ?? [];
if (testCase.expectSticker) {
expect(actions, testCase.name).toEqual(
expect.arrayContaining(["sticker", "sticker-search"]),
);
} else {
expect(actions, testCase.name).not.toContain("sticker");
expect(actions, testCase.name).not.toContain("sticker-search");
}
}
});
it("honors account-scoped action gates during discovery", () => {
const cfg = {
channels: {
telegram: {
botToken: "tok-default",
actions: {
reactions: false,
poll: true,
},
accounts: {
work: {
botToken: "tok-work",
actions: {
reactions: true,
poll: false,
},
},
},
},
},
} as OpenClawConfig;
const defaultActions =
telegramMessageActions.describeMessageTool?.({
cfg,
accountId: "default",
})?.actions ?? [];
const workActions =
telegramMessageActions.describeMessageTool?.({
cfg,
accountId: "work",
})?.actions ?? [];
expect(defaultActions).toContain("poll");
expect(defaultActions).not.toContain("react");
expect(workActions).toContain("react");
expect(workActions).not.toContain("poll");
});
it("normalizes reaction message identifiers before dispatch", async () => {
const cfg = { channels: { telegram: { botToken: "tok" } } } as OpenClawConfig;
const cases = [
{
name: "numeric channelId/messageId",
params: {
channelId: 123,
messageId: 456,
emoji: "ok",
},
expectedChannelField: "channelId",
expectedChannelValue: "123",
expectedMessageId: "456",
},
{
name: "snake_case message_id",
params: {
channelId: 123,
message_id: "456",
emoji: "ok",
},
expectedChannelField: "channelId",
expectedChannelValue: "123",
expectedMessageId: "456",
},
{
name: "toolContext fallback",
params: {
chatId: "123",
emoji: "ok",
},
toolContext: { currentMessageId: "9001" },
expectedChannelField: "chatId",
expectedChannelValue: "123",
expectedMessageId: "9001",
},
{
name: "missing messageId soft-falls through",
params: {
chatId: "123",
emoji: "ok",
},
expectedChannelField: "chatId",
expectedChannelValue: "123",
expectedMessageId: undefined,
},
] as const;
for (const testCase of cases) {
handleTelegramActionMock.mockClear();
await telegramMessageActions.handleAction?.({
channel: "telegram",
action: "react",
params: testCase.params,
cfg,
toolContext: "toolContext" in testCase ? testCase.toolContext : undefined,
});
const call = handleTelegramActionMock.mock.calls[0]?.[0] as
| Record<string, unknown>
| undefined;
expect(call, testCase.name).toBeDefined();
expect(call?.action, testCase.name).toBe("react");
expect(String(call?.[testCase.expectedChannelField]), testCase.name).toBe(
testCase.expectedChannelValue,
);
if (testCase.expectedMessageId === undefined) {
expect(call?.messageId, testCase.name).toBeUndefined();
} else {
expect(String(call?.messageId), testCase.name).toBe(testCase.expectedMessageId);
}
}
});
});