mirror of https://github.com/openclaw/openclaw.git
test: extract message action plugin dispatch coverage
This commit is contained in:
parent
a9fd34058f
commit
9044a10c5f
|
|
@ -0,0 +1,439 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { jsonResult } from "../../agents/tools/common.js";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { runMessageAction } from "./message-action-runner.js";
|
||||
|
||||
function createAlwaysConfiguredPluginConfig(account: Record<string, unknown> = { enabled: true }) {
|
||||
return {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => account,
|
||||
isConfigured: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
describe("runMessageAction plugin dispatch", () => {
|
||||
describe("media caption behavior", () => {
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
});
|
||||
|
||||
it("promotes caption to message for media sends when message is empty", async () => {
|
||||
const sendMedia = vi.fn().mockResolvedValue({
|
||||
channel: "testchat",
|
||||
messageId: "m1",
|
||||
chatId: "c1",
|
||||
});
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "testchat",
|
||||
source: "test",
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "testchat",
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: vi.fn().mockResolvedValue({
|
||||
channel: "testchat",
|
||||
messageId: "t1",
|
||||
chatId: "c1",
|
||||
}),
|
||||
sendMedia,
|
||||
},
|
||||
}),
|
||||
},
|
||||
]),
|
||||
);
|
||||
const cfg = {
|
||||
channels: {
|
||||
testchat: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await runMessageAction({
|
||||
cfg,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "testchat",
|
||||
target: "channel:abc",
|
||||
media: "https://example.com/cat.png",
|
||||
caption: "caption-only text",
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(sendMedia).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "caption-only text",
|
||||
mediaUrl: "https://example.com/cat.png",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("card-only send behavior", () => {
|
||||
const handleAction = vi.fn(async ({ params }: { params: Record<string, unknown> }) =>
|
||||
jsonResult({
|
||||
ok: true,
|
||||
card: params.card ?? null,
|
||||
message: params.message ?? null,
|
||||
}),
|
||||
);
|
||||
|
||||
const cardPlugin: ChannelPlugin = {
|
||||
id: "cardchat",
|
||||
meta: {
|
||||
id: "cardchat",
|
||||
label: "Card Chat",
|
||||
selectionLabel: "Card Chat",
|
||||
docsPath: "/channels/cardchat",
|
||||
blurb: "Card-only send test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: createAlwaysConfiguredPluginConfig(),
|
||||
actions: {
|
||||
listActions: () => ["send"],
|
||||
supportsAction: ({ action }) => action === "send",
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "cardchat",
|
||||
source: "test",
|
||||
plugin: cardPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("allows card-only sends without text or media", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
cardchat: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const card = {
|
||||
type: "AdaptiveCard",
|
||||
version: "1.4",
|
||||
body: [{ type: "TextBlock", text: "Card-only payload" }],
|
||||
};
|
||||
|
||||
const result = await runMessageAction({
|
||||
cfg,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "cardchat",
|
||||
target: "channel:test-card",
|
||||
card,
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(result.handledBy).toBe("plugin");
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
expect(result.payload).toMatchObject({
|
||||
ok: true,
|
||||
card,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("telegram plugin poll forwarding", () => {
|
||||
const handleAction = vi.fn(async ({ params }: { params: Record<string, unknown> }) =>
|
||||
jsonResult({
|
||||
ok: true,
|
||||
forwarded: {
|
||||
to: params.to ?? null,
|
||||
pollQuestion: params.pollQuestion ?? null,
|
||||
pollOption: params.pollOption ?? null,
|
||||
pollDurationSeconds: params.pollDurationSeconds ?? null,
|
||||
pollPublic: params.pollPublic ?? null,
|
||||
threadId: params.threadId ?? null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const telegramPollPlugin: ChannelPlugin = {
|
||||
id: "telegram",
|
||||
meta: {
|
||||
id: "telegram",
|
||||
label: "Telegram",
|
||||
selectionLabel: "Telegram",
|
||||
docsPath: "/channels/telegram",
|
||||
blurb: "Telegram poll forwarding test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: createAlwaysConfiguredPluginConfig(),
|
||||
messaging: {
|
||||
targetResolver: {
|
||||
looksLikeId: () => true,
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
listActions: () => ["poll"],
|
||||
supportsAction: ({ action }) => action === "poll",
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "telegram",
|
||||
source: "test",
|
||||
plugin: telegramPollPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("forwards telegram poll params through plugin dispatch", async () => {
|
||||
const result = await runMessageAction({
|
||||
cfg: {
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "tok",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
action: "poll",
|
||||
params: {
|
||||
channel: "telegram",
|
||||
target: "telegram:123",
|
||||
pollQuestion: "Lunch?",
|
||||
pollOption: ["Pizza", "Sushi"],
|
||||
pollDurationSeconds: 120,
|
||||
pollPublic: true,
|
||||
threadId: "42",
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("poll");
|
||||
expect(result.handledBy).toBe("plugin");
|
||||
expect(handleAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "poll",
|
||||
channel: "telegram",
|
||||
params: expect.objectContaining({
|
||||
to: "telegram:123",
|
||||
pollQuestion: "Lunch?",
|
||||
pollOption: ["Pizza", "Sushi"],
|
||||
pollDurationSeconds: 120,
|
||||
pollPublic: true,
|
||||
threadId: "42",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(result.payload).toMatchObject({
|
||||
ok: true,
|
||||
forwarded: {
|
||||
to: "telegram:123",
|
||||
pollQuestion: "Lunch?",
|
||||
pollOption: ["Pizza", "Sushi"],
|
||||
pollDurationSeconds: 120,
|
||||
pollPublic: true,
|
||||
threadId: "42",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("components parsing", () => {
|
||||
const handleAction = vi.fn(async ({ params }: { params: Record<string, unknown> }) =>
|
||||
jsonResult({
|
||||
ok: true,
|
||||
components: params.components ?? null,
|
||||
}),
|
||||
);
|
||||
|
||||
const componentsPlugin: ChannelPlugin = {
|
||||
id: "discord",
|
||||
meta: {
|
||||
id: "discord",
|
||||
label: "Discord",
|
||||
selectionLabel: "Discord",
|
||||
docsPath: "/channels/discord",
|
||||
blurb: "Discord components send test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: createAlwaysConfiguredPluginConfig({}),
|
||||
actions: {
|
||||
listActions: () => ["send"],
|
||||
supportsAction: ({ action }) => action === "send",
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "discord",
|
||||
source: "test",
|
||||
plugin: componentsPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("parses components JSON strings before plugin dispatch", async () => {
|
||||
const components = {
|
||||
text: "hello",
|
||||
buttons: [{ label: "A", customId: "a" }],
|
||||
};
|
||||
const result = await runMessageAction({
|
||||
cfg: {} as OpenClawConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "discord",
|
||||
target: "channel:123",
|
||||
message: "hi",
|
||||
components: JSON.stringify(components),
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
expect(result.payload).toMatchObject({ ok: true, components });
|
||||
});
|
||||
|
||||
it("throws on invalid components JSON strings", async () => {
|
||||
await expect(
|
||||
runMessageAction({
|
||||
cfg: {} as OpenClawConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "discord",
|
||||
target: "channel:123",
|
||||
message: "hi",
|
||||
components: "{not-json}",
|
||||
},
|
||||
dryRun: false,
|
||||
}),
|
||||
).rejects.toThrow(/--components must be valid JSON/);
|
||||
|
||||
expect(handleAction).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("accountId defaults", () => {
|
||||
const handleAction = vi.fn(async () => jsonResult({ ok: true }));
|
||||
const accountPlugin: ChannelPlugin = {
|
||||
id: "discord",
|
||||
meta: {
|
||||
id: "discord",
|
||||
label: "Discord",
|
||||
selectionLabel: "Discord",
|
||||
docsPath: "/channels/discord",
|
||||
blurb: "Discord test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
actions: {
|
||||
listActions: () => ["send"],
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "discord",
|
||||
source: "test",
|
||||
plugin: accountPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "uses defaultAccountId override",
|
||||
args: {
|
||||
cfg: {} as OpenClawConfig,
|
||||
defaultAccountId: "ops",
|
||||
},
|
||||
expectedAccountId: "ops",
|
||||
},
|
||||
{
|
||||
name: "falls back to agent binding account",
|
||||
args: {
|
||||
cfg: {
|
||||
bindings: [
|
||||
{ agentId: "agent-b", match: { channel: "discord", accountId: "account-b" } },
|
||||
],
|
||||
} as OpenClawConfig,
|
||||
agentId: "agent-b",
|
||||
},
|
||||
expectedAccountId: "account-b",
|
||||
},
|
||||
])("$name", async ({ args, expectedAccountId }) => {
|
||||
await runMessageAction({
|
||||
...args,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "discord",
|
||||
target: "channel:123",
|
||||
message: "hi",
|
||||
},
|
||||
});
|
||||
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
const ctx = (handleAction.mock.calls as unknown as Array<[unknown]>)[0]?.[0] as
|
||||
| {
|
||||
accountId?: string | null;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
| undefined;
|
||||
if (!ctx) {
|
||||
throw new Error("expected action context");
|
||||
}
|
||||
expect(ctx.accountId).toBe(expectedAccountId);
|
||||
expect(ctx.params.accountId).toBe(expectedAccountId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -9,7 +9,7 @@ import { jsonResult } from "../../agents/tools/common.js";
|
|||
import type { ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { createIMessageTestPlugin } from "../../test-utils/imessage-test-plugin.js";
|
||||
import { loadWebMedia } from "../../web/media.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../tmp-openclaw-dir.js";
|
||||
|
|
@ -105,14 +105,6 @@ async function expectSandboxMediaRewrite(params: {
|
|||
);
|
||||
}
|
||||
|
||||
function createAlwaysConfiguredPluginConfig(account: Record<string, unknown> = { enabled: true }) {
|
||||
return {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => account,
|
||||
isConfigured: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
let createPluginRuntime: typeof import("../../plugins/runtime/index.js").createPluginRuntime;
|
||||
let setSlackRuntime: typeof import("../../../extensions/slack/src/runtime.js").setSlackRuntime;
|
||||
let setTelegramRuntime: typeof import("../../../extensions/telegram/src/runtime.js").setTelegramRuntime;
|
||||
|
|
@ -825,433 +817,3 @@ describe("runMessageAction sandboxed media validation", () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("runMessageAction media caption behavior", () => {
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
});
|
||||
|
||||
it("promotes caption to message for media sends when message is empty", async () => {
|
||||
const sendMedia = vi.fn().mockResolvedValue({
|
||||
channel: "testchat",
|
||||
messageId: "m1",
|
||||
chatId: "c1",
|
||||
});
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "testchat",
|
||||
source: "test",
|
||||
plugin: createOutboundTestPlugin({
|
||||
id: "testchat",
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: vi.fn().mockResolvedValue({
|
||||
channel: "testchat",
|
||||
messageId: "t1",
|
||||
chatId: "c1",
|
||||
}),
|
||||
sendMedia,
|
||||
},
|
||||
}),
|
||||
},
|
||||
]),
|
||||
);
|
||||
const cfg = {
|
||||
channels: {
|
||||
testchat: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await runMessageAction({
|
||||
cfg,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "testchat",
|
||||
target: "channel:abc",
|
||||
media: "https://example.com/cat.png",
|
||||
caption: "caption-only text",
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(sendMedia).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "caption-only text",
|
||||
mediaUrl: "https://example.com/cat.png",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("runMessageAction card-only send behavior", () => {
|
||||
const handleAction = vi.fn(async ({ params }: { params: Record<string, unknown> }) =>
|
||||
jsonResult({
|
||||
ok: true,
|
||||
card: params.card ?? null,
|
||||
message: params.message ?? null,
|
||||
}),
|
||||
);
|
||||
|
||||
const cardPlugin: ChannelPlugin = {
|
||||
id: "cardchat",
|
||||
meta: {
|
||||
id: "cardchat",
|
||||
label: "Card Chat",
|
||||
selectionLabel: "Card Chat",
|
||||
docsPath: "/channels/cardchat",
|
||||
blurb: "Card-only send test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: createAlwaysConfiguredPluginConfig(),
|
||||
actions: {
|
||||
listActions: () => ["send"],
|
||||
supportsAction: ({ action }) => action === "send",
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "cardchat",
|
||||
source: "test",
|
||||
plugin: cardPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("allows card-only sends without text or media", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
cardchat: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const card = {
|
||||
type: "AdaptiveCard",
|
||||
version: "1.4",
|
||||
body: [{ type: "TextBlock", text: "Card-only payload" }],
|
||||
};
|
||||
|
||||
const result = await runMessageAction({
|
||||
cfg,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "cardchat",
|
||||
target: "channel:test-card",
|
||||
card,
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(result.handledBy).toBe("plugin");
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
expect(result.payload).toMatchObject({
|
||||
ok: true,
|
||||
card,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("runMessageAction telegram plugin poll forwarding", () => {
|
||||
const handleAction = vi.fn(async ({ params }: { params: Record<string, unknown> }) =>
|
||||
jsonResult({
|
||||
ok: true,
|
||||
forwarded: {
|
||||
to: params.to ?? null,
|
||||
pollQuestion: params.pollQuestion ?? null,
|
||||
pollOption: params.pollOption ?? null,
|
||||
pollDurationSeconds: params.pollDurationSeconds ?? null,
|
||||
pollPublic: params.pollPublic ?? null,
|
||||
threadId: params.threadId ?? null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const telegramPollPlugin: ChannelPlugin = {
|
||||
id: "telegram",
|
||||
meta: {
|
||||
id: "telegram",
|
||||
label: "Telegram",
|
||||
selectionLabel: "Telegram",
|
||||
docsPath: "/channels/telegram",
|
||||
blurb: "Telegram poll forwarding test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: createAlwaysConfiguredPluginConfig(),
|
||||
messaging: {
|
||||
targetResolver: {
|
||||
looksLikeId: () => true,
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
listActions: () => ["poll"],
|
||||
supportsAction: ({ action }) => action === "poll",
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "telegram",
|
||||
source: "test",
|
||||
plugin: telegramPollPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("forwards telegram poll params through plugin dispatch", async () => {
|
||||
const result = await runMessageAction({
|
||||
cfg: {
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "tok",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
action: "poll",
|
||||
params: {
|
||||
channel: "telegram",
|
||||
target: "telegram:123",
|
||||
pollQuestion: "Lunch?",
|
||||
pollOption: ["Pizza", "Sushi"],
|
||||
pollDurationSeconds: 120,
|
||||
pollPublic: true,
|
||||
threadId: "42",
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("poll");
|
||||
expect(result.handledBy).toBe("plugin");
|
||||
expect(handleAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "poll",
|
||||
channel: "telegram",
|
||||
params: expect.objectContaining({
|
||||
to: "telegram:123",
|
||||
pollQuestion: "Lunch?",
|
||||
pollOption: ["Pizza", "Sushi"],
|
||||
pollDurationSeconds: 120,
|
||||
pollPublic: true,
|
||||
threadId: "42",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(result.payload).toMatchObject({
|
||||
ok: true,
|
||||
forwarded: {
|
||||
to: "telegram:123",
|
||||
pollQuestion: "Lunch?",
|
||||
pollOption: ["Pizza", "Sushi"],
|
||||
pollDurationSeconds: 120,
|
||||
pollPublic: true,
|
||||
threadId: "42",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("runMessageAction components parsing", () => {
|
||||
const handleAction = vi.fn(async ({ params }: { params: Record<string, unknown> }) =>
|
||||
jsonResult({
|
||||
ok: true,
|
||||
components: params.components ?? null,
|
||||
}),
|
||||
);
|
||||
|
||||
const componentsPlugin: ChannelPlugin = {
|
||||
id: "discord",
|
||||
meta: {
|
||||
id: "discord",
|
||||
label: "Discord",
|
||||
selectionLabel: "Discord",
|
||||
docsPath: "/channels/discord",
|
||||
blurb: "Discord components send test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: createAlwaysConfiguredPluginConfig({}),
|
||||
actions: {
|
||||
listActions: () => ["send"],
|
||||
supportsAction: ({ action }) => action === "send",
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "discord",
|
||||
source: "test",
|
||||
plugin: componentsPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("parses components JSON strings before plugin dispatch", async () => {
|
||||
const components = {
|
||||
text: "hello",
|
||||
buttons: [{ label: "A", customId: "a" }],
|
||||
};
|
||||
const result = await runMessageAction({
|
||||
cfg: {} as OpenClawConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "discord",
|
||||
target: "channel:123",
|
||||
message: "hi",
|
||||
components: JSON.stringify(components),
|
||||
},
|
||||
dryRun: false,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
expect(result.payload).toMatchObject({ ok: true, components });
|
||||
});
|
||||
|
||||
it("throws on invalid components JSON strings", async () => {
|
||||
await expect(
|
||||
runMessageAction({
|
||||
cfg: {} as OpenClawConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "discord",
|
||||
target: "channel:123",
|
||||
message: "hi",
|
||||
components: "{not-json}",
|
||||
},
|
||||
dryRun: false,
|
||||
}),
|
||||
).rejects.toThrow(/--components must be valid JSON/);
|
||||
|
||||
expect(handleAction).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("runMessageAction accountId defaults", () => {
|
||||
const handleAction = vi.fn(async () => jsonResult({ ok: true }));
|
||||
const accountPlugin: ChannelPlugin = {
|
||||
id: "discord",
|
||||
meta: {
|
||||
id: "discord",
|
||||
label: "Discord",
|
||||
selectionLabel: "Discord",
|
||||
docsPath: "/channels/discord",
|
||||
blurb: "Discord test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
actions: {
|
||||
listActions: () => ["send"],
|
||||
handleAction,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "discord",
|
||||
source: "test",
|
||||
plugin: accountPlugin,
|
||||
},
|
||||
]),
|
||||
);
|
||||
handleAction.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("propagates defaultAccountId into params", async () => {
|
||||
await runMessageAction({
|
||||
cfg: {} as OpenClawConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "discord",
|
||||
target: "channel:123",
|
||||
message: "hi",
|
||||
},
|
||||
defaultAccountId: "ops",
|
||||
});
|
||||
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
const ctx = (handleAction.mock.calls as unknown as Array<[unknown]>)[0]?.[0] as
|
||||
| {
|
||||
accountId?: string | null;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
| undefined;
|
||||
if (!ctx) {
|
||||
throw new Error("expected action context");
|
||||
}
|
||||
expect(ctx.accountId).toBe("ops");
|
||||
expect(ctx.params.accountId).toBe("ops");
|
||||
});
|
||||
|
||||
it("falls back to the agent's bound account when accountId is omitted", async () => {
|
||||
await runMessageAction({
|
||||
cfg: {
|
||||
bindings: [{ agentId: "agent-b", match: { channel: "discord", accountId: "account-b" } }],
|
||||
} as OpenClawConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
channel: "discord",
|
||||
target: "channel:123",
|
||||
message: "hi",
|
||||
},
|
||||
agentId: "agent-b",
|
||||
});
|
||||
|
||||
expect(handleAction).toHaveBeenCalled();
|
||||
const ctx = (handleAction.mock.calls as unknown as Array<[unknown]>)[0]?.[0] as
|
||||
| {
|
||||
accountId?: string | null;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
| undefined;
|
||||
if (!ctx) {
|
||||
throw new Error("expected action context");
|
||||
}
|
||||
expect(ctx.accountId).toBe("account-b");
|
||||
expect(ctx.params.accountId).toBe("account-b");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue