mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 20:44:47 +00:00
test: clear whatsapp send api broad matchers
This commit is contained in:
@@ -42,18 +42,50 @@ describe("createWebSendApi", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
expect(typeof value).toBe("object");
|
||||
expect(value).not.toBeNull();
|
||||
if (typeof value !== "object" || value === null) {
|
||||
throw new Error(`${label} was not an object`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function expectRecordFields(record: Record<string, unknown>, fields: Record<string, unknown>) {
|
||||
for (const [key, value] of Object.entries(fields)) {
|
||||
expect(record[key]).toEqual(value);
|
||||
}
|
||||
}
|
||||
|
||||
function requireSendContent(callIndex = 0): Record<string, unknown> {
|
||||
return requireRecord(sendMessage.mock.calls[callIndex]?.[1], "sent message content");
|
||||
}
|
||||
|
||||
function requireSendOptions(callIndex = 0): Record<string, unknown> {
|
||||
return requireRecord(sendMessage.mock.calls[callIndex]?.[2], "sent message options");
|
||||
}
|
||||
|
||||
function expectSendContentFields(callIndex: number, fields: Record<string, unknown>) {
|
||||
expectRecordFields(requireSendContent(callIndex), fields);
|
||||
}
|
||||
|
||||
function expectSendResultFields(
|
||||
result: Awaited<ReturnType<typeof api.sendMessage | typeof api.sendReaction>>,
|
||||
fields: Record<string, unknown>,
|
||||
) {
|
||||
expectRecordFields(requireRecord(result, "send result"), fields);
|
||||
}
|
||||
|
||||
it("uses sendOptions fileName for outbound documents", async () => {
|
||||
const payload = Buffer.from("pdf");
|
||||
await api.sendMessage("+1555", "doc", payload, "application/pdf", { fileName: "invoice.pdf" });
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
document: payload,
|
||||
fileName: "invoice.pdf",
|
||||
caption: "doc",
|
||||
mimetype: "application/pdf",
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expectSendContentFields(0, {
|
||||
document: payload,
|
||||
fileName: "invoice.pdf",
|
||||
caption: "doc",
|
||||
mimetype: "application/pdf",
|
||||
});
|
||||
expect(recordChannelActivity).toHaveBeenCalledWith({
|
||||
channel: "whatsapp",
|
||||
accountId: "main",
|
||||
@@ -64,21 +96,19 @@ describe("createWebSendApi", () => {
|
||||
it("falls back to default document filename when fileName is absent", async () => {
|
||||
const payload = Buffer.from("pdf");
|
||||
await api.sendMessage("+1555", "doc", payload, "application/pdf");
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
document: payload,
|
||||
fileName: "file",
|
||||
caption: "doc",
|
||||
mimetype: "application/pdf",
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expectSendContentFields(0, {
|
||||
document: payload,
|
||||
fileName: "file",
|
||||
caption: "doc",
|
||||
mimetype: "application/pdf",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends plain text messages", async () => {
|
||||
const res = await api.sendMessage("+1555", "hello");
|
||||
expect(sendMessage).toHaveBeenCalledWith("1555@s.whatsapp.net", { text: "hello" });
|
||||
expect(res).toMatchObject({
|
||||
expectSendResultFields(res, {
|
||||
kind: "text",
|
||||
messageId: "msg-1",
|
||||
providerAccepted: true,
|
||||
@@ -119,14 +149,12 @@ describe("createWebSendApi", () => {
|
||||
it("supports image media with caption", async () => {
|
||||
const payload = Buffer.from("img");
|
||||
await api.sendMessage("+1555", "cap", payload, "image/jpeg");
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
image: payload,
|
||||
caption: "cap",
|
||||
mimetype: "image/jpeg",
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expectSendContentFields(0, {
|
||||
image: payload,
|
||||
caption: "cap",
|
||||
mimetype: "image/jpeg",
|
||||
});
|
||||
});
|
||||
|
||||
it("adds native mention metadata to group media captions", async () => {
|
||||
@@ -144,28 +172,24 @@ describe("createWebSendApi", () => {
|
||||
|
||||
await api.sendMessage("120363000000000000@g.us", "cap @15551234567", payload, "image/jpeg");
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"120363000000000000@g.us",
|
||||
expect.objectContaining({
|
||||
image: payload,
|
||||
caption: "cap @15551234567",
|
||||
mimetype: "image/jpeg",
|
||||
mentions: ["15551234567@s.whatsapp.net"],
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("120363000000000000@g.us");
|
||||
expectSendContentFields(0, {
|
||||
image: payload,
|
||||
caption: "cap @15551234567",
|
||||
mimetype: "image/jpeg",
|
||||
mentions: ["15551234567@s.whatsapp.net"],
|
||||
});
|
||||
});
|
||||
|
||||
it("supports audio as push-to-talk voice note", async () => {
|
||||
const payload = Buffer.from("aud");
|
||||
await api.sendMessage("+1555", "", payload, "audio/ogg", { accountId: "alt" });
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
audio: payload,
|
||||
ptt: true,
|
||||
mimetype: "audio/ogg",
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expectSendContentFields(0, {
|
||||
audio: payload,
|
||||
ptt: true,
|
||||
mimetype: "audio/ogg",
|
||||
});
|
||||
expect(recordChannelActivity).toHaveBeenCalledWith({
|
||||
channel: "whatsapp",
|
||||
accountId: "alt",
|
||||
@@ -179,19 +203,16 @@ describe("createWebSendApi", () => {
|
||||
.mockResolvedValueOnce({ key: { id: "voice-1" } })
|
||||
.mockResolvedValueOnce({ key: { id: "voice-text-1" } });
|
||||
const res = await api.sendMessage("+1555", "voice text", payload, "audio/ogg");
|
||||
expect(sendMessage).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
audio: payload,
|
||||
ptt: true,
|
||||
mimetype: "audio/ogg",
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expectSendContentFields(0, {
|
||||
audio: payload,
|
||||
ptt: true,
|
||||
mimetype: "audio/ogg",
|
||||
});
|
||||
expect(sendMessage).toHaveBeenNthCalledWith(2, "1555@s.whatsapp.net", {
|
||||
text: "voice text",
|
||||
});
|
||||
expect(res).toMatchObject({
|
||||
expectSendResultFields(res, {
|
||||
kind: "media",
|
||||
messageId: "voice-1",
|
||||
providerAccepted: true,
|
||||
@@ -205,15 +226,13 @@ describe("createWebSendApi", () => {
|
||||
it("supports video media and gifPlayback option", async () => {
|
||||
const payload = Buffer.from("vid");
|
||||
await api.sendMessage("+1555", "cap", payload, "video/mp4", { gifPlayback: true });
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
video: payload,
|
||||
caption: "cap",
|
||||
mimetype: "video/mp4",
|
||||
gifPlayback: true,
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expectSendContentFields(0, {
|
||||
video: payload,
|
||||
caption: "cap",
|
||||
mimetype: "video/mp4",
|
||||
gifPlayback: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to unknown messageId if Baileys result does not expose key.id", async () => {
|
||||
@@ -228,12 +247,12 @@ describe("createWebSendApi", () => {
|
||||
options: ["a", "b"],
|
||||
maxSelections: 2,
|
||||
});
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
poll: { name: "Q?", values: ["a", "b"], selectableCount: 2 },
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expect(requireSendContent().poll).toEqual({
|
||||
name: "Q?",
|
||||
values: ["a", "b"],
|
||||
selectableCount: 2,
|
||||
});
|
||||
expect(res.messageId).toBe("msg-1");
|
||||
expect(recordChannelActivity).toHaveBeenCalledWith({
|
||||
channel: "whatsapp",
|
||||
@@ -244,21 +263,16 @@ describe("createWebSendApi", () => {
|
||||
|
||||
it("sends reactions with participant JID normalization", async () => {
|
||||
const res = await api.sendReaction("+1555", "msg-2", "👍", false, "+1999");
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
react: {
|
||||
text: "👍",
|
||||
key: expect.objectContaining({
|
||||
remoteJid: "1555@s.whatsapp.net",
|
||||
id: "msg-2",
|
||||
fromMe: false,
|
||||
participant: "1999@s.whatsapp.net",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(res).toMatchObject({
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
const react = requireRecord(requireSendContent().react, "reaction content");
|
||||
expect(react.text).toBe("👍");
|
||||
expectRecordFields(requireRecord(react.key, "reaction key"), {
|
||||
remoteJid: "1555@s.whatsapp.net",
|
||||
id: "msg-2",
|
||||
fromMe: false,
|
||||
participant: "1999@s.whatsapp.net",
|
||||
});
|
||||
expectSendResultFields(res, {
|
||||
kind: "reaction",
|
||||
messageId: "msg-1",
|
||||
providerAccepted: true,
|
||||
@@ -270,7 +284,7 @@ describe("createWebSendApi", () => {
|
||||
|
||||
const res = await api.sendMessage("+1555", "hello");
|
||||
|
||||
expect(res).toMatchObject({
|
||||
expectSendResultFields(res, {
|
||||
kind: "text",
|
||||
messageId: "unknown",
|
||||
providerAccepted: false,
|
||||
@@ -280,38 +294,28 @@ describe("createWebSendApi", () => {
|
||||
|
||||
it("keeps direct-chat reactions without a participant key", async () => {
|
||||
await api.sendReaction("+1555", "msg-2", "👍", false);
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
react: {
|
||||
text: "👍",
|
||||
key: expect.objectContaining({
|
||||
remoteJid: "1555@s.whatsapp.net",
|
||||
id: "msg-2",
|
||||
fromMe: false,
|
||||
participant: undefined,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
const react = requireRecord(requireSendContent().react, "reaction content");
|
||||
expect(react.text).toBe("👍");
|
||||
expectRecordFields(requireRecord(react.key, "reaction key"), {
|
||||
remoteJid: "1555@s.whatsapp.net",
|
||||
id: "msg-2",
|
||||
fromMe: false,
|
||||
participant: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves LID participants in reaction keys", async () => {
|
||||
await api.sendReaction("12345@g.us", "msg-2", "👍", false, "123@lid");
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"12345@g.us",
|
||||
expect.objectContaining({
|
||||
react: {
|
||||
text: "👍",
|
||||
key: expect.objectContaining({
|
||||
remoteJid: "12345@g.us",
|
||||
id: "msg-2",
|
||||
fromMe: false,
|
||||
participant: "123@lid",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("12345@g.us");
|
||||
const react = requireRecord(requireSendContent().react, "reaction content");
|
||||
expect(react.text).toBe("👍");
|
||||
expectRecordFields(requireRecord(react.key, "reaction key"), {
|
||||
remoteJid: "12345@g.us",
|
||||
id: "msg-2",
|
||||
fromMe: false,
|
||||
participant: "123@lid",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends composing presence updates to the recipient JID", async () => {
|
||||
@@ -336,13 +340,11 @@ describe("createWebSendApi", () => {
|
||||
|
||||
await api.sendMessage("123", "hello", mediaBuffer, undefined);
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"123@s.whatsapp.net",
|
||||
expect.objectContaining({
|
||||
document: mediaBuffer,
|
||||
mimetype: "application/octet-stream",
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("123@s.whatsapp.net");
|
||||
expectSendContentFields(0, {
|
||||
document: mediaBuffer,
|
||||
mimetype: "application/octet-stream",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not set mediaType when mediaBuffer is absent", async () => {
|
||||
@@ -362,18 +364,13 @@ describe("createWebSendApi", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"1555@s.whatsapp.net",
|
||||
{ text: "hello" },
|
||||
expect.objectContaining({
|
||||
quoted: expect.objectContaining({
|
||||
key: expect.objectContaining({
|
||||
remoteJid: "277038292303944@lid",
|
||||
id: "quoted-1",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("1555@s.whatsapp.net");
|
||||
expect(sendMessage.mock.calls[0]?.[1]).toEqual({ text: "hello" });
|
||||
const quoted = requireRecord(requireSendOptions().quoted, "quoted message");
|
||||
expectRecordFields(requireRecord(quoted.key, "quoted key"), {
|
||||
remoteJid: "277038292303944@lid",
|
||||
id: "quoted-1",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -382,7 +379,13 @@ describe("createWebSendApi", () => {
|
||||
// otherwise messages going to LID-addressed contacts vanish into a
|
||||
// sender-only ghost chat.
|
||||
describe("createWebSendApi LID resolution (issue #67378)", () => {
|
||||
const sendMessage = vi.fn(async () => ({ key: { id: "msg-1" } }));
|
||||
const sendMessage = vi.fn(
|
||||
async (
|
||||
_jid: string,
|
||||
_content: AnyMessageContent,
|
||||
_options?: MiscMessageGenerationOptions,
|
||||
): Promise<WAMessage | undefined> => ({ key: { id: "msg-1" } }) as WAMessage,
|
||||
);
|
||||
const sendPresenceUpdate = vi.fn(async () => {});
|
||||
let authDir: string;
|
||||
|
||||
@@ -423,10 +426,10 @@ describe("createWebSendApi LID resolution (issue #67378)", () => {
|
||||
authDir,
|
||||
});
|
||||
await api.sendPoll("+15555550000", { question: "Q?", options: ["a", "b"] });
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
"987654@lid",
|
||||
expect.objectContaining({ poll: expect.any(Object) }),
|
||||
);
|
||||
expect(sendMessage.mock.calls[0]?.[0]).toBe("987654@lid");
|
||||
expect(typeof sendMessage.mock.calls[0]?.[1]).toBe("object");
|
||||
expect(sendMessage.mock.calls[0]?.[1]).not.toBeNull();
|
||||
expect("poll" in (sendMessage.mock.calls[0]?.[1] as Record<string, unknown>)).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves PN to LID for sendComposingTo presence", async () => {
|
||||
|
||||
Reference in New Issue
Block a user