From c6433f8de4ece1ed07f6412e8844e5ce9e431dd2 Mon Sep 17 00:00:00 2001 From: Josh Lehman Date: Fri, 20 Mar 2026 20:40:07 -0700 Subject: [PATCH] test: align extension seam mocks Regeneration-Prompt: | Clean up extension tests that still mocked old src/** internals after the plugin-sdk/runtime seam refactors. Inspect the production imports first, then update only the tests whose mocked dependency no longer matches the live seam. Keep the change test-only, prefer mocking openclaw/plugin-sdk/* entry points over core internals, and remove redundant dead mocks where the seam mock already covers the behavior. Validate with targeted vitest runs for the touched Telegram, Slack, Discord, WhatsApp, and Feishu tests. --- .../discord/src/send.webhook-activity.test.ts | 8 +- ...monitor.acp-init-failure.lifecycle.test.ts | 7 -- .../src/monitor.bot-menu.lifecycle.test.ts | 7 -- ...tor.broadcast.reply-once.lifecycle.test.ts | 7 -- .../src/monitor.card-action.lifecycle.test.ts | 7 -- .../src/monitor.reply-once.lifecycle.test.ts | 7 -- .../src/monitor/events/interactions.test.ts | 12 +- ...bot-message-context.thread-binding.test.ts | 5 +- .../bot-message-context.topic-agentid.test.ts | 6 +- .../bot-native-commands.session-meta.test.ts | 34 +++--- extensions/telegram/src/send.proxy.test.ts | 4 +- .../src/auto-reply/heartbeat-runner.test.ts | 114 +++++++++--------- extensions/whatsapp/src/inbound.media.test.ts | 17 +-- .../whatsapp/src/inbound/send-api.test.ts | 2 +- .../whatsapp/src/login.coverage.test.ts | 24 ++-- 15 files changed, 106 insertions(+), 155 deletions(-) diff --git a/extensions/discord/src/send.webhook-activity.test.ts b/extensions/discord/src/send.webhook-activity.test.ts index 04354936050..be6fec554c5 100644 --- a/extensions/discord/src/send.webhook-activity.test.ts +++ b/extensions/discord/src/send.webhook-activity.test.ts @@ -4,16 +4,16 @@ import { sendWebhookMessageDiscord } from "./send.js"; const recordChannelActivityMock = vi.hoisted(() => vi.fn()); const loadConfigMock = vi.hoisted(() => vi.fn(() => ({ channels: { discord: {} } }))); -vi.mock("../../../src/config/config.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, loadConfig: () => loadConfigMock(), }; }); -vi.mock("../../../src/infra/channel-activity.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, recordChannelActivity: (...args: unknown[]) => recordChannelActivityMock(...args), diff --git a/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test.ts b/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test.ts index d98bbec9e7c..3ab43677868 100644 --- a/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test.ts +++ b/extensions/feishu/src/monitor.acp-init-failure.lifecycle.test.ts @@ -64,13 +64,6 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { }; }); -vi.mock("../../../src/infra/outbound/session-binding-service.js", () => ({ - getSessionBindingService: () => ({ - resolveByConversation: resolveBoundConversationMock, - touch: touchBindingMock, - }), -})); - function createLifecycleConfig(): ClawdbotConfig { return { session: { mainKey: "main", scope: "per-sender" }, diff --git a/extensions/feishu/src/monitor.bot-menu.lifecycle.test.ts b/extensions/feishu/src/monitor.bot-menu.lifecycle.test.ts index e235af4d8ec..cd988862bdf 100644 --- a/extensions/feishu/src/monitor.bot-menu.lifecycle.test.ts +++ b/extensions/feishu/src/monitor.bot-menu.lifecycle.test.ts @@ -75,13 +75,6 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { }; }); -vi.mock("../../../src/infra/outbound/session-binding-service.js", () => ({ - getSessionBindingService: () => ({ - resolveByConversation: resolveBoundConversationMock, - touch: touchBindingMock, - }), -})); - function createLifecycleConfig(): ClawdbotConfig { return { channels: { diff --git a/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test.ts b/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test.ts index 839ea934454..da29c4a6dbb 100644 --- a/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test.ts +++ b/extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test.ts @@ -71,13 +71,6 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { }; }); -vi.mock("../../../src/infra/outbound/session-binding-service.js", () => ({ - getSessionBindingService: () => ({ - resolveByConversation: resolveBoundConversationMock, - touch: touchBindingMock, - }), -})); - function createLifecycleConfig(): ClawdbotConfig { return { broadcast: { diff --git a/extensions/feishu/src/monitor.card-action.lifecycle.test.ts b/extensions/feishu/src/monitor.card-action.lifecycle.test.ts index c5908b29487..de34dafa29a 100644 --- a/extensions/feishu/src/monitor.card-action.lifecycle.test.ts +++ b/extensions/feishu/src/monitor.card-action.lifecycle.test.ts @@ -77,13 +77,6 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { }; }); -vi.mock("../../../src/infra/outbound/session-binding-service.js", () => ({ - getSessionBindingService: () => ({ - resolveByConversation: resolveBoundConversationMock, - touch: touchBindingMock, - }), -})); - function createLifecycleConfig(): ClawdbotConfig { return { channels: { diff --git a/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts b/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts index 4a965110613..43ba7bdf3e6 100644 --- a/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts +++ b/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts @@ -71,13 +71,6 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { }; }); -vi.mock("../../../src/infra/outbound/session-binding-service.js", () => ({ - getSessionBindingService: () => ({ - resolveByConversation: resolveBoundConversationMock, - touch: touchBindingMock, - }), -})); - function createLifecycleConfig(): ClawdbotConfig { return { messages: { diff --git a/extensions/slack/src/monitor/events/interactions.test.ts b/extensions/slack/src/monitor/events/interactions.test.ts index 5b71ebd11fd..0e2599f0747 100644 --- a/extensions/slack/src/monitor/events/interactions.test.ts +++ b/extensions/slack/src/monitor/events/interactions.test.ts @@ -10,20 +10,20 @@ const dispatchPluginInteractiveHandlerMock = vi.fn(async () => ({ const resolvePluginConversationBindingApprovalMock = vi.fn(); const buildPluginBindingResolvedTextMock = vi.fn(() => "Binding updated."); -vi.mock("../../../../../src/infra/system-events.js", () => ({ +vi.mock("openclaw/plugin-sdk/infra-runtime", () => ({ enqueueSystemEvent: (...args: unknown[]) => (enqueueSystemEventMock as (...innerArgs: unknown[]) => unknown)(...args), })); -vi.mock("../../../../../src/plugins/interactive.js", () => ({ +vi.mock("openclaw/plugin-sdk/plugin-runtime", () => ({ dispatchPluginInteractiveHandler: (...args: unknown[]) => (dispatchPluginInteractiveHandlerMock as (...innerArgs: unknown[]) => unknown)(...args), })); -vi.mock("../../../../../src/plugins/conversation-binding.js", async () => { - const actual = await vi.importActual< - typeof import("../../../../../src/plugins/conversation-binding.js") - >("../../../../../src/plugins/conversation-binding.js"); +vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/conversation-runtime", + ); return { ...actual, resolvePluginConversationBindingApproval: (...args: unknown[]) => diff --git a/extensions/telegram/src/bot-message-context.thread-binding.test.ts b/extensions/telegram/src/bot-message-context.thread-binding.test.ts index e635b6f4a11..70a5d4f8c1c 100644 --- a/extensions/telegram/src/bot-message-context.thread-binding.test.ts +++ b/extensions/telegram/src/bot-message-context.thread-binding.test.ts @@ -9,9 +9,8 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock("../../../src/infra/outbound/session-binding-service.js", async (importOriginal) => { - const actual = - await importOriginal(); +vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, getSessionBindingService: () => ({ diff --git a/extensions/telegram/src/bot-message-context.topic-agentid.test.ts b/extensions/telegram/src/bot-message-context.topic-agentid.test.ts index 57c0c8209a0..6a278b4c1a8 100644 --- a/extensions/telegram/src/bot-message-context.topic-agentid.test.ts +++ b/extensions/telegram/src/bot-message-context.topic-agentid.test.ts @@ -1,5 +1,5 @@ +import { loadConfig } from "openclaw/plugin-sdk/config-runtime"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { loadConfig } from "../../../src/config/config.js"; const { defaultRouteConfig } = vi.hoisted(() => ({ defaultRouteConfig: { @@ -11,8 +11,8 @@ const { defaultRouteConfig } = vi.hoisted(() => ({ }, })); -vi.mock("../../../src/config/config.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, loadConfig: vi.fn(() => defaultRouteConfig), diff --git a/extensions/telegram/src/bot-native-commands.session-meta.test.ts b/extensions/telegram/src/bot-native-commands.session-meta.test.ts index bfe314d4140..0ed7c389098 100644 --- a/extensions/telegram/src/bot-native-commands.session-meta.test.ts +++ b/extensions/telegram/src/bot-native-commands.session-meta.test.ts @@ -1,5 +1,5 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../../../src/config/config.js"; import type { ResolvedAgentRoute } from "../../../src/routing/resolve-route.js"; import type { TelegramBotDeps } from "./bot-deps.js"; import { @@ -56,6 +56,11 @@ const replyMocks = vi.hoisted(() => ({ const deliveryMocks = vi.hoisted(() => ({ deliverReplies: vi.fn(async () => ({ delivered: true })), })); +const pluginRuntimeMocks = vi.hoisted(() => ({ + getPluginCommandSpecs: vi.fn(() => []), + matchPluginCommand: vi.fn(() => null), + executePluginCommand: vi.fn(async () => ({ text: "ok" })), +})); const sessionBindingMocks = vi.hoisted(() => ({ resolveByConversation: vi.fn< (ref: unknown) => { bindingId: string; targetSessionKey: string } | null @@ -123,28 +128,19 @@ vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { listSkillCommandsForAgents: vi.fn(() => []), }; }); +vi.mock("openclaw/plugin-sdk/plugin-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getPluginCommandSpecs: pluginRuntimeMocks.getPluginCommandSpecs, + matchPluginCommand: pluginRuntimeMocks.matchPluginCommand, + executePluginCommand: pluginRuntimeMocks.executePluginCommand, + }; +}); vi.mock("../../../src/config/sessions.js", () => ({ recordSessionMetaFromInbound: sessionMocks.recordSessionMetaFromInbound, resolveStorePath: sessionMocks.resolveStorePath, })); -vi.mock("../../../src/pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: vi.fn(async () => []), -})); -vi.mock("../../../src/infra/outbound/session-binding-service.js", () => ({ - getSessionBindingService: () => ({ - bind: vi.fn(), - getCapabilities: vi.fn(), - listBySession: vi.fn(), - resolveByConversation: (ref: unknown) => sessionBindingMocks.resolveByConversation(ref), - touch: (bindingId: string, at?: number) => sessionBindingMocks.touch(bindingId, at), - unbind: vi.fn(), - }), -})); -vi.mock("../../../src/plugins/commands.js", () => ({ - getPluginCommandSpecs: vi.fn(() => []), - matchPluginCommand: vi.fn(() => null), - executePluginCommand: vi.fn(async () => ({ text: "ok" })), -})); vi.mock("./bot/delivery.js", () => ({ deliverReplies: deliveryMocks.deliverReplies, })); diff --git a/extensions/telegram/src/send.proxy.test.ts b/extensions/telegram/src/send.proxy.test.ts index 6c17b33fe38..be6a7b4faaf 100644 --- a/extensions/telegram/src/send.proxy.test.ts +++ b/extensions/telegram/src/send.proxy.test.ts @@ -21,8 +21,8 @@ const { resolveTelegramFetch } = vi.hoisted(() => ({ resolveTelegramFetch: vi.fn(), })); -vi.mock("../../../src/config/config.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, loadConfig, diff --git a/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts b/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts index 234b4dddfd5..fec4b4d72cb 100644 --- a/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts +++ b/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts @@ -1,7 +1,7 @@ +import type { getReplyFromConfig } from "openclaw/plugin-sdk/reply-runtime"; +import { HEARTBEAT_TOKEN } from "openclaw/plugin-sdk/reply-runtime"; +import { redactIdentifier } from "openclaw/plugin-sdk/text-runtime"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { getReplyFromConfig } from "../../../../src/auto-reply/reply.js"; -import { HEARTBEAT_TOKEN } from "../../../../src/auto-reply/tokens.js"; -import { redactIdentifier } from "../../../../src/logging/redact-identifier.js"; import type { sendMessageWhatsApp } from "../send.js"; const state = vi.hoisted(() => ({ @@ -22,78 +22,66 @@ const state = vi.hoisted(() => ({ heartbeatWarnLogs: [] as string[], })); -vi.mock("../../../../src/agents/current-time.js", () => ({ - appendCronStyleCurrentTimeLine: (body: string) => - `${body}\nCurrent time: 2026-02-15T00:00:00Z (mock)`, -})); +vi.mock("openclaw/plugin-sdk/agent-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + appendCronStyleCurrentTimeLine: (body: string) => + `${body}\nCurrent time: 2026-02-15T00:00:00Z (mock)`, + }; +}); // Perf: this module otherwise pulls a large dependency graph that we don't need // for these unit tests. -vi.mock("../../../../src/auto-reply/reply.js", () => ({ - getReplyFromConfig: vi.fn(async () => undefined), -})); +vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getReplyFromConfig: vi.fn(async () => undefined), + }; +}); -vi.mock("../../../../src/channels/plugins/whatsapp-heartbeat.js", () => ({ - resolveWhatsAppHeartbeatRecipients: () => [], -})); +vi.mock("openclaw/plugin-sdk/channel-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveWhatsAppHeartbeatRecipients: () => [], + }; +}); -vi.mock("../../../../src/config/config.js", () => ({ - loadConfig: () => ({ agents: { defaults: {} }, session: {} }), -})); +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => ({ agents: { defaults: {} }, session: {} }), + loadSessionStore: () => state.store, + resolveSessionKey: () => "k", + resolveStorePath: () => "/tmp/store.json", + updateSessionStore: async (_path: string, updater: (store: typeof state.store) => void) => { + updater(state.store); + }, + }; +}); -vi.mock("../../../../src/routing/session-key.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/routing", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, normalizeMainKey: () => null, }; }); -vi.mock("../../../../src/infra/heartbeat-visibility.js", () => ({ - resolveHeartbeatVisibility: () => state.visibility, -})); - -vi.mock("../../../../src/config/sessions.js", () => ({ - loadSessionStore: () => state.store, - resolveSessionKey: () => "k", - resolveStorePath: () => "/tmp/store.json", - updateSessionStore: async (_path: string, updater: (store: typeof state.store) => void) => { - updater(state.store); - }, -})); - vi.mock("./session-snapshot.js", () => ({ getSessionSnapshot: () => state.snapshot, })); -vi.mock("../../../../src/infra/heartbeat-events.js", () => ({ - emitHeartbeatEvent: (event: unknown) => state.events.push(event), - resolveIndicatorType: (status: string) => `indicator:${status}`, -})); - -vi.mock("../../../../src/logging.js", async (importOriginal) => { - const actual = await importOriginal(); - const createStubLogger = () => ({ - info: () => undefined, - warn: () => undefined, - error: () => undefined, - child: createStubLogger, - }); +vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, - getChildLogger: () => ({ - info: (...args: unknown[]) => state.loggerInfoCalls.push(args), - warn: (...args: unknown[]) => state.loggerWarnCalls.push(args), - }), - createSubsystemLogger: () => createStubLogger(), - }; -}); - -vi.mock("openclaw/plugin-sdk/state-paths", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - resolveOAuthDir: () => "/tmp/openclaw-oauth", + resolveHeartbeatVisibility: () => state.visibility, + emitHeartbeatEvent: (event: unknown) => state.events.push(event), + resolveIndicatorType: (status: string) => `indicator:${status}`, }; }); @@ -108,10 +96,22 @@ vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => { }; return { ...actual, + getChildLogger: () => ({ + info: (...args: unknown[]) => state.loggerInfoCalls.push(args), + warn: (...args: unknown[]) => state.loggerWarnCalls.push(args), + }), createSubsystemLogger: () => logger, }; }); +vi.mock("openclaw/plugin-sdk/state-paths", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveOAuthDir: () => "/tmp/openclaw-oauth", + }; +}); + vi.mock("../auth-store.js", () => ({ WA_WEB_AUTH_DIR: "/tmp/openclaw-oauth/whatsapp/default", resolveDefaultWebAuthDir: () => "/tmp/openclaw-oauth/whatsapp/default", diff --git a/extensions/whatsapp/src/inbound.media.test.ts b/extensions/whatsapp/src/inbound.media.test.ts index 7ed52cace45..196457c84ec 100644 --- a/extensions/whatsapp/src/inbound.media.test.ts +++ b/extensions/whatsapp/src/inbound.media.test.ts @@ -4,12 +4,10 @@ import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const readAllowFromStoreMock = vi.fn().mockResolvedValue([]); -const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true }); const saveMediaBufferSpy = vi.fn(); -vi.mock("../../../src/config/config.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, loadConfig: vi.fn().mockReturnValue({ @@ -26,17 +24,6 @@ vi.mock("../../../src/config/config.js", async (importOriginal) => { }; }); -vi.mock("../../../src/pairing/pairing-store.js", () => { - return { - readChannelAllowFromStore(...args: unknown[]) { - return readAllowFromStoreMock(...args); - }, - upsertChannelPairingRequest(...args: unknown[]) { - return upsertPairingRequestMock(...args); - }, - }; -}); - vi.mock("../../../src/media/store.js", async (importOriginal) => { const actual = await importOriginal(); return { diff --git a/extensions/whatsapp/src/inbound/send-api.test.ts b/extensions/whatsapp/src/inbound/send-api.test.ts index e7bfcdce360..4e2d3127961 100644 --- a/extensions/whatsapp/src/inbound/send-api.test.ts +++ b/extensions/whatsapp/src/inbound/send-api.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const recordChannelActivity = vi.fn(); -vi.mock("../../../../src/infra/channel-activity.js", () => ({ +vi.mock("openclaw/plugin-sdk/infra-runtime", () => ({ recordChannelActivity: (...args: unknown[]) => recordChannelActivity(...args), })); diff --git a/extensions/whatsapp/src/login.coverage.test.ts b/extensions/whatsapp/src/login.coverage.test.ts index dda665ccdce..89f0eb8a407 100644 --- a/extensions/whatsapp/src/login.coverage.test.ts +++ b/extensions/whatsapp/src/login.coverage.test.ts @@ -19,18 +19,22 @@ function resolveTestAuthDir() { const authDir = resolveTestAuthDir(); -vi.mock("../../../src/config/config.js", () => ({ - loadConfig: () => - ({ - channels: { - whatsapp: { - accounts: { - default: { enabled: true, authDir: resolveTestAuthDir() }, +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => + ({ + channels: { + whatsapp: { + accounts: { + default: { enabled: true, authDir: resolveTestAuthDir() }, + }, }, }, - }, - }) as never, -})); + }) as never, + }; +}); vi.mock("./session.js", () => { const authDir = resolveTestAuthDir();