From 0f8531dea66e99a888b6d170006704f2167269ad Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 22:56:55 +0000 Subject: [PATCH] test: share synology channel harness --- .../src/channel.integration.test.ts | 39 +--------- .../synology-chat/src/channel.test-mocks.ts | 73 +++++++++++++++++++ extensions/synology-chat/src/channel.test.ts | 56 +------------- 3 files changed, 81 insertions(+), 87 deletions(-) create mode 100644 extensions/synology-chat/src/channel.test-mocks.ts diff --git a/extensions/synology-chat/src/channel.integration.test.ts b/extensions/synology-chat/src/channel.integration.test.ts index b9cb5484621..e5d1e7f24c9 100644 --- a/extensions/synology-chat/src/channel.integration.test.ts +++ b/extensions/synology-chat/src/channel.integration.test.ts @@ -1,5 +1,9 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { + dispatchReplyWithBufferedBlockDispatcher, + registerPluginHttpRouteMock, +} from "./channel.test-mocks.js"; import { makeFormBody, makeReq, makeRes } from "./test-http-utils.js"; type RegisteredRoute = { @@ -8,41 +12,6 @@ type RegisteredRoute = { handler: (req: IncomingMessage, res: ServerResponse) => Promise; }; -const registerPluginHttpRouteMock = vi.fn<(params: RegisteredRoute) => () => void>(() => vi.fn()); -const dispatchReplyWithBufferedBlockDispatcher = vi.fn().mockResolvedValue({ counts: {} }); - -vi.mock("openclaw/plugin-sdk/synology-chat", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - DEFAULT_ACCOUNT_ID: "default", - setAccountEnabledInConfigSection: vi.fn((_opts: any) => ({})), - registerPluginHttpRoute: registerPluginHttpRouteMock, - buildChannelConfigSchema: vi.fn((schema: any) => ({ schema })), - createFixedWindowRateLimiter: vi.fn(() => ({ - isRateLimited: vi.fn(() => false), - size: vi.fn(() => 0), - clear: vi.fn(), - })), - }; -}); - -vi.mock("./runtime.js", () => ({ - getSynologyRuntime: vi.fn(() => ({ - config: { loadConfig: vi.fn().mockResolvedValue({}) }, - channel: { - reply: { - dispatchReplyWithBufferedBlockDispatcher, - }, - }, - })), -})); - -vi.mock("./client.js", () => ({ - sendMessage: vi.fn().mockResolvedValue(true), - sendFileUrl: vi.fn().mockResolvedValue(true), -})); - const { createSynologyChatPlugin } = await import("./channel.js"); describe("Synology channel wiring integration", () => { beforeEach(() => { diff --git a/extensions/synology-chat/src/channel.test-mocks.ts b/extensions/synology-chat/src/channel.test-mocks.ts new file mode 100644 index 00000000000..b27c7ad434c --- /dev/null +++ b/extensions/synology-chat/src/channel.test-mocks.ts @@ -0,0 +1,73 @@ +import type { IncomingMessage, ServerResponse } from "node:http"; +import { vi } from "vitest"; + +export type RegisteredRoute = { + path: string; + accountId: string; + handler: (req: IncomingMessage, res: ServerResponse) => Promise; +}; + +export const registerPluginHttpRouteMock = vi.fn<(params: RegisteredRoute) => () => void>(() => + vi.fn(), +); + +export const dispatchReplyWithBufferedBlockDispatcher = vi.fn().mockResolvedValue({ counts: {} }); + +async function readRequestBodyWithLimitForTest(req: IncomingMessage): Promise { + return await new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + req.on("data", (chunk) => { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + }); + req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); + req.on("error", reject); + }); +} + +vi.mock("openclaw/plugin-sdk/synology-chat", () => ({ + DEFAULT_ACCOUNT_ID: "default", + setAccountEnabledInConfigSection: vi.fn((_opts: unknown) => ({})), + registerPluginHttpRoute: registerPluginHttpRouteMock, + buildChannelConfigSchema: vi.fn((schema: unknown) => ({ schema })), + readRequestBodyWithLimit: vi.fn(readRequestBodyWithLimitForTest), + isRequestBodyLimitError: vi.fn(() => false), + requestBodyErrorToText: vi.fn(() => "Request body too large"), + createFixedWindowRateLimiter: vi.fn(() => ({ + isRateLimited: vi.fn(() => false), + size: vi.fn(() => 0), + clear: vi.fn(), + })), +})); + +vi.mock("./client.js", () => ({ + sendMessage: vi.fn().mockResolvedValue(true), + sendFileUrl: vi.fn().mockResolvedValue(true), +})); + +vi.mock("./runtime.js", () => ({ + getSynologyRuntime: vi.fn(() => ({ + config: { loadConfig: vi.fn().mockResolvedValue({}) }, + channel: { + reply: { + dispatchReplyWithBufferedBlockDispatcher, + }, + }, + })), +})); + +export function makeSecurityAccount(overrides: Record = {}) { + return { + accountId: "default", + enabled: true, + token: "t", + incomingUrl: "https://nas/incoming", + nasHost: "h", + webhookPath: "/w", + dmPolicy: "allowlist" as const, + allowedUserIds: [], + rateLimitPerMinute: 30, + botName: "Bot", + allowInsecureSsl: false, + ...overrides, + }; +} diff --git a/extensions/synology-chat/src/channel.test.ts b/extensions/synology-chat/src/channel.test.ts index 2814d437c6b..bdce5f37d79 100644 --- a/extensions/synology-chat/src/channel.test.ts +++ b/extensions/synology-chat/src/channel.test.ts @@ -1,40 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -// Mock external dependencies -vi.mock("openclaw/plugin-sdk/synology-chat", () => ({ - DEFAULT_ACCOUNT_ID: "default", - setAccountEnabledInConfigSection: vi.fn((_opts: any) => ({})), - registerPluginHttpRoute: vi.fn(() => vi.fn()), - buildChannelConfigSchema: vi.fn((schema: any) => ({ schema })), - createFixedWindowRateLimiter: vi.fn(() => ({ - isRateLimited: vi.fn(() => false), - size: vi.fn(() => 0), - clear: vi.fn(), - })), -})); - -vi.mock("./client.js", () => ({ - sendMessage: vi.fn().mockResolvedValue(true), - sendFileUrl: vi.fn().mockResolvedValue(true), -})); +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { makeSecurityAccount, registerPluginHttpRouteMock } from "./channel.test-mocks.js"; vi.mock("./webhook-handler.js", () => ({ createWebhookHandler: vi.fn(() => vi.fn()), })); -vi.mock("./runtime.js", () => ({ - getSynologyRuntime: vi.fn(() => ({ - config: { loadConfig: vi.fn().mockResolvedValue({}) }, - channel: { - reply: { - dispatchReplyWithBufferedBlockDispatcher: vi.fn().mockResolvedValue({ - counts: {}, - }), - }, - }, - })), -})); - vi.mock("zod", () => ({ z: { object: vi.fn(() => ({ @@ -44,24 +14,6 @@ vi.mock("zod", () => ({ })); const { createSynologyChatPlugin } = await import("./channel.js"); -const { registerPluginHttpRoute } = await import("openclaw/plugin-sdk/synology-chat"); - -function makeSecurityAccount(overrides: Record = {}) { - return { - accountId: "default", - enabled: true, - token: "t", - incomingUrl: "https://nas/incoming", - nasHost: "h", - webhookPath: "/w", - dmPolicy: "allowlist" as const, - allowedUserIds: [], - rateLimitPerMinute: 30, - botName: "Bot", - allowInsecureSsl: false, - ...overrides, - }; -} describe("createSynologyChatPlugin", () => { it("returns a plugin object with all required sections", () => { @@ -321,7 +273,7 @@ describe("createSynologyChatPlugin", () => { }); it("startAccount refuses allowlist accounts with empty allowedUserIds", async () => { - const registerMock = vi.mocked(registerPluginHttpRoute); + const registerMock = registerPluginHttpRouteMock; registerMock.mockClear(); const plugin = createSynologyChatPlugin(); const { ctx, abortController } = makeStartAccountCtx({ @@ -341,7 +293,7 @@ describe("createSynologyChatPlugin", () => { it("deregisters stale route before re-registering same account/path", async () => { const unregisterFirst = vi.fn(); const unregisterSecond = vi.fn(); - const registerMock = vi.mocked(registerPluginHttpRoute); + const registerMock = registerPluginHttpRouteMock; registerMock.mockReturnValueOnce(unregisterFirst).mockReturnValueOnce(unregisterSecond); const plugin = createSynologyChatPlugin();