From 06ae5e9d21ea2be50679b5ef8bd07a7dfbc3fb64 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 04:32:38 +0000 Subject: [PATCH] refactor(telegram): share native command test menu helpers --- .../bot-native-commands.menu-test-support.ts | 158 ++++++++++++++++++ .../src/bot-native-commands.registry.test.ts | 150 +++-------------- .../telegram/src/bot-native-commands.test.ts | 158 +++++------------- 3 files changed, 227 insertions(+), 239 deletions(-) create mode 100644 extensions/telegram/src/bot-native-commands.menu-test-support.ts diff --git a/extensions/telegram/src/bot-native-commands.menu-test-support.ts b/extensions/telegram/src/bot-native-commands.menu-test-support.ts new file mode 100644 index 00000000000..9af54d3d1bc --- /dev/null +++ b/extensions/telegram/src/bot-native-commands.menu-test-support.ts @@ -0,0 +1,158 @@ +import { expect, vi } from "vitest"; +import type { OpenClawConfig } from "../../../src/config/config.js"; +import type { TelegramAccountConfig } from "../../../src/config/types.js"; +import type { RuntimeEnv } from "../../../src/runtime.js"; + +type NativeCommandBot = { + api: { + setMyCommands: ReturnType; + sendMessage: ReturnType; + }; + command: ReturnType; +}; + +type RegisterTelegramNativeCommandsParams = { + bot: NativeCommandBot; + cfg: OpenClawConfig; + runtime: RuntimeEnv; + accountId: string; + telegramCfg: TelegramAccountConfig; + allowFrom: string[]; + groupAllowFrom: string[]; + replyToMode: string; + textLimit: number; + useAccessGroups: boolean; + nativeEnabled: boolean; + nativeSkillsEnabled: boolean; + nativeDisabledExplicit: boolean; + resolveGroupPolicy: () => { allowlistEnabled: boolean; allowed: boolean }; + resolveTelegramGroupConfig: () => { + groupConfig: undefined; + topicConfig: undefined; + }; + shouldSkipUpdate: () => boolean; + opts: { token: string }; +}; + +type RegisteredCommand = { + command: string; + description: string; +}; + +const skillCommandMocks = vi.hoisted(() => ({ + listSkillCommandsForAgents: vi.fn(() => []), +})); + +const deliveryMocks = vi.hoisted(() => ({ + deliverReplies: vi.fn(async () => ({ delivered: true })), +})); + +export const listSkillCommandsForAgents = skillCommandMocks.listSkillCommandsForAgents; +export const deliverReplies = deliveryMocks.deliverReplies; + +vi.mock("../../../src/auto-reply/skill-commands.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + listSkillCommandsForAgents, + }; +}); + +vi.mock("./bot/delivery.js", () => ({ + deliverReplies, +})); + +export async function waitForRegisteredCommands( + setMyCommands: ReturnType, +): Promise { + await vi.waitFor(() => { + expect(setMyCommands).toHaveBeenCalled(); + }); + return setMyCommands.mock.calls[0]?.[0] as RegisteredCommand[]; +} + +export function resetNativeCommandMenuMocks() { + listSkillCommandsForAgents.mockClear(); + listSkillCommandsForAgents.mockReturnValue([]); + deliverReplies.mockClear(); + deliverReplies.mockResolvedValue({ delivered: true }); +} + +export function createCommandBot() { + const commandHandlers = new Map Promise>(); + const sendMessage = vi.fn().mockResolvedValue(undefined); + const setMyCommands = vi.fn().mockResolvedValue(undefined); + const bot = { + api: { + setMyCommands, + sendMessage, + }, + command: vi.fn((name: string, cb: (ctx: unknown) => Promise) => { + commandHandlers.set(name, cb); + }), + } as unknown as RegisterTelegramNativeCommandsParams["bot"]; + return { bot, commandHandlers, sendMessage, setMyCommands }; +} + +export function createNativeCommandTestParams( + cfg: OpenClawConfig, + params: Partial = {}, +): RegisterTelegramNativeCommandsParams { + return { + bot: + params.bot ?? + ({ + api: { + setMyCommands: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue(undefined), + }, + command: vi.fn(), + } as unknown as RegisterTelegramNativeCommandsParams["bot"]), + cfg, + runtime: params.runtime ?? ({} as RuntimeEnv), + accountId: params.accountId ?? "default", + telegramCfg: params.telegramCfg ?? ({} as TelegramAccountConfig), + allowFrom: params.allowFrom ?? [], + groupAllowFrom: params.groupAllowFrom ?? [], + replyToMode: params.replyToMode ?? "off", + textLimit: params.textLimit ?? 4000, + useAccessGroups: params.useAccessGroups ?? false, + nativeEnabled: params.nativeEnabled ?? true, + nativeSkillsEnabled: params.nativeSkillsEnabled ?? true, + nativeDisabledExplicit: params.nativeDisabledExplicit ?? false, + resolveGroupPolicy: + params.resolveGroupPolicy ?? + (() => + ({ + allowlistEnabled: false, + allowed: true, + }) as ReturnType), + resolveTelegramGroupConfig: + params.resolveTelegramGroupConfig ?? + (() => ({ + groupConfig: undefined, + topicConfig: undefined, + })), + shouldSkipUpdate: params.shouldSkipUpdate ?? (() => false), + opts: params.opts ?? { token: "token" }, + }; +} + +export function createPrivateCommandContext(params?: { + match?: string; + messageId?: number; + date?: number; + chatId?: number; + userId?: number; + username?: string; +}) { + return { + match: params?.match ?? "", + message: { + message_id: params?.messageId ?? 1, + date: params?.date ?? Math.floor(Date.now() / 1000), + chat: { id: params?.chatId ?? 123, type: "private" as const }, + from: { id: params?.userId ?? 456, username: params?.username ?? "alice" }, + }, + }; +} diff --git a/extensions/telegram/src/bot-native-commands.registry.test.ts b/extensions/telegram/src/bot-native-commands.registry.test.ts index a6fb431c349..c1f9fc1d0a6 100644 --- a/extensions/telegram/src/bot-native-commands.registry.test.ts +++ b/extensions/telegram/src/bot-native-commands.registry.test.ts @@ -1,102 +1,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../../src/config/config.js"; -import type { TelegramAccountConfig } from "../../../src/config/types.js"; import { clearPluginCommands, registerPluginCommand } from "../../../src/plugins/commands.js"; -import type { RuntimeEnv } from "../../../src/runtime.js"; import { registerTelegramNativeCommands } from "./bot-native-commands.js"; - -const { listSkillCommandsForAgents } = vi.hoisted(() => ({ - listSkillCommandsForAgents: vi.fn(() => []), -})); -const deliveryMocks = vi.hoisted(() => ({ - deliverReplies: vi.fn(async () => ({ delivered: true })), -})); - -vi.mock("../../../src/auto-reply/skill-commands.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - listSkillCommandsForAgents, - }; -}); - -vi.mock("./bot/delivery.js", () => ({ - deliverReplies: deliveryMocks.deliverReplies, -})); +import { + createCommandBot, + createNativeCommandTestParams, + createPrivateCommandContext, + deliverReplies, + resetNativeCommandMenuMocks, + waitForRegisteredCommands, +} from "./bot-native-commands.menu-test-support.js"; describe("registerTelegramNativeCommands real plugin registry", () => { - type RegisteredCommand = { - command: string; - description: string; - }; - - function createCommandBot() { - const commandHandlers = new Map Promise>(); - const sendMessage = vi.fn().mockResolvedValue(undefined); - const setMyCommands = vi.fn().mockResolvedValue(undefined); - const bot = { - api: { - setMyCommands, - sendMessage, - }, - command: vi.fn((name: string, cb: (ctx: unknown) => Promise) => { - commandHandlers.set(name, cb); - }), - } as unknown as Parameters[0]["bot"]; - return { bot, commandHandlers, sendMessage, setMyCommands }; - } - - async function waitForRegisteredCommands( - setMyCommands: ReturnType, - ): Promise { - await vi.waitFor(() => { - expect(setMyCommands).toHaveBeenCalled(); - }); - return setMyCommands.mock.calls[0]?.[0] as RegisteredCommand[]; - } - - const buildParams = (cfg: OpenClawConfig, accountId = "default") => - ({ - bot: { - api: { - setMyCommands: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn().mockResolvedValue(undefined), - }, - command: vi.fn(), - } as unknown as Parameters[0]["bot"], - cfg, - runtime: {} as RuntimeEnv, - accountId, - telegramCfg: {} as TelegramAccountConfig, - allowFrom: [], - groupAllowFrom: [], - replyToMode: "off", - textLimit: 4000, - useAccessGroups: false, - nativeEnabled: true, - nativeSkillsEnabled: true, - nativeDisabledExplicit: false, - resolveGroupPolicy: () => - ({ - allowlistEnabled: false, - allowed: true, - }) as ReturnType< - Parameters[0]["resolveGroupPolicy"] - >, - resolveTelegramGroupConfig: () => ({ - groupConfig: undefined, - topicConfig: undefined, - }), - shouldSkipUpdate: () => false, - opts: { token: "token" }, - }) satisfies Parameters[0]; - beforeEach(() => { clearPluginCommands(); - deliveryMocks.deliverReplies.mockClear(); - deliveryMocks.deliverReplies.mockResolvedValue({ delivered: true }); - listSkillCommandsForAgents.mockClear(); - listSkillCommandsForAgents.mockReturnValue([]); + resetNativeCommandMenuMocks(); }); afterEach(() => { @@ -117,7 +35,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => { ).toEqual({ ok: true }); registerTelegramNativeCommands({ - ...buildParams({}), + ...createNativeCommandTestParams({}), bot, }); @@ -129,17 +47,9 @@ describe("registerTelegramNativeCommands real plugin registry", () => { const handler = commandHandlers.get("pair"); expect(handler).toBeTruthy(); - await handler?.({ - match: "now", - message: { - message_id: 1, - date: Math.floor(Date.now() / 1000), - chat: { id: 123, type: "private" }, - from: { id: 456, username: "alice" }, - }, - }); + await handler?.(createPrivateCommandContext({ match: "now" })); - expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith( + expect(deliverReplies).toHaveBeenCalledWith( expect.objectContaining({ replies: [expect.objectContaining({ text: "paired:now" })], }), @@ -165,7 +75,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => { ).toEqual({ ok: true }); registerTelegramNativeCommands({ - ...buildParams({}), + ...createNativeCommandTestParams({}), bot, }); @@ -177,17 +87,9 @@ describe("registerTelegramNativeCommands real plugin registry", () => { const handler = commandHandlers.get("pair_device"); expect(handler).toBeTruthy(); - await handler?.({ - match: "now", - message: { - message_id: 2, - date: Math.floor(Date.now() / 1000), - chat: { id: 123, type: "private" }, - from: { id: 456, username: "alice" }, - }, - }); + await handler?.(createPrivateCommandContext({ match: "now", messageId: 2 })); - expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith( + expect(deliverReplies).toHaveBeenCalledWith( expect.objectContaining({ replies: [expect.objectContaining({ text: "paired:now" })], }), @@ -209,7 +111,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => { ).toEqual({ ok: true }); registerTelegramNativeCommands({ - ...buildParams({}, "default"), + ...createNativeCommandTestParams({}, { accountId: "default" }), bot, nativeEnabled: false, }); @@ -232,7 +134,7 @@ describe("registerTelegramNativeCommands real plugin registry", () => { ).toEqual({ ok: true }); registerTelegramNativeCommands({ - ...buildParams({ + ...createNativeCommandTestParams({ commands: { allowFrom: { telegram: ["999"] } } as OpenClawConfig["commands"], }), bot, @@ -245,17 +147,17 @@ describe("registerTelegramNativeCommands real plugin registry", () => { const handler = commandHandlers.get("pair"); expect(handler).toBeTruthy(); - await handler?.({ - match: "now", - message: { - message_id: 10, + await handler?.( + createPrivateCommandContext({ + match: "now", + messageId: 10, date: 123456, - chat: { id: 123, type: "private" }, - from: { id: 111, username: "nope" }, - }, - }); + userId: 111, + username: "nope", + }), + ); - expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith( + expect(deliverReplies).toHaveBeenCalledWith( expect.objectContaining({ replies: [expect.objectContaining({ text: "paired:now" })], }), diff --git a/extensions/telegram/src/bot-native-commands.test.ts b/extensions/telegram/src/bot-native-commands.test.ts index bc843293fc5..6dba343524f 100644 --- a/extensions/telegram/src/bot-native-commands.test.ts +++ b/extensions/telegram/src/bot-native-commands.test.ts @@ -6,99 +6,38 @@ import { TELEGRAM_COMMAND_NAME_PATTERN } from "../../../src/config/telegram-cust import type { TelegramAccountConfig } from "../../../src/config/types.js"; import type { RuntimeEnv } from "../../../src/runtime.js"; import { registerTelegramNativeCommands } from "./bot-native-commands.js"; +import { + createCommandBot, + createNativeCommandTestParams, + createPrivateCommandContext, + deliverReplies, + listSkillCommandsForAgents, + resetNativeCommandMenuMocks, + waitForRegisteredCommands, +} from "./bot-native-commands.menu-test-support.js"; -const { listSkillCommandsForAgents } = vi.hoisted(() => ({ - listSkillCommandsForAgents: vi.fn(() => []), -})); const pluginCommandMocks = vi.hoisted(() => ({ getPluginCommandSpecs: vi.fn(() => []), matchPluginCommand: vi.fn(() => null), executePluginCommand: vi.fn(async () => ({ text: "ok" })), })); -const deliveryMocks = vi.hoisted(() => ({ - deliverReplies: vi.fn(async () => ({ delivered: true })), -})); - -vi.mock("../../../src/auto-reply/skill-commands.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - listSkillCommandsForAgents, - }; -}); vi.mock("../../../src/plugins/commands.js", () => ({ getPluginCommandSpecs: pluginCommandMocks.getPluginCommandSpecs, matchPluginCommand: pluginCommandMocks.matchPluginCommand, executePluginCommand: pluginCommandMocks.executePluginCommand, })); -vi.mock("./bot/delivery.js", () => ({ - deliverReplies: deliveryMocks.deliverReplies, -})); describe("registerTelegramNativeCommands", () => { - type RegisteredCommand = { - command: string; - description: string; - }; - - async function waitForRegisteredCommands( - setMyCommands: ReturnType, - ): Promise { - await vi.waitFor(() => { - expect(setMyCommands).toHaveBeenCalled(); - }); - return setMyCommands.mock.calls[0]?.[0] as RegisteredCommand[]; - } - beforeEach(() => { - listSkillCommandsForAgents.mockClear(); - listSkillCommandsForAgents.mockReturnValue([]); + resetNativeCommandMenuMocks(); pluginCommandMocks.getPluginCommandSpecs.mockClear(); pluginCommandMocks.getPluginCommandSpecs.mockReturnValue([]); pluginCommandMocks.matchPluginCommand.mockClear(); pluginCommandMocks.matchPluginCommand.mockReturnValue(null); pluginCommandMocks.executePluginCommand.mockClear(); pluginCommandMocks.executePluginCommand.mockResolvedValue({ text: "ok" }); - deliveryMocks.deliverReplies.mockClear(); - deliveryMocks.deliverReplies.mockResolvedValue({ delivered: true }); }); - const buildParams = (cfg: OpenClawConfig, accountId = "default") => - ({ - bot: { - api: { - setMyCommands: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn().mockResolvedValue(undefined), - }, - command: vi.fn(), - } as unknown as Parameters[0]["bot"], - cfg, - runtime: {} as RuntimeEnv, - accountId, - telegramCfg: {} as TelegramAccountConfig, - allowFrom: [], - groupAllowFrom: [], - replyToMode: "off", - textLimit: 4000, - useAccessGroups: false, - nativeEnabled: true, - nativeSkillsEnabled: true, - nativeDisabledExplicit: false, - resolveGroupPolicy: () => - ({ - allowlistEnabled: false, - allowed: true, - }) as ReturnType< - Parameters[0]["resolveGroupPolicy"] - >, - resolveTelegramGroupConfig: () => ({ - groupConfig: undefined, - topicConfig: undefined, - }), - shouldSkipUpdate: () => false, - opts: { token: "token" }, - }) satisfies Parameters[0]; - it("scopes skill commands when account binding exists", () => { const cfg: OpenClawConfig = { agents: { @@ -112,7 +51,7 @@ describe("registerTelegramNativeCommands", () => { ], }; - registerTelegramNativeCommands(buildParams(cfg, "bot-a")); + registerTelegramNativeCommands(createNativeCommandTestParams(cfg, { accountId: "bot-a" })); expect(listSkillCommandsForAgents).toHaveBeenCalledWith({ cfg, @@ -127,7 +66,7 @@ describe("registerTelegramNativeCommands", () => { }, }; - registerTelegramNativeCommands(buildParams(cfg, "bot-a")); + registerTelegramNativeCommands(createNativeCommandTestParams(cfg, { accountId: "bot-a" })); expect(listSkillCommandsForAgents).toHaveBeenCalledWith({ cfg, @@ -147,7 +86,7 @@ describe("registerTelegramNativeCommands", () => { const runtimeLog = vi.fn(); registerTelegramNativeCommands({ - ...buildParams(cfg), + ...createNativeCommandTestParams(cfg), bot: { api: { setMyCommands, @@ -174,7 +113,7 @@ describe("registerTelegramNativeCommands", () => { const command = vi.fn(); registerTelegramNativeCommands({ - ...buildParams({}), + ...createNativeCommandTestParams({}), bot: { api: { setMyCommands, @@ -202,7 +141,7 @@ describe("registerTelegramNativeCommands", () => { ] as never); registerTelegramNativeCommands({ - ...buildParams({}), + ...createNativeCommandTestParams({}), bot: { api: { setMyCommands, @@ -259,31 +198,24 @@ describe("registerTelegramNativeCommands", () => { } as never); registerTelegramNativeCommands({ - ...buildParams(cfg), - bot: { - api: { - setMyCommands: vi.fn().mockResolvedValue(undefined), - sendMessage, - }, - command: vi.fn((name: string, cb: (ctx: unknown) => Promise) => { - commandHandlers.set(name, cb); - }), - } as unknown as Parameters[0]["bot"], + ...createNativeCommandTestParams(cfg, { + bot: { + api: { + setMyCommands: vi.fn().mockResolvedValue(undefined), + sendMessage, + }, + command: vi.fn((name: string, cb: (ctx: unknown) => Promise) => { + commandHandlers.set(name, cb); + }), + } as unknown as Parameters[0]["bot"], + }), }); const handler = commandHandlers.get("plug"); expect(handler).toBeTruthy(); - await handler?.({ - match: "", - message: { - message_id: 1, - date: Math.floor(Date.now() / 1000), - chat: { id: 123, type: "private" }, - from: { id: 456, username: "alice" }, - }, - }); + await handler?.(createPrivateCommandContext()); - expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith( + expect(deliverReplies).toHaveBeenCalledWith( expect.objectContaining({ mediaLocalRoots: expect.arrayContaining([path.join(STATE_DIR, "workspace-work")]), }), @@ -310,32 +242,28 @@ describe("registerTelegramNativeCommands", () => { } as never); registerTelegramNativeCommands({ - ...buildParams({}), - bot: { - api: { - setMyCommands: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn().mockResolvedValue(undefined), + ...createNativeCommandTestParams( + {}, + { + bot: { + api: { + setMyCommands: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue(undefined), + }, + command: vi.fn((name: string, cb: (ctx: unknown) => Promise) => { + commandHandlers.set(name, cb); + }), + } as unknown as Parameters[0]["bot"], }, - command: vi.fn((name: string, cb: (ctx: unknown) => Promise) => { - commandHandlers.set(name, cb); - }), - } as unknown as Parameters[0]["bot"], + ), telegramCfg: { silentErrorReplies: true } as TelegramAccountConfig, }); const handler = commandHandlers.get("plug"); expect(handler).toBeTruthy(); - await handler?.({ - match: "", - message: { - message_id: 1, - date: Math.floor(Date.now() / 1000), - chat: { id: 123, type: "private" }, - from: { id: 456, username: "alice" }, - }, - }); + await handler?.(createPrivateCommandContext()); - expect(deliveryMocks.deliverReplies).toHaveBeenCalledWith( + expect(deliverReplies).toHaveBeenCalledWith( expect.objectContaining({ silent: true, replies: [expect.objectContaining({ isError: true })],