test: extract message action context coverage

This commit is contained in:
Peter Steinberger
2026-03-13 21:10:37 +00:00
parent 9c08312121
commit c355b8a671

View File

@@ -111,8 +111,9 @@ describe("runMessageAction context isolation", () => {
setActivePluginRegistry(createTestRegistry([]));
});
it("allows send when target matches current channel", async () => {
const result = await runDrySend({
it.each([
{
name: "allows send when target matches current channel",
cfg: slackConfig,
actionParams: {
channel: "slack",
@@ -120,39 +121,27 @@ describe("runMessageAction context isolation", () => {
message: "hi",
},
toolContext: { currentChannelId: "C12345678" },
});
expect(result.kind).toBe("send");
});
it("accepts legacy to parameter for send", async () => {
const result = await runDrySend({
},
{
name: "accepts legacy to parameter for send",
cfg: slackConfig,
actionParams: {
channel: "slack",
to: "#C12345678",
message: "hi",
},
});
expect(result.kind).toBe("send");
});
it("defaults to current channel when target is omitted", async () => {
const result = await runDrySend({
},
{
name: "defaults to current channel when target is omitted",
cfg: slackConfig,
actionParams: {
channel: "slack",
message: "hi",
},
toolContext: { currentChannelId: "C12345678" },
});
expect(result.kind).toBe("send");
});
it("allows media-only send when target matches current channel", async () => {
const result = await runDrySend({
},
{
name: "allows media-only send when target matches current channel",
cfg: slackConfig,
actionParams: {
channel: "slack",
@@ -160,6 +149,25 @@ describe("runMessageAction context isolation", () => {
media: "https://example.com/note.ogg",
},
toolContext: { currentChannelId: "C12345678" },
},
{
name: "allows send when poll booleans are explicitly false",
cfg: slackConfig,
actionParams: {
channel: "slack",
target: "#C12345678",
message: "hi",
pollMulti: false,
pollAnonymous: false,
pollPublic: false,
},
toolContext: { currentChannelId: "C12345678" },
},
])("$name", async ({ cfg, actionParams, toolContext }) => {
const result = await runDrySend({
cfg,
actionParams,
...(toolContext ? { toolContext } : {}),
});
expect(result.kind).toBe("send");
@@ -178,144 +186,111 @@ describe("runMessageAction context isolation", () => {
).rejects.toThrow(/message required/i);
});
it("rejects send actions that include poll creation params", async () => {
await expect(
runDrySend({
cfg: slackConfig,
actionParams: {
channel: "slack",
target: "#C12345678",
message: "hi",
pollQuestion: "Ready?",
pollOption: ["Yes", "No"],
},
toolContext: { currentChannelId: "C12345678" },
}),
).rejects.toThrow(/use action "poll" instead of "send"/i);
});
it("rejects send actions that include string-encoded poll params", async () => {
await expect(
runDrySend({
cfg: slackConfig,
actionParams: {
channel: "slack",
target: "#C12345678",
message: "hi",
pollDurationSeconds: "60",
pollPublic: "true",
},
toolContext: { currentChannelId: "C12345678" },
}),
).rejects.toThrow(/use action "poll" instead of "send"/i);
});
it("rejects send actions that include snake_case poll params", async () => {
await expect(
runDrySend({
cfg: slackConfig,
actionParams: {
channel: "slack",
target: "#C12345678",
message: "hi",
poll_question: "Ready?",
poll_option: ["Yes", "No"],
poll_public: "true",
},
toolContext: { currentChannelId: "C12345678" },
}),
).rejects.toThrow(/use action "poll" instead of "send"/i);
});
it("allows send when poll booleans are explicitly false", async () => {
const result = await runDrySend({
cfg: slackConfig,
it.each([
{
name: "structured poll params",
actionParams: {
channel: "slack",
target: "#C12345678",
message: "hi",
pollMulti: false,
pollAnonymous: false,
pollPublic: false,
pollQuestion: "Ready?",
pollOption: ["Yes", "No"],
},
toolContext: { currentChannelId: "C12345678" },
});
expect(result.kind).toBe("send");
});
it("blocks send when target differs from current channel", async () => {
const result = await runDrySend({
cfg: slackConfig,
},
{
name: "string-encoded poll params",
actionParams: {
channel: "slack",
target: "channel:C99999999",
target: "#C12345678",
message: "hi",
pollDurationSeconds: "60",
pollPublic: "true",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
});
expect(result.kind).toBe("send");
});
it("blocks thread-reply when channelId differs from current channel", async () => {
const result = await runDryAction({
cfg: slackConfig,
action: "thread-reply",
},
{
name: "snake_case poll params",
actionParams: {
channel: "slack",
target: "C99999999",
target: "#C12345678",
message: "hi",
poll_question: "Ready?",
poll_option: ["Yes", "No"],
poll_public: "true",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
});
expect(result.kind).toBe("action");
},
])("rejects send actions that include $name", async ({ actionParams }) => {
await expect(
runDrySend({
cfg: slackConfig,
actionParams,
toolContext: { currentChannelId: "C12345678" },
}),
).rejects.toThrow(/use action "poll" instead of "send"/i);
});
it.each([
{
name: "whatsapp",
name: "send when target differs from current slack channel",
run: () =>
runDrySend({
cfg: slackConfig,
actionParams: {
channel: "slack",
target: "channel:C99999999",
message: "hi",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
}),
expectedKind: "send",
},
{
name: "thread-reply when channelId differs from current slack channel",
run: () =>
runDryAction({
cfg: slackConfig,
action: "thread-reply",
actionParams: {
channel: "slack",
target: "C99999999",
message: "hi",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
}),
expectedKind: "action",
},
])("blocks cross-context UI handoff for $name", async ({ run, expectedKind }) => {
const result = await run();
expect(result.kind).toBe(expectedKind);
});
it.each([
{
name: "whatsapp match",
channel: "whatsapp",
target: "123@g.us",
currentChannelId: "123@g.us",
},
{
name: "imessage",
name: "imessage match",
channel: "imessage",
target: "imessage:+15551234567",
currentChannelId: "imessage:+15551234567",
},
] as const)("allows $name send when target matches current context", async (testCase) => {
const result = await runDrySend({
cfg: whatsappConfig,
actionParams: {
channel: testCase.channel,
target: testCase.target,
message: "hi",
},
toolContext: { currentChannelId: testCase.currentChannelId },
});
expect(result.kind).toBe("send");
});
it.each([
{
name: "whatsapp",
name: "whatsapp mismatch",
channel: "whatsapp",
target: "456@g.us",
currentChannelId: "123@g.us",
currentChannelProvider: "whatsapp",
},
{
name: "imessage",
name: "imessage mismatch",
channel: "imessage",
target: "imessage:+15551230000",
currentChannelId: "imessage:+15551234567",
currentChannelProvider: "imessage",
},
] as const)("blocks $name send when target differs from current context", async (testCase) => {
] as const)("$name", async (testCase) => {
const result = await runDrySend({
cfg: whatsappConfig,
actionParams: {
@@ -325,106 +300,115 @@ describe("runMessageAction context isolation", () => {
},
toolContext: {
currentChannelId: testCase.currentChannelId,
currentChannelProvider: testCase.currentChannelProvider,
...(testCase.currentChannelProvider
? { currentChannelProvider: testCase.currentChannelProvider }
: {}),
},
});
expect(result.kind).toBe("send");
});
it("infers channel + target from tool context when missing", async () => {
const multiConfig = {
channels: {
slack: {
botToken: "xoxb-test",
appToken: "xapp-test",
it.each([
{
name: "infers channel + target from tool context when missing",
cfg: {
channels: {
slack: {
botToken: "xoxb-test",
appToken: "xapp-test",
},
telegram: {
token: "tg-test",
},
},
telegram: {
token: "tg-test",
},
},
} as OpenClawConfig;
const result = await runDrySend({
cfg: multiConfig,
} as OpenClawConfig,
action: "send" as const,
actionParams: {
message: "hi",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
});
expect(result.kind).toBe("send");
expect(result.channel).toBe("slack");
});
it("falls back to tool-context provider when channel param is an id", async () => {
const result = await runDrySend({
expectedKind: "send",
expectedChannel: "slack",
},
{
name: "falls back to tool-context provider when channel param is an id",
cfg: slackConfig,
action: "send" as const,
actionParams: {
channel: "C12345678",
target: "#C12345678",
message: "hi",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
});
expect(result.kind).toBe("send");
expect(result.channel).toBe("slack");
});
it("falls back to tool-context provider for broadcast channel ids", async () => {
const result = await runDryAction({
expectedKind: "send",
expectedChannel: "slack",
},
{
name: "falls back to tool-context provider for broadcast channel ids",
cfg: slackConfig,
action: "broadcast",
action: "broadcast" as const,
actionParams: {
targets: ["channel:C12345678"],
channel: "C12345678",
message: "hi",
},
toolContext: { currentChannelProvider: "slack" },
expectedKind: "broadcast",
expectedChannel: "slack",
},
])("$name", async ({ cfg, action, actionParams, toolContext, expectedKind, expectedChannel }) => {
const result = await runDryAction({
cfg,
action,
actionParams,
toolContext,
});
expect(result.kind).toBe("broadcast");
expect(result.channel).toBe("slack");
expect(result.kind).toBe(expectedKind);
expect(result.channel).toBe(expectedChannel);
});
it("blocks cross-provider sends by default", async () => {
await expect(
runDrySend({
cfg: slackConfig,
actionParams: {
channel: "telegram",
target: "@opsbot",
message: "hi",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
}),
).rejects.toThrow(/Cross-context messaging denied/);
});
it("blocks same-provider cross-context when disabled", async () => {
const cfg = {
...slackConfig,
tools: {
message: {
crossContext: {
allowWithinProvider: false,
it.each([
{
name: "blocks cross-provider sends by default",
cfg: slackConfig,
actionParams: {
channel: "telegram",
target: "@opsbot",
message: "hi",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
message: /Cross-context messaging denied/,
},
{
name: "blocks same-provider cross-context when disabled",
cfg: {
...slackConfig,
tools: {
message: {
crossContext: {
allowWithinProvider: false,
},
},
},
} as OpenClawConfig,
actionParams: {
channel: "slack",
target: "channel:C99999999",
message: "hi",
},
} as OpenClawConfig;
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
message: /Cross-context messaging denied/,
},
])("$name", async ({ cfg, actionParams, toolContext, message }) => {
await expect(
runDrySend({
cfg,
actionParams: {
channel: "slack",
target: "channel:C99999999",
message: "hi",
},
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
actionParams,
toolContext,
}),
).rejects.toThrow(/Cross-context messaging denied/);
).rejects.toThrow(message);
});
it.each([