mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 23:56:07 +00:00
test: clear feishu channel broad matchers
This commit is contained in:
@@ -59,6 +59,36 @@ function getDescribedActions(cfg: OpenClawConfig, accountId?: string): string[]
|
||||
return [...(feishuPlugin.actions?.describeMessageTool?.({ cfg, accountId })?.actions ?? [])];
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new Error(`Expected ${label}`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function requireArray(value: unknown, label: string): unknown[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error(`Expected ${label}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function mockCallArg(mock: unknown, callIndex: number, argIndex: number, label: string) {
|
||||
const calls = (mock as { mock?: { calls?: unknown[][] } }).mock?.calls;
|
||||
if (!Array.isArray(calls)) {
|
||||
throw new Error(`Expected ${label} mock calls`);
|
||||
}
|
||||
const call = calls[callIndex];
|
||||
if (!call) {
|
||||
throw new Error(`Expected ${label} call ${callIndex + 1}`);
|
||||
}
|
||||
return call[argIndex];
|
||||
}
|
||||
|
||||
function resultDetails(result: unknown) {
|
||||
return requireRecord(requireRecord(result, "action result").details, "action result details");
|
||||
}
|
||||
|
||||
afterAll(() => {
|
||||
vi.doUnmock("./probe.js");
|
||||
vi.doUnmock("./client.js");
|
||||
@@ -93,14 +123,16 @@ describe("feishuPlugin.status.probeAccount", () => {
|
||||
});
|
||||
|
||||
expect(probeFeishuMock).toHaveBeenCalledTimes(1);
|
||||
expect(probeFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
accountId: "main",
|
||||
appId: "cli_main",
|
||||
appSecret: "secret_main",
|
||||
}),
|
||||
const probeArgs = requireRecord(
|
||||
mockCallArg(probeFeishuMock, 0, 0, "probeFeishu"),
|
||||
"probe args",
|
||||
);
|
||||
expect(result).toMatchObject({ ok: true, appId: "cli_main" });
|
||||
expect(probeArgs.accountId).toBe("main");
|
||||
expect(probeArgs.appId).toBe("cli_main");
|
||||
expect(probeArgs.appSecret).toBe("secret_main");
|
||||
const resultRecord = requireRecord(result, "probe result");
|
||||
expect(resultRecord.ok).toBe(true);
|
||||
expect(resultRecord.appId).toBe("cli_main");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,13 +163,13 @@ describe("feishuPlugin.pairing.notifyApproval", () => {
|
||||
accountId: "work",
|
||||
});
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cfg,
|
||||
to: "ou_user",
|
||||
accountId: "work",
|
||||
}),
|
||||
const sendArgs = requireRecord(
|
||||
mockCallArg(sendMessageFeishuMock, 0, 0, "sendMessageFeishu"),
|
||||
"send args",
|
||||
);
|
||||
expect(sendArgs.cfg).toBe(cfg);
|
||||
expect(sendArgs.to).toBe("ou_user");
|
||||
expect(sendArgs.accountId).toBe("work");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -311,7 +343,10 @@ describe("feishuPlugin actions", () => {
|
||||
replyToMessageId: undefined,
|
||||
replyInThread: false,
|
||||
});
|
||||
expect(result?.details).toMatchObject({ ok: true, messageId: "om_sent", chatId: "oc_group_1" });
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
expect(details.messageId).toBe("om_sent");
|
||||
expect(details.chatId).toBe("oc_group_1");
|
||||
});
|
||||
|
||||
it("renders presentation messages as cards", async () => {
|
||||
@@ -331,29 +366,33 @@ describe("feishuPlugin actions", () => {
|
||||
toolContext: {},
|
||||
} as never);
|
||||
|
||||
expect(sendCardFeishuMock).toHaveBeenCalledWith({
|
||||
cfg,
|
||||
to: "chat:oc_group_1",
|
||||
card: expect.objectContaining({
|
||||
schema: "2.0",
|
||||
header: {
|
||||
title: { tag: "plain_text", content: "Status" },
|
||||
template: "blue",
|
||||
},
|
||||
body: {
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "Build completed",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
accountId: undefined,
|
||||
replyToMessageId: undefined,
|
||||
replyInThread: false,
|
||||
const sendCardArgs = requireRecord(
|
||||
mockCallArg(sendCardFeishuMock, 0, 0, "sendCardFeishu"),
|
||||
"send card args",
|
||||
);
|
||||
expect(sendCardArgs.cfg).toBe(cfg);
|
||||
expect(sendCardArgs.to).toBe("chat:oc_group_1");
|
||||
expect(sendCardArgs.accountId).toBeUndefined();
|
||||
expect(sendCardArgs.replyToMessageId).toBeUndefined();
|
||||
expect(sendCardArgs.replyInThread).toBe(false);
|
||||
const card = requireRecord(sendCardArgs.card, "card");
|
||||
expect(card.schema).toBe("2.0");
|
||||
expect(card.header).toEqual({
|
||||
title: { tag: "plain_text", content: "Status" },
|
||||
template: "blue",
|
||||
});
|
||||
expect(result?.details).toMatchObject({ ok: true, messageId: "om_card", chatId: "oc_group_1" });
|
||||
expect(card.body).toEqual({
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "Build completed",
|
||||
},
|
||||
],
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
expect(details.messageId).toBe("om_card");
|
||||
expect(details.chatId).toBe("oc_group_1");
|
||||
});
|
||||
|
||||
it("renders presentation button labels into the card fallback", async () => {
|
||||
@@ -377,20 +416,17 @@ describe("feishuPlugin actions", () => {
|
||||
toolContext: {},
|
||||
} as never);
|
||||
|
||||
expect(sendCardFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
card: expect.objectContaining({
|
||||
body: {
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "- Run help",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
const sendCardArgs = requireRecord(
|
||||
mockCallArg(sendCardFeishuMock, 0, 0, "sendCardFeishu"),
|
||||
"send card args",
|
||||
);
|
||||
const card = requireRecord(sendCardArgs.card, "card");
|
||||
expect(requireRecord(card.body, "card body").elements).toEqual([
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "- Run help",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders presentation select labels into the card fallback", async () => {
|
||||
@@ -415,20 +451,17 @@ describe("feishuPlugin actions", () => {
|
||||
toolContext: {},
|
||||
} as never);
|
||||
|
||||
expect(sendCardFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
card: expect.objectContaining({
|
||||
body: {
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "Pick one:\n- Option A",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
const sendCardArgs = requireRecord(
|
||||
mockCallArg(sendCardFeishuMock, 0, 0, "sendCardFeishu"),
|
||||
"send card args",
|
||||
);
|
||||
const card = requireRecord(sendCardArgs.card, "card");
|
||||
expect(requireRecord(card.body, "card body").elements).toEqual([
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "Pick one:\n- Option A",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("sends media through the outbound adapter", async () => {
|
||||
@@ -460,7 +493,7 @@ describe("feishuPlugin actions", () => {
|
||||
mediaLocalRoots: ["/tmp"],
|
||||
replyToId: undefined,
|
||||
});
|
||||
expect(result?.details).toMatchObject({ messageId: "om_media" });
|
||||
expect(resultDetails(result).messageId).toBe("om_media");
|
||||
});
|
||||
|
||||
it("passes asVoice through media sends", async () => {
|
||||
@@ -483,12 +516,12 @@ describe("feishuPlugin actions", () => {
|
||||
mediaLocalRoots: [],
|
||||
} as never);
|
||||
|
||||
expect(feishuOutboundSendMediaMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mediaUrl: "https://example.com/reply.mp3",
|
||||
audioAsVoice: true,
|
||||
}),
|
||||
const mediaArgs = requireRecord(
|
||||
mockCallArg(feishuOutboundSendMediaMock, 0, 0, "feishuOutbound.sendMedia"),
|
||||
"media args",
|
||||
);
|
||||
expect(mediaArgs.mediaUrl).toBe("https://example.com/reply.mp3");
|
||||
expect(mediaArgs.audioAsVoice).toBe(true);
|
||||
});
|
||||
|
||||
it("reads messages", async () => {
|
||||
@@ -510,10 +543,11 @@ describe("feishuPlugin actions", () => {
|
||||
messageId: "om_1",
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
message: expect.objectContaining({ messageId: "om_1", content: "hello" }),
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
const message = requireRecord(details.message, "read message");
|
||||
expect(message.messageId).toBe("om_1");
|
||||
expect(message.content).toBe("hello");
|
||||
});
|
||||
|
||||
it("returns an error result when message reads fail", async () => {
|
||||
@@ -549,7 +583,10 @@ describe("feishuPlugin actions", () => {
|
||||
card: undefined,
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(result?.details).toMatchObject({ ok: true, messageId: "om_2", contentType: "post" });
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
expect(details.messageId).toBe("om_2");
|
||||
expect(details.contentType).toBe("post");
|
||||
});
|
||||
|
||||
it("sends explicit thread replies with reply_in_thread semantics", async () => {
|
||||
@@ -571,11 +608,10 @@ describe("feishuPlugin actions", () => {
|
||||
replyToMessageId: "om_parent",
|
||||
replyInThread: true,
|
||||
});
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
action: "thread-reply",
|
||||
messageId: "om_reply",
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
expect(details.action).toBe("thread-reply");
|
||||
expect(details.messageId).toBe("om_reply");
|
||||
});
|
||||
|
||||
it("auto-threads `send` text against the inbound trigger in group_topic sessions", async () => {
|
||||
@@ -618,12 +654,12 @@ describe("feishuPlugin actions", () => {
|
||||
toolContext: { currentMessageId: "om_inbound" },
|
||||
} as never);
|
||||
|
||||
expect(sendCardFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_inbound",
|
||||
replyInThread: true,
|
||||
}),
|
||||
const sendCardArgs = requireRecord(
|
||||
mockCallArg(sendCardFeishuMock, 0, 0, "sendCardFeishu"),
|
||||
"send card args",
|
||||
);
|
||||
expect(sendCardArgs.replyToMessageId).toBe("om_inbound");
|
||||
expect(sendCardArgs.replyInThread).toBe(true);
|
||||
});
|
||||
|
||||
it("auto-threads `send` media against the inbound trigger in group_topic sessions", async () => {
|
||||
@@ -647,14 +683,12 @@ describe("feishuPlugin actions", () => {
|
||||
mediaLocalRoots: ["/tmp"],
|
||||
} as never);
|
||||
|
||||
expect(feishuOutboundSendMediaMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
threadId: "om_inbound",
|
||||
}),
|
||||
);
|
||||
expect(feishuOutboundSendMediaMock).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ replyToId: expect.anything() }),
|
||||
const mediaArgs = requireRecord(
|
||||
mockCallArg(feishuOutboundSendMediaMock, 0, 0, "feishuOutbound.sendMedia"),
|
||||
"media args",
|
||||
);
|
||||
expect(mediaArgs.threadId).toBe("om_inbound");
|
||||
expect("replyToId" in mediaArgs).toBe(false);
|
||||
});
|
||||
|
||||
it("auto-threads `send` in group_topic_sender sessions too", async () => {
|
||||
@@ -669,12 +703,12 @@ describe("feishuPlugin actions", () => {
|
||||
toolContext: { currentMessageId: "om_inbound" },
|
||||
} as never);
|
||||
|
||||
expect(sendMessageFeishuMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_inbound",
|
||||
replyInThread: true,
|
||||
}),
|
||||
const sendArgs = requireRecord(
|
||||
mockCallArg(sendMessageFeishuMock, 0, 0, "sendMessageFeishu"),
|
||||
"send args",
|
||||
);
|
||||
expect(sendArgs.replyToMessageId).toBe("om_inbound");
|
||||
expect(sendArgs.replyInThread).toBe(true);
|
||||
});
|
||||
|
||||
it("does not auto-thread `send` in plain group sessions (no topic)", async () => {
|
||||
@@ -736,10 +770,9 @@ describe("feishuPlugin actions", () => {
|
||||
messageId: "om_pin",
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
pin: expect.objectContaining({ messageId: "om_pin" }),
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
expect(requireRecord(details.pin, "pin").messageId).toBe("om_pin");
|
||||
});
|
||||
|
||||
it("lists pins", async () => {
|
||||
@@ -767,10 +800,10 @@ describe("feishuPlugin actions", () => {
|
||||
pageToken: undefined,
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
pins: [expect.objectContaining({ messageId: "om_pin" })],
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
const pins = requireArray(details.pins, "pins");
|
||||
expect(requireRecord(pins[0], "pin").messageId).toBe("om_pin");
|
||||
});
|
||||
|
||||
it("removes pins", async () => {
|
||||
@@ -786,7 +819,9 @@ describe("feishuPlugin actions", () => {
|
||||
messageId: "om_pin",
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(result?.details).toMatchObject({ ok: true, messageId: "om_pin" });
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
expect(details.messageId).toBe("om_pin");
|
||||
});
|
||||
|
||||
it("fetches channel info", async () => {
|
||||
@@ -802,10 +837,11 @@ describe("feishuPlugin actions", () => {
|
||||
|
||||
expect(createFeishuClientMock).toHaveBeenCalled();
|
||||
expect(getChatInfoMock).toHaveBeenCalledWith({ tag: "client" }, "oc_group_1");
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
channel: expect.objectContaining({ chat_id: "oc_group_1", name: "Eng" }),
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
const channel = requireRecord(details.channel, "channel");
|
||||
expect(channel.chat_id).toBe("oc_group_1");
|
||||
expect(channel.name).toBe("Eng");
|
||||
});
|
||||
|
||||
it("fetches member lists from a chat", async () => {
|
||||
@@ -830,10 +866,12 @@ describe("feishuPlugin actions", () => {
|
||||
undefined,
|
||||
"open_id",
|
||||
);
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
members: [expect.objectContaining({ member_id: "ou_1", name: "Alice" })],
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
const members = requireArray(details.members, "members");
|
||||
const member = requireRecord(members[0], "member");
|
||||
expect(member.member_id).toBe("ou_1");
|
||||
expect(member.name).toBe("Alice");
|
||||
});
|
||||
|
||||
it("fetches individual member info", async () => {
|
||||
@@ -848,10 +886,11 @@ describe("feishuPlugin actions", () => {
|
||||
} as never);
|
||||
|
||||
expect(getFeishuMemberInfoMock).toHaveBeenCalledWith({ tag: "client" }, "ou_1", "open_id");
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
member: expect.objectContaining({ member_id: "ou_1", name: "Alice" }),
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
const member = requireRecord(details.member, "member");
|
||||
expect(member.member_id).toBe("ou_1");
|
||||
expect(member.name).toBe("Alice");
|
||||
});
|
||||
|
||||
it("infers user_id lookups from the userId alias", async () => {
|
||||
@@ -907,11 +946,12 @@ describe("feishuPlugin actions", () => {
|
||||
fallbackToStatic: false,
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(result?.details).toMatchObject({
|
||||
ok: true,
|
||||
groups: [expect.objectContaining({ id: "oc_group_1" })],
|
||||
peers: [expect.objectContaining({ id: "ou_1" })],
|
||||
});
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
const groups = requireArray(details.groups, "groups");
|
||||
const peers = requireArray(details.peers, "peers");
|
||||
expect(requireRecord(groups[0], "group").id).toBe("oc_group_1");
|
||||
expect(requireRecord(peers[0], "peer").id).toBe("ou_1");
|
||||
});
|
||||
|
||||
it("fails channel-list when live discovery fails", async () => {
|
||||
@@ -959,7 +999,9 @@ describe("feishuPlugin actions", () => {
|
||||
accountId: undefined,
|
||||
});
|
||||
expect(removeReactionFeishuMock).toHaveBeenCalledTimes(2);
|
||||
expect(result?.details).toMatchObject({ ok: true, removed: 2 });
|
||||
const details = resultDetails(result);
|
||||
expect(details.ok).toBe(true);
|
||||
expect(details.removed).toBe(2);
|
||||
});
|
||||
|
||||
it("fails for missing params on supported actions", async () => {
|
||||
@@ -992,13 +1034,13 @@ describe("feishuPlugin actions", () => {
|
||||
mediaLocalRoots: [],
|
||||
} as never);
|
||||
|
||||
expect(feishuOutboundSendMediaMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: "chat:oc_group_1",
|
||||
mediaUrl: "https://example.com/image.png",
|
||||
}),
|
||||
const mediaArgs = requireRecord(
|
||||
mockCallArg(feishuOutboundSendMediaMock, 0, 0, "feishuOutbound.sendMedia"),
|
||||
"media args",
|
||||
);
|
||||
expect(result?.details).toMatchObject({ messageId: "om_media_only" });
|
||||
expect(mediaArgs.to).toBe("chat:oc_group_1");
|
||||
expect(mediaArgs.mediaUrl).toBe("https://example.com/image.png");
|
||||
expect(resultDetails(result).messageId).toBe("om_media_only");
|
||||
});
|
||||
|
||||
it("fails for unsupported action names", async () => {
|
||||
|
||||
Reference in New Issue
Block a user