Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
289 lines
9.5 KiB
TypeScript
289 lines
9.5 KiB
TypeScript
// Telegram tests cover bot native commands.registry plugin behavior.
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
import { clearPluginCommands, registerPluginCommand } from "openclaw/plugin-sdk/plugin-runtime";
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
|
|
let registerTelegramNativeCommands: typeof import("./bot-native-commands.js").registerTelegramNativeCommands;
|
|
let setActivePluginRegistry: typeof import("openclaw/plugin-sdk/plugin-test-runtime").setActivePluginRegistry;
|
|
let createCommandBot: typeof import("./bot-native-commands.menu-test-support.js").createCommandBot;
|
|
let createNativeCommandTestParams: typeof import("./bot-native-commands.menu-test-support.js").createNativeCommandTestParams;
|
|
let createPrivateCommandContext: typeof import("./bot-native-commands.menu-test-support.js").createPrivateCommandContext;
|
|
let deliverReplies: typeof import("./bot-native-commands.menu-test-support.js").deliverReplies;
|
|
let editMessageTelegram: typeof import("./bot-native-commands.menu-test-support.js").editMessageTelegram;
|
|
let resetNativeCommandMenuMocks: typeof import("./bot-native-commands.menu-test-support.js").resetNativeCommandMenuMocks;
|
|
let waitForRegisteredCommands: typeof import("./bot-native-commands.menu-test-support.js").waitForRegisteredCommands;
|
|
|
|
function createTelegramPluginRegistry() {
|
|
return {
|
|
plugins: [],
|
|
tools: [],
|
|
hooks: [],
|
|
typedHooks: [],
|
|
channels: [
|
|
{
|
|
pluginId: "telegram",
|
|
source: "test",
|
|
plugin: {
|
|
id: "telegram",
|
|
meta: {
|
|
id: "telegram",
|
|
label: "Telegram",
|
|
selectionLabel: "Telegram",
|
|
docsPath: "/channels/telegram",
|
|
blurb: "test stub.",
|
|
},
|
|
capabilities: { chatTypes: ["direct"] },
|
|
config: {
|
|
listAccountIds: () => ["default"],
|
|
resolveAccount: () => ({}),
|
|
},
|
|
commands: {
|
|
nativeCommandsAutoEnabled: true,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
channelSetups: [
|
|
{
|
|
pluginId: "telegram",
|
|
source: "test",
|
|
enabled: true,
|
|
plugin: {
|
|
id: "telegram",
|
|
},
|
|
},
|
|
],
|
|
providers: [],
|
|
speechProviders: [],
|
|
mediaUnderstandingProviders: [],
|
|
imageGenerationProviders: [],
|
|
videoGenerationProviders: [],
|
|
webFetchProviders: [],
|
|
webSearchProviders: [],
|
|
migrationProviders: [],
|
|
gatewayHandlers: {},
|
|
httpRoutes: [],
|
|
cliRegistrars: [],
|
|
services: [],
|
|
commands: [],
|
|
conversationBindingResolvedHandlers: [],
|
|
diagnostics: [],
|
|
};
|
|
}
|
|
|
|
function registerPairPluginCommand(params?: {
|
|
nativeNames?: { telegram?: string; discord?: string };
|
|
nativeProgressMessages?: { telegram?: string; default?: string };
|
|
}) {
|
|
expect(
|
|
registerPluginCommand("demo-plugin", {
|
|
name: "pair",
|
|
...(params?.nativeNames ? { nativeNames: params.nativeNames } : {}),
|
|
...(params?.nativeProgressMessages
|
|
? { nativeProgressMessages: params.nativeProgressMessages }
|
|
: {}),
|
|
description: "Pair device",
|
|
acceptsArgs: true,
|
|
requireAuth: false,
|
|
handler: async ({ args }) => ({ text: `paired:${args ?? ""}` }),
|
|
}),
|
|
).toEqual({ ok: true });
|
|
}
|
|
|
|
async function registerPairMenu(params: {
|
|
bot: ReturnType<typeof createCommandBot>["bot"];
|
|
setMyCommands: ReturnType<typeof createCommandBot>["setMyCommands"];
|
|
nativeNames?: { telegram?: string; discord?: string };
|
|
nativeProgressMessages?: { telegram?: string; default?: string };
|
|
}) {
|
|
registerPairPluginCommand({
|
|
...(params.nativeNames ? { nativeNames: params.nativeNames } : {}),
|
|
...(params.nativeProgressMessages
|
|
? { nativeProgressMessages: params.nativeProgressMessages }
|
|
: {}),
|
|
});
|
|
|
|
registerTelegramNativeCommands({
|
|
...createNativeCommandTestParams({}),
|
|
bot: params.bot,
|
|
});
|
|
|
|
return await waitForRegisteredCommands(params.setMyCommands);
|
|
}
|
|
|
|
function requireCommandHandler(
|
|
commandHandlers: ReturnType<typeof createCommandBot>["commandHandlers"],
|
|
commandName: string,
|
|
) {
|
|
const handler = commandHandlers.get(commandName);
|
|
if (!handler) {
|
|
throw new Error(`expected ${commandName} command handler`);
|
|
}
|
|
return handler;
|
|
}
|
|
|
|
function expectRegisteredCommand(
|
|
commands: Array<{ command: string; description: string }>,
|
|
expected: { command: string; description: string },
|
|
): void {
|
|
expect(
|
|
commands.some(
|
|
(command) =>
|
|
command.command === expected.command && command.description === expected.description,
|
|
),
|
|
).toBe(true);
|
|
}
|
|
|
|
function expectLastDeliveredReplyText(text: string): void {
|
|
const calls = deliverReplies.mock.calls as unknown[][];
|
|
const payload = calls.at(-1)?.[0] as { replies?: Array<{ text?: string }> } | undefined;
|
|
expect(payload?.replies?.map((reply) => reply.text)).toEqual([text]);
|
|
}
|
|
|
|
function mockCall(mock: { mock: { calls: unknown[][] } }, index: number): unknown[] {
|
|
const call = mock.mock.calls[index];
|
|
if (!call) {
|
|
throw new Error(`expected mock call ${index}`);
|
|
}
|
|
return call;
|
|
}
|
|
|
|
describe("registerTelegramNativeCommands real plugin registry", () => {
|
|
beforeAll(async () => {
|
|
({ setActivePluginRegistry } = await import("openclaw/plugin-sdk/plugin-test-runtime"));
|
|
({ registerTelegramNativeCommands } = await import("./bot-native-commands.js"));
|
|
({
|
|
createCommandBot,
|
|
createNativeCommandTestParams,
|
|
createPrivateCommandContext,
|
|
deliverReplies,
|
|
editMessageTelegram,
|
|
resetNativeCommandMenuMocks,
|
|
waitForRegisteredCommands,
|
|
} = await import("./bot-native-commands.menu-test-support.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
setActivePluginRegistry(createTelegramPluginRegistry() as never);
|
|
clearPluginCommands();
|
|
resetNativeCommandMenuMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
clearPluginCommands();
|
|
});
|
|
|
|
it("registers and executes plugin commands through the real plugin registry", async () => {
|
|
const { bot, commandHandlers, sendMessage, setMyCommands } = createCommandBot();
|
|
|
|
const registeredCommands = await registerPairMenu({ bot, setMyCommands });
|
|
expectRegisteredCommand(registeredCommands, { command: "pair", description: "Pair device" });
|
|
|
|
const handler = requireCommandHandler(commandHandlers, "pair");
|
|
|
|
await handler(createPrivateCommandContext({ match: "now" }));
|
|
|
|
expectLastDeliveredReplyText("paired:now");
|
|
expect(sendMessage).not.toHaveBeenCalledWith(123, "Command not found.");
|
|
});
|
|
|
|
it("uses plugin command metadata to send and edit a Telegram progress placeholder", async () => {
|
|
const { bot, commandHandlers, setMyCommands, sendMessage } = createCommandBot();
|
|
|
|
await registerPairMenu({
|
|
bot,
|
|
setMyCommands,
|
|
nativeProgressMessages: {
|
|
telegram:
|
|
"Running pair now...\n\nI'll edit this message with the final result when it's ready.",
|
|
},
|
|
});
|
|
|
|
const handler = requireCommandHandler(commandHandlers, "pair");
|
|
|
|
await handler(createPrivateCommandContext({ match: "now" }));
|
|
|
|
const sendCall = mockCall(sendMessage, 0);
|
|
expect(sendCall[0]).toBe(100);
|
|
expect(sendCall[1]).toContain("Running pair now");
|
|
expect(sendCall[2]).toBeUndefined();
|
|
const editCall = mockCall(editMessageTelegram, 0);
|
|
expect(editCall[0]).toBe(100);
|
|
expect(editCall[1]).toBe(999);
|
|
expect(editCall[2]).toBe("paired:now");
|
|
expect((editCall[3] as { accountId?: string }).accountId).toBe("default");
|
|
expect(deliverReplies).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("round-trips Telegram native aliases through the real plugin registry", async () => {
|
|
const { bot, commandHandlers, sendMessage, setMyCommands } = createCommandBot();
|
|
|
|
const registeredCommands = await registerPairMenu({
|
|
bot,
|
|
setMyCommands,
|
|
nativeNames: {
|
|
telegram: "pair_device",
|
|
discord: "pairdiscord",
|
|
},
|
|
});
|
|
expectRegisteredCommand(registeredCommands, {
|
|
command: "pair_device",
|
|
description: "Pair device",
|
|
});
|
|
|
|
const handler = requireCommandHandler(commandHandlers, "pair_device");
|
|
|
|
await handler(createPrivateCommandContext({ match: "now", messageId: 2 }));
|
|
|
|
expectLastDeliveredReplyText("paired:now");
|
|
expect(sendMessage).not.toHaveBeenCalledWith(123, "Command not found.");
|
|
});
|
|
|
|
it("keeps real plugin command handlers available when native menu registration is disabled", () => {
|
|
const { bot, commandHandlers, setMyCommands } = createCommandBot();
|
|
|
|
registerPairPluginCommand();
|
|
|
|
registerTelegramNativeCommands({
|
|
...createNativeCommandTestParams({}, { accountId: "default" }),
|
|
bot,
|
|
nativeEnabled: false,
|
|
});
|
|
|
|
expect(setMyCommands).not.toHaveBeenCalled();
|
|
expect(commandHandlers.has("pair")).toBe(true);
|
|
});
|
|
|
|
it("allows requireAuth:false plugin commands for unauthorized senders through the real registry", async () => {
|
|
const { bot, commandHandlers, sendMessage, setMyCommands } = createCommandBot();
|
|
|
|
registerPairPluginCommand();
|
|
|
|
registerTelegramNativeCommands({
|
|
...createNativeCommandTestParams({
|
|
commands: { allowFrom: { telegram: ["999"] } } as OpenClawConfig["commands"],
|
|
}),
|
|
bot,
|
|
allowFrom: ["999"],
|
|
nativeEnabled: false,
|
|
});
|
|
|
|
expect(setMyCommands).not.toHaveBeenCalled();
|
|
|
|
const handler = requireCommandHandler(commandHandlers, "pair");
|
|
|
|
await handler(
|
|
createPrivateCommandContext({
|
|
match: "now",
|
|
messageId: 10,
|
|
date: 123456,
|
|
userId: 111,
|
|
username: "nope",
|
|
}),
|
|
);
|
|
|
|
expectLastDeliveredReplyText("paired:now");
|
|
expect(sendMessage).not.toHaveBeenCalled();
|
|
});
|
|
});
|