import type { IncomingMessage, ServerResponse } from "node:http"; import type { Mock } from "vitest"; import { vi } from "vitest"; export type RegisteredRoute = { path: string; accountId: string; handler: (req: IncomingMessage, res: ServerResponse) => Promise; }; export const registerPluginHttpRouteMock: Mock<(params: RegisteredRoute) => () => void> = vi.fn( () => vi.fn(), ); export const dispatchReplyWithBufferedBlockDispatcher: Mock< () => Promise<{ counts: Record }> > = 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/setup", async () => { const actual = await vi.importActual("openclaw/plugin-sdk/setup"); return { ...actual, DEFAULT_ACCOUNT_ID: "default", }; }); vi.mock("openclaw/plugin-sdk/channel-config-schema", async () => { const actual = await vi.importActual("openclaw/plugin-sdk/channel-config-schema"); return { ...actual, buildChannelConfigSchema: vi.fn((schema: unknown) => ({ schema })), }; }); vi.mock("openclaw/plugin-sdk/webhook-ingress", async () => { const actual = await vi.importActual("openclaw/plugin-sdk/webhook-ingress"); return { ...actual, registerPluginHttpRoute: registerPluginHttpRouteMock, 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, }; }