mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 16:34:45 +00:00
test: clear slack message action broad matchers
This commit is contained in:
@@ -16,6 +16,32 @@ function expectForwardedCfg(invoke: ReturnType<typeof createInvokeSpy>, cfg: unk
|
||||
expect(invoke.mock.calls[0]?.[1]).toBe(cfg);
|
||||
}
|
||||
|
||||
function firstAction(invoke: ReturnType<typeof createInvokeSpy>) {
|
||||
const action = invoke.mock.calls[0]?.[0];
|
||||
if (!action || typeof action !== "object") {
|
||||
throw new Error("expected first invoke action");
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
function blockAt(action: Record<string, unknown>, index: number) {
|
||||
const blocks = action.blocks as Array<Record<string, unknown>> | undefined;
|
||||
const block = blocks?.[index];
|
||||
if (!block) {
|
||||
throw new Error(`expected Slack block ${index}`);
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
function elementAt(block: Record<string, unknown>, index: number) {
|
||||
const elements = block.elements as Array<Record<string, unknown>> | undefined;
|
||||
const element = elements?.[index];
|
||||
if (!element) {
|
||||
throw new Error(`expected Slack block element ${index}`);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
describe("handleSlackMessageAction", () => {
|
||||
it("merges presentation and interactive blocks when sending", async () => {
|
||||
const invoke = createInvokeSpy();
|
||||
@@ -44,16 +70,11 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
const action = invoke.mock.calls[0]?.[0] as {
|
||||
blocks?: Array<{ type?: string; elements?: Array<{ value?: string }> }>;
|
||||
};
|
||||
expect(action.blocks).toEqual([
|
||||
expect.objectContaining({ type: "section" }),
|
||||
expect.objectContaining({
|
||||
type: "actions",
|
||||
elements: [expect.objectContaining({ value: "approve" })],
|
||||
}),
|
||||
]);
|
||||
const action = firstAction(invoke);
|
||||
expect(blockAt(action, 0).type).toBe("section");
|
||||
const actionsBlock = blockAt(action, 1);
|
||||
expect(actionsBlock.type).toBe("actions");
|
||||
expect(elementAt(actionsBlock, 0).value).toBe("approve");
|
||||
});
|
||||
|
||||
it("keeps generated Slack control ids unique when presentation and interactive controls are merged", async () => {
|
||||
@@ -88,23 +109,13 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
const action = invoke.mock.calls[0]?.[0] as {
|
||||
blocks?: Array<{
|
||||
block_id?: string;
|
||||
elements?: Array<{ action_id?: string; value?: string }>;
|
||||
}>;
|
||||
};
|
||||
|
||||
expect(action.blocks).toEqual([
|
||||
expect.objectContaining({
|
||||
block_id: "openclaw_reply_buttons_1",
|
||||
elements: [expect.objectContaining({ action_id: "openclaw:reply_button:1:1" })],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
block_id: "openclaw_reply_buttons_2",
|
||||
elements: [expect.objectContaining({ action_id: "openclaw:reply_button:2:1" })],
|
||||
}),
|
||||
]);
|
||||
const action = firstAction(invoke);
|
||||
const firstButtons = blockAt(action, 0);
|
||||
expect(firstButtons.block_id).toBe("openclaw_reply_buttons_1");
|
||||
expect(elementAt(firstButtons, 0).action_id).toBe("openclaw:reply_button:1:1");
|
||||
const secondButtons = blockAt(action, 1);
|
||||
expect(secondButtons.block_id).toBe("openclaw_reply_buttons_2");
|
||||
expect(elementAt(secondButtons, 0).action_id).toBe("openclaw:reply_button:2:1");
|
||||
});
|
||||
|
||||
it("passes media and rendered interactive blocks through for split Slack delivery", async () => {
|
||||
@@ -134,23 +145,16 @@ describe("handleSlackMessageAction", () => {
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledOnce();
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "sendMessage",
|
||||
to: "channel:C1",
|
||||
content: "Approval required",
|
||||
mediaUrl: "https://example.com/report.md",
|
||||
blocks: [
|
||||
expect.objectContaining({
|
||||
type: "actions",
|
||||
elements: [expect.objectContaining({ value: "approve" })],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
cfg,
|
||||
undefined,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("sendMessage");
|
||||
expect(action.to).toBe("channel:C1");
|
||||
expect(action.content).toBe("Approval required");
|
||||
expect(action.mediaUrl).toBe("https://example.com/report.md");
|
||||
const actionsBlock = blockAt(action, 0);
|
||||
expect(actionsBlock.type).toBe("actions");
|
||||
expect(elementAt(actionsBlock, 0).value).toBe("approve");
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("passes replyBroadcast through for Slack thread sends", async () => {
|
||||
@@ -172,17 +176,14 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "sendMessage",
|
||||
to: "channel:C1",
|
||||
content: "Visible from the channel",
|
||||
threadTs: "111.222",
|
||||
replyBroadcast: true,
|
||||
}),
|
||||
cfg,
|
||||
undefined,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("sendMessage");
|
||||
expect(action.to).toBe("channel:C1");
|
||||
expect(action.content).toBe("Visible from the channel");
|
||||
expect(action.threadTs).toBe("111.222");
|
||||
expect(action.replyBroadcast).toBe(true);
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("passes topLevel through so same-channel Slack sends can suppress thread inheritance", async () => {
|
||||
@@ -203,17 +204,14 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "sendMessage",
|
||||
to: "channel:C1",
|
||||
content: "Visible in the parent channel",
|
||||
threadTs: undefined,
|
||||
topLevel: true,
|
||||
}),
|
||||
cfg,
|
||||
undefined,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("sendMessage");
|
||||
expect(action.to).toBe("channel:C1");
|
||||
expect(action.content).toBe("Visible in the parent channel");
|
||||
expect(action.threadTs).toBeUndefined();
|
||||
expect(action.topLevel).toBe(true);
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("treats threadId null as a Slack top-level send request", async () => {
|
||||
@@ -234,15 +232,12 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "sendMessage",
|
||||
threadTs: undefined,
|
||||
topLevel: true,
|
||||
}),
|
||||
cfg,
|
||||
undefined,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("sendMessage");
|
||||
expect(action.threadTs).toBeUndefined();
|
||||
expect(action.topLevel).toBe(true);
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps upload-file to the internal uploadFile action", async () => {
|
||||
@@ -266,20 +261,16 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "uploadFile",
|
||||
to: "user:U1",
|
||||
filePath: "/tmp/report.png",
|
||||
initialComment: "fresh build",
|
||||
filename: "build.png",
|
||||
title: "Build Screenshot",
|
||||
threadTs: "111.222",
|
||||
}),
|
||||
cfg,
|
||||
undefined,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("uploadFile");
|
||||
expect(action.to).toBe("user:U1");
|
||||
expect(action.filePath).toBe("/tmp/report.png");
|
||||
expect(action.initialComment).toBe("fresh build");
|
||||
expect(action.filename).toBe("build.png");
|
||||
expect(action.title).toBe("Build Screenshot");
|
||||
expect(action.threadTs).toBe("111.222");
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects replyBroadcast for upload-file", async () => {
|
||||
@@ -320,18 +311,14 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "uploadFile",
|
||||
to: "C1",
|
||||
filePath: "/tmp/chart.png",
|
||||
initialComment: "chart attached",
|
||||
threadTs: "333.444",
|
||||
}),
|
||||
cfg,
|
||||
undefined,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("uploadFile");
|
||||
expect(action.to).toBe("C1");
|
||||
expect(action.filePath).toBe("/tmp/chart.png");
|
||||
expect(action.initialComment).toBe("chart attached");
|
||||
expect(action.threadTs).toBe("333.444");
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps upload-file path alias to filePath", async () => {
|
||||
@@ -352,17 +339,13 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "uploadFile",
|
||||
to: "channel:C1",
|
||||
filePath: "/tmp/report.txt",
|
||||
initialComment: "path alias",
|
||||
}),
|
||||
cfg,
|
||||
undefined,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("uploadFile");
|
||||
expect(action.to).toBe("channel:C1");
|
||||
expect(action.filePath).toBe("/tmp/report.txt");
|
||||
expect(action.initialComment).toBe("path alias");
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards messageId for read actions", async () => {
|
||||
@@ -381,14 +364,11 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "readMessages",
|
||||
channelId: "C1",
|
||||
messageId: "1712345678.654321",
|
||||
}),
|
||||
{},
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("readMessages");
|
||||
expect(action.channelId).toBe("C1");
|
||||
expect(action.messageId).toBe("1712345678.654321");
|
||||
expect(invoke.mock.calls[0]?.[1]).toEqual({});
|
||||
});
|
||||
|
||||
it("requires filePath, path, or media for upload-file", async () => {
|
||||
@@ -425,15 +405,11 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "downloadFile",
|
||||
fileId: "F123",
|
||||
channelId: "C1",
|
||||
threadId: "111.222",
|
||||
}),
|
||||
cfg,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("downloadFile");
|
||||
expect(action.fileId).toBe("F123");
|
||||
expect(action.channelId).toBe("C1");
|
||||
expect(action.threadId).toBe("111.222");
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
});
|
||||
|
||||
@@ -455,15 +431,11 @@ describe("handleSlackMessageAction", () => {
|
||||
invoke: invoke as never,
|
||||
});
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "downloadFile",
|
||||
fileId: "F999",
|
||||
channelId: "channel:C2",
|
||||
threadId: "333.444",
|
||||
}),
|
||||
cfg,
|
||||
);
|
||||
const action = firstAction(invoke);
|
||||
expect(action.action).toBe("downloadFile");
|
||||
expect(action.fileId).toBe("F999");
|
||||
expect(action.channelId).toBe("channel:C2");
|
||||
expect(action.threadId).toBe("333.444");
|
||||
expectForwardedCfg(invoke, cfg);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user