





















@@ -1,9 +1,10 @@
1-import { beforeEach, describe, expect, it, vi } from "vitest";
1+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2233// Hoisted mocks used across tests so vi.mock factories can reference them.
4-const { resolvePolicyMock, buildContextMock } = vi.hoisted(() => ({
4+const { resolvePolicyMock, buildContextMock, runMessageReceivedMock } = vi.hoisted(() => ({
55resolvePolicyMock: vi.fn(),
66buildContextMock: vi.fn(),
7+runMessageReceivedMock: vi.fn(async () => undefined),
78}));
89910vi.mock("../../inbound-policy.js", async (importOriginal) => {
@@ -30,6 +31,13 @@ vi.mock("./inbound-dispatch.js", async (importOriginal) => {
3031};
3132});
323334+vi.mock("openclaw/plugin-sdk/plugin-runtime", () => ({
35+getGlobalHookRunner: () => ({
36+hasHooks: (hookName: string) => hookName === "message_received",
37+runMessageReceived: runMessageReceivedMock,
38+}),
39+}));
40+3341vi.mock("../../identity.js", async (importOriginal) => {
3442const actual = await importOriginal<typeof import("../../identity.js")>();
3543return {
@@ -113,6 +121,7 @@ vi.mock("./runtime-api.js", async (importOriginal) => {
113121};
114122});
115123124+import { clearInternalHooks, registerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
116125import { processMessage } from "./process-message.js";
117126118127// ---------------------------------------------------------------------------
@@ -172,9 +181,9 @@ const baseRoute = {
172181matchedBy: "default",
173182};
174183175-function callProcessMessage() {
184+function callProcessMessage(overrides: { cfg?: unknown } = {}) {
176185return processMessage({
177-cfg: {} as never,
186+cfg: (overrides.cfg ?? {}) as never,
178187msg: baseMsg as never,
179188route: baseRoute as never,
180189groupHistoryKey: "whatsapp:default:group:123@g.us",
@@ -201,6 +210,8 @@ describe("processMessage group system prompt wiring", () => {
201210beforeEach(() => {
202211buildContextMock.mockReset();
203212resolvePolicyMock.mockReset();
213+runMessageReceivedMock.mockClear();
214+clearInternalHooks();
204215buildContextMock.mockImplementation(
205216(params: { groupSystemPrompt?: string; combinedBody?: string }) => ({
206217GroupSystemPrompt: params.groupSystemPrompt,
@@ -209,6 +220,10 @@ describe("processMessage group system prompt wiring", () => {
209220);
210221});
211222223+afterEach(() => {
224+clearInternalHooks();
225+});
226+212227it("resolves group systemPrompt from account config and passes it into buildWhatsAppInboundContext", async () => {
213228resolvePolicyMock.mockReturnValue(
214229makePolicy(makeAccount({ [GROUP_JID]: { systemPrompt: "from config" } })),
@@ -218,4 +233,91 @@ describe("processMessage group system prompt wiring", () => {
218233219234expect(buildContextMock.mock.calls[0][0].groupSystemPrompt).toBe("from config");
220235});
236+237+it("fires message_received hooks with canonical WhatsApp correlation fields", async () => {
238+const internalReceived = vi.fn();
239+registerInternalHook("message:received", internalReceived);
240+resolvePolicyMock.mockReturnValue(makePolicy(makeAccount()));
241+buildContextMock.mockImplementationOnce(() => ({
242+Body: "hi",
243+BodyForCommands: "hi",
244+RawBody: "hi",
245+CommandBody: "hi",
246+From: GROUP_JID,
247+To: "+15550001111",
248+SessionKey: baseRoute.sessionKey,
249+AccountId: "default",
250+MessageSid: "msg1",
251+SenderId: "+15550002222",
252+SenderName: "Alice",
253+SenderE164: "+15550002222",
254+Timestamp: 1710000000,
255+Provider: "whatsapp",
256+Surface: "whatsapp",
257+OriginatingChannel: "whatsapp",
258+OriginatingTo: GROUP_JID,
259+GroupSubject: "Test Group",
260+}));
261+262+await callProcessMessage({
263+cfg: {
264+channels: {
265+whatsapp: {
266+pluginHooks: {
267+messageReceived: true,
268+},
269+},
270+},
271+},
272+});
273+await Promise.resolve();
274+await Promise.resolve();
275+276+expect(runMessageReceivedMock).toHaveBeenCalledTimes(1);
277+expect(runMessageReceivedMock).toHaveBeenCalledWith(
278+expect.objectContaining({
279+from: GROUP_JID,
280+content: "hi",
281+timestamp: 1710000000,
282+messageId: "msg1",
283+senderId: "+15550002222",
284+sessionKey: baseRoute.sessionKey,
285+}),
286+expect.objectContaining({
287+channelId: "whatsapp",
288+accountId: "default",
289+conversationId: GROUP_JID,
290+sessionKey: baseRoute.sessionKey,
291+messageId: "msg1",
292+senderId: "+15550002222",
293+}),
294+);
295+expect(internalReceived).toHaveBeenCalledWith(
296+expect.objectContaining({
297+type: "message",
298+action: "received",
299+sessionKey: baseRoute.sessionKey,
300+context: expect.objectContaining({
301+from: GROUP_JID,
302+content: "hi",
303+timestamp: 1710000000,
304+channelId: "whatsapp",
305+accountId: "default",
306+conversationId: GROUP_JID,
307+messageId: "msg1",
308+}),
309+}),
310+);
311+});
312+313+it("does not fire WhatsApp message_received hooks without explicit opt-in", async () => {
314+const internalReceived = vi.fn();
315+registerInternalHook("message:received", internalReceived);
316+resolvePolicyMock.mockReturnValue(makePolicy(makeAccount()));
317+318+await callProcessMessage();
319+320+expect(runMessageReceivedMock).not.toHaveBeenCalled();
321+expect(internalReceived).not.toHaveBeenCalled();
322+});
221323});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。