* test: align extension runtime mocks with plugin-sdk Update stale extension tests to mock the plugin-sdk runtime barrels that production code now imports, and harden the Signal tool-result harness around system-event assertions so the channels lane matches current extension boundaries. Regeneration-Prompt: | Verify the failing channels-lane tests against current origin/main in an isolated worktree before changing anything. If the failures reproduce on main, keep the fix test-only unless production behavior is clearly wrong. Recent extension refactors moved Telegram, WhatsApp, and Signal code onto plugin-sdk runtime barrels, so update stale tests that still mock old core module paths to intercept the seams production code now uses. For Signal reaction notifications, avoid brittle assertions that depend on shared queued system-event state when a direct harness spy on enqueue behavior is sufficient. Preserve scope: only touch the failing tests and their local harness, then rerun the reproduced targeted tests plus the full channels lane and repo check gate. * test: fix extension test drift on main * fix: lazy-load bundled web search plugin registry * test: make matrix sweeper failure injection portable * fix: split heavy matrix runtime-api seams * fix: simplify bundled web search id lookup * test: tolerate windows env key casing
241 lines
7.5 KiB
TypeScript
241 lines
7.5 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("openclaw/plugin-sdk/config-runtime", async () => {
|
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/config-runtime")>(
|
|
"openclaw/plugin-sdk/config-runtime",
|
|
);
|
|
return {
|
|
...actual,
|
|
loadConfig: vi.fn().mockReturnValue({
|
|
channels: {
|
|
whatsapp: {
|
|
allowFrom: ["*"], // Allow all in tests
|
|
},
|
|
},
|
|
messages: {
|
|
messagePrefix: undefined,
|
|
responsePrefix: undefined,
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
vi.mock("../../../src/pairing/pairing-store.js", () => {
|
|
return {
|
|
readChannelAllowFromStore(...args: unknown[]) {
|
|
return readAllowFromStoreMock(...args);
|
|
},
|
|
upsertChannelPairingRequest(...args: unknown[]) {
|
|
return upsertPairingRequestMock(...args);
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("openclaw/plugin-sdk/media-runtime", async () => {
|
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/media-runtime")>(
|
|
"openclaw/plugin-sdk/media-runtime",
|
|
);
|
|
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();
|
|
});
|
|
});
|