























@@ -27,6 +27,25 @@ import {
2727startWebAutoReplyMonitor,
2828} from "./auto-reply.test-harness.js";
292930+type DrainSelectionEntry = {
31+channel: string;
32+accountId?: string | null;
33+lastError?: string;
34+};
35+type DrainPendingDeliveriesCall = {
36+drainKey: string;
37+logLabel: string;
38+selectEntry: (entry: DrainSelectionEntry) => { match: boolean; bypassBackoff: boolean };
39+};
40+41+const deliveryQueueMocks = vi.hoisted(() => ({
42+drainPendingDeliveries: vi.fn(async (_opts: unknown) => undefined),
43+}));
44+45+vi.mock("openclaw/plugin-sdk/delivery-queue-runtime", () => ({
46+drainPendingDeliveries: deliveryQueueMocks.drainPendingDeliveries,
47+}));
48+3049installWebAutoReplyTestHomeHooks();
31503251function requireOnMessage(
@@ -247,6 +266,78 @@ describe("web auto-reply connection", () => {
247266expect(sleep).toHaveBeenCalled();
248267});
249268269+it("drains pending deliveries while connected and stops after close", async () => {
270+vi.useFakeTimers();
271+try {
272+const sleep = vi.fn(async () => {});
273+const scripted = createScriptedWebListenerFactory();
274+const { controller, run } = startWebAutoReplyMonitor({
275+monitorWebChannelFn: monitorWebChannel as never,
276+listenerFactory: scripted.listenerFactory,
277+ sleep,
278+accountId: "work",
279+});
280+281+await vi.waitFor(
282+() => {
283+expect(scripted.getListenerCount()).toBe(1);
284+},
285+{ timeout: 250, interval: 2 },
286+);
287+expect(deliveryQueueMocks.drainPendingDeliveries).toHaveBeenCalledWith(
288+expect.objectContaining({
289+drainKey: "whatsapp:work",
290+logLabel: "WhatsApp reconnect drain",
291+}),
292+);
293+294+deliveryQueueMocks.drainPendingDeliveries.mockClear();
295+await vi.advanceTimersByTimeAsync(30_000);
296+await vi.waitFor(() => {
297+expect(deliveryQueueMocks.drainPendingDeliveries).toHaveBeenCalledTimes(1);
298+});
299+300+const periodicCall = deliveryQueueMocks.drainPendingDeliveries.mock.calls.at(-1)?.[0] as
301+| DrainPendingDeliveriesCall
302+| undefined;
303+expect(periodicCall).toBeDefined();
304+if (!periodicCall) {
305+throw new Error("Expected WhatsApp periodic drain call");
306+}
307+expect(periodicCall.drainKey).toBe("whatsapp:work");
308+expect(periodicCall.logLabel).toBe("WhatsApp periodic drain");
309+expect(
310+periodicCall.selectEntry({
311+channel: "whatsapp",
312+accountId: "work",
313+}),
314+).toEqual({ match: true, bypassBackoff: false });
315+expect(
316+periodicCall.selectEntry({
317+channel: "whatsapp",
318+accountId: "default",
319+}),
320+).toEqual({ match: false, bypassBackoff: false });
321+expect(
322+periodicCall.selectEntry({
323+channel: "telegram",
324+accountId: "work",
325+}),
326+).toEqual({ match: false, bypassBackoff: false });
327+328+controller.abort();
329+scripted.resolveClose(0, { status: 499, isLoggedOut: false, error: "aborted" });
330+await Promise.resolve();
331+await run;
332+333+deliveryQueueMocks.drainPendingDeliveries.mockClear();
334+await vi.advanceTimersByTimeAsync(30_000);
335+expect(deliveryQueueMocks.drainPendingDeliveries).not.toHaveBeenCalled();
336+} finally {
337+vi.useRealTimers();
338+}
339+});
340+250341it("treats status 440 as non-retryable and stops without retrying", async () => {
251342const sleep = vi.fn(async () => {});
252343const scripted = createScriptedWebListenerFactory();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。