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);
}
}
});
});
Messung V0.5 in Prozent C=99 H=98 G=98
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-13)
¤
*© Formatika GbR, Deutschland