


























@@ -0,0 +1,250 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+3+const events: string[] = [];
4+const transcribeFirstAudioMock = vi.fn();
5+const maybeSendAckReactionMock = vi.fn();
6+const processMessageMock = vi.fn();
7+const maybeBroadcastMessageMock = vi.fn();
8+9+vi.mock("./audio-preflight.runtime.js", () => ({
10+transcribeFirstAudio: (...args: unknown[]) => transcribeFirstAudioMock(...args),
11+}));
12+13+vi.mock("./ack-reaction.js", () => ({
14+maybeSendAckReaction: (...args: unknown[]) => maybeSendAckReactionMock(...args),
15+}));
16+17+vi.mock("./process-message.js", () => ({
18+processMessage: (...args: unknown[]) => processMessageMock(...args),
19+}));
20+21+vi.mock("./broadcast.js", () => ({
22+maybeBroadcastMessage: (...args: unknown[]) => maybeBroadcastMessageMock(...args),
23+}));
24+25+vi.mock("./group-gating.js", () => ({
26+applyGroupGating: vi.fn(async () => ({ shouldProcess: true })),
27+}));
28+29+vi.mock("./last-route.js", () => ({
30+updateLastRouteInBackground: () => {},
31+}));
32+33+vi.mock("./peer.js", () => ({
34+resolvePeerId: (msg: { from: string }) => msg.from,
35+}));
36+37+vi.mock("../config.runtime.js", () => ({
38+loadConfig: () => ({
39+channels: {
40+whatsapp: {
41+ackReaction: { enabled: true },
42+},
43+},
44+}),
45+}));
46+47+vi.mock("../../group-session-key.js", () => ({
48+resolveWhatsAppGroupSessionRoute: (route: unknown) => route,
49+}));
50+51+vi.mock("../../identity.js", () => ({
52+getPrimaryIdentityId: () => undefined,
53+getSenderIdentity: () => ({ e164: "+15550000002", name: "Alice" }),
54+}));
55+56+vi.mock("../../text-runtime.js", () => ({
57+normalizeE164: (value: string) => value,
58+}));
59+60+vi.mock("openclaw/plugin-sdk/routing", () => ({
61+buildGroupHistoryKey: () => "group-key",
62+resolveAgentRoute: () => ({
63+agentId: "main",
64+accountId: "default",
65+sessionKey: "agent:main:whatsapp:+15550000002",
66+mainSessionKey: "agent:main:main",
67+}),
68+}));
69+70+import type { WebInboundMsg } from "../types.js";
71+import { createWebOnMessageHandler } from "./on-message.js";
72+73+function makeAudioMsg(): WebInboundMsg {
74+return {
75+id: "msg-1",
76+from: "+15550000002",
77+to: "+15550000001",
78+accessControlPassed: true,
79+body: "<media:audio>",
80+chatType: "direct",
81+mediaType: "audio/ogg; codecs=opus",
82+mediaPath: "/tmp/voice.ogg",
83+timestamp: 1700000000,
84+accountId: "default",
85+} as WebInboundMsg;
86+}
87+88+function makeGroupAudioMsg(): WebInboundMsg {
89+return {
90+ ...makeAudioMsg(),
91+from: "1203630@g.us",
92+chatId: "1203630@g.us",
93+chatType: "group",
94+conversationId: "1203630@g.us",
95+wasMentioned: false,
96+} as WebInboundMsg;
97+}
98+99+function makeEchoTracker() {
100+return {
101+has: () => false,
102+forget: () => {},
103+rememberText: () => {},
104+buildCombinedKey: (p: { combinedBody: string }) => p.combinedBody,
105+};
106+}
107+108+describe("createWebOnMessageHandler audio preflight", () => {
109+beforeEach(() => {
110+events.length = 0;
111+maybeBroadcastMessageMock.mockReset();
112+maybeBroadcastMessageMock.mockImplementation(async () => false);
113+maybeSendAckReactionMock.mockReset();
114+maybeSendAckReactionMock.mockImplementation(async () => {
115+events.push("ack");
116+});
117+transcribeFirstAudioMock.mockReset();
118+transcribeFirstAudioMock.mockImplementation(async () => {
119+events.push("stt");
120+return "transcribed voice note";
121+});
122+processMessageMock.mockReset();
123+processMessageMock.mockResolvedValue(true);
124+});
125+126+it("sends ack reaction before audio preflight for voice notes", async () => {
127+const handler = createWebOnMessageHandler({
128+cfg: {
129+channels: {
130+whatsapp: {
131+ackReaction: { enabled: true },
132+},
133+},
134+} as never,
135+verbose: false,
136+connectionId: "conn-1",
137+maxMediaBytes: 1024 * 1024,
138+groupHistoryLimit: 20,
139+groupHistories: new Map(),
140+groupMemberNames: new Map(),
141+echoTracker: makeEchoTracker() as never,
142+backgroundTasks: new Set(),
143+replyResolver: vi.fn() as never,
144+replyLogger: {
145+info: () => {},
146+warn: () => {},
147+debug: () => {},
148+error: () => {},
149+} as never,
150+baseMentionConfig: {} as never,
151+account: { authDir: "/tmp/auth", accountId: "default" },
152+});
153+154+await handler(makeAudioMsg());
155+156+expect(events).toEqual(["ack", "stt"]);
157+expect(processMessageMock).toHaveBeenCalledWith(
158+expect.objectContaining({
159+preflightAudioTranscript: "transcribed voice note",
160+ackAlreadySent: true,
161+}),
162+);
163+});
164+165+it("skips early DM ack/preflight when access-control was not explicitly passed through", async () => {
166+167+const handler = createWebOnMessageHandler({
168+cfg: {
169+channels: {
170+whatsapp: {
171+ackReaction: { enabled: true },
172+},
173+},
174+} as never,
175+verbose: false,
176+connectionId: "conn-1",
177+maxMediaBytes: 1024 * 1024,
178+groupHistoryLimit: 20,
179+groupHistories: new Map(),
180+groupMemberNames: new Map(),
181+echoTracker: makeEchoTracker() as never,
182+backgroundTasks: new Set(),
183+replyResolver: vi.fn() as never,
184+replyLogger: {
185+info: () => {},
186+warn: () => {},
187+debug: () => {},
188+error: () => {},
189+} as never,
190+baseMentionConfig: {} as never,
191+account: { authDir: "/tmp/auth", accountId: "default" },
192+});
193+194+await handler({ ...makeAudioMsg(), accessControlPassed: undefined });
195+196+expect(events).toEqual([]);
197+expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
198+expect(maybeSendAckReactionMock).not.toHaveBeenCalled();
199+expect(processMessageMock).toHaveBeenCalledWith(
200+expect.not.objectContaining({
201+preflightAudioTranscript: expect.anything(),
202+ackAlreadySent: true,
203+}),
204+);
205+});
206+207+it("preserves per-agent ack checks for group broadcast voice notes", async () => {
208+maybeBroadcastMessageMock.mockImplementation(
209+async (params: { ackAlreadySent?: boolean; preflightAudioTranscript?: string | null }) => {
210+expect(params.preflightAudioTranscript).toBe("transcribed voice note");
211+expect(params.ackAlreadySent).toBeUndefined();
212+return true;
213+},
214+);
215+const handler = createWebOnMessageHandler({
216+cfg: {
217+channels: {
218+whatsapp: {
219+ackReaction: { enabled: true },
220+},
221+},
222+broadcast: {
223+"1203630@g.us": ["main", "backup"],
224+},
225+} as never,
226+verbose: false,
227+connectionId: "conn-1",
228+maxMediaBytes: 1024 * 1024,
229+groupHistoryLimit: 20,
230+groupHistories: new Map(),
231+groupMemberNames: new Map(),
232+echoTracker: makeEchoTracker() as never,
233+backgroundTasks: new Set(),
234+replyResolver: vi.fn() as never,
235+replyLogger: {
236+info: () => {},
237+warn: () => {},
238+debug: () => {},
239+error: () => {},
240+} as never,
241+baseMentionConfig: {} as never,
242+account: { authDir: "/tmp/auth", accountId: "default" },
243+});
244+245+await handler(makeGroupAudioMsg());
246+247+expect(events).toEqual(["ack", "stt"]);
248+expect(processMessageMock).not.toHaveBeenCalled();
249+});
250+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。