diff --git a/src/telegram/bot-native-commands.group-auth.test.ts b/src/telegram/bot-native-commands.group-auth.test.ts index 77d73497c26..cca25aedc2c 100644 --- a/src/telegram/bot-native-commands.group-auth.test.ts +++ b/src/telegram/bot-native-commands.group-auth.test.ts @@ -1,26 +1,12 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import type { ChannelGroupPolicy } from "../config/group-policy.js"; import type { TelegramAccountConfig } from "../config/types.js"; -import type { RuntimeEnv } from "../runtime.js"; -import { registerTelegramNativeCommands } from "./bot-native-commands.js"; - -const getPluginCommandSpecs = vi.hoisted(() => vi.fn(() => [])); -const matchPluginCommand = vi.hoisted(() => vi.fn(() => null)); -const executePluginCommand = vi.hoisted(() => vi.fn(async () => ({ text: "ok" }))); - -vi.mock("../plugins/commands.js", () => ({ - getPluginCommandSpecs, - matchPluginCommand, - executePluginCommand, -})); - -const deliverReplies = vi.hoisted(() => vi.fn(async () => {})); -vi.mock("./bot/delivery.js", () => ({ deliverReplies })); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: vi.fn(async () => []), -})); +import { + createNativeCommandsHarness, + createTelegramGroupCommandContext, + findNotAuthorizedCalls, +} from "./bot-native-commands.test-helpers.js"; describe("native command auth in groups", () => { function setup(params: { @@ -32,32 +18,12 @@ describe("native command auth in groups", () => { groupConfig?: Record; resolveGroupPolicy?: () => ChannelGroupPolicy; }) { - const handlers: Record Promise> = {}; - const sendMessage = vi.fn().mockResolvedValue(undefined); - const bot = { - api: { - setMyCommands: vi.fn().mockResolvedValue(undefined), - sendMessage, - }, - command: (name: string, handler: (ctx: unknown) => Promise) => { - handlers[name] = handler; - }, - } as const; - - registerTelegramNativeCommands({ - bot: bot as unknown as Parameters[0]["bot"], + return createNativeCommandsHarness({ cfg: params.cfg ?? ({} as OpenClawConfig), - runtime: {} as unknown as RuntimeEnv, - accountId: "default", telegramCfg: params.telegramCfg ?? ({} as TelegramAccountConfig), allowFrom: params.allowFrom ?? [], groupAllowFrom: params.groupAllowFrom ?? [], - replyToMode: "off", - textLimit: 4000, useAccessGroups: params.useAccessGroups ?? false, - nativeEnabled: true, - nativeSkillsEnabled: false, - nativeDisabledExplicit: false, resolveGroupPolicy: params.resolveGroupPolicy ?? (() => @@ -65,15 +31,8 @@ describe("native command auth in groups", () => { allowlistEnabled: false, allowed: true, }) as ChannelGroupPolicy), - resolveTelegramGroupConfig: () => ({ - groupConfig: params.groupConfig as undefined, - topicConfig: undefined, - }), - shouldSkipUpdate: () => false, - opts: { token: "token" }, + groupConfig: params.groupConfig, }); - - return { handlers, sendMessage }; } it("authorizes native commands in groups when sender is in groupAllowFrom", async () => { @@ -83,23 +42,11 @@ describe("native command auth in groups", () => { // no allowFrom — sender is NOT in DM allowlist }); - const ctx = { - message: { - chat: { id: -100999, type: "supergroup", is_forum: true }, - from: { id: 12345, username: "testuser" }, - message_thread_id: 42, - message_id: 1, - date: 1700000000, - }, - match: "", - }; + const ctx = createTelegramGroupCommandContext(); await handlers.status?.(ctx); - // should NOT send "not authorized" rejection - const notAuthCalls = sendMessage.mock.calls.filter( - (call) => typeof call[1] === "string" && call[1].includes("not authorized"), - ); + const notAuthCalls = findNotAuthorizedCalls(sendMessage); expect(notAuthCalls).toHaveLength(0); }); @@ -117,22 +64,11 @@ describe("native command auth in groups", () => { useAccessGroups: true, }); - const ctx = { - message: { - chat: { id: -100999, type: "supergroup", is_forum: true }, - from: { id: 12345, username: "testuser" }, - message_thread_id: 42, - message_id: 1, - date: 1700000000, - }, - match: "", - }; + const ctx = createTelegramGroupCommandContext(); await handlers.status?.(ctx); - const notAuthCalls = sendMessage.mock.calls.filter( - (call) => typeof call[1] === "string" && call[1].includes("not authorized"), - ); + const notAuthCalls = findNotAuthorizedCalls(sendMessage); expect(notAuthCalls).toHaveLength(0); }); @@ -149,16 +85,7 @@ describe("native command auth in groups", () => { useAccessGroups: true, }); - const ctx = { - message: { - chat: { id: -100999, type: "supergroup", is_forum: true }, - from: { id: 12345, username: "testuser" }, - message_thread_id: 42, - message_id: 1, - date: 1700000000, - }, - match: "", - }; + const ctx = createTelegramGroupCommandContext(); await handlers.status?.(ctx); @@ -189,16 +116,7 @@ describe("native command auth in groups", () => { }) as ChannelGroupPolicy, }); - const ctx = { - message: { - chat: { id: -100999, type: "supergroup", is_forum: true }, - from: { id: 12345, username: "testuser" }, - message_thread_id: 42, - message_id: 1, - date: 1700000000, - }, - match: "", - }; + const ctx = createTelegramGroupCommandContext(); await handlers.status?.(ctx); @@ -226,16 +144,7 @@ describe("native command auth in groups", () => { }) as ChannelGroupPolicy, }); - const ctx = { - message: { - chat: { id: -100999, type: "supergroup", is_forum: true }, - from: { id: 12345, username: "testuser" }, - message_thread_id: 42, - message_id: 1, - date: 1700000000, - }, - match: "", - }; + const ctx = createTelegramGroupCommandContext(); await handlers.status?.(ctx); @@ -253,22 +162,13 @@ describe("native command auth in groups", () => { useAccessGroups: true, }); - const ctx = { - message: { - chat: { id: -100999, type: "supergroup", is_forum: true }, - from: { id: 12345, username: "intruder" }, - message_thread_id: 42, - message_id: 1, - date: 1700000000, - }, - match: "", - }; + const ctx = createTelegramGroupCommandContext({ + username: "intruder", + }); await handlers.status?.(ctx); - const notAuthCalls = sendMessage.mock.calls.filter( - (call) => typeof call[1] === "string" && call[1].includes("not authorized"), - ); + const notAuthCalls = findNotAuthorizedCalls(sendMessage); expect(notAuthCalls.length).toBeGreaterThan(0); }); @@ -279,16 +179,9 @@ describe("native command auth in groups", () => { useAccessGroups: true, }); - const ctx = { - message: { - chat: { id: -100999, type: "supergroup", is_forum: true }, - from: { id: 12345, username: "intruder" }, - message_thread_id: 42, - message_id: 1, - date: 1700000000, - }, - match: "", - }; + const ctx = createTelegramGroupCommandContext({ + username: "intruder", + }); await handlers.status?.(ctx); diff --git a/src/telegram/bot-native-commands.plugin-auth.test.ts b/src/telegram/bot-native-commands.plugin-auth.test.ts index f6f6d16c2fc..6312fa08b7b 100644 --- a/src/telegram/bot-native-commands.plugin-auth.test.ts +++ b/src/telegram/bot-native-commands.plugin-auth.test.ts @@ -1,26 +1,13 @@ import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; -import type { ChannelGroupPolicy } from "../config/group-policy.js"; import type { TelegramAccountConfig } from "../config/types.js"; -import type { RuntimeEnv } from "../runtime.js"; -import { registerTelegramNativeCommands } from "./bot-native-commands.js"; - -const getPluginCommandSpecs = vi.hoisted(() => vi.fn()); -const matchPluginCommand = vi.hoisted(() => vi.fn()); -const executePluginCommand = vi.hoisted(() => vi.fn()); - -vi.mock("../plugins/commands.js", () => ({ +import { + createNativeCommandsHarness, + deliverReplies, + executePluginCommand, getPluginCommandSpecs, matchPluginCommand, - executePluginCommand, -})); - -const deliverReplies = vi.hoisted(() => vi.fn(async () => {})); -vi.mock("./bot/delivery.js", () => ({ deliverReplies })); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: vi.fn(async () => []), -})); +} from "./bot-native-commands.test-helpers.js"; describe("registerTelegramNativeCommands (plugin auth)", () => { it("does not register plugin commands in menu when native=false but keeps handlers available", () => { @@ -30,44 +17,10 @@ describe("registerTelegramNativeCommands (plugin auth)", () => { })); getPluginCommandSpecs.mockReturnValue(specs); - const handlers: Record Promise> = {}; - const setMyCommands = vi.fn().mockResolvedValue(undefined); - const log = vi.fn(); - const bot = { - api: { - setMyCommands, - sendMessage: vi.fn(), - }, - command: (name: string, handler: (ctx: unknown) => Promise) => { - handlers[name] = handler; - }, - } as const; - - registerTelegramNativeCommands({ - bot: bot as unknown as Parameters[0]["bot"], + const { handlers, setMyCommands, log } = createNativeCommandsHarness({ cfg: {} as OpenClawConfig, - runtime: { log } as unknown as RuntimeEnv, - accountId: "default", telegramCfg: {} as TelegramAccountConfig, - allowFrom: [], - groupAllowFrom: [], - replyToMode: "off", - textLimit: 4000, - useAccessGroups: false, nativeEnabled: false, - nativeSkillsEnabled: false, - nativeDisabledExplicit: false, - resolveGroupPolicy: () => - ({ - allowlistEnabled: false, - allowed: true, - }) as ChannelGroupPolicy, - resolveTelegramGroupConfig: () => ({ - groupConfig: undefined, - topicConfig: undefined, - }), - shouldSkipUpdate: () => false, - opts: { token: "token" }, }); expect(setMyCommands).not.toHaveBeenCalled(); @@ -87,46 +40,11 @@ describe("registerTelegramNativeCommands (plugin auth)", () => { matchPluginCommand.mockReturnValue({ command, args: undefined }); executePluginCommand.mockResolvedValue({ text: "ok" }); - const handlers: Record Promise> = {}; - const bot = { - api: { - setMyCommands: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn(), - }, - command: (name: string, handler: (ctx: unknown) => Promise) => { - handlers[name] = handler; - }, - } as const; - - const cfg = {} as OpenClawConfig; - const telegramCfg = {} as TelegramAccountConfig; - const resolveGroupPolicy = () => - ({ - allowlistEnabled: false, - allowed: true, - }) as ChannelGroupPolicy; - - registerTelegramNativeCommands({ - bot: bot as unknown as Parameters[0]["bot"], - cfg, - runtime: {} as unknown as RuntimeEnv, - accountId: "default", - telegramCfg, + const { handlers, bot } = createNativeCommandsHarness({ + cfg: {} as OpenClawConfig, + telegramCfg: {} as TelegramAccountConfig, allowFrom: ["999"], - groupAllowFrom: [], - replyToMode: "off", - textLimit: 4000, - useAccessGroups: false, nativeEnabled: false, - nativeSkillsEnabled: false, - nativeDisabledExplicit: false, - resolveGroupPolicy, - resolveTelegramGroupConfig: () => ({ - groupConfig: undefined, - topicConfig: undefined, - }), - shouldSkipUpdate: () => false, - opts: { token: "token" }, }); const ctx = { diff --git a/src/telegram/bot-native-commands.test-helpers.ts b/src/telegram/bot-native-commands.test-helpers.ts index b79d61d48a3..cb5745aed0d 100644 --- a/src/telegram/bot-native-commands.test-helpers.ts +++ b/src/telegram/bot-native-commands.test-helpers.ts @@ -1,49 +1,113 @@ +import { vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; +import type { ChannelGroupPolicy } from "../config/group-policy.js"; import type { TelegramAccountConfig } from "../config/types.js"; import type { RuntimeEnv } from "../runtime.js"; -import type { registerTelegramNativeCommands } from "./bot-native-commands.js"; +import { registerTelegramNativeCommands } from "./bot-native-commands.js"; -type RegisterTelegramNativeCommandParams = Parameters[0]; +const pluginCommandMocks = vi.hoisted(() => ({ + getPluginCommandSpecs: vi.fn(() => []), + matchPluginCommand: vi.fn(() => null), + executePluginCommand: vi.fn(async () => ({ text: "ok" })), +})); +export const getPluginCommandSpecs = pluginCommandMocks.getPluginCommandSpecs; +export const matchPluginCommand = pluginCommandMocks.matchPluginCommand; +export const executePluginCommand = pluginCommandMocks.executePluginCommand; -export function createNativeCommandTestParams(params: { - bot: RegisterTelegramNativeCommandParams["bot"]; +vi.mock("../plugins/commands.js", () => ({ + getPluginCommandSpecs: pluginCommandMocks.getPluginCommandSpecs, + matchPluginCommand: pluginCommandMocks.matchPluginCommand, + executePluginCommand: pluginCommandMocks.executePluginCommand, +})); + +const deliveryMocks = vi.hoisted(() => ({ + deliverReplies: vi.fn(async () => {}), +})); +export const deliverReplies = deliveryMocks.deliverReplies; +vi.mock("./bot/delivery.js", () => ({ deliverReplies: deliveryMocks.deliverReplies })); +vi.mock("../pairing/pairing-store.js", () => ({ + readChannelAllowFromStore: vi.fn(async () => []), +})); + +export function createNativeCommandsHarness(params?: { cfg?: OpenClawConfig; runtime?: RuntimeEnv; - accountId?: string; telegramCfg?: TelegramAccountConfig; allowFrom?: string[]; groupAllowFrom?: string[]; - replyToMode?: RegisterTelegramNativeCommandParams["replyToMode"]; - textLimit?: number; useAccessGroups?: boolean; nativeEnabled?: boolean; - nativeSkillsEnabled?: boolean; - nativeDisabledExplicit?: boolean; - resolveTelegramGroupConfig?: RegisterTelegramNativeCommandParams["resolveTelegramGroupConfig"]; - opts?: RegisterTelegramNativeCommandParams["opts"]; -}): RegisterTelegramNativeCommandParams { - return { - bot: params.bot, - cfg: params.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 ?? 4096, - useAccessGroups: params.useAccessGroups ?? false, - nativeEnabled: params.nativeEnabled ?? true, - nativeSkillsEnabled: params.nativeSkillsEnabled ?? true, - nativeDisabledExplicit: params.nativeDisabledExplicit ?? false, - resolveGroupPolicy: () => ({ allowlistEnabled: false, allowed: true }), - resolveTelegramGroupConfig: - params.resolveTelegramGroupConfig ?? - (() => ({ - groupConfig: undefined, - topicConfig: undefined, - })), + groupConfig?: Record; + resolveGroupPolicy?: () => ChannelGroupPolicy; +}) { + const handlers: Record Promise> = {}; + const sendMessage = vi.fn().mockResolvedValue(undefined); + const setMyCommands = vi.fn().mockResolvedValue(undefined); + const log = vi.fn(); + const bot = { + api: { + setMyCommands, + sendMessage, + }, + command: (name: string, handler: (ctx: unknown) => Promise) => { + handlers[name] = handler; + }, + } as const; + + registerTelegramNativeCommands({ + bot: bot as unknown as Parameters[0]["bot"], + cfg: params?.cfg ?? ({} as OpenClawConfig), + runtime: params?.runtime ?? ({ log } as unknown as RuntimeEnv), + accountId: "default", + telegramCfg: params?.telegramCfg ?? ({} as TelegramAccountConfig), + allowFrom: params?.allowFrom ?? [], + groupAllowFrom: params?.groupAllowFrom ?? [], + replyToMode: "off", + textLimit: 4000, + useAccessGroups: params?.useAccessGroups ?? false, + nativeEnabled: params?.nativeEnabled ?? true, + nativeSkillsEnabled: false, + nativeDisabledExplicit: false, + resolveGroupPolicy: + params?.resolveGroupPolicy ?? + (() => + ({ + allowlistEnabled: false, + allowed: true, + }) as ChannelGroupPolicy), + resolveTelegramGroupConfig: () => ({ + groupConfig: params?.groupConfig as undefined, + topicConfig: undefined, + }), shouldSkipUpdate: () => false, - opts: params.opts ?? { token: "token" }, + opts: { token: "token" }, + }); + + return { handlers, sendMessage, setMyCommands, log, bot }; +} + +export function createTelegramGroupCommandContext(params?: { + senderId?: number; + username?: string; + threadId?: number; +}) { + return { + message: { + chat: { id: -100999, type: "supergroup", is_forum: true }, + from: { + id: params?.senderId ?? 12345, + username: params?.username ?? "testuser", + }, + message_thread_id: params?.threadId ?? 42, + message_id: 1, + date: 1700000000, + }, + match: "", }; } + +export function findNotAuthorizedCalls(sendMessage: ReturnType) { + return sendMessage.mock.calls.filter( + (call) => typeof call[1] === "string" && call[1].includes("not authorized"), + ); +}