diff --git a/extensions/discord/src/audit.test.ts b/extensions/discord/src/audit.test.ts index c1b276f320b..d5b1fd6148a 100644 --- a/extensions/discord/src/audit.test.ts +++ b/extensions/discord/src/audit.test.ts @@ -1,8 +1,12 @@ import { describe, expect, it, vi } from "vitest"; -vi.mock("./send.js", () => ({ - fetchChannelPermissionsDiscord: vi.fn(), -})); +vi.mock("./send.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetchChannelPermissionsDiscord: vi.fn(), + }; +}); describe("discord audit", () => { it("collects numeric channel ids and counts unresolved keys", async () => { diff --git a/extensions/discord/src/monitor/message-handler.process.test.ts b/extensions/discord/src/monitor/message-handler.process.test.ts index fc04211a38f..20b7c897a00 100644 --- a/extensions/discord/src/monitor/message-handler.process.test.ts +++ b/extensions/discord/src/monitor/message-handler.process.test.ts @@ -67,10 +67,14 @@ const configSessionsMocks = vi.hoisted(() => ({ const readSessionUpdatedAt = configSessionsMocks.readSessionUpdatedAt; const resolveStorePath = configSessionsMocks.resolveStorePath; -vi.mock("../send.js", () => ({ - reactMessageDiscord: sendMocks.reactMessageDiscord, - removeReactionDiscord: sendMocks.removeReactionDiscord, -})); +vi.mock("../send.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + reactMessageDiscord: sendMocks.reactMessageDiscord, + removeReactionDiscord: sendMocks.removeReactionDiscord, + }; +}); vi.mock("../send.messages.js", () => ({ editMessageDiscord: deliveryMocks.editMessageDiscord, diff --git a/extensions/discord/src/monitor/reply-delivery.test.ts b/extensions/discord/src/monitor/reply-delivery.test.ts index bd4d0e91dfd..bbfbe6eeae8 100644 --- a/extensions/discord/src/monitor/reply-delivery.test.ts +++ b/extensions/discord/src/monitor/reply-delivery.test.ts @@ -12,11 +12,15 @@ const sendVoiceMessageDiscordMock = vi.hoisted(() => vi.fn()); const sendWebhookMessageDiscordMock = vi.hoisted(() => vi.fn()); const sendDiscordTextMock = vi.hoisted(() => vi.fn()); -vi.mock("../send.js", () => ({ - sendMessageDiscord: (...args: unknown[]) => sendMessageDiscordMock(...args), - sendVoiceMessageDiscord: (...args: unknown[]) => sendVoiceMessageDiscordMock(...args), - sendWebhookMessageDiscord: (...args: unknown[]) => sendWebhookMessageDiscordMock(...args), -})); +vi.mock("../send.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + sendMessageDiscord: (...args: unknown[]) => sendMessageDiscordMock(...args), + sendVoiceMessageDiscord: (...args: unknown[]) => sendVoiceMessageDiscordMock(...args), + sendWebhookMessageDiscord: (...args: unknown[]) => sendWebhookMessageDiscordMock(...args), + }; +}); vi.mock("../send.shared.js", () => ({ sendDiscordText: (...args: unknown[]) => sendDiscordTextMock(...args), 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..14de19a63fb 100644 --- a/extensions/discord/src/monitor/thread-bindings.discord-api.test.ts +++ b/extensions/discord/src/monitor/thread-bindings.discord-api.test.ts @@ -24,10 +24,14 @@ vi.mock("../client.js", () => ({ createDiscordRestClient: hoisted.createDiscordRestClient, })); -vi.mock("../send.js", () => ({ - sendMessageDiscord: (...args: unknown[]) => hoisted.sendMessageDiscord(...args), - sendWebhookMessageDiscord: (...args: unknown[]) => hoisted.sendWebhookMessageDiscord(...args), -})); +vi.mock("../send.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + sendMessageDiscord: (...args: unknown[]) => hoisted.sendMessageDiscord(...args), + sendWebhookMessageDiscord: (...args: unknown[]) => hoisted.sendWebhookMessageDiscord(...args), + }; +}); const { maybeSendBindingMessage, resolveChannelIdForBinding } = await import("./thread-bindings.discord-api.js"); diff --git a/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts b/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts index 237cc6b8081..88c76435bab 100644 --- a/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts +++ b/extensions/discord/src/monitor/thread-bindings.lifecycle.test.ts @@ -41,10 +41,14 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock("../send.js", () => ({ - sendMessageDiscord: hoisted.sendMessageDiscord, - sendWebhookMessageDiscord: hoisted.sendWebhookMessageDiscord, -})); +vi.mock("../send.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + sendMessageDiscord: hoisted.sendMessageDiscord, + sendWebhookMessageDiscord: hoisted.sendWebhookMessageDiscord, + }; +}); vi.mock("../send.messages.js", () => ({ createThreadDiscord: hoisted.createThreadDiscord, diff --git a/extensions/discord/src/voice/manager.e2e.test.ts b/extensions/discord/src/voice/manager.e2e.test.ts index 0889e351bf5..64d3a7b2f14 100644 --- a/extensions/discord/src/voice/manager.e2e.test.ts +++ b/extensions/discord/src/voice/manager.e2e.test.ts @@ -85,9 +85,13 @@ vi.mock("@discordjs/voice", () => ({ joinVoiceChannel: joinVoiceChannelMock, })); -vi.mock("openclaw/plugin-sdk/routing", () => ({ - resolveAgentRoute: resolveAgentRouteMock, -})); +vi.mock("openclaw/plugin-sdk/routing", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveAgentRoute: resolveAgentRouteMock, + }; +}); vi.mock("openclaw/plugin-sdk/agent-runtime", async (importOriginal) => { const actual = await importOriginal(); diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index ea1c098e7b6..16051165c1e 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -11,6 +11,13 @@ const createTelegramDraftStream = vi.hoisted(() => vi.fn()); const dispatchReplyWithBufferedBlockDispatcher = vi.hoisted(() => vi.fn()); const deliverReplies = vi.hoisted(() => vi.fn()); const editMessageTelegram = vi.hoisted(() => vi.fn()); +const createForumTopicTelegram = vi.hoisted(() => vi.fn()); +const deleteMessageTelegram = vi.hoisted(() => vi.fn()); +const editForumTopicTelegram = vi.hoisted(() => vi.fn()); +const reactMessageTelegram = vi.hoisted(() => vi.fn()); +const sendMessageTelegram = vi.hoisted(() => vi.fn()); +const sendPollTelegram = vi.hoisted(() => vi.fn()); +const sendStickerTelegram = vi.hoisted(() => vi.fn()); const loadSessionStore = vi.hoisted(() => vi.fn()); const resolveStorePath = vi.hoisted(() => vi.fn(() => "/tmp/sessions.json")); @@ -27,11 +34,18 @@ vi.mock("./bot/delivery.js", () => ({ })); vi.mock("./send.js", () => ({ + createForumTopicTelegram, + deleteMessageTelegram, + editForumTopicTelegram, editMessageTelegram, + reactMessageTelegram, + sendMessageTelegram, + sendPollTelegram, + sendStickerTelegram, })); -vi.mock("../../../src/config/sessions.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, loadSessionStore, @@ -41,6 +55,10 @@ vi.mock("../../../src/config/sessions.js", async (importOriginal) => { vi.mock("./sticker-cache.js", () => ({ cacheSticker: vi.fn(), + getCachedSticker: () => null, + getCacheStats: () => ({ count: 0 }), + searchStickers: () => [], + getAllCachedStickers: () => [], describeStickerImage: vi.fn(), })); @@ -154,6 +172,9 @@ describe("dispatchTelegramMessage draft streaming", () => { replyToMode: "first", streamMode: params.streamMode ?? "partial", textLimit: 4096, + telegramDeps: { + dispatchReplyWithBufferedBlockDispatcher, + } as unknown as NonNullable[0]["telegramDeps"]>, telegramCfg: params.telegramCfg ?? {}, opts: { token: "token" }, }); diff --git a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts index f2f8f89ce63..4fd9493ca98 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts @@ -1,3 +1,6 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { resetInboundDedupe } from "openclaw/plugin-sdk/reply-runtime"; import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime"; @@ -29,11 +32,22 @@ const { loadWebMedia } = vi.hoisted((): { loadWebMedia: AnyMock } => ({ loadWebMedia: vi.fn(), })); +function maybePrefixReplyText(text: unknown, responsePrefix?: string): unknown { + if (typeof text !== "string") { + return text; + } + const normalizedPrefix = responsePrefix?.trim(); + if (!normalizedPrefix || !text.trim() || text.startsWith(normalizedPrefix)) { + return text; + } + return `${normalizedPrefix} ${text}`; +} + export function getLoadWebMediaMock(): AnyMock { return loadWebMedia; } -vi.doMock("openclaw/plugin-sdk/web-media", () => ({ +vi.mock("openclaw/plugin-sdk/web-media", () => ({ loadWebMedia, })); @@ -49,18 +63,11 @@ const { resolveStorePathMock } = vi.hoisted( export function getLoadConfigMock(): AnyMock { return loadConfig; } -vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, loadConfig, - }; -}); - -vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, resolveStorePath: resolveStorePathMock, }; }); @@ -86,7 +93,7 @@ export function getUpsertChannelPairingRequestMock(): AnyAsyncMock { return upsertChannelPairingRequest; } -vi.doMock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { +vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, @@ -117,7 +124,15 @@ const skillCommandsHoisted = vi.hoisted(() => ({ const reply = await skillCommandsHoisted.replySpy(params.ctx, params.replyOptions); const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply]; for (const payload of payloads) { - await params.dispatcherOptions?.deliver?.(payload, { kind: "final" }); + await params.dispatcherOptions?.deliver?.( + { + ...payload, + text: maybePrefixReplyText(payload.text, params.dispatcherOptions.responsePrefix) as + | string + | undefined, + }, + { kind: "final" }, + ); } return result; }, @@ -128,7 +143,7 @@ export const replySpy = skillCommandsHoisted.replySpy; export const dispatchReplyWithBufferedBlockDispatcher = skillCommandsHoisted.dispatchReplyWithBufferedBlockDispatcher; -vi.doMock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { +vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, @@ -146,7 +161,7 @@ const systemEventsHoisted = vi.hoisted(() => ({ export const enqueueSystemEventSpy: MockFn = systemEventsHoisted.enqueueSystemEventSpy; -vi.doMock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { +vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, @@ -159,7 +174,7 @@ const sentMessageCacheHoisted = vi.hoisted(() => ({ })); export const wasSentByBot = sentMessageCacheHoisted.wasSentByBot; -vi.doMock("./sent-message-cache.js", () => ({ +vi.mock("./sent-message-cache.js", () => ({ wasSentByBot: sentMessageCacheHoisted.wasSentByBot, recordSentMessage: vi.fn(), clearSentMessageCache: vi.fn(), @@ -347,6 +362,9 @@ export function makeForumGroupMessageCtx(params?: { beforeEach(() => { resetInboundDedupe(); + fs.rmSync(path.join(os.homedir(), ".openclaw", "credentials", "telegram-pairing.json"), { + force: true, + }); loadConfig.mockReset(); loadConfig.mockReturnValue(DEFAULT_TELEGRAM_TEST_CONFIG); resolveStorePathMock.mockReset(); @@ -376,7 +394,15 @@ beforeEach(() => { const reply = await replySpy(params.ctx, params.replyOptions); const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply]; for (const payload of payloads) { - await params.dispatcherOptions?.deliver?.(payload, { kind: "final" }); + await params.dispatcherOptions?.deliver?.( + { + ...payload, + text: maybePrefixReplyText(payload.text, params.dispatcherOptions.responsePrefix) as + | string + | undefined, + }, + { kind: "final" }, + ); } return result; }, diff --git a/extensions/telegram/src/bot.create-telegram-bot.test.ts b/extensions/telegram/src/bot.create-telegram-bot.test.ts index 7fbab89cdab..ecca8c1eb6c 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test.ts @@ -1,7 +1,12 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { + clearRuntimeConfigSnapshot, + setRuntimeConfigSnapshot, + type OpenClawConfig, +} from "../../../src/config/config.js"; import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js"; import { withEnvAsync } from "../../../test/helpers/extensions/env.js"; import { useFrozenTime, useRealTime } from "../../../test/helpers/extensions/frozen-time.js"; @@ -43,16 +48,20 @@ const { setTelegramBotRuntimeForTest( telegramBotRuntimeForTest as unknown as Parameters[0], ); -const createTelegramBot = (opts: Parameters[0]) => - createTelegramBotBase({ - ...opts, - telegramDeps: telegramBotDepsForTest, - }); - const loadConfig = getLoadConfigMock(); const loadWebMedia = getLoadWebMediaMock(); const readChannelAllowFromStore = getReadChannelAllowFromStoreMock(); const upsertChannelPairingRequest = getUpsertChannelPairingRequestMock(); +const resolveHarnessConfig = () => (loadConfig as unknown as () => OpenClawConfig)(); +const createTelegramBot = (opts: Parameters[0]) => { + const cfg = opts.config ?? resolveHarnessConfig(); + setRuntimeConfigSnapshot(cfg); + return createTelegramBotBase({ + ...opts, + config: cfg, + telegramDeps: telegramBotDepsForTest, + }); +}; const ORIGINAL_TZ = process.env.TZ; const TELEGRAM_TEST_TIMINGS = { @@ -68,6 +77,9 @@ describe("createTelegramBot", () => { afterAll(() => { process.env.TZ = ORIGINAL_TZ; }); + afterEach(() => { + clearRuntimeConfigSnapshot(); + }); // groupPolicy tests @@ -217,6 +229,9 @@ describe("createTelegramBot", () => { ] as const; for (const testCase of cases) { + fs.rmSync(path.join(os.homedir(), ".openclaw", "credentials", "telegram-pairing.json"), { + force: true, + }); onSpy.mockClear(); sendMessageSpy.mockClear(); replySpy.mockClear(); @@ -250,10 +265,14 @@ describe("createTelegramBot", () => { if (testCase.expectPairingText) { expect(sendMessageSpy.mock.calls[0]?.[0], testCase.name).toBe(1234); const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]); + const codeMatch = pairingText.match(/Pairing code: ([A-Z0-9]{8})/); expect(pairingText, testCase.name).toContain("Your Telegram user id: 999"); expect(pairingText, testCase.name).toContain("Pairing code:"); - expect(pairingText, testCase.name).toContain("PAIRME12"); - expect(pairingText, testCase.name).toContain("openclaw pairing approve telegram PAIRME12"); + expect(codeMatch, testCase.name).not.toBeNull(); + const pairingCode = codeMatch?.[1] ?? ""; + expect(pairingText, testCase.name).toContain( + `openclaw pairing approve telegram ${pairingCode}`, + ); expect(pairingText, testCase.name).not.toContain(""); } } @@ -1065,26 +1084,30 @@ describe("createTelegramBot", () => { mediaUrl: "https://example.com/fun", }); - loadWebMedia.mockResolvedValueOnce({ - buffer: Buffer.from("GIF89a"), - contentType: "image/gif", - fileName: "fun.gif", - }); + const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response(Buffer.from("GIF89a"), { + status: 200, + headers: { "content-type": "image/gif" }, + }), + ); + try { + createTelegramBot({ token: "tok" }); + const handler = getOnHandler("message") as (ctx: Record) => Promise; - createTelegramBot({ token: "tok" }); - const handler = getOnHandler("message") as (ctx: Record) => Promise; - - await handler({ - message: { - chat: { id: 1234, type: "private" }, - text: "hello world", - date: 1736380800, - message_id: 5, - from: { first_name: "Ada" }, - }, - me: { username: "openclaw_bot" }, - getFile: async () => ({ download: async () => new Uint8Array() }), - }); + await handler({ + message: { + chat: { id: 1234, type: "private" }, + text: "hello world", + date: 1736380800, + message_id: 5, + from: { first_name: "Ada" }, + }, + me: { username: "openclaw_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }); + } finally { + fetchSpy.mockRestore(); + } expect(sendAnimationSpy).toHaveBeenCalledTimes(1); expect(sendAnimationSpy).toHaveBeenCalledWith("1234", expect.anything(), { diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index 2de1e06fc6d..103bdfce1d7 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -1,7 +1,12 @@ import { rm } from "node:fs/promises"; import type { PluginInteractiveTelegramHandlerContext } from "openclaw/plugin-sdk/core"; -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { expectChannelInboundContextContract as expectInboundContextContract } from "../../../src/channels/plugins/contracts/suites.js"; +import { + clearRuntimeConfigSnapshot, + setRuntimeConfigSnapshot, + type OpenClawConfig, +} from "../../../src/config/config.js"; import { clearPluginInteractiveHandlers, registerPluginInteractiveHandler, @@ -38,14 +43,18 @@ const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } setTelegramBotRuntimeForTest( telegramBotRuntimeForTest as unknown as Parameters[0], ); -const createTelegramBot = (opts: Parameters[0]) => - createTelegramBotBase({ - ...opts, - telegramDeps: telegramBotDepsForTest, - }); - const loadConfig = getLoadConfigMock(); const readChannelAllowFromStore = getReadChannelAllowFromStoreMock(); +const resolveHarnessConfig = () => (loadConfig as unknown as () => OpenClawConfig)(); +const createTelegramBot = (opts: Parameters[0]) => { + const cfg = opts.config ?? resolveHarnessConfig(); + setRuntimeConfigSnapshot(cfg); + return createTelegramBotBase({ + ...opts, + config: cfg, + telegramDeps: telegramBotDepsForTest, + }); +}; function resolveSkillCommands(config: Parameters[0]) { void config; @@ -62,6 +71,9 @@ describe("createTelegramBot", () => { afterAll(() => { process.env.TZ = ORIGINAL_TZ; }); + afterEach(() => { + clearRuntimeConfigSnapshot(); + }); beforeEach(() => { setMyCommandsSpy.mockClear(); diff --git a/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts b/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts index a0022abaa8c..53fa49ddcb8 100644 --- a/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts +++ b/extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts @@ -22,57 +22,80 @@ 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", () => ({ - normalizeMainKey: () => null, -})); +vi.mock("openclaw/plugin-sdk/routing", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + normalizeMainKey: () => null, + DEFAULT_ACCOUNT_ID: actual.DEFAULT_ACCOUNT_ID ?? "default", + }; +}); -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("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveHeartbeatVisibility: () => state.visibility, + emitHeartbeatEvent: (event: unknown) => state.events.push(event), + resolveIndicatorType: (status: string) => `indicator:${status}`, + }; +}); 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", () => ({ - getChildLogger: () => ({ - info: (...args: unknown[]) => state.loggerInfoCalls.push(args), - warn: (...args: unknown[]) => state.loggerWarnCalls.push(args), - }), -})); +vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getChildLogger: () => ({ + info: (...args: unknown[]) => state.loggerInfoCalls.push(args), + warn: (...args: unknown[]) => state.loggerWarnCalls.push(args), + }), + }; +}); vi.mock("./loggers.js", () => ({ whatsappHeartbeatLog: { @@ -87,6 +110,7 @@ vi.mock("../reconnect.js", () => ({ vi.mock("../send.js", () => ({ sendMessageWhatsApp: vi.fn(async () => ({ messageId: "m1" })), + sendReactionWhatsApp: vi.fn(async () => undefined), })); vi.mock("../session.js", () => ({ diff --git a/extensions/whatsapp/src/inbound/access-control.test-harness.ts b/extensions/whatsapp/src/inbound/access-control.test-harness.ts index 495615a3cbb..3096b6d70bd 100644 --- a/extensions/whatsapp/src/inbound/access-control.test-harness.ts +++ b/extensions/whatsapp/src/inbound/access-control.test-harness.ts @@ -41,7 +41,11 @@ vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { }; }); -vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); +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), + }; +}); diff --git a/extensions/whatsapp/src/outbound-adapter.poll.test.ts b/extensions/whatsapp/src/outbound-adapter.poll.test.ts index 46c9696cc98..9b16610722f 100644 --- a/extensions/whatsapp/src/outbound-adapter.poll.test.ts +++ b/extensions/whatsapp/src/outbound-adapter.poll.test.ts @@ -9,9 +9,13 @@ vi.mock("../../../src/globals.js", () => ({ shouldLogVerbose: () => false, })); -vi.mock("./send.js", () => ({ - sendPollWhatsApp: hoisted.sendPollWhatsApp, -})); +vi.mock("./send.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + sendPollWhatsApp: hoisted.sendPollWhatsApp, + }; +}); import { whatsappOutbound } from "./outbound-adapter.js";