From 97c8f4999eb1408e5ef8cb01ade67e02a69fed04 Mon Sep 17 00:00:00 2001 From: cpojer Date: Tue, 17 Feb 2026 14:31:55 +0900 Subject: [PATCH] chore: Fix types in tests 27/N. --- src/slack/monitor/monitor.test.ts | 2 + src/utils/usage-format.test.ts | 2 +- ...asts-sequentially-configured-order.test.ts | 1 + .../auto-reply.typing-controller-idle.test.ts | 15 ++- ...resses-common-formats-jpeg-cap.e2e.test.ts | 18 +++- ...to-reply.web-auto-reply.last-route.test.ts | 6 +- ...ply.web-auto-reply.monitor-logging.test.ts | 4 + ...onnects-after-connection-close.e2e.test.ts | 97 +++++++++++++------ .../auto-reply/web-auto-reply-monitor.test.ts | 4 +- src/web/inbound.media.test.ts | 9 +- src/web/inbound/send-api.test.ts | 2 +- src/web/login.test.ts | 4 +- src/web/media.test.ts | 8 +- ...tor-inbox.streams-inbound-messages.test.ts | 4 +- src/web/session.test.ts | 4 +- 15 files changed, 129 insertions(+), 51 deletions(-) diff --git a/src/slack/monitor/monitor.test.ts b/src/slack/monitor/monitor.test.ts index 0194642f79..1592eaf713 100644 --- a/src/slack/monitor/monitor.test.ts +++ b/src/slack/monitor/monitor.test.ts @@ -94,6 +94,8 @@ const baseParams = () => ({ textLimit: 4000, ackReactionScope: "group-mentions", mediaMaxBytes: 1, + threadHistoryScope: "thread" as const, + threadInheritParent: false, removeAckAfterReply: false, }); diff --git a/src/utils/usage-format.test.ts b/src/utils/usage-format.test.ts index 8d7985f4df..25dac6d612 100644 --- a/src/utils/usage-format.test.ts +++ b/src/utils/usage-format.test.ts @@ -35,7 +35,7 @@ describe("usage-format", () => { }, }, }, - } as OpenClawConfig; + } as unknown as OpenClawConfig; const cost = resolveModelCostConfig({ provider: "test", diff --git a/src/web/auto-reply.broadcast-groups.broadcasts-sequentially-configured-order.test.ts b/src/web/auto-reply.broadcast-groups.broadcasts-sequentially-configured-order.test.ts index c5302661f0..bb609a05c1 100644 --- a/src/web/auto-reply.broadcast-groups.broadcasts-sequentially-configured-order.test.ts +++ b/src/web/auto-reply.broadcast-groups.broadcasts-sequentially-configured-order.test.ts @@ -158,6 +158,7 @@ describe("broadcast groups", () => { from: "+1000", conversationId: "+1000", to: "+2000", + accountId: "default", body: "hello", timestamp: Date.now(), chatType: "direct", diff --git a/src/web/auto-reply.typing-controller-idle.test.ts b/src/web/auto-reply.typing-controller-idle.test.ts index 4180420ee3..32d66c69e2 100644 --- a/src/web/auto-reply.typing-controller-idle.test.ts +++ b/src/web/auto-reply.typing-controller-idle.test.ts @@ -11,6 +11,18 @@ import { installWebAutoReplyTestHomeHooks(); +function createMockListener() { + return { + close: vi.fn(async () => undefined), + onClose: new Promise(() => {}), + signalClose: vi.fn(), + sendMessage: vi.fn(async () => ({ messageId: "msg-1" })), + sendPoll: vi.fn(async () => ({ messageId: "poll-1" })), + sendReaction: vi.fn(async () => undefined), + sendComposingTo: vi.fn(async () => undefined), + }; +} + describe("typing controller idle", () => { installWebAutoReplyUnitTestHooks(); @@ -53,11 +65,12 @@ describe("typing controller idle", () => { timestamp: Date.now(), chatType: "direct", chatId: "direct:+1000", + accountId: "default", sendComposing, reply, sendMedia, }); - return { close: vi.fn().mockResolvedValue(undefined) }; + return createMockListener(); }, false, replyResolver, diff --git a/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts b/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts index da3f7ae0c9..78c2d0bb55 100644 --- a/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts +++ b/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts @@ -16,6 +16,18 @@ describe("web auto-reply", () => { installWebAutoReplyUnitTestHooks({ pinDns: true }); type ListenerFactory = NonNullable[1]>; + function createMockListener() { + return { + close: vi.fn(async () => undefined), + onClose: new Promise(() => {}), + signalClose: vi.fn(), + sendMessage: vi.fn(async () => ({ messageId: "msg-1" })), + sendPoll: vi.fn(async () => ({ messageId: "poll-1" })), + sendReaction: vi.fn(async () => undefined), + sendComposingTo: vi.fn(async () => undefined), + }; + } + async function setupSingleInboundMessage(params: { resolverValue: { text: string; mediaUrl: string }; sendMedia: ReturnType; @@ -28,7 +40,7 @@ describe("web auto-reply", () => { let capturedOnMessage: ((msg: WebInboundMessage) => Promise) | undefined; const listenerFactory: ListenerFactory = async ({ onMessage }) => { capturedOnMessage = onMessage; - return { close: vi.fn() }; + return createMockListener(); }; await monitorWebChannel(false, listenerFactory, false, resolver); @@ -115,7 +127,7 @@ describe("web auto-reply", () => { let capturedOnMessage: ((msg: WebInboundMessage) => Promise) | undefined; const listenerFactory: ListenerFactory = async ({ onMessage }) => { capturedOnMessage = onMessage; - return { close: vi.fn() }; + return createMockListener(); }; const big = await fmt.make(sharedRaw, { width, height }); @@ -173,7 +185,7 @@ describe("web auto-reply", () => { let capturedOnMessage: ((msg: WebInboundMessage) => Promise) | undefined; const listenerFactory: ListenerFactory = async ({ onMessage }) => { capturedOnMessage = onMessage; - return { close: vi.fn() }; + return createMockListener(); }; const bigPng = await sharp({ diff --git a/src/web/auto-reply.web-auto-reply.last-route.test.ts b/src/web/auto-reply.web-auto-reply.last-route.test.ts index 69530fe35e..a810b2ece2 100644 --- a/src/web/auto-reply.web-auto-reply.last-route.test.ts +++ b/src/web/auto-reply.web-auto-reply.last-route.test.ts @@ -36,7 +36,9 @@ function createHandlerForTest(opts: { cfg: OpenClawConfig; replyResolver: unknow groupMemberNames: new Map(), echoTracker: createEchoTracker({ maxItems: 10 }), backgroundTasks, - replyResolver: opts.replyResolver, + replyResolver: opts.replyResolver as Parameters< + typeof createWebOnMessageHandler + >[0]["replyResolver"], replyLogger: makeReplyLogger(), baseMentionConfig: buildMentionConfig(opts.cfg), account: {}, @@ -74,7 +76,7 @@ function buildInboundMessage(params: { timestamp: params.timestamp, chatType: params.chatType, chatId: params.chatId, - accountId: params.accountId, + accountId: params.accountId ?? "default", senderE164: params.senderE164, senderName: params.senderName, selfE164: params.selfE164, diff --git a/src/web/auto-reply.web-auto-reply.monitor-logging.test.ts b/src/web/auto-reply.web-auto-reply.monitor-logging.test.ts index 0838f6c73b..f946906191 100644 --- a/src/web/auto-reply.web-auto-reply.monitor-logging.test.ts +++ b/src/web/auto-reply.web-auto-reply.monitor-logging.test.ts @@ -77,7 +77,11 @@ describe("web auto-reply monitor logging", () => { await capturedOnMessage?.({ body: "hello", from: "+1", + conversationId: "+1", to: "+2", + accountId: "default", + chatType: "direct", + chatId: "+1", id: "msg1", sendComposing: vi.fn(), reply: vi.fn(), diff --git a/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.e2e.test.ts b/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.e2e.test.ts index 1441c0293b..3b69c123fb 100644 --- a/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.e2e.test.ts +++ b/src/web/auto-reply.web-auto-reply.reconnects-after-connection-close.e2e.test.ts @@ -6,6 +6,7 @@ import { makeSessionStore, setLoadConfigMock, } from "./auto-reply.test-harness.js"; +import type { WebInboundMessage } from "./inbound.js"; installWebAutoReplyTestHomeHooks(); @@ -19,7 +20,7 @@ function createRuntime() { function startMonitorWebChannel(params: { monitorWebChannelFn: (...args: unknown[]) => Promise; - listenerFactory: (...args: unknown[]) => Promise; + listenerFactory: unknown; sleep: ReturnType; signal?: AbortSignal; reconnect?: { initialMs: number; maxMs: number; maxAttempts: number; factor: number }; @@ -43,6 +44,32 @@ function startMonitorWebChannel(params: { return { runtime, controller, run }; } +function makeInboundMessage(params: { + body: string; + from: string; + to: string; + id?: string; + timestamp?: number; + sendComposing: ReturnType; + reply: ReturnType; + sendMedia: ReturnType; +}): WebInboundMessage { + return { + body: params.body, + from: params.from, + to: params.to, + id: params.id, + timestamp: params.timestamp, + conversationId: params.from, + accountId: "default", + chatType: "direct", + chatId: params.from, + sendComposing: params.sendComposing as unknown as WebInboundMessage["sendComposing"], + reply: params.reply as unknown as WebInboundMessage["reply"], + sendMedia: params.sendMedia as unknown as WebInboundMessage["sendMedia"], + }; +} + describe("web auto-reply", () => { installWebAutoReplyUnitTestHooks(); @@ -133,15 +160,17 @@ describe("web auto-reply", () => { // The watchdog only needs `lastMessageAt` to be set. Don't await full message // processing here since it can schedule timers and become flaky under load. - void capturedOnMessage?.({ - body: "hi", - from: "+1", - to: "+2", - id: "m1", - sendComposing, - reply, - sendMedia, - }); + void capturedOnMessage?.( + makeInboundMessage({ + body: "hi", + from: "+1", + to: "+2", + id: "m1", + sendComposing, + reply, + sendMedia, + }), + ); await vi.advanceTimersByTimeAsync(31 * 60 * 1000); await Promise.resolve(); @@ -174,7 +203,7 @@ describe("web auto-reply", () => { const run = monitorWebChannel( false, - listenerFactory, + listenerFactory as never, true, async () => ({ text: "ok" }), runtime as never, @@ -236,30 +265,34 @@ describe("web auto-reply", () => { session: { store: store.storePath }, })); - await monitorWebChannel(false, listenerFactory, false, resolver); + await monitorWebChannel(false, listenerFactory as never, false, resolver); expect(capturedOnMessage).toBeDefined(); // Two messages from the same sender with fixed timestamps - await capturedOnMessage?.({ - body: "first", - from: "+1", - to: "+2", - id: "m1", - timestamp: 1735689600000, // Jan 1 2025 00:00:00 UTC - sendComposing, - reply, - sendMedia, - }); - await capturedOnMessage?.({ - body: "second", - from: "+1", - to: "+2", - id: "m2", - timestamp: 1735693200000, // Jan 1 2025 01:00:00 UTC - sendComposing, - reply, - sendMedia, - }); + await capturedOnMessage?.( + makeInboundMessage({ + body: "first", + from: "+1", + to: "+2", + id: "m1", + timestamp: 1735689600000, // Jan 1 2025 00:00:00 UTC + sendComposing, + reply, + sendMedia, + }), + ); + await capturedOnMessage?.( + makeInboundMessage({ + body: "second", + from: "+1", + to: "+2", + id: "m2", + timestamp: 1735693200000, // Jan 1 2025 01:00:00 UTC + sendComposing, + reply, + sendMedia, + }), + ); expect(resolver).toHaveBeenCalledTimes(2); const firstArgs = resolver.mock.calls[0][0]; diff --git a/src/web/auto-reply/web-auto-reply-monitor.test.ts b/src/web/auto-reply/web-auto-reply-monitor.test.ts index f21d7e9e6b..40253e9ac8 100644 --- a/src/web/auto-reply/web-auto-reply-monitor.test.ts +++ b/src/web/auto-reply/web-auto-reply-monitor.test.ts @@ -4,7 +4,7 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { resolveAgentRoute } from "../../routing/resolve-route.js"; import { buildMentionConfig } from "./mentions.js"; -import { applyGroupGating } from "./monitor/group-gating.js"; +import { applyGroupGating, type GroupHistoryEntry } from "./monitor/group-gating.js"; import { buildInboundLine, formatReplyContext } from "./monitor/message-line.js"; let sessionDir: string | undefined; @@ -41,7 +41,7 @@ function runGroupGating(params: { conversationId?: string; agentId?: string; }) { - const groupHistories = new Map(); + const groupHistories = new Map(); const conversationId = params.conversationId ?? "123@g.us"; const agentId = params.agentId ?? "main"; const sessionKey = `agent:${agentId}:whatsapp:group:${conversationId}`; diff --git a/src/web/inbound.media.test.ts b/src/web/inbound.media.test.ts index 2ebdd5f123..606b244f1a 100644 --- a/src/web/inbound.media.test.ts +++ b/src/web/inbound.media.test.ts @@ -112,7 +112,12 @@ describe("web inbound media saves with extension", () => { it("stores image extension, extracts caption mentions, and keeps document filename", async () => { const onMessage = vi.fn(); - const listener = await monitorWebInbox({ verbose: false, onMessage }); + const listener = await monitorWebInbox({ + verbose: false, + onMessage, + accountId: "default", + authDir: path.join(HOME, "wa-auth"), + }); const { createWaSocket } = await import("./session.js"); const realSock = await ( createWaSocket as unknown as () => Promise<{ @@ -194,6 +199,8 @@ describe("web inbound media saves with extension", () => { verbose: false, onMessage, mediaMaxMb: 1, + accountId: "default", + authDir: path.join(HOME, "wa-auth"), }); const { createWaSocket } = await import("./session.js"); const realSock = await ( diff --git a/src/web/inbound/send-api.test.ts b/src/web/inbound/send-api.test.ts index a827f93adc..ec9d054f49 100644 --- a/src/web/inbound/send-api.test.ts +++ b/src/web/inbound/send-api.test.ts @@ -108,7 +108,7 @@ describe("createWebSendApi", () => { }); it("falls back to unknown messageId if Baileys result does not expose key.id", async () => { - sendMessage.mockResolvedValueOnce("ok"); + sendMessage.mockResolvedValueOnce({ key: {} }); const res = await api.sendMessage("+1555", "hello"); expect(res.messageId).toBe("unknown"); }); diff --git a/src/web/login.test.ts b/src/web/login.test.ts index 08f495eeac..545c47af9a 100644 --- a/src/web/login.test.ts +++ b/src/web/login.test.ts @@ -37,7 +37,9 @@ describe("web login", () => { }); it("loginWeb waits for connection and closes", async () => { - const sock = await createWaSocket(); + const sock = await ( + createWaSocket as unknown as () => Promise<{ ws: { close: () => void } }> + )(); const close = vi.spyOn(sock.ws, "close"); const waiter: typeof waitForWaConnection = vi.fn().mockResolvedValue(undefined); await loginWeb(false, waiter); diff --git a/src/web/media.test.ts b/src/web/media.test.ts index 298e819d65..d484cdc8f3 100644 --- a/src/web/media.test.ts +++ b/src/web/media.test.ts @@ -187,7 +187,7 @@ describe("web media loading", () => { status: 404, statusText: "Not Found", url: "https://example.com/missing.jpg", - } as Response); + } as unknown as Response); await expect(loadWebMedia("https://example.com/missing.jpg", 1024 * 1024)).rejects.toThrow( /Failed to fetch media from https:\/\/example\.com\/missing\.jpg.*HTTP 404/i, @@ -225,7 +225,7 @@ describe("web media loading", () => { arrayBuffer: async () => Buffer.alloc(2048).buffer, headers: { get: () => "image/png" }, status: 200, - } as Response); + } as unknown as Response); await expect(loadWebMediaRaw("https://example.com/too-big.png", 1024)).rejects.toThrow( /exceeds maxBytes 1024/i, @@ -251,7 +251,7 @@ describe("web media loading", () => { }, }, status: 200, - } as Response); + } as unknown as Response); const result = await loadWebMedia("https://example.com/download?id=1", 1024 * 1024); @@ -274,7 +274,7 @@ describe("web media loading", () => { gifBytes.buffer.slice(gifBytes.byteOffset, gifBytes.byteOffset + gifBytes.byteLength), headers: { get: () => "image/gif" }, status: 200, - } as Response); + } as unknown as Response); const result = await loadWebMedia("https://example.com/animation.gif", 1024 * 1024); diff --git a/src/web/monitor-inbox.streams-inbound-messages.test.ts b/src/web/monitor-inbox.streams-inbound-messages.test.ts index bbc6ffba40..c8c656335d 100644 --- a/src/web/monitor-inbox.streams-inbound-messages.test.ts +++ b/src/web/monitor-inbox.streams-inbound-messages.test.ts @@ -281,7 +281,9 @@ describe("web monitor inbox", () => { expect(onMessage).toHaveBeenCalledTimes(2); - resolveFirst?.(); + if (typeof resolveFirst === "function") { + resolveFirst(); + } await listener.close(); }); diff --git a/src/web/session.test.ts b/src/web/session.test.ts index ffc0e2af19..c6eeb03e98 100644 --- a/src/web/session.test.ts +++ b/src/web/session.test.ts @@ -172,7 +172,7 @@ describe("web session", () => { inFlight -= 1; }); useMultiFileAuthStateMock.mockResolvedValueOnce({ - state: { creds: {}, keys: {} }, + state: { creds: {} as never, keys: {} as never }, saveCreds, }); @@ -185,7 +185,7 @@ describe("web session", () => { await new Promise((resolve) => setImmediate(resolve)); expect(inFlight).toBe(1); - if (release) { + if (typeof release === "function") { release(); }