From b8b7a6e0fa1f093bde5bb49e3439fb39f5937e74 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 19:34:53 +0000 Subject: [PATCH] refactor(test): dedupe web monitor inbox test setup --- ...ssages-from-senders-allowfrom-list.test.ts | 174 ++++------------- ...unauthorized-senders-not-allowfrom.test.ts | 176 ++++-------------- ...captures-media-path-image-messages.test.ts | 117 ++---------- ...tor-inbox.streams-inbound-messages.test.ts | 144 +++----------- src/web/monitor-inbox.test-harness.ts | 123 ++++++++++++ 5 files changed, 243 insertions(+), 491 deletions(-) create mode 100644 src/web/monitor-inbox.test-harness.ts diff --git a/src/web/monitor-inbox.allows-messages-from-senders-allowfrom-list.test.ts b/src/web/monitor-inbox.allows-messages-from-senders-allowfrom-list.test.ts index 3c21832da7a..9891485a8be 100644 --- a/src/web/monitor-inbox.allows-messages-from-senders-allowfrom-list.test.ts +++ b/src/web/monitor-inbox.allows-messages-from-senders-allowfrom-list.test.ts @@ -1,100 +1,19 @@ -import { vi } from "vitest"; +import "./monitor-inbox.test-harness.js"; +import { describe, expect, it, vi } from "vitest"; +import { monitorWebInbox } from "./inbound.js"; +import { + DEFAULT_ACCOUNT_ID, + getAuthDir, + getSock, + installWebMonitorInboxUnitTestHooks, + mockLoadConfig, + upsertPairingRequestMock, +} from "./monitor-inbox.test-harness.js"; -vi.mock("../media/store.js", () => ({ - saveMediaBuffer: vi.fn().mockResolvedValue({ - id: "mid", - path: "/tmp/mid", - size: 1, - contentType: "image/jpeg", - }), -})); - -const mockLoadConfig = vi.fn().mockReturnValue({ - channels: { - whatsapp: { - // Allow all in tests by default - allowFrom: ["*"], - }, - }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, -}); - -const readAllowFromStoreMock = vi.fn().mockResolvedValue([]); -const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true }); - -vi.mock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: () => mockLoadConfig(), - }; -}); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); - -vi.mock("./session.js", () => { - const { EventEmitter } = require("node:events"); - const ev = new EventEmitter(); - const sock = { - ev, - ws: { close: vi.fn() }, - sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn().mockResolvedValue(undefined), - readMessages: vi.fn().mockResolvedValue(undefined), - updateMediaMessage: vi.fn(), - logger: {}, - signalRepository: { - lidMapping: { - getPNForLID: vi.fn().mockResolvedValue(null), - }, - }, - user: { id: "123@s.whatsapp.net" }, - }; - return { - createWaSocket: vi.fn().mockResolvedValue(sock), - waitForWaConnection: vi.fn().mockResolvedValue(undefined), - getStatusCode: vi.fn(() => 500), - }; -}); - -const { createWaSocket } = await import("./session.js"); -const _getSock = () => (createWaSocket as unknown as () => Promise>)(); - -import fsSync from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { resetLogger, setLoggerOverride } from "../logging.js"; -import { monitorWebInbox, resetWebInboundDedupe } from "./inbound.js"; - -const ACCOUNT_ID = "default"; const nowSeconds = (offsetMs = 0) => Math.floor((Date.now() + offsetMs) / 1000); -let authDir: string; describe("web monitor inbox", () => { - beforeEach(() => { - vi.clearAllMocks(); - readAllowFromStoreMock.mockResolvedValue([]); - upsertPairingRequestMock.mockResolvedValue({ - code: "PAIRCODE", - created: true, - }); - resetWebInboundDedupe(); - authDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-")); - }); - - afterEach(() => { - resetLogger(); - setLoggerOverride(null); - vi.useRealTimers(); - fsSync.rmSync(authDir, { recursive: true, force: true }); - }); + installWebMonitorInboxUnitTestHooks(); it("allows messages from senders in allowFrom list", async () => { mockLoadConfig.mockReturnValue({ @@ -113,11 +32,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -142,15 +61,6 @@ describe("web monitor inbox", () => { }), ); - // Reset mock for other tests - mockLoadConfig.mockReturnValue({ - channels: { whatsapp: { allowFrom: ["*"] } }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, - }); - await listener.close(); }); @@ -173,11 +83,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); // Message from self (sock.user.id is "123@s.whatsapp.net" in mock) const upsert = { @@ -199,15 +109,6 @@ describe("web monitor inbox", () => { expect.objectContaining({ body: "self message", from: "+123" }), ); - // Reset mock for other tests - mockLoadConfig.mockReturnValue({ - channels: { whatsapp: { allowFrom: ["*"] } }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, - }); - await listener.close(); }); @@ -221,11 +122,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); // Message from someone else should be blocked const upsertBlocked = { @@ -302,15 +203,6 @@ describe("web monitor inbox", () => { }), ); - // Reset mock for other tests - mockLoadConfig.mockReturnValue({ - channels: { whatsapp: { allowFrom: ["*"] } }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, - }); - await listener.close(); }); @@ -331,11 +223,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -387,11 +279,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -430,11 +322,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "append", @@ -475,10 +367,10 @@ describe("web monitor inbox", () => { const listener = await monitorWebInbox({ verbose: false, onMessage: vi.fn(), - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), }); - const sock = await createWaSocket(); + const sock = getSock(); await listener.sendReaction("12345@g.us", "msg123", "👍", false, "+6421000000"); diff --git a/src/web/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test.ts b/src/web/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test.ts index 8baf6ba196c..d60cec85f0b 100644 --- a/src/web/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test.ts +++ b/src/web/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test.ts @@ -1,100 +1,18 @@ -import { vi } from "vitest"; +import "./monitor-inbox.test-harness.js"; +import { describe, expect, it, vi } from "vitest"; +import { monitorWebInbox } from "./inbound.js"; +import { + DEFAULT_ACCOUNT_ID, + getAuthDir, + getSock, + installWebMonitorInboxUnitTestHooks, + mockLoadConfig, +} from "./monitor-inbox.test-harness.js"; -vi.mock("../media/store.js", () => ({ - saveMediaBuffer: vi.fn().mockResolvedValue({ - id: "mid", - path: "/tmp/mid", - size: 1, - contentType: "image/jpeg", - }), -})); - -const mockLoadConfig = vi.fn().mockReturnValue({ - channels: { - whatsapp: { - // Allow all in tests by default - allowFrom: ["*"], - }, - }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, -}); - -const readAllowFromStoreMock = vi.fn().mockResolvedValue([]); -const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true }); - -vi.mock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: () => mockLoadConfig(), - }; -}); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); - -vi.mock("./session.js", () => { - const { EventEmitter } = require("node:events"); - const ev = new EventEmitter(); - const sock = { - ev, - ws: { close: vi.fn() }, - sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn().mockResolvedValue(undefined), - readMessages: vi.fn().mockResolvedValue(undefined), - updateMediaMessage: vi.fn(), - logger: {}, - signalRepository: { - lidMapping: { - getPNForLID: vi.fn().mockResolvedValue(null), - }, - }, - user: { id: "123@s.whatsapp.net" }, - }; - return { - createWaSocket: vi.fn().mockResolvedValue(sock), - waitForWaConnection: vi.fn().mockResolvedValue(undefined), - getStatusCode: vi.fn(() => 500), - }; -}); - -const { createWaSocket } = await import("./session.js"); -const _getSock = () => (createWaSocket as unknown as () => Promise>)(); - -import fsSync from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { resetLogger, setLoggerOverride } from "../logging.js"; -import { monitorWebInbox, resetWebInboundDedupe } from "./inbound.js"; - -const _ACCOUNT_ID = "default"; const nowSeconds = (offsetMs = 0) => Math.floor((Date.now() + offsetMs) / 1000); -let authDir: string; describe("web monitor inbox", () => { - beforeEach(() => { - vi.clearAllMocks(); - readAllowFromStoreMock.mockResolvedValue([]); - upsertPairingRequestMock.mockResolvedValue({ - code: "PAIRCODE", - created: true, - }); - resetWebInboundDedupe(); - authDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-")); - }); - - afterEach(() => { - resetLogger(); - setLoggerOverride(null); - vi.useRealTimers(); - fsSync.rmSync(authDir, { recursive: true, force: true }); - }); + installWebMonitorInboxUnitTestHooks(); it("blocks messages from unauthorized senders not in allowFrom", async () => { // Test for auto-recovery fix: early allowFrom filtering prevents Bad MAC errors @@ -115,11 +33,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); // Message from unauthorized sender +999 (not in allowFrom) const upsert = { @@ -152,15 +70,6 @@ describe("web monitor inbox", () => { text: expect.stringContaining("Pairing code: PAIRCODE"), }); - // Reset mock for other tests - mockLoadConfig.mockReturnValue({ - channels: { whatsapp: { allowFrom: ["*"] } }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, - }); - await listener.close(); }); @@ -181,11 +90,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -207,15 +116,6 @@ describe("web monitor inbox", () => { ); expect(sock.readMessages).not.toHaveBeenCalled(); - // Reset mock for other tests - mockLoadConfig.mockReturnValue({ - channels: { whatsapp: { allowFrom: ["*"] } }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, - }); - await listener.close(); }); @@ -223,12 +123,12 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, sendReadReceipts: false, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -262,11 +162,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -308,11 +208,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -357,11 +257,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -406,11 +306,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -458,11 +358,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", @@ -508,11 +408,11 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, - accountId: _ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), onMessage, }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", diff --git a/src/web/monitor-inbox.captures-media-path-image-messages.test.ts b/src/web/monitor-inbox.captures-media-path-image-messages.test.ts index e70e5def546..092358382fd 100644 --- a/src/web/monitor-inbox.captures-media-path-image-messages.test.ts +++ b/src/web/monitor-inbox.captures-media-path-image-messages.test.ts @@ -1,105 +1,24 @@ -import { vi } from "vitest"; - -vi.mock("../media/store.js", () => ({ - saveMediaBuffer: vi.fn().mockResolvedValue({ - id: "mid", - path: "/tmp/mid", - size: 1, - contentType: "image/jpeg", - }), -})); - -const mockLoadConfig = vi.fn().mockReturnValue({ - channels: { - whatsapp: { - // Allow all in tests by default - allowFrom: ["*"], - }, - }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, -}); - -const readAllowFromStoreMock = vi.fn().mockResolvedValue([]); -const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true }); - -vi.mock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: () => mockLoadConfig(), - }; -}); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); - -vi.mock("./session.js", () => { - const { EventEmitter } = require("node:events"); - const ev = new EventEmitter(); - const sock = { - ev, - ws: { close: vi.fn() }, - sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn().mockResolvedValue(undefined), - readMessages: vi.fn().mockResolvedValue(undefined), - updateMediaMessage: vi.fn(), - logger: {}, - signalRepository: { - lidMapping: { - getPNForLID: vi.fn().mockResolvedValue(null), - }, - }, - user: { id: "123@s.whatsapp.net" }, - }; - return { - createWaSocket: vi.fn().mockResolvedValue(sock), - waitForWaConnection: vi.fn().mockResolvedValue(undefined), - getStatusCode: vi.fn(() => 500), - }; -}); - -const { createWaSocket } = await import("./session.js"); -const _getSock = () => (createWaSocket as unknown as () => Promise>)(); - import crypto from "node:crypto"; import fsSync from "node:fs"; import os from "node:os"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { resetLogger, setLoggerOverride } from "../logging.js"; -import { monitorWebInbox, resetWebInboundDedupe } from "./inbound.js"; - -const _ACCOUNT_ID = "default"; -let authDir: string; +import "./monitor-inbox.test-harness.js"; +import { describe, expect, it, vi } from "vitest"; +import { setLoggerOverride } from "../logging.js"; +import { monitorWebInbox } from "./inbound.js"; +import { + getSock, + installWebMonitorInboxUnitTestHooks, + mockLoadConfig, +} from "./monitor-inbox.test-harness.js"; describe("web monitor inbox", () => { - beforeEach(() => { - vi.clearAllMocks(); - readAllowFromStoreMock.mockResolvedValue([]); - upsertPairingRequestMock.mockResolvedValue({ - code: "PAIRCODE", - created: true, - }); - resetWebInboundDedupe(); - authDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-")); - }); - - afterEach(() => { - resetLogger(); - setLoggerOverride(null); - vi.useRealTimers(); - fsSync.rmSync(authDir, { recursive: true, force: true }); - }); + installWebMonitorInboxUnitTestHooks(); it("captures media path for image messages", async () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -134,7 +53,7 @@ describe("web monitor inbox", () => { it("sets gifPlayback on outbound video payloads when requested", async () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const buf = Buffer.from("gifvid"); await listener.sendMessage("+1555", "gif", buf, "video/mp4", { @@ -156,7 +75,7 @@ describe("web monitor inbox", () => { verbose: false, onMessage: vi.fn(), }); - const sock = await createWaSocket(); + const sock = getSock(); const reasonPromise = listener.onClose; sock.ev.emit("connection.update", { connection: "close", @@ -174,7 +93,7 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -199,7 +118,7 @@ describe("web monitor inbox", () => { it("includes participant when marking group messages read", async () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -232,7 +151,7 @@ describe("web monitor inbox", () => { it("passes through group messages with participant metadata", async () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -272,7 +191,7 @@ describe("web monitor inbox", () => { it("unwraps ephemeral messages, preserves mentions, and still delivers group pings", async () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -331,7 +250,7 @@ describe("web monitor inbox", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ diff --git a/src/web/monitor-inbox.streams-inbound-messages.test.ts b/src/web/monitor-inbox.streams-inbound-messages.test.ts index 61e8dd383a0..a274aa3468f 100644 --- a/src/web/monitor-inbox.streams-inbound-messages.test.ts +++ b/src/web/monitor-inbox.streams-inbound-messages.test.ts @@ -1,99 +1,17 @@ -import { vi } from "vitest"; - -vi.mock("../media/store.js", () => ({ - saveMediaBuffer: vi.fn().mockResolvedValue({ - id: "mid", - path: "/tmp/mid", - size: 1, - contentType: "image/jpeg", - }), -})); - -const mockLoadConfig = vi.fn().mockReturnValue({ - channels: { - whatsapp: { - // Allow all in tests by default - allowFrom: ["*"], - }, - }, - messages: { - messagePrefix: undefined, - responsePrefix: undefined, - }, -}); - -const readAllowFromStoreMock = vi.fn().mockResolvedValue([]); -const upsertPairingRequestMock = vi.fn().mockResolvedValue({ code: "PAIRCODE", created: true }); - -vi.mock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: () => mockLoadConfig(), - }; -}); - -vi.mock("../pairing/pairing-store.js", () => ({ - readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), - upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), -})); - -vi.mock("./session.js", () => { - const { EventEmitter } = require("node:events"); - const ev = new EventEmitter(); - const sock = { - ev, - ws: { close: vi.fn() }, - sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), - sendMessage: vi.fn().mockResolvedValue(undefined), - readMessages: vi.fn().mockResolvedValue(undefined), - updateMediaMessage: vi.fn(), - logger: {}, - signalRepository: { - lidMapping: { - getPNForLID: vi.fn().mockResolvedValue(null), - }, - }, - user: { id: "123@s.whatsapp.net" }, - }; - return { - createWaSocket: vi.fn().mockResolvedValue(sock), - waitForWaConnection: vi.fn().mockResolvedValue(undefined), - getStatusCode: vi.fn(() => 500), - }; -}); - -const { createWaSocket } = await import("./session.js"); -const _getSock = () => (createWaSocket as unknown as () => Promise>)(); - import fsSync from "node:fs"; -import os from "node:os"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { resetLogger, setLoggerOverride } from "../logging.js"; -import { monitorWebInbox, resetWebInboundDedupe } from "./inbound.js"; - -const ACCOUNT_ID = "default"; -let authDir: string; +import "./monitor-inbox.test-harness.js"; +import { describe, expect, it, vi } from "vitest"; +import { monitorWebInbox } from "./inbound.js"; +import { + DEFAULT_ACCOUNT_ID, + getAuthDir, + getSock, + installWebMonitorInboxUnitTestHooks, +} from "./monitor-inbox.test-harness.js"; describe("web monitor inbox", () => { - beforeEach(() => { - vi.clearAllMocks(); - readAllowFromStoreMock.mockResolvedValue([]); - upsertPairingRequestMock.mockResolvedValue({ - code: "PAIRCODE", - created: true, - }); - resetWebInboundDedupe(); - authDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-")); - }); - - afterEach(() => { - resetLogger(); - setLoggerOverride(null); - vi.useRealTimers(); - fsSync.rmSync(authDir, { recursive: true, force: true }); - }); + installWebMonitorInboxUnitTestHooks(); it("streams inbound messages", async () => { const onMessage = vi.fn(async (msg) => { @@ -104,10 +22,10 @@ describe("web monitor inbox", () => { const listener = await monitorWebInbox({ verbose: false, onMessage, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), }); - const sock = await createWaSocket(); + const sock = getSock(); expect(sock.sendPresenceUpdate).toHaveBeenCalledWith("available"); const upsert = { type: "notify", @@ -152,10 +70,10 @@ describe("web monitor inbox", () => { const listener = await monitorWebInbox({ verbose: false, onMessage, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -185,10 +103,10 @@ describe("web monitor inbox", () => { const listener = await monitorWebInbox({ verbose: false, onMessage, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), }); - const sock = await createWaSocket(); + const sock = getSock(); const getPNForLID = vi.spyOn(sock.signalRepository.lidMapping, "getPNForLID"); sock.signalRepository.lidMapping.getPNForLID.mockResolvedValueOnce("999:0@s.whatsapp.net"); const upsert = { @@ -219,17 +137,17 @@ describe("web monitor inbox", () => { return; }); fsSync.writeFileSync( - path.join(authDir, "lid-mapping-555_reverse.json"), + path.join(getAuthDir(), "lid-mapping-555_reverse.json"), JSON.stringify("1555"), ); const listener = await monitorWebInbox({ verbose: false, onMessage, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), }); - const sock = await createWaSocket(); + const sock = getSock(); const getPNForLID = vi.spyOn(sock.signalRepository.lidMapping, "getPNForLID"); const upsert = { type: "notify", @@ -262,10 +180,10 @@ describe("web monitor inbox", () => { const listener = await monitorWebInbox({ verbose: false, onMessage, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), }); - const sock = await createWaSocket(); + const sock = getSock(); const getPNForLID = vi.spyOn(sock.signalRepository.lidMapping, "getPNForLID"); sock.signalRepository.lidMapping.getPNForLID.mockResolvedValueOnce("444:0@s.whatsapp.net"); const upsert = { @@ -313,10 +231,10 @@ describe("web monitor inbox", () => { const listener = await monitorWebInbox({ verbose: false, onMessage, - accountId: ACCOUNT_ID, - authDir, + accountId: DEFAULT_ACCOUNT_ID, + authDir: getAuthDir(), }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -348,7 +266,7 @@ describe("web monitor inbox", () => { }); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ @@ -393,7 +311,7 @@ describe("web monitor inbox", () => { }); const listener = await monitorWebInbox({ verbose: false, onMessage }); - const sock = await createWaSocket(); + const sock = getSock(); const upsert = { type: "notify", messages: [ diff --git a/src/web/monitor-inbox.test-harness.ts b/src/web/monitor-inbox.test-harness.ts new file mode 100644 index 00000000000..3237c377439 --- /dev/null +++ b/src/web/monitor-inbox.test-harness.ts @@ -0,0 +1,123 @@ +import { EventEmitter } from "node:events"; +import fsSync from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, vi } from "vitest"; +import { resetLogger, setLoggerOverride } from "../logging.js"; + +export const DEFAULT_ACCOUNT_ID = "default"; + +export const DEFAULT_WEB_INBOX_CONFIG = { + channels: { + whatsapp: { + // Allow all in tests by default. + allowFrom: ["*"], + }, + }, + messages: { + messagePrefix: undefined, + responsePrefix: undefined, + }, +} as const; + +export const mockLoadConfig = vi.fn().mockReturnValue(DEFAULT_WEB_INBOX_CONFIG); + +export const readAllowFromStoreMock = vi.fn().mockResolvedValue([]); +export const upsertPairingRequestMock = vi + .fn() + .mockResolvedValue({ code: "PAIRCODE", created: true }); + +export type MockSock = ReturnType; + +function createMockSock() { + const ev = new EventEmitter(); + return { + ev, + ws: { close: vi.fn() }, + sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue(undefined), + readMessages: vi.fn().mockResolvedValue(undefined), + updateMediaMessage: vi.fn(), + logger: {}, + signalRepository: { + lidMapping: { + getPNForLID: vi.fn().mockResolvedValue(null), + }, + }, + user: { id: "123@s.whatsapp.net" }, + }; +} + +const sock: MockSock = createMockSock(); + +vi.mock("../media/store.js", () => ({ + saveMediaBuffer: vi.fn().mockResolvedValue({ + id: "mid", + path: "/tmp/mid", + size: 1, + contentType: "image/jpeg", + }), +})); + +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => mockLoadConfig(), + }; +}); + +vi.mock("../pairing/pairing-store.js", () => ({ + readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args), + upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args), +})); + +vi.mock("./session.js", () => ({ + createWaSocket: vi.fn().mockResolvedValue(sock), + waitForWaConnection: vi.fn().mockResolvedValue(undefined), + getStatusCode: vi.fn(() => 500), +})); + +export function getSock(): MockSock { + return sock; +} + +let authDir: string | undefined; + +export function getAuthDir(): string { + if (!authDir) { + throw new Error("authDir not initialized; call installWebMonitorInboxUnitTestHooks()"); + } + return authDir; +} + +export function installWebMonitorInboxUnitTestHooks(opts?: { authDir?: boolean }) { + const createAuthDir = opts?.authDir ?? true; + + beforeEach(async () => { + vi.clearAllMocks(); + mockLoadConfig.mockReturnValue(DEFAULT_WEB_INBOX_CONFIG); + readAllowFromStoreMock.mockResolvedValue([]); + upsertPairingRequestMock.mockResolvedValue({ + code: "PAIRCODE", + created: true, + }); + const { resetWebInboundDedupe } = await import("./inbound.js"); + resetWebInboundDedupe(); + if (createAuthDir) { + authDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-")); + } else { + authDir = undefined; + } + }); + + afterEach(() => { + resetLogger(); + setLoggerOverride(null); + vi.useRealTimers(); + if (authDir) { + fsSync.rmSync(authDir, { recursive: true, force: true }); + authDir = undefined; + } + }); +}