mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 11:20:21 +00:00
309 lines
8.4 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
});
|
|
});
|