From 07433680137e4ba88fa4f54bb10ea2545bd145c9 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 22 Mar 2026 17:54:52 -0700 Subject: [PATCH] fix(test): split discord monitor agent components --- .../monitor/monitor.agent-components.test.ts | 253 ++++++++++++++++++ .../discord/src/monitor/monitor.test.ts | 214 --------------- 2 files changed, 253 insertions(+), 214 deletions(-) create mode 100644 extensions/discord/src/monitor/monitor.agent-components.test.ts diff --git a/extensions/discord/src/monitor/monitor.agent-components.test.ts b/extensions/discord/src/monitor/monitor.agent-components.test.ts new file mode 100644 index 00000000000..4ac1fedcdb2 --- /dev/null +++ b/extensions/discord/src/monitor/monitor.agent-components.test.ts @@ -0,0 +1,253 @@ +import type { ButtonInteraction, ComponentData, StringSelectMenuInteraction } from "@buape/carbon"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime"; +import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { peekSystemEvents, resetSystemEventsForTest } from "../../../../src/infra/system-events.ts"; +import { createAgentComponentButton, createAgentSelectMenu } from "./agent-components.js"; + +const readAllowFromStoreMock = vi.hoisted(() => vi.fn()); +const upsertPairingRequestMock = vi.hoisted(() => vi.fn()); + +vi.mock("openclaw/plugin-sdk/security-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + readStoreAllowFromForDmPolicy: async (params: { + provider: string; + accountId: string; + dmPolicy?: string | null; + shouldRead?: boolean | null; + }) => { + if (params.shouldRead === false || params.dmPolicy === "allowlist") { + return []; + } + return await readAllowFromStoreMock(params.provider, params.accountId); + }, + }; +}); + +vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), + }; +}); +vi.mock("openclaw/plugin-sdk/conversation-runtime.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), + }; +}); + +describe("agent components", () => { + const defaultDmSessionKey = buildAgentSessionKey({ + agentId: "main", + channel: "discord", + accountId: "default", + peer: { kind: "direct", id: "123456789" }, + }); + + const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig; + + const createBaseDmInteraction = (overrides: Record = {}) => { + const reply = vi.fn().mockResolvedValue(undefined); + const defer = vi.fn().mockResolvedValue(undefined); + const interaction = { + rawData: { channel_id: "dm-channel" }, + user: { id: "123456789", username: "Alice", discriminator: "1234" }, + defer, + reply, + ...overrides, + }; + return { interaction, defer, reply }; + }; + + const createDmButtonInteraction = (overrides: Partial = {}) => { + const { interaction, defer, reply } = createBaseDmInteraction( + overrides as Record, + ); + return { + interaction: interaction as unknown as ButtonInteraction, + defer, + reply, + }; + }; + + const createDmSelectInteraction = (overrides: Partial = {}) => { + const { interaction, defer, reply } = createBaseDmInteraction({ + values: ["alpha"], + ...(overrides as Record), + }); + return { + interaction: interaction as unknown as StringSelectMenuInteraction, + defer, + reply, + }; + }; + + beforeEach(() => { + readAllowFromStoreMock.mockClear().mockResolvedValue([]); + upsertPairingRequestMock.mockClear().mockResolvedValue({ code: "PAIRCODE", created: true }); + resetSystemEventsForTest(); + }); + + it("sends pairing reply when DM sender is not allowlisted", async () => { + const button = createAgentComponentButton({ + cfg: createCfg(), + accountId: "default", + dmPolicy: "pairing", + }); + const { interaction, defer, reply } = createDmButtonInteraction(); + + await button.run(interaction, { componentId: "hello" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledTimes(1); + const pairingText = String(reply.mock.calls[0]?.[0]?.content ?? ""); + expect(pairingText).toContain("Pairing code:"); + const code = pairingText.match(/Pairing code:\s*([A-Z2-9]{8})/)?.[1]; + expect(code).toBeDefined(); + expect(pairingText).toContain(`openclaw pairing approve discord ${code}`); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]); + expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default"); + }); + + it("blocks DM interactions in allowlist mode when sender is not in configured allowFrom", async () => { + const button = createAgentComponentButton({ + cfg: createCfg(), + accountId: "default", + dmPolicy: "allowlist", + }); + const { interaction, defer, reply } = createDmButtonInteraction(); + + await button.run(interaction, { componentId: "hello" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith({ + content: "You are not authorized to use this button.", + ephemeral: true, + }); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]); + expect(readAllowFromStoreMock).not.toHaveBeenCalled(); + }); + + it("authorizes DM interactions from pairing-store entries in pairing mode", async () => { + readAllowFromStoreMock.mockResolvedValue(["123456789"]); + const button = createAgentComponentButton({ + cfg: createCfg(), + accountId: "default", + dmPolicy: "pairing", + }); + const { interaction, defer, reply } = createDmButtonInteraction(); + + await button.run(interaction, { componentId: "hello" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ + "[Discord component: hello clicked by Alice#1234 (123456789)]", + ]); + expect(upsertPairingRequestMock).not.toHaveBeenCalled(); + expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default"); + }); + + it("allows DM component interactions in open mode without reading pairing store", async () => { + readAllowFromStoreMock.mockResolvedValue(["123456789"]); + const button = createAgentComponentButton({ + cfg: createCfg(), + accountId: "default", + dmPolicy: "open", + }); + const { interaction, defer, reply } = createDmButtonInteraction(); + + await button.run(interaction, { componentId: "hello" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ + "[Discord component: hello clicked by Alice#1234 (123456789)]", + ]); + expect(readAllowFromStoreMock).not.toHaveBeenCalled(); + }); + + it("blocks DM component interactions in disabled mode without reading pairing store", async () => { + readAllowFromStoreMock.mockResolvedValue(["123456789"]); + const button = createAgentComponentButton({ + cfg: createCfg(), + accountId: "default", + dmPolicy: "disabled", + }); + const { interaction, defer, reply } = createDmButtonInteraction(); + + await button.run(interaction, { componentId: "hello" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith({ + content: "DM interactions are disabled.", + ephemeral: true, + }); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]); + expect(readAllowFromStoreMock).not.toHaveBeenCalled(); + }); + + it("matches tag-based allowlist entries for DM select menus", async () => { + const select = createAgentSelectMenu({ + cfg: createCfg(), + accountId: "default", + discordConfig: { dangerouslyAllowNameMatching: true } as DiscordAccountConfig, + dmPolicy: "allowlist", + allowFrom: ["Alice#1234"], + }); + const { interaction, defer, reply } = createDmSelectInteraction(); + + await select.run(interaction, { componentId: "hello" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ + "[Discord select menu: hello interacted by Alice#1234 (123456789) (selected: alpha)]", + ]); + expect(readAllowFromStoreMock).not.toHaveBeenCalled(); + }); + + it("accepts cid payloads for agent button interactions", async () => { + const button = createAgentComponentButton({ + cfg: createCfg(), + accountId: "default", + dmPolicy: "allowlist", + allowFrom: ["123456789"], + }); + const { interaction, defer, reply } = createDmButtonInteraction(); + + await button.run(interaction, { cid: "hello_cid" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ + "[Discord component: hello_cid clicked by Alice#1234 (123456789)]", + ]); + expect(readAllowFromStoreMock).not.toHaveBeenCalled(); + }); + + it("keeps malformed percent cid values without throwing", async () => { + const button = createAgentComponentButton({ + cfg: createCfg(), + accountId: "default", + dmPolicy: "allowlist", + allowFrom: ["123456789"], + }); + const { interaction, defer, reply } = createDmButtonInteraction(); + + await button.run(interaction, { cid: "hello%2G" } as ComponentData); + + expect(defer).not.toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); + expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ + "[Discord component: hello%2G clicked by Alice#1234 (123456789)]", + ]); + expect(readAllowFromStoreMock).not.toHaveBeenCalled(); + }); +}); diff --git a/extensions/discord/src/monitor/monitor.test.ts b/extensions/discord/src/monitor/monitor.test.ts index 4fc5679f143..fa9e961dd76 100644 --- a/extensions/discord/src/monitor/monitor.test.ts +++ b/extensions/discord/src/monitor/monitor.test.ts @@ -10,9 +10,7 @@ import type { GatewayPresenceUpdate } from "discord-api-types/v10"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime"; import { buildPluginBindingApprovalCustomId } from "openclaw/plugin-sdk/conversation-runtime"; -import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { peekSystemEvents, resetSystemEventsForTest } from "../../../../src/infra/system-events.ts"; import { clearDiscordComponentEntries, registerDiscordComponentEntries, @@ -22,8 +20,6 @@ import { import type { DiscordComponentEntry, DiscordModalEntry } from "../components.js"; import * as sendComponents from "../send.components.js"; import { - createAgentComponentButton, - createAgentSelectMenu, createDiscordComponentButton, createDiscordComponentStringSelect, createDiscordComponentModal, @@ -158,216 +154,6 @@ vi.mock("openclaw/plugin-sdk/plugin-runtime", async (importOriginal) => { }; }); -describe("agent components", () => { - const defaultDmSessionKey = buildAgentSessionKey({ - agentId: "main", - channel: "discord", - accountId: "default", - peer: { kind: "direct", id: "123456789" }, - }); - - const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig; - - const createBaseDmInteraction = (overrides: Record = {}) => { - const reply = vi.fn().mockResolvedValue(undefined); - const defer = vi.fn().mockResolvedValue(undefined); - const interaction = { - rawData: { channel_id: "dm-channel" }, - user: { id: "123456789", username: "Alice", discriminator: "1234" }, - defer, - reply, - ...overrides, - }; - return { interaction, defer, reply }; - }; - - const createDmButtonInteraction = (overrides: Partial = {}) => { - const { interaction, defer, reply } = createBaseDmInteraction( - overrides as Record, - ); - return { - interaction: interaction as unknown as ButtonInteraction, - defer, - reply, - }; - }; - - const createDmSelectInteraction = (overrides: Partial = {}) => { - const { interaction, defer, reply } = createBaseDmInteraction({ - values: ["alpha"], - ...(overrides as Record), - }); - return { - interaction: interaction as unknown as StringSelectMenuInteraction, - defer, - reply, - }; - }; - - beforeEach(() => { - readAllowFromStoreMock.mockClear().mockResolvedValue([]); - upsertPairingRequestMock.mockClear().mockResolvedValue({ code: "PAIRCODE", created: true }); - resetSystemEventsForTest(); - }); - - it("sends pairing reply when DM sender is not allowlisted", async () => { - const button = createAgentComponentButton({ - cfg: createCfg(), - accountId: "default", - dmPolicy: "pairing", - }); - const { interaction, defer, reply } = createDmButtonInteraction(); - - await button.run(interaction, { componentId: "hello" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledTimes(1); - const pairingText = String(reply.mock.calls[0]?.[0]?.content ?? ""); - expect(pairingText).toContain("Pairing code:"); - const code = pairingText.match(/Pairing code:\s*([A-Z2-9]{8})/)?.[1]; - expect(code).toBeDefined(); - expect(pairingText).toContain(`openclaw pairing approve discord ${code}`); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]); - expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default"); - }); - - it("blocks DM interactions in allowlist mode when sender is not in configured allowFrom", async () => { - const button = createAgentComponentButton({ - cfg: createCfg(), - accountId: "default", - dmPolicy: "allowlist", - }); - const { interaction, defer, reply } = createDmButtonInteraction(); - - await button.run(interaction, { componentId: "hello" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledWith({ - content: "You are not authorized to use this button.", - ephemeral: true, - }); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]); - expect(readAllowFromStoreMock).not.toHaveBeenCalled(); - }); - - it("authorizes DM interactions from pairing-store entries in pairing mode", async () => { - readAllowFromStoreMock.mockResolvedValue(["123456789"]); - const button = createAgentComponentButton({ - cfg: createCfg(), - accountId: "default", - dmPolicy: "pairing", - }); - const { interaction, defer, reply } = createDmButtonInteraction(); - - await button.run(interaction, { componentId: "hello" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ - "[Discord component: hello clicked by Alice#1234 (123456789)]", - ]); - expect(upsertPairingRequestMock).not.toHaveBeenCalled(); - expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default"); - }); - - it("allows DM component interactions in open mode without reading pairing store", async () => { - readAllowFromStoreMock.mockResolvedValue(["123456789"]); - const button = createAgentComponentButton({ - cfg: createCfg(), - accountId: "default", - dmPolicy: "open", - }); - const { interaction, defer, reply } = createDmButtonInteraction(); - - await button.run(interaction, { componentId: "hello" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ - "[Discord component: hello clicked by Alice#1234 (123456789)]", - ]); - expect(readAllowFromStoreMock).not.toHaveBeenCalled(); - }); - - it("blocks DM component interactions in disabled mode without reading pairing store", async () => { - readAllowFromStoreMock.mockResolvedValue(["123456789"]); - const button = createAgentComponentButton({ - cfg: createCfg(), - accountId: "default", - dmPolicy: "disabled", - }); - const { interaction, defer, reply } = createDmButtonInteraction(); - - await button.run(interaction, { componentId: "hello" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledWith({ - content: "DM interactions are disabled.", - ephemeral: true, - }); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([]); - expect(readAllowFromStoreMock).not.toHaveBeenCalled(); - }); - - it("matches tag-based allowlist entries for DM select menus", async () => { - const select = createAgentSelectMenu({ - cfg: createCfg(), - accountId: "default", - discordConfig: { dangerouslyAllowNameMatching: true } as DiscordAccountConfig, - dmPolicy: "allowlist", - allowFrom: ["Alice#1234"], - }); - const { interaction, defer, reply } = createDmSelectInteraction(); - - await select.run(interaction, { componentId: "hello" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ - "[Discord select menu: hello interacted by Alice#1234 (123456789) (selected: alpha)]", - ]); - expect(readAllowFromStoreMock).not.toHaveBeenCalled(); - }); - - it("accepts cid payloads for agent button interactions", async () => { - const button = createAgentComponentButton({ - cfg: createCfg(), - accountId: "default", - dmPolicy: "allowlist", - allowFrom: ["123456789"], - }); - const { interaction, defer, reply } = createDmButtonInteraction(); - - await button.run(interaction, { cid: "hello_cid" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ - "[Discord component: hello_cid clicked by Alice#1234 (123456789)]", - ]); - expect(readAllowFromStoreMock).not.toHaveBeenCalled(); - }); - - it("keeps malformed percent cid values without throwing", async () => { - const button = createAgentComponentButton({ - cfg: createCfg(), - accountId: "default", - dmPolicy: "allowlist", - allowFrom: ["123456789"], - }); - const { interaction, defer, reply } = createDmButtonInteraction(); - - await button.run(interaction, { cid: "hello%2G" } as ComponentData); - - expect(defer).not.toHaveBeenCalled(); - expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true }); - expect(peekSystemEvents(defaultDmSessionKey)).toEqual([ - "[Discord component: hello%2G clicked by Alice#1234 (123456789)]", - ]); - expect(readAllowFromStoreMock).not.toHaveBeenCalled(); - }); -}); - describe("discord component interactions", () => { let editDiscordComponentMessageMock: ReturnType; const createCfg = (): OpenClawConfig =>