231 lines
7.3 KiB
TypeScript
231 lines
7.3 KiB
TypeScript
import crypto from "node:crypto";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const readAllowFromStoreMock = vi.fn().mockResolvedValue([]);
|
|
const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true });
|
|
const saveMediaBufferSpy = vi.fn();
|
|
|
|
vi.mock("../config/config.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../config/config.js")>();
|
|
return {
|
|
...actual,
|
|
loadConfig: vi.fn().mockReturnValue({
|
|
channels: {
|
|
whatsapp: {
|
|
allowFrom: ["*"], // Allow all in tests
|
|
},
|
|
},
|
|
messages: {
|
|
messagePrefix: undefined,
|
|
responsePrefix: undefined,
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
vi.mock("../pairing/pairing-store.js", () => ({
|
|
readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
|
|
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
|
}));
|
|
|
|
vi.mock("../media/store.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../media/store.js")>();
|
|
return {
|
|
...actual,
|
|
saveMediaBuffer: vi.fn(async (...args: Parameters<typeof actual.saveMediaBuffer>) => {
|
|
saveMediaBufferSpy(...args);
|
|
return actual.saveMediaBuffer(...args);
|
|
}),
|
|
};
|
|
});
|
|
|
|
const HOME = path.join(os.tmpdir(), `openclaw-inbound-media-${crypto.randomUUID()}`);
|
|
process.env.HOME = HOME;
|
|
|
|
vi.mock("@whiskeysockets/baileys", async () => {
|
|
const actual =
|
|
await vi.importActual<typeof import("@whiskeysockets/baileys")>("@whiskeysockets/baileys");
|
|
const jpegBuffer = Buffer.from([
|
|
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
|
|
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
|
|
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
|
|
0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
|
|
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01,
|
|
0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x14, 0x00, 0x01,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
|
0xc4, 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
|
|
0xff, 0xd9,
|
|
]);
|
|
return {
|
|
...actual,
|
|
downloadMediaMessage: vi.fn().mockResolvedValue(jpegBuffer),
|
|
};
|
|
});
|
|
|
|
vi.mock("./session.js", () => {
|
|
const { EventEmitter } = require("node:events");
|
|
const ev = new EventEmitter();
|
|
const sock = {
|
|
ev,
|
|
ws: { close: vi.fn() },
|
|
sendPresenceUpdate: vi.fn().mockResolvedValue(undefined),
|
|
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
readMessages: vi.fn().mockResolvedValue(undefined),
|
|
updateMediaMessage: vi.fn(),
|
|
logger: {},
|
|
user: { id: "me@s.whatsapp.net" },
|
|
};
|
|
return {
|
|
createWaSocket: vi.fn().mockResolvedValue(sock),
|
|
waitForWaConnection: vi.fn().mockResolvedValue(undefined),
|
|
getStatusCode: vi.fn(() => 200),
|
|
};
|
|
});
|
|
|
|
import { monitorWebInbox, resetWebInboundDedupe } from "./inbound.js";
|
|
let createWaSocket: typeof import("./session.js").createWaSocket;
|
|
|
|
async function waitForMessage(onMessage: ReturnType<typeof vi.fn>) {
|
|
await vi.waitFor(() => expect(onMessage).toHaveBeenCalledTimes(1), {
|
|
interval: 1,
|
|
timeout: 250,
|
|
});
|
|
return onMessage.mock.calls[0][0];
|
|
}
|
|
|
|
describe("web inbound media saves with extension", () => {
|
|
async function getMockSocket() {
|
|
return (await createWaSocket(false, false)) as unknown as {
|
|
ev: import("node:events").EventEmitter;
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
saveMediaBufferSpy.mockClear();
|
|
resetWebInboundDedupe();
|
|
});
|
|
|
|
beforeAll(async () => {
|
|
({ createWaSocket } = await import("./session.js"));
|
|
await fs.rm(HOME, { recursive: true, force: true });
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await fs.rm(HOME, { recursive: true, force: true });
|
|
});
|
|
|
|
it("stores image extension, extracts caption mentions, and keeps document filename", async () => {
|
|
const onMessage = vi.fn();
|
|
const listener = await monitorWebInbox({
|
|
verbose: false,
|
|
onMessage,
|
|
accountId: "default",
|
|
authDir: path.join(HOME, "wa-auth"),
|
|
});
|
|
const realSock = await getMockSocket();
|
|
|
|
realSock.ev.emit("messages.upsert", {
|
|
type: "notify",
|
|
messages: [
|
|
{
|
|
key: { id: "img1", fromMe: false, remoteJid: "111@s.whatsapp.net" },
|
|
message: { imageMessage: { mimetype: "image/jpeg" } },
|
|
messageTimestamp: 1_700_000_001,
|
|
},
|
|
],
|
|
});
|
|
|
|
const first = await waitForMessage(onMessage);
|
|
const mediaPath = first.mediaPath;
|
|
expect(mediaPath).toBeDefined();
|
|
expect(path.extname(mediaPath as string)).toBe(".jpg");
|
|
const stat = await fs.stat(mediaPath as string);
|
|
expect(stat.size).toBeGreaterThan(0);
|
|
|
|
onMessage.mockClear();
|
|
realSock.ev.emit("messages.upsert", {
|
|
type: "notify",
|
|
messages: [
|
|
{
|
|
key: {
|
|
id: "img2",
|
|
fromMe: false,
|
|
remoteJid: "123@g.us",
|
|
participant: "999@s.whatsapp.net",
|
|
},
|
|
message: {
|
|
messageContextInfo: {},
|
|
imageMessage: {
|
|
caption: "@bot",
|
|
contextInfo: { mentionedJid: ["999@s.whatsapp.net"] },
|
|
mimetype: "image/jpeg",
|
|
},
|
|
},
|
|
messageTimestamp: 1_700_000_002,
|
|
},
|
|
],
|
|
});
|
|
|
|
const second = await waitForMessage(onMessage);
|
|
expect(second.chatType).toBe("group");
|
|
expect(second.mentionedJids).toEqual(["999@s.whatsapp.net"]);
|
|
|
|
onMessage.mockClear();
|
|
const fileName = "invoice.pdf";
|
|
realSock.ev.emit("messages.upsert", {
|
|
type: "notify",
|
|
messages: [
|
|
{
|
|
key: { id: "doc1", fromMe: false, remoteJid: "333@s.whatsapp.net" },
|
|
message: { documentMessage: { mimetype: "application/pdf", fileName } },
|
|
messageTimestamp: 1_700_000_004,
|
|
},
|
|
],
|
|
});
|
|
|
|
const third = await waitForMessage(onMessage);
|
|
expect(third.mediaFileName).toBe(fileName);
|
|
expect(saveMediaBufferSpy).toHaveBeenCalled();
|
|
const lastCall = saveMediaBufferSpy.mock.calls.at(-1);
|
|
expect(lastCall?.[4]).toBe(fileName);
|
|
|
|
await listener.close();
|
|
});
|
|
|
|
it("passes mediaMaxMb to saveMediaBuffer", async () => {
|
|
const onMessage = vi.fn();
|
|
const listener = await monitorWebInbox({
|
|
verbose: false,
|
|
onMessage,
|
|
mediaMaxMb: 1,
|
|
accountId: "default",
|
|
authDir: path.join(HOME, "wa-auth"),
|
|
});
|
|
const realSock = await getMockSocket();
|
|
|
|
const upsert = {
|
|
type: "notify",
|
|
messages: [
|
|
{
|
|
key: { id: "img3", fromMe: false, remoteJid: "222@s.whatsapp.net" },
|
|
message: { imageMessage: { mimetype: "image/jpeg" } },
|
|
messageTimestamp: 1_700_000_003,
|
|
},
|
|
],
|
|
};
|
|
|
|
realSock.ev.emit("messages.upsert", upsert);
|
|
|
|
await waitForMessage(onMessage);
|
|
expect(saveMediaBufferSpy).toHaveBeenCalled();
|
|
const lastCall = saveMediaBufferSpy.mock.calls.at(-1);
|
|
expect(lastCall?.[3]).toBe(1 * 1024 * 1024);
|
|
|
|
await listener.close();
|
|
});
|
|
});
|