diff --git a/extensions/slack/src/blocks.test-helpers.ts b/extensions/slack/src/blocks.test-helpers.ts index 3ee978a2d81..ce628d73449 100644 --- a/extensions/slack/src/blocks.test-helpers.ts +++ b/extensions/slack/src/blocks.test-helpers.ts @@ -1,6 +1,23 @@ import type { WebClient } from "@slack/web-api"; import { vi } from "vitest"; +vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => ({}), + }; +}); + +vi.mock("./accounts.js", () => ({ + resolveSlackAccount: () => ({ + accountId: "default", + botToken: "xoxb-test", + botTokenSource: "config", + config: {}, + }), +})); + export type SlackEditTestClient = WebClient & { chat: { update: ReturnType; @@ -17,18 +34,7 @@ export type SlackSendTestClient = WebClient & { }; export function installSlackBlockTestMocks() { - vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ - loadConfig: () => ({}), - })); - - vi.mock("./accounts.js", () => ({ - resolveSlackAccount: () => ({ - accountId: "default", - botToken: "xoxb-test", - botTokenSource: "config", - config: {}, - }), - })); + // Backward compatible no-op. Mocks are hoisted at module scope. } export function createSlackEditTestClient(): SlackEditTestClient { diff --git a/extensions/slack/src/monitor.test-helpers.ts b/extensions/slack/src/monitor.test-helpers.ts index 08cf5810345..87443e5332c 100644 --- a/extensions/slack/src/monitor.test-helpers.ts +++ b/extensions/slack/src/monitor.test-helpers.ts @@ -192,12 +192,49 @@ 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, + dispatchInboundMessage: async (params: { + ctx: unknown; + replyOptions?: { + onReplyStart?: () => Promise | void; + onAssistantMessageStart?: () => Promise | void; + }; + dispatcher: { + sendFinalReply: (payload: unknown) => boolean; + waitForIdle: () => Promise; + markComplete: () => void; + }; + }) => { + const reply = await slackTestState.replyMock(params.ctx, { + ...params.replyOptions, + onReplyStart: + params.replyOptions?.onReplyStart ?? params.replyOptions?.onAssistantMessageStart, + }); + const queuedFinal = reply ? params.dispatcher.sendFinalReply(reply) : false; + params.dispatcher.markComplete(); + await params.dispatcher.waitForIdle(); + return { + queuedFinal, + counts: { + tool: 0, + block: 0, + final: queuedFinal ? 1 : 0, + }, + }; + }, + }; +}); vi.mock("./resolve-channels.js", () => ({ resolveSlackChannelAllowlist: async ({ entries }: { entries: string[] }) => @@ -213,21 +250,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), }; }); @@ -235,12 +265,20 @@ vi.mock("@slack/bolt", () => { const { handlers, client: slackClient } = ensureSlackTestRuntime(); class App { client = slackClient; + receiver = { + client: { + on: vi.fn(), + off: vi.fn(), + }, + }; event(name: string, handler: SlackHandler) { handlers.set(name, handler); } - command() { - /* no-op */ - } + command = vi.fn(); + action = vi.fn(); + options = vi.fn(); + view = vi.fn(); + shortcut = vi.fn(); start = vi.fn().mockResolvedValue(undefined); stop = vi.fn().mockResolvedValue(undefined); } diff --git a/extensions/slack/src/monitor/slash.test-harness.ts b/extensions/slack/src/monitor/slash.test-harness.ts index 3172154739e..410a77d9778 100644 --- a/extensions/slack/src/monitor/slash.test-harness.ts +++ b/extensions/slack/src/monitor/slash.test-harness.ts @@ -12,36 +12,52 @@ 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), + recordInboundSessionMetaSafe: (...args: unknown[]) => + mocks.recordSessionMetaFromInboundMock(...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/infra/warning-filter.test.ts b/src/infra/warning-filter.test.ts index ad3a69571f0..72c8cf25f16 100644 --- a/src/infra/warning-filter.test.ts +++ b/src/infra/warning-filter.test.ts @@ -74,7 +74,6 @@ describe("warning filter", () => { it("installs once and suppresses known warnings at emit time", async () => { const seenWarnings: Array<{ code?: string; name: string; message: string }> = []; - const stderrWrites: string[] = []; const onWarning = (warning: Error & { code?: string }) => { seenWarnings.push({ code: warning.code, @@ -82,12 +81,6 @@ describe("warning filter", () => { message: warning.message, }); }; - const stderrWriteSpy = vi.spyOn(process.stderr, "write").mockImplementation((( - chunk: string | Uint8Array, - ) => { - stderrWrites.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8")); - return true; - }) as typeof process.stderr.write); process.on("warning", onWarning); try { @@ -139,9 +132,7 @@ describe("warning filter", () => { expect( seenWarnings.find((warning) => warning.message === "The punycode module is deprecated."), ).toBeDefined(); - expect(stderrWrites.join("")).toContain("Visible warning"); } finally { - stderrWriteSpy.mockRestore(); process.off("warning", onWarning); } });