From 6a9b23d2b3178cc51727c3f5116a0ab34cc2e9d6 Mon Sep 17 00:00:00 2001 From: joshavant <830519+joshavant@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:49:40 -0500 Subject: [PATCH] Tests: align extension-fast inferable lane mocks --- .../discord/src/monitor/message-utils.test.ts | 29 ++++--- .../discord/src/monitor/monitor.test.ts | 86 ++++++++++--------- .../thread-bindings.discord-api.test.ts | 4 + extensions/slack/src/monitor.test-helpers.ts | 38 ++++---- .../slack/src/monitor/slash.test-harness.ts | 70 +++++++++------ src/line/bot-handlers.test.ts | 18 +++- src/line/download.test.ts | 4 + src/line/monitor.lifecycle.test.ts | 4 + src/line/probe.test.ts | 5 ++ src/line/send.test.ts | 4 + 10 files changed, 167 insertions(+), 95 deletions(-) diff --git a/extensions/discord/src/monitor/message-utils.test.ts b/extensions/discord/src/monitor/message-utils.test.ts index 0a29fc5b0ab..92d4250bac3 100644 --- a/extensions/discord/src/monitor/message-utils.test.ts +++ b/extensions/discord/src/monitor/message-utils.test.ts @@ -2,20 +2,29 @@ import { ChannelType, type Client, type Message } from "@buape/carbon"; import { StickerFormatType } from "discord-api-types/v10"; import { beforeEach, describe, expect, it, vi } from "vitest"; +vi.hoisted(() => { + vi.resetModules(); +}); + const fetchRemoteMedia = vi.fn(); const saveMediaBuffer = vi.fn(); -vi.mock("../../../../src/media/fetch.js", () => ({ - fetchRemoteMedia: (...args: unknown[]) => fetchRemoteMedia(...args), -})); +vi.mock("openclaw/plugin-sdk/media-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetchRemoteMedia: (...args: unknown[]) => fetchRemoteMedia(...args), + saveMediaBuffer: (...args: unknown[]) => saveMediaBuffer(...args), + }; +}); -vi.mock("../../../../src/media/store.js", () => ({ - saveMediaBuffer: (...args: unknown[]) => saveMediaBuffer(...args), -})); - -vi.mock("../../../../src/globals.js", () => ({ - logVerbose: () => {}, -})); +vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + logVerbose: () => {}, + }; +}); const { __resetDiscordChannelInfoCacheForTest, diff --git a/extensions/discord/src/monitor/monitor.test.ts b/extensions/discord/src/monitor/monitor.test.ts index da916c4bd2b..5d7c2e2139e 100644 --- a/extensions/discord/src/monitor/monitor.test.ts +++ b/extensions/discord/src/monitor/monitor.test.ts @@ -46,6 +46,10 @@ import { resolveDiscordReplyDeliveryPlan, } from "./threading.js"; +vi.hoisted(() => { + vi.resetModules(); +}); + const readAllowFromStoreMock = vi.hoisted(() => vi.fn()); const upsertPairingRequestMock = vi.hoisted(() => vi.fn()); const enqueueSystemEventMock = vi.hoisted(() => vi.fn()); @@ -59,45 +63,12 @@ const resolvePluginConversationBindingApprovalMock = vi.hoisted(() => vi.fn()); const buildPluginBindingResolvedTextMock = vi.hoisted(() => vi.fn()); let lastDispatchCtx: Record | undefined; -vi.mock("../../../../src/pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); - -vi.mock("../../../../src/infra/system-events.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args), - }; -}); - -vi.mock("../../../../src/auto-reply/reply/provider-dispatcher.js", () => ({ - dispatchReplyWithBufferedBlockDispatcher: (...args: unknown[]) => dispatchReplyMock(...args), -})); - -vi.mock("./reply-delivery.js", () => ({ - deliverDiscordReply: (...args: unknown[]) => deliverDiscordReplyMock(...args), -})); - -vi.mock("../../../../src/channels/session.js", () => ({ - recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args), -})); - -vi.mock("../../../../src/config/sessions.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - readSessionUpdatedAt: (...args: unknown[]) => readSessionUpdatedAtMock(...args), - resolveStorePath: (...args: unknown[]) => resolveStorePathMock(...args), - }; -}); - -vi.mock("../../../../src/plugins/conversation-binding.js", async (importOriginal) => { - const actual = - await importOriginal(); +vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, + readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), + upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), resolvePluginConversationBindingApproval: (...args: unknown[]) => resolvePluginConversationBindingApprovalMock(...args), buildPluginBindingResolvedText: (...args: unknown[]) => @@ -105,8 +76,45 @@ vi.mock("../../../../src/plugins/conversation-binding.js", async (importOriginal }; }); -vi.mock("../../../../src/plugins/interactive.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("./reply-delivery.js", () => ({ + deliverDiscordReply: (...args: unknown[]) => deliverDiscordReplyMock(...args), +})); + +vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args), + }; +}); + +vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + dispatchReplyWithBufferedBlockDispatcher: (...args: unknown[]) => dispatchReplyMock(...args), + }; +}); + +vi.mock("openclaw/plugin-sdk/channel-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args), + }; +}); + +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + readSessionUpdatedAt: (...args: unknown[]) => readSessionUpdatedAtMock(...args), + resolveStorePath: (...args: unknown[]) => resolveStorePathMock(...args), + }; +}); + +vi.mock("openclaw/plugin-sdk/plugin-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, dispatchPluginInteractiveHandler: (...args: unknown[]) => diff --git a/extensions/discord/src/monitor/thread-bindings.discord-api.test.ts b/extensions/discord/src/monitor/thread-bindings.discord-api.test.ts index eb085235da7..bb95b642e26 100644 --- a/extensions/discord/src/monitor/thread-bindings.discord-api.test.ts +++ b/extensions/discord/src/monitor/thread-bindings.discord-api.test.ts @@ -3,6 +3,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../../../src/config/config.js"; import type { ThreadBindingRecord } from "./thread-bindings.types.js"; +vi.hoisted(() => { + vi.resetModules(); +}); + const hoisted = vi.hoisted(() => { const restGet = vi.fn(); const sendMessageDiscord = vi.fn(); diff --git a/extensions/slack/src/monitor.test-helpers.ts b/extensions/slack/src/monitor.test-helpers.ts index 08cf5810345..301531361d5 100644 --- a/extensions/slack/src/monitor.test-helpers.ts +++ b/extensions/slack/src/monitor.test-helpers.ts @@ -1,5 +1,9 @@ import { Mock, vi } from "vitest"; +vi.hoisted(() => { + vi.resetModules(); +}); + type SlackHandler = (args: unknown) => Promise; type SlackProviderMonitor = (params: { botToken: string; @@ -192,12 +196,21 @@ vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { return { ...actual, loadConfig: () => slackTestState.config, + resolveStorePath: vi.fn(() => "/tmp/openclaw-sessions.json"), + updateLastRoute: (...args: unknown[]) => slackTestState.updateLastRouteMock(...args), + resolveSessionKey: vi.fn(), + readSessionUpdatedAt: vi.fn(() => undefined), + recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), }; }); -vi.mock("openclaw/plugin-sdk/reply-runtime", () => ({ - getReplyFromConfig: (...args: unknown[]) => slackTestState.replyMock(...args), -})); +vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getReplyFromConfig: (...args: unknown[]) => slackTestState.replyMock(...args), + }; +}); vi.mock("./resolve-channels.js", () => ({ resolveSlackChannelAllowlist: async ({ entries }: { entries: string[] }) => @@ -213,21 +226,14 @@ vi.mock("./send.js", () => ({ sendMessageSlack: (...args: unknown[]) => slackTestState.sendMock(...args), })); -vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => slackTestState.readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => - slackTestState.upsertPairingRequestMock(...args), -})); - -vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, - resolveStorePath: vi.fn(() => "/tmp/openclaw-sessions.json"), - updateLastRoute: (...args: unknown[]) => slackTestState.updateLastRouteMock(...args), - resolveSessionKey: vi.fn(), - readSessionUpdatedAt: vi.fn(() => undefined), - recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), + readChannelAllowFromStore: (...args: unknown[]) => + slackTestState.readAllowFromStoreMock(...args), + upsertChannelPairingRequest: (...args: unknown[]) => + slackTestState.upsertPairingRequestMock(...args), }; }); diff --git a/extensions/slack/src/monitor/slash.test-harness.ts b/extensions/slack/src/monitor/slash.test-harness.ts index 3172154739e..593b1443362 100644 --- a/extensions/slack/src/monitor/slash.test-harness.ts +++ b/extensions/slack/src/monitor/slash.test-harness.ts @@ -1,5 +1,9 @@ import { vi } from "vitest"; +vi.hoisted(() => { + vi.resetModules(); +}); + const mocks = vi.hoisted(() => ({ dispatchMock: vi.fn(), readAllowFromStoreMock: vi.fn(), @@ -12,36 +16,50 @@ const mocks = vi.hoisted(() => ({ resolveStorePathMock: vi.fn(), })); -vi.mock("openclaw/plugin-sdk/reply-runtime", () => ({ - dispatchReplyWithDispatcher: (...args: unknown[]) => mocks.dispatchMock(...args), -})); +vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + dispatchReplyWithDispatcher: (...args: unknown[]) => mocks.dispatchMock(...args), + finalizeInboundContext: (...args: unknown[]) => mocks.finalizeInboundContextMock(...args), + }; +}); -vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => mocks.readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => mocks.upsertPairingRequestMock(...args), -})); +vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + readChannelAllowFromStore: (...args: unknown[]) => mocks.readAllowFromStoreMock(...args), + upsertChannelPairingRequest: (...args: unknown[]) => mocks.upsertPairingRequestMock(...args), + }; +}); -vi.mock("openclaw/plugin-sdk/routing", () => ({ - resolveAgentRoute: (...args: unknown[]) => mocks.resolveAgentRouteMock(...args), -})); +vi.mock("openclaw/plugin-sdk/routing", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveAgentRoute: (...args: unknown[]) => mocks.resolveAgentRouteMock(...args), + }; +}); -vi.mock("openclaw/plugin-sdk/reply-runtime", () => ({ - finalizeInboundContext: (...args: unknown[]) => mocks.finalizeInboundContextMock(...args), -})); +vi.mock("openclaw/plugin-sdk/channel-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveConversationLabel: (...args: unknown[]) => mocks.resolveConversationLabelMock(...args), + createReplyPrefixOptions: (...args: unknown[]) => mocks.createReplyPrefixOptionsMock(...args), + }; +}); -vi.mock("openclaw/plugin-sdk/channel-runtime", () => ({ - resolveConversationLabel: (...args: unknown[]) => mocks.resolveConversationLabelMock(...args), -})); - -vi.mock("openclaw/plugin-sdk/channel-runtime", () => ({ - createReplyPrefixOptions: (...args: unknown[]) => mocks.createReplyPrefixOptionsMock(...args), -})); - -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ - recordSessionMetaFromInbound: (...args: unknown[]) => - mocks.recordSessionMetaFromInboundMock(...args), - resolveStorePath: (...args: unknown[]) => mocks.resolveStorePathMock(...args), -})); +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + recordSessionMetaFromInbound: (...args: unknown[]) => + mocks.recordSessionMetaFromInboundMock(...args), + resolveStorePath: (...args: unknown[]) => mocks.resolveStorePathMock(...args), + }; +}); type SlashHarnessMocks = { dispatchMock: ReturnType; diff --git a/src/line/bot-handlers.test.ts b/src/line/bot-handlers.test.ts index 21d54c41f0d..4d625c2b18b 100644 --- a/src/line/bot-handlers.test.ts +++ b/src/line/bot-handlers.test.ts @@ -2,6 +2,10 @@ import type { MessageEvent, PostbackEvent } from "@line/bot-sdk"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { LineAccountConfig } from "./types.js"; +vi.hoisted(() => { + vi.resetModules(); +}); + // Avoid pulling in globals/pairing/media dependencies; this suite only asserts // allowlist/groupPolicy gating and message-context wiring. vi.mock("../globals.js", () => ({ @@ -206,10 +210,16 @@ describe("handleLineWebhookEvents", () => { }); beforeEach(() => { - buildLineMessageContextMock.mockClear(); - buildLinePostbackContextMock.mockClear(); - readAllowFromStoreMock.mockClear(); - upsertPairingRequestMock.mockClear(); + buildLineMessageContextMock.mockReset().mockResolvedValue({ + ctxPayload: { From: "line:group:group-1" }, + replyToken: "reply-token", + route: { agentId: "default" }, + isGroup: true, + accountId: "default", + }); + buildLinePostbackContextMock.mockReset().mockResolvedValue(null as unknown); + readAllowFromStoreMock.mockReset().mockResolvedValue([]); + upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "CODE", created: true }); }); it("blocks group messages when groupPolicy is disabled", async () => { diff --git a/src/line/download.test.ts b/src/line/download.test.ts index 1a0d12b2a3b..9c239f760cc 100644 --- a/src/line/download.test.ts +++ b/src/line/download.test.ts @@ -3,6 +3,10 @@ import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; +vi.hoisted(() => { + vi.resetModules(); +}); + const getMessageContentMock = vi.hoisted(() => vi.fn()); vi.mock("@line/bot-sdk", () => ({ diff --git a/src/line/monitor.lifecycle.test.ts b/src/line/monitor.lifecycle.test.ts index d1ad3194096..66317e7f90e 100644 --- a/src/line/monitor.lifecycle.test.ts +++ b/src/line/monitor.lifecycle.test.ts @@ -2,6 +2,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import type { RuntimeEnv } from "../runtime.js"; +vi.hoisted(() => { + vi.resetModules(); +}); + const { createLineBotMock, registerPluginHttpRouteMock, unregisterHttpMock } = vi.hoisted(() => ({ createLineBotMock: vi.fn(() => ({ account: { accountId: "default" }, diff --git a/src/line/probe.test.ts b/src/line/probe.test.ts index 737a2d8f892..690dd5974e5 100644 --- a/src/line/probe.test.ts +++ b/src/line/probe.test.ts @@ -1,4 +1,9 @@ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; + +vi.hoisted(() => { + vi.resetModules(); +}); + const { getBotInfoMock, MessagingApiClientMock } = vi.hoisted(() => { const getBotInfoMock = vi.fn(); const MessagingApiClientMock = vi.fn(function () { diff --git a/src/line/send.test.ts b/src/line/send.test.ts index 01695925932..76dbc6da8f1 100644 --- a/src/line/send.test.ts +++ b/src/line/send.test.ts @@ -1,5 +1,9 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +vi.hoisted(() => { + vi.resetModules(); +}); + const { pushMessageMock, replyMessageMock,