From b20ae13c6b13efcb13aa8dcc125cce0a25b1fb75 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:44:24 -0500 Subject: [PATCH] fix(ci): repair discord message handler tests --- .../src/monitor.tool-result.test-harness.ts | 72 ++++---- .../monitor/message-handler.process.test.ts | 169 +++++++++++------- 2 files changed, 136 insertions(+), 105 deletions(-) diff --git a/extensions/discord/src/monitor.tool-result.test-harness.ts b/extensions/discord/src/monitor.tool-result.test-harness.ts index e8b666cb22d..d14f10b5f5d 100644 --- a/extensions/discord/src/monitor.tool-result.test-harness.ts +++ b/extensions/discord/src/monitor.tool-result.test-harness.ts @@ -9,26 +9,25 @@ export const readAllowFromStoreMock: MockFn = vi.fn(); export const upsertPairingRequestMock: MockFn = vi.fn(); export const loadConfigMock: MockFn = vi.fn(); -vi.mock("./send.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - sendMessageDiscord: (...args: unknown[]) => sendMock(...args), - reactMessageDiscord: async (...args: unknown[]) => { - reactMock(...args); - }, - }; +const sendModule = await import("./send.js"); +vi.spyOn(sendModule, "sendMessageDiscord").mockImplementation( + (...args) => sendMock(...args) as never, +); +vi.spyOn(sendModule, "reactMessageDiscord").mockImplementation(async (...args) => { + reactMock(...args); + return { ok: true }; }); -vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - dispatchInboundMessage: (...args: unknown[]) => dispatchMock(...args), - dispatchInboundMessageWithDispatcher: (...args: unknown[]) => dispatchMock(...args), - dispatchInboundMessageWithBufferedDispatcher: (...args: unknown[]) => dispatchMock(...args), - }; -}); +const replyRuntimeModule = await import("openclaw/plugin-sdk/reply-runtime"); +vi.spyOn(replyRuntimeModule, "dispatchInboundMessage").mockImplementation( + (...args) => dispatchMock(...args) as never, +); +vi.spyOn(replyRuntimeModule, "dispatchInboundMessageWithDispatcher").mockImplementation( + (...args) => dispatchMock(...args) as never, +); +vi.spyOn(replyRuntimeModule, "dispatchInboundMessageWithBufferedDispatcher").mockImplementation( + (...args) => dispatchMock(...args) as never, +); function createPairingStoreMocks() { return { @@ -41,22 +40,23 @@ function createPairingStoreMocks() { }; } -vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - ...createPairingStoreMocks(), - }; -}); +const conversationRuntimeModule = await import("openclaw/plugin-sdk/conversation-runtime"); +vi.spyOn(conversationRuntimeModule, "readChannelAllowFromStore").mockImplementation( + createPairingStoreMocks().readChannelAllowFromStore, +); +vi.spyOn(conversationRuntimeModule, "upsertChannelPairingRequest").mockImplementation( + createPairingStoreMocks().upsertChannelPairingRequest, +); -vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: (...args: unknown[]) => loadConfigMock(...args), - readSessionUpdatedAt: vi.fn(() => undefined), - resolveStorePath: vi.fn(() => "/tmp/openclaw-sessions.json"), - updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), - resolveSessionKey: vi.fn(), - }; -}); +const configRuntimeModule = await import("openclaw/plugin-sdk/config-runtime"); +vi.spyOn(configRuntimeModule, "loadConfig").mockImplementation( + (...args) => loadConfigMock(...args) as never, +); +vi.spyOn(configRuntimeModule, "readSessionUpdatedAt").mockImplementation(() => undefined); +vi.spyOn(configRuntimeModule, "resolveStorePath").mockImplementation( + () => "/tmp/openclaw-sessions.json", +); +vi.spyOn(configRuntimeModule, "updateLastRoute").mockImplementation( + (...args) => updateLastRouteMock(...args) as never, +); +vi.spyOn(configRuntimeModule, "resolveSessionKey").mockImplementation(vi.fn() as never); diff --git a/extensions/discord/src/monitor/message-handler.process.test.ts b/extensions/discord/src/monitor/message-handler.process.test.ts index db10dfeb696..021214b88f7 100644 --- a/extensions/discord/src/monitor/message-handler.process.test.ts +++ b/extensions/discord/src/monitor/message-handler.process.test.ts @@ -2,8 +2,12 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { DEFAULT_EMOJIS } from "../../../../src/channels/status-reactions.js"; const sendMocks = vi.hoisted(() => ({ - reactMessageDiscord: vi.fn(async () => {}), - removeReactionDiscord: vi.fn(async () => {}), + reactMessageDiscord: vi.fn< + (channelId: string, messageId: string, emoji: string, opts?: unknown) => Promise + >(async () => {}), + removeReactionDiscord: vi.fn< + (channelId: string, messageId: string, emoji: string, opts?: unknown) => Promise + >(async () => {}), })); function createMockDraftStream() { return { @@ -17,8 +21,15 @@ function createMockDraftStream() { } const deliveryMocks = vi.hoisted(() => ({ - editMessageDiscord: vi.fn(async () => ({})), - deliverDiscordReply: vi.fn(async () => {}), + editMessageDiscord: vi.fn< + ( + channelId: string, + messageId: string, + payload: unknown, + opts?: unknown, + ) => Promise + >(async () => ({ id: "m1" }) as import("discord-api-types/v10").APIMessage), + deliverDiscordReply: vi.fn<(params: unknown) => Promise>(async () => {}), createDiscordDraftStream: vi.fn(() => createMockDraftStream()), })); const editMessageDiscord = deliveryMocks.editMessageDiscord; @@ -46,15 +57,23 @@ type DispatchInboundParams = { }; }; const dispatchInboundMessage = vi.hoisted(() => - vi.fn(async (_params?: DispatchInboundParams) => ({ + vi.fn< + ( + params?: DispatchInboundParams, + ) => Promise<{ queuedFinal: boolean; counts: { final: number; tool: number; block: number } }> + >(async (_params?: DispatchInboundParams) => ({ queuedFinal: false, counts: { final: 0, tool: 0, block: 0 }, })), ); -const recordInboundSession = vi.hoisted(() => vi.fn(async () => {})); +const recordInboundSession = vi.hoisted(() => + vi.fn<(params?: unknown) => Promise>(async () => {}), +); const configSessionsMocks = vi.hoisted(() => ({ - readSessionUpdatedAt: vi.fn(() => undefined), - resolveStorePath: vi.fn(() => "/tmp/openclaw-discord-process-test-sessions.json"), + readSessionUpdatedAt: vi.fn<(params?: unknown) => number | undefined>(() => undefined), + resolveStorePath: vi.fn<(path?: unknown, opts?: unknown) => string>( + () => "/tmp/openclaw-discord-process-test-sessions.json", + ), })); const readSessionUpdatedAt = configSessionsMocks.readSessionUpdatedAt; const resolveStorePath = configSessionsMocks.resolveStorePath; @@ -64,73 +83,85 @@ let threadBindingTesting: typeof import("./thread-bindings.js").__testing; let createThreadBindingManager: typeof import("./thread-bindings.js").createThreadBindingManager; let processDiscordMessage: typeof import("./message-handler.process.js").processDiscordMessage; -vi.mock("../send.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - addRoleDiscord: vi.fn(), - reactMessageDiscord: sendMocks.reactMessageDiscord, - removeReactionDiscord: sendMocks.removeReactionDiscord, - }; -}); +const sendModule = await import("../send.js"); +vi.spyOn(sendModule, "reactMessageDiscord").mockImplementation( + async (channelId, messageId, emoji, opts) => { + await sendMocks.reactMessageDiscord(channelId, messageId, emoji, opts); + return { ok: true }; + }, +); +vi.spyOn(sendModule, "removeReactionDiscord").mockImplementation( + async (channelId, messageId, emoji, opts) => { + await sendMocks.removeReactionDiscord(channelId, messageId, emoji, opts); + return { ok: true }; + }, +); -vi.mock("../send.messages.js", () => ({ - editMessageDiscord: deliveryMocks.editMessageDiscord, -})); +const sendMessagesModule = await import("../send.messages.js"); +vi.spyOn(sendMessagesModule, "editMessageDiscord").mockImplementation( + (( + channelId: Parameters[0], + messageId: Parameters[1], + payload: Parameters[2], + opts: Parameters[3], + ) => deliveryMocks.editMessageDiscord(channelId, messageId, payload, opts) as never) as never, +); -vi.mock("../draft-stream.js", () => ({ - createDiscordDraftStream: deliveryMocks.createDiscordDraftStream, -})); +const draftStreamModule = await import("../draft-stream.js"); +vi.spyOn(draftStreamModule, "createDiscordDraftStream").mockImplementation( + deliveryMocks.createDiscordDraftStream, +); -vi.mock("./reply-delivery.js", () => ({ - deliverDiscordReply: deliveryMocks.deliverDiscordReply, -})); +const replyDeliveryModule = await import("./reply-delivery.js"); +vi.spyOn(replyDeliveryModule, "deliverDiscordReply").mockImplementation( + ((params: Parameters[0]) => + deliveryMocks.deliverDiscordReply(params) as never) as never, +); -vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - dispatchInboundMessage, - createReplyDispatcherWithTyping: vi.fn( - (opts: { deliver: (payload: unknown, info: { kind: string }) => Promise | void }) => ({ - dispatcher: { - sendToolResult: vi.fn(() => true), - sendBlockReply: vi.fn((payload: unknown) => { - void opts.deliver(payload as never, { kind: "block" }); - return true; - }), - sendFinalReply: vi.fn((payload: unknown) => { - void opts.deliver(payload as never, { kind: "final" }); - return true; - }), - waitForIdle: vi.fn(async () => {}), - getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })), - markComplete: vi.fn(), - }, - replyOptions: {}, - markDispatchIdle: vi.fn(), - markRunComplete: vi.fn(), - }), - ), - }; -}); +const replyRuntimeModule = await import("openclaw/plugin-sdk/reply-runtime"); +vi.spyOn(replyRuntimeModule, "dispatchInboundMessage").mockImplementation( + ((params: Parameters[0]) => + dispatchInboundMessage(params as DispatchInboundParams) as never) as never, +); +vi.spyOn(replyRuntimeModule, "createReplyDispatcherWithTyping").mockImplementation(((opts: { + deliver: (payload: unknown, info: { kind: string }) => Promise | void; +}) => ({ + dispatcher: { + sendToolResult: vi.fn(() => true), + sendBlockReply: vi.fn((payload: unknown) => { + void opts.deliver(payload as never, { kind: "block" }); + return true; + }), + sendFinalReply: vi.fn((payload: unknown) => { + void opts.deliver(payload as never, { kind: "final" }); + return true; + }), + waitForIdle: vi.fn(async () => {}), + getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })), + markComplete: vi.fn(), + }, + replyOptions: {}, + markDispatchIdle: vi.fn(), + markRunComplete: vi.fn(), +})) as never); -vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - recordInboundSession, - }; -}); +const conversationRuntimeModule = await import("openclaw/plugin-sdk/conversation-runtime"); +vi.spyOn(conversationRuntimeModule, "recordInboundSession").mockImplementation( + ((params: Parameters[0]) => + recordInboundSession(params) as never) as never, +); -vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - readSessionUpdatedAt: configSessionsMocks.readSessionUpdatedAt, - resolveStorePath: configSessionsMocks.resolveStorePath, - }; -}); +const configRuntimeModule = await import("openclaw/plugin-sdk/config-runtime"); +vi.spyOn(configRuntimeModule, "readSessionUpdatedAt").mockImplementation( + ((params: Parameters[0]) => + configSessionsMocks.readSessionUpdatedAt(params) as never) as never, +); +vi.spyOn(configRuntimeModule, "resolveStorePath").mockImplementation( + (( + path: Parameters[0], + opts: Parameters[1], + ) => configSessionsMocks.resolveStorePath(path, opts) as never) as never, +); const BASE_CHANNEL_ROUTE = { agentId: "main",