test: clear whatsapp send api broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 13:19:05 +01:00
parent 24edb84146
commit 9ac4aef7c2

View File

@@ -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 () => {