diff --git a/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.e2e.test.ts b/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.e2e.test.ts index c64f2cd106..3a97fe8b39 100644 --- a/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.e2e.test.ts +++ b/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.e2e.test.ts @@ -1,83 +1,21 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js"; -import { resetSystemEventsForTest } from "../infra/system-events.js"; +import { describe, expect, it, vi } from "vitest"; import { monitorSignalProvider } from "./monitor.js"; +import { + config, + flush, + getSignalToolResultTestMocks, + installSignalToolResultTestHooks, + setSignalToolResultTestConfig, +} from "./monitor.tool-result.test-harness.js"; -const sendMock = vi.fn(); -const replyMock = vi.fn(); -const updateLastRouteMock = vi.fn(); -let config: Record = {}; -const readAllowFromStoreMock = vi.fn(); -const upsertPairingRequestMock = vi.fn(); +installSignalToolResultTestHooks(); -vi.mock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: () => config, - }; -}); - -vi.mock("../auto-reply/reply.js", () => ({ - getReplyFromConfig: (...args: unknown[]) => replyMock(...args), -})); - -vi.mock("./send.js", () => ({ - sendMessageSignal: (...args: unknown[]) => sendMock(...args), - sendTypingSignal: vi.fn().mockResolvedValue(true), - sendReadReceiptSignal: vi.fn().mockResolvedValue(true), -})); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); - -vi.mock("../config/sessions.js", () => ({ - resolveStorePath: vi.fn(() => "/tmp/openclaw-sessions.json"), - updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), - readSessionUpdatedAt: vi.fn(() => undefined), - recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), -})); - -const streamMock = vi.fn(); -const signalCheckMock = vi.fn(); -const signalRpcRequestMock = vi.fn(); - -vi.mock("./client.js", () => ({ - streamSignalEvents: (...args: unknown[]) => streamMock(...args), - signalCheck: (...args: unknown[]) => signalCheckMock(...args), - signalRpcRequest: (...args: unknown[]) => signalRpcRequestMock(...args), -})); - -vi.mock("./daemon.js", () => ({ - spawnSignalDaemon: vi.fn(() => ({ stop: vi.fn() })), -})); - -const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); - -beforeEach(() => { - resetInboundDedupe(); - config = { - messages: { responsePrefix: "PFX" }, - channels: { - signal: { autoStart: false, dmPolicy: "open", allowFrom: ["*"] }, - }, - }; - sendMock.mockReset().mockResolvedValue(undefined); - replyMock.mockReset(); - updateLastRouteMock.mockReset(); - streamMock.mockReset(); - signalCheckMock.mockReset().mockResolvedValue({}); - signalRpcRequestMock.mockReset().mockResolvedValue({}); - readAllowFromStoreMock.mockReset().mockResolvedValue([]); - upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true }); - resetSystemEventsForTest(); -}); +const { replyMock, sendMock, streamMock, upsertPairingRequestMock } = + getSignalToolResultTestMocks(); describe("monitorSignalProvider tool results", () => { it("pairs uuid-only senders with a uuid allowlist entry", async () => { - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, @@ -88,7 +26,7 @@ describe("monitorSignalProvider tool results", () => { allowFrom: [], }, }, - }; + }); const abortController = new AbortController(); const uuid = "123e4567-e89b-12d3-a456-426614174000"; diff --git a/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index b67d30788d..eb95570211 100644 --- a/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -1,88 +1,27 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; -import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js"; -import { peekSystemEvents, resetSystemEventsForTest } from "../infra/system-events.js"; +import { peekSystemEvents } from "../infra/system-events.js"; import { resolveAgentRoute } from "../routing/resolve-route.js"; import { normalizeE164 } from "../utils.js"; import { monitorSignalProvider } from "./monitor.js"; +import { + config, + flush, + getSignalToolResultTestMocks, + installSignalToolResultTestHooks, + setSignalToolResultTestConfig, +} from "./monitor.tool-result.test-harness.js"; -const waitForTransportReadyMock = vi.hoisted(() => vi.fn()); -const sendMock = vi.fn(); -const replyMock = vi.fn(); -const updateLastRouteMock = vi.fn(); -let config: Record = {}; -const readAllowFromStoreMock = vi.fn(); -const upsertPairingRequestMock = vi.fn(); +installSignalToolResultTestHooks(); -vi.mock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: () => config, - }; -}); - -vi.mock("../auto-reply/reply.js", () => ({ - getReplyFromConfig: (...args: unknown[]) => replyMock(...args), -})); - -vi.mock("./send.js", () => ({ - sendMessageSignal: (...args: unknown[]) => sendMock(...args), - sendTypingSignal: vi.fn().mockResolvedValue(true), - sendReadReceiptSignal: vi.fn().mockResolvedValue(true), -})); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); - -vi.mock("../config/sessions.js", () => ({ - resolveStorePath: vi.fn(() => "/tmp/openclaw-sessions.json"), - updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), - readSessionUpdatedAt: vi.fn(() => undefined), - recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), -})); - -const streamMock = vi.fn(); -const signalCheckMock = vi.fn(); -const signalRpcRequestMock = vi.fn(); - -vi.mock("./client.js", () => ({ - streamSignalEvents: (...args: unknown[]) => streamMock(...args), - signalCheck: (...args: unknown[]) => signalCheckMock(...args), - signalRpcRequest: (...args: unknown[]) => signalRpcRequestMock(...args), -})); - -vi.mock("./daemon.js", () => ({ - spawnSignalDaemon: vi.fn(() => ({ stop: vi.fn() })), -})); - -vi.mock("../infra/transport-ready.js", () => ({ - waitForTransportReady: (...args: unknown[]) => waitForTransportReadyMock(...args), -})); - -const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); - -beforeEach(() => { - resetInboundDedupe(); - config = { - messages: { responsePrefix: "PFX" }, - channels: { - signal: { autoStart: false, dmPolicy: "open", allowFrom: ["*"] }, - }, - }; - sendMock.mockReset().mockResolvedValue(undefined); - replyMock.mockReset(); - updateLastRouteMock.mockReset(); - streamMock.mockReset(); - signalCheckMock.mockReset().mockResolvedValue({}); - signalRpcRequestMock.mockReset().mockResolvedValue({}); - readAllowFromStoreMock.mockReset().mockResolvedValue([]); - upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true }); - waitForTransportReadyMock.mockReset().mockResolvedValue(undefined); - resetSystemEventsForTest(); -}); +const { + replyMock, + sendMock, + streamMock, + updateLastRouteMock, + upsertPairingRequestMock, + waitForTransportReadyMock, +} = getSignalToolResultTestMocks(); describe("monitorSignalProvider tool results", () => { it("uses bounded readiness checks when auto-starting the daemon", async () => { @@ -93,13 +32,13 @@ describe("monitorSignalProvider tool results", () => { throw new Error(`exit ${code}`); }) as (code: number) => never, }; - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, signal: { autoStart: true, dmPolicy: "open", allowFrom: ["*"] }, }, - }; + }); const abortController = new AbortController(); streamMock.mockImplementation(async () => { abortController.abort(); @@ -134,7 +73,7 @@ describe("monitorSignalProvider tool results", () => { throw new Error(`exit ${code}`); }) as (code: number) => never, }; - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, @@ -145,7 +84,7 @@ describe("monitorSignalProvider tool results", () => { startupTimeoutMs: 60_000, }, }, - }; + }); const abortController = new AbortController(); streamMock.mockImplementation(async () => { abortController.abort(); @@ -176,7 +115,7 @@ describe("monitorSignalProvider tool results", () => { throw new Error(`exit ${code}`); }) as (code: number) => never, }; - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, @@ -187,7 +126,7 @@ describe("monitorSignalProvider tool results", () => { startupTimeoutMs: 180_000, }, }, - }; + }); const abortController = new AbortController(); streamMock.mockImplementation(async () => { abortController.abort(); @@ -244,7 +183,7 @@ describe("monitorSignalProvider tool results", () => { }); it("replies with pairing code when dmPolicy is pairing and no allowFrom is set", async () => { - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, @@ -255,7 +194,7 @@ describe("monitorSignalProvider tool results", () => { allowFrom: [], }, }, - }; + }); const abortController = new AbortController(); streamMock.mockImplementation(async ({ onEvent }) => { @@ -367,7 +306,7 @@ describe("monitorSignalProvider tool results", () => { }); it("enqueues system events for reaction notifications", async () => { - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, @@ -379,7 +318,7 @@ describe("monitorSignalProvider tool results", () => { reactionNotifications: "all", }, }, - }; + }); const abortController = new AbortController(); streamMock.mockImplementation(async ({ onEvent }) => { @@ -421,7 +360,7 @@ describe("monitorSignalProvider tool results", () => { }); it("notifies on own reactions when target includes uuid + phone", async () => { - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, @@ -434,7 +373,7 @@ describe("monitorSignalProvider tool results", () => { reactionNotifications: "own", }, }, - }; + }); const abortController = new AbortController(); streamMock.mockImplementation(async ({ onEvent }) => { @@ -516,7 +455,7 @@ describe("monitorSignalProvider tool results", () => { }); it("does not resend pairing code when a request is already pending", async () => { - config = { + setSignalToolResultTestConfig({ ...config, channels: { ...config.channels, @@ -527,7 +466,7 @@ describe("monitorSignalProvider tool results", () => { allowFrom: [], }, }, - }; + }); const abortController = new AbortController(); upsertPairingRequestMock .mockResolvedValueOnce({ code: "PAIRCODE", created: true }) diff --git a/src/signal/monitor.tool-result.test-harness.ts b/src/signal/monitor.tool-result.test-harness.ts new file mode 100644 index 0000000000..21d0ba9bdc --- /dev/null +++ b/src/signal/monitor.tool-result.test-harness.ts @@ -0,0 +1,103 @@ +import { beforeEach, vi } from "vitest"; +import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js"; +import { resetSystemEventsForTest } from "../infra/system-events.js"; + +const waitForTransportReadyMock = vi.hoisted(() => vi.fn()); +const sendMock = vi.hoisted(() => vi.fn()); +const replyMock = vi.hoisted(() => vi.fn()); +const updateLastRouteMock = vi.hoisted(() => vi.fn()); +const readAllowFromStoreMock = vi.hoisted(() => vi.fn()); +const upsertPairingRequestMock = vi.hoisted(() => vi.fn()); +const streamMock = vi.hoisted(() => vi.fn()); +const signalCheckMock = vi.hoisted(() => vi.fn()); +const signalRpcRequestMock = vi.hoisted(() => vi.fn()); + +export function getSignalToolResultTestMocks() { + return { + waitForTransportReadyMock, + sendMock, + replyMock, + updateLastRouteMock, + readAllowFromStoreMock, + upsertPairingRequestMock, + streamMock, + signalCheckMock, + signalRpcRequestMock, + }; +} + +export let config: Record = {}; + +export function setSignalToolResultTestConfig(next: Record) { + config = next; +} + +export const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); + +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => config, + }; +}); + +vi.mock("../auto-reply/reply.js", () => ({ + getReplyFromConfig: (...args: unknown[]) => replyMock(...args), +})); + +vi.mock("./send.js", () => ({ + sendMessageSignal: (...args: unknown[]) => sendMock(...args), + sendTypingSignal: vi.fn().mockResolvedValue(true), + sendReadReceiptSignal: vi.fn().mockResolvedValue(true), +})); + +vi.mock("../pairing/pairing-store.js", () => ({ + readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), + upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), +})); + +vi.mock("../config/sessions.js", () => ({ + resolveStorePath: vi.fn(() => "/tmp/openclaw-sessions.json"), + updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), + readSessionUpdatedAt: vi.fn(() => undefined), + recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock("./client.js", () => ({ + streamSignalEvents: (...args: unknown[]) => streamMock(...args), + signalCheck: (...args: unknown[]) => signalCheckMock(...args), + signalRpcRequest: (...args: unknown[]) => signalRpcRequestMock(...args), +})); + +vi.mock("./daemon.js", () => ({ + spawnSignalDaemon: vi.fn(() => ({ stop: vi.fn() })), +})); + +vi.mock("../infra/transport-ready.js", () => ({ + waitForTransportReady: (...args: unknown[]) => waitForTransportReadyMock(...args), +})); + +export function installSignalToolResultTestHooks() { + beforeEach(() => { + resetInboundDedupe(); + config = { + messages: { responsePrefix: "PFX" }, + channels: { + signal: { autoStart: false, dmPolicy: "open", allowFrom: ["*"] }, + }, + }; + + sendMock.mockReset().mockResolvedValue(undefined); + replyMock.mockReset(); + updateLastRouteMock.mockReset(); + streamMock.mockReset(); + signalCheckMock.mockReset().mockResolvedValue({}); + signalRpcRequestMock.mockReset().mockResolvedValue({}); + readAllowFromStoreMock.mockReset().mockResolvedValue([]); + upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true }); + waitForTransportReadyMock.mockReset().mockResolvedValue(undefined); + + resetSystemEventsForTest(); + }); +}