diff --git a/src/agents/tools/browser-tool.test.ts b/src/agents/tools/browser-tool.test.ts index 189dc1eda76..eaaec53f10c 100644 --- a/src/agents/tools/browser-tool.test.ts +++ b/src/agents/tools/browser-tool.test.ts @@ -108,16 +108,33 @@ function mockSingleBrowserProxyNode() { ]); } -describe("browser tool snapshot maxChars", () => { +function resetBrowserToolMocks() { + vi.clearAllMocks(); + configMocks.loadConfig.mockReturnValue({ browser: {} }); + nodesUtilsMocks.listNodes.mockResolvedValue([]); +} + +function registerBrowserToolAfterEachReset() { afterEach(() => { - vi.clearAllMocks(); - configMocks.loadConfig.mockReturnValue({ browser: {} }); - nodesUtilsMocks.listNodes.mockResolvedValue([]); + resetBrowserToolMocks(); }); +} + +async function runSnapshotToolCall(params: { + snapshotFormat: "ai" | "aria"; + refs?: "aria" | "dom"; + maxChars?: number; + profile?: string; +}) { + const tool = createBrowserTool(); + await tool.execute?.("call-1", { action: "snapshot", ...params }); +} + +describe("browser tool snapshot maxChars", () => { + registerBrowserToolAfterEachReset(); it("applies the default ai snapshot limit", async () => { - const tool = createBrowserTool(); - await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai" }); + await runSnapshotToolCall({ snapshotFormat: "ai" }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith( undefined, @@ -184,8 +201,7 @@ describe("browser tool snapshot maxChars", () => { configMocks.loadConfig.mockReturnValue({ browser: { snapshotDefaults: { mode: "efficient" } }, }); - const tool = createBrowserTool(); - await tool.execute?.("call-1", { action: "snapshot", snapshotFormat: "ai" }); + await runSnapshotToolCall({ snapshotFormat: "ai" }); expect(browserClientMocks.browserSnapshot).toHaveBeenCalledWith( undefined, @@ -263,11 +279,7 @@ describe("browser tool snapshot maxChars", () => { }); describe("browser tool url alias support", () => { - afterEach(() => { - vi.clearAllMocks(); - configMocks.loadConfig.mockReturnValue({ browser: {} }); - nodesUtilsMocks.listNodes.mockResolvedValue([]); - }); + registerBrowserToolAfterEachReset(); it("accepts url alias for open", async () => { const tool = createBrowserTool(); @@ -308,11 +320,7 @@ describe("browser tool url alias support", () => { }); describe("browser tool act compatibility", () => { - afterEach(() => { - vi.clearAllMocks(); - configMocks.loadConfig.mockReturnValue({ browser: {} }); - nodesUtilsMocks.listNodes.mockResolvedValue([]); - }); + registerBrowserToolAfterEachReset(); it("accepts flattened act params for backward compatibility", async () => { const tool = createBrowserTool(); @@ -364,10 +372,7 @@ describe("browser tool act compatibility", () => { }); describe("browser tool snapshot labels", () => { - afterEach(() => { - vi.clearAllMocks(); - configMocks.loadConfig.mockReturnValue({ browser: {} }); - }); + registerBrowserToolAfterEachReset(); it("returns image + text when labels are requested", async () => { const tool = createBrowserTool(); @@ -409,11 +414,7 @@ describe("browser tool snapshot labels", () => { }); describe("browser tool external content wrapping", () => { - afterEach(() => { - vi.clearAllMocks(); - configMocks.loadConfig.mockReturnValue({ browser: {} }); - nodesUtilsMocks.listNodes.mockResolvedValue([]); - }); + registerBrowserToolAfterEachReset(); it("wraps aria snapshots as external content", async () => { browserClientMocks.browserSnapshot.mockResolvedValueOnce({ @@ -525,11 +526,7 @@ describe("browser tool external content wrapping", () => { }); describe("browser tool act stale target recovery", () => { - afterEach(() => { - vi.clearAllMocks(); - configMocks.loadConfig.mockReturnValue({ browser: {} }); - nodesUtilsMocks.listNodes.mockResolvedValue([]); - }); + registerBrowserToolAfterEachReset(); it("retries chrome act once without targetId when tab id is stale", async () => { browserActionsMocks.browserAct diff --git a/src/agents/tools/telegram-actions.test.ts b/src/agents/tools/telegram-actions.test.ts index ea7fcddcbb5..6b4f2314a6b 100644 --- a/src/agents/tools/telegram-actions.test.ts +++ b/src/agents/tools/telegram-actions.test.ts @@ -51,6 +51,22 @@ describe("handleTelegramAction", () => { } as OpenClawConfig; } + async function sendInlineButtonsMessage(params: { + to: string; + buttons: Array>; + inlineButtons: "dm" | "group" | "all"; + }) { + await handleTelegramAction( + { + action: "sendMessage", + to: params.to, + content: "Choose", + buttons: params.buttons, + }, + telegramConfig({ capabilities: { inlineButtons: params.inlineButtons } }), + ); + } + async function expectReactionAdded(reactionLevel: "minimal" | "extensive") { await handleTelegramAction(defaultReactionAction, reactionConfig(reactionLevel)); expect(reactMessageTelegram).toHaveBeenCalledWith( @@ -103,9 +119,6 @@ describe("handleTelegramAction", () => { }); it("accepts snake_case message_id for reactions", async () => { - const cfg = { - channels: { telegram: { botToken: "tok", reactionLevel: "minimal" } }, - } as OpenClawConfig; await handleTelegramAction( { action: "react", @@ -113,7 +126,7 @@ describe("handleTelegramAction", () => { message_id: "456", emoji: "✅", }, - cfg, + reactionConfig("minimal"), ); expect(reactMessageTelegram).toHaveBeenCalledWith( "123", @@ -143,9 +156,6 @@ describe("handleTelegramAction", () => { }); it("removes reactions on empty emoji", async () => { - const cfg = { - channels: { telegram: { botToken: "tok", reactionLevel: "minimal" } }, - } as OpenClawConfig; await handleTelegramAction( { action: "react", @@ -153,7 +163,7 @@ describe("handleTelegramAction", () => { messageId: "456", emoji: "", }, - cfg, + reactionConfig("minimal"), ); expect(reactMessageTelegram).toHaveBeenCalledWith( "123", @@ -476,44 +486,29 @@ describe("handleTelegramAction", () => { }); it("allows inline buttons in DMs with tg: prefixed targets", async () => { - const cfg = telegramConfig({ capabilities: { inlineButtons: "dm" } }); - await handleTelegramAction( - { - action: "sendMessage", - to: "tg:5232990709", - content: "Choose", - buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]], - }, - cfg, - ); + await sendInlineButtonsMessage({ + to: "tg:5232990709", + buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]], + inlineButtons: "dm", + }); expect(sendMessageTelegram).toHaveBeenCalled(); }); it("allows inline buttons in groups with topic targets", async () => { - const cfg = telegramConfig({ capabilities: { inlineButtons: "group" } }); - await handleTelegramAction( - { - action: "sendMessage", - to: "telegram:group:-1001234567890:topic:456", - content: "Choose", - buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]], - }, - cfg, - ); + await sendInlineButtonsMessage({ + to: "telegram:group:-1001234567890:topic:456", + buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]], + inlineButtons: "group", + }); expect(sendMessageTelegram).toHaveBeenCalled(); }); it("sends messages with inline keyboard buttons when enabled", async () => { - const cfg = telegramConfig({ capabilities: { inlineButtons: "all" } }); - await handleTelegramAction( - { - action: "sendMessage", - to: "@testchannel", - content: "Choose", - buttons: [[{ text: " Option A ", callback_data: " cmd:a " }]], - }, - cfg, - ); + await sendInlineButtonsMessage({ + to: "@testchannel", + buttons: [[{ text: " Option A ", callback_data: " cmd:a " }]], + inlineButtons: "all", + }); expect(sendMessageTelegram).toHaveBeenCalledWith( "@testchannel", "Choose", @@ -524,24 +519,19 @@ describe("handleTelegramAction", () => { }); it("forwards optional button style", async () => { - const cfg = telegramConfig({ capabilities: { inlineButtons: "all" } }); - await handleTelegramAction( - { - action: "sendMessage", - to: "@testchannel", - content: "Choose", - buttons: [ - [ - { - text: "Option A", - callback_data: "cmd:a", - style: "primary", - }, - ], + await sendInlineButtonsMessage({ + to: "@testchannel", + inlineButtons: "all", + buttons: [ + [ + { + text: "Option A", + callback_data: "cmd:a", + style: "primary", + }, ], - }, - cfg, - ); + ], + }); expect(sendMessageTelegram).toHaveBeenCalledWith( "@testchannel", "Choose", @@ -601,6 +591,25 @@ describe("readTelegramButtons", () => { }); describe("handleTelegramAction per-account gating", () => { + function accountTelegramConfig(params: { + accounts: Record< + string, + { botToken: string; actions?: { sticker?: boolean; reactions?: boolean } } + >; + topLevelBotToken?: string; + topLevelActions?: { reactions?: boolean }; + }): OpenClawConfig { + return { + channels: { + telegram: { + ...(params.topLevelBotToken ? { botToken: params.topLevelBotToken } : {}), + ...(params.topLevelActions ? { actions: params.topLevelActions } : {}), + accounts: params.accounts, + }, + }, + } as OpenClawConfig; + } + async function expectAccountStickerSend(cfg: OpenClawConfig, accountId = "media") { await handleTelegramAction( { action: "sendSticker", to: "123", fileId: "sticker-id", accountId }, @@ -614,15 +623,11 @@ describe("handleTelegramAction per-account gating", () => { } it("allows sticker when account config enables it", async () => { - const cfg = { - channels: { - telegram: { - accounts: { - media: { botToken: "tok-media", actions: { sticker: true } }, - }, - }, + const cfg = accountTelegramConfig({ + accounts: { + media: { botToken: "tok-media", actions: { sticker: true } }, }, - } as OpenClawConfig; + }); await expectAccountStickerSend(cfg); }); @@ -647,30 +652,22 @@ describe("handleTelegramAction per-account gating", () => { it("uses account-merged config, not top-level config", async () => { // Top-level has no sticker enabled, but the account does - const cfg = { - channels: { - telegram: { - botToken: "tok-base", - accounts: { - media: { botToken: "tok-media", actions: { sticker: true } }, - }, - }, + const cfg = accountTelegramConfig({ + topLevelBotToken: "tok-base", + accounts: { + media: { botToken: "tok-media", actions: { sticker: true } }, }, - } as OpenClawConfig; + }); await expectAccountStickerSend(cfg); }); it("inherits top-level reaction gate when account overrides sticker only", async () => { - const cfg = { - channels: { - telegram: { - actions: { reactions: false }, - accounts: { - media: { botToken: "tok-media", actions: { sticker: true } }, - }, - }, + const cfg = accountTelegramConfig({ + topLevelActions: { reactions: false }, + accounts: { + media: { botToken: "tok-media", actions: { sticker: true } }, }, - } as OpenClawConfig; + }); const result = await handleTelegramAction( { @@ -689,16 +686,12 @@ describe("handleTelegramAction per-account gating", () => { }); it("allows account to explicitly re-enable top-level disabled reaction gate", async () => { - const cfg = { - channels: { - telegram: { - actions: { reactions: false }, - accounts: { - media: { botToken: "tok-media", actions: { sticker: true, reactions: true } }, - }, - }, + const cfg = accountTelegramConfig({ + topLevelActions: { reactions: false }, + accounts: { + media: { botToken: "tok-media", actions: { sticker: true, reactions: true } }, }, - } as OpenClawConfig; + }); await handleTelegramAction( {