fix(test): split discord monitor agent components

This commit is contained in:
Vincent Koc
2026-03-22 17:54:52 -07:00
parent bb8e2fceff
commit 0743368013
2 changed files with 253 additions and 214 deletions

View File

@@ -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<typeof import("openclaw/plugin-sdk/security-runtime")>();
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<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
return {
...actual,
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
};
});
vi.mock("openclaw/plugin-sdk/conversation-runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
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<string, unknown> = {}) => {
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<ButtonInteraction> = {}) => {
const { interaction, defer, reply } = createBaseDmInteraction(
overrides as Record<string, unknown>,
);
return {
interaction: interaction as unknown as ButtonInteraction,
defer,
reply,
};
};
const createDmSelectInteraction = (overrides: Partial<StringSelectMenuInteraction> = {}) => {
const { interaction, defer, reply } = createBaseDmInteraction({
values: ["alpha"],
...(overrides as Record<string, unknown>),
});
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();
});
});

View File

@@ -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<string, unknown> = {}) => {
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<ButtonInteraction> = {}) => {
const { interaction, defer, reply } = createBaseDmInteraction(
overrides as Record<string, unknown>,
);
return {
interaction: interaction as unknown as ButtonInteraction,
defer,
reply,
};
};
const createDmSelectInteraction = (overrides: Partial<StringSelectMenuInteraction> = {}) => {
const { interaction, defer, reply } = createBaseDmInteraction({
values: ["alpha"],
...(overrides as Record<string, unknown>),
});
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<typeof vi.spyOn>;
const createCfg = (): OpenClawConfig =>