From 5c7ab8eae3067a164419d09ba3af4b8286419f45 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Feb 2026 11:28:27 +0000 Subject: [PATCH] test(zalo): broaden webhook monitor coverage --- extensions/zalo/src/monitor.webhook.test.ts | 336 +++++++------------- 1 file changed, 114 insertions(+), 222 deletions(-) diff --git a/extensions/zalo/src/monitor.webhook.test.ts b/extensions/zalo/src/monitor.webhook.test.ts index 97162544b6f..af998bee674 100644 --- a/extensions/zalo/src/monitor.webhook.test.ts +++ b/extensions/zalo/src/monitor.webhook.test.ts @@ -21,113 +21,84 @@ async function withServer(handler: RequestListener, fn: (baseUrl: string) => Pro } } +const DEFAULT_ACCOUNT: ResolvedZaloAccount = { + accountId: "default", + enabled: true, + token: "tok", + tokenSource: "config", + config: {}, +}; + +const webhookRequestHandler: RequestListener = async (req, res) => { + const handled = await handleZaloWebhookRequest(req, res); + if (!handled) { + res.statusCode = 404; + res.end("not found"); + } +}; + +function registerTarget(params: { + path: string; + secret?: string; + statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void; +}): () => void { + return registerZaloWebhookTarget({ + token: "tok", + account: DEFAULT_ACCOUNT, + config: {} as OpenClawConfig, + runtime: {}, + core: {} as PluginRuntime, + secret: params.secret ?? "secret", + path: params.path, + mediaMaxMb: 5, + statusSink: params.statusSink, + }); +} + describe("handleZaloWebhookRequest", () => { it("returns 400 for non-object payloads", async () => { - const core = {} as PluginRuntime; - const account: ResolvedZaloAccount = { - accountId: "default", - enabled: true, - token: "tok", - tokenSource: "config", - config: {}, - }; - const unregister = registerZaloWebhookTarget({ - token: "tok", - account, - config: {} as OpenClawConfig, - runtime: {}, - core, - secret: "secret", - path: "/hook", - mediaMaxMb: 5, - }); + const unregister = registerTarget({ path: "/hook" }); try { - await withServer( - async (req, res) => { - const handled = await handleZaloWebhookRequest(req, res); - if (!handled) { - res.statusCode = 404; - res.end("not found"); - } - }, - async (baseUrl) => { - const response = await fetch(`${baseUrl}/hook`, { - method: "POST", - headers: { - "x-bot-api-secret-token": "secret", - "content-type": "application/json", - }, - body: "null", - }); + await withServer(webhookRequestHandler, async (baseUrl) => { + const response = await fetch(`${baseUrl}/hook`, { + method: "POST", + headers: { + "x-bot-api-secret-token": "secret", + "content-type": "application/json", + }, + body: "null", + }); - expect(response.status).toBe(400); - expect(await response.text()).toBe("Bad Request"); - }, - ); + expect(response.status).toBe(400); + expect(await response.text()).toBe("Bad Request"); + }); } finally { unregister(); } }); it("rejects ambiguous routing when multiple targets match the same secret", async () => { - const core = {} as PluginRuntime; - const account: ResolvedZaloAccount = { - accountId: "default", - enabled: true, - token: "tok", - tokenSource: "config", - config: {}, - }; const sinkA = vi.fn(); const sinkB = vi.fn(); - const unregisterA = registerZaloWebhookTarget({ - token: "tok", - account, - config: {} as OpenClawConfig, - runtime: {}, - core, - secret: "secret", - path: "/hook", - mediaMaxMb: 5, - statusSink: sinkA, - }); - const unregisterB = registerZaloWebhookTarget({ - token: "tok", - account, - config: {} as OpenClawConfig, - runtime: {}, - core, - secret: "secret", - path: "/hook", - mediaMaxMb: 5, - statusSink: sinkB, - }); + const unregisterA = registerTarget({ path: "/hook", statusSink: sinkA }); + const unregisterB = registerTarget({ path: "/hook", statusSink: sinkB }); try { - await withServer( - async (req, res) => { - const handled = await handleZaloWebhookRequest(req, res); - if (!handled) { - res.statusCode = 404; - res.end("not found"); - } - }, - async (baseUrl) => { - const response = await fetch(`${baseUrl}/hook`, { - method: "POST", - headers: { - "x-bot-api-secret-token": "secret", - "content-type": "application/json", - }, - body: "{}", - }); + await withServer(webhookRequestHandler, async (baseUrl) => { + const response = await fetch(`${baseUrl}/hook`, { + method: "POST", + headers: { + "x-bot-api-secret-token": "secret", + "content-type": "application/json", + }, + body: "{}", + }); - expect(response.status).toBe(401); - expect(sinkA).not.toHaveBeenCalled(); - expect(sinkB).not.toHaveBeenCalled(); - }, - ); + expect(response.status).toBe(401); + expect(sinkA).not.toHaveBeenCalled(); + expect(sinkB).not.toHaveBeenCalled(); + }); } finally { unregisterA(); unregisterB(); @@ -135,73 +106,29 @@ describe("handleZaloWebhookRequest", () => { }); it("returns 415 for non-json content-type", async () => { - const core = {} as PluginRuntime; - const account: ResolvedZaloAccount = { - accountId: "default", - enabled: true, - token: "tok", - tokenSource: "config", - config: {}, - }; - const unregister = registerZaloWebhookTarget({ - token: "tok", - account, - config: {} as OpenClawConfig, - runtime: {}, - core, - secret: "secret", - path: "/hook-content-type", - mediaMaxMb: 5, - }); + const unregister = registerTarget({ path: "/hook-content-type" }); try { - await withServer( - async (req, res) => { - const handled = await handleZaloWebhookRequest(req, res); - if (!handled) { - res.statusCode = 404; - res.end("not found"); - } - }, - async (baseUrl) => { - const response = await fetch(`${baseUrl}/hook-content-type`, { - method: "POST", - headers: { - "x-bot-api-secret-token": "secret", - "content-type": "text/plain", - }, - body: "{}", - }); + await withServer(webhookRequestHandler, async (baseUrl) => { + const response = await fetch(`${baseUrl}/hook-content-type`, { + method: "POST", + headers: { + "x-bot-api-secret-token": "secret", + "content-type": "text/plain", + }, + body: "{}", + }); - expect(response.status).toBe(415); - }, - ); + expect(response.status).toBe(415); + }); } finally { unregister(); } }); it("deduplicates webhook replay by event_name + message_id", async () => { - const core = {} as PluginRuntime; - const account: ResolvedZaloAccount = { - accountId: "default", - enabled: true, - token: "tok", - tokenSource: "config", - config: {}, - }; const sink = vi.fn(); - const unregister = registerZaloWebhookTarget({ - token: "tok", - account, - config: {} as OpenClawConfig, - runtime: {}, - core, - secret: "secret", - path: "/hook-replay", - mediaMaxMb: 5, - statusSink: sink, - }); + const unregister = registerTarget({ path: "/hook-replay", statusSink: sink }); const payload = { event_name: "message.text.received", @@ -215,91 +142,56 @@ describe("handleZaloWebhookRequest", () => { }; try { - await withServer( - async (req, res) => { - const handled = await handleZaloWebhookRequest(req, res); - if (!handled) { - res.statusCode = 404; - res.end("not found"); - } - }, - async (baseUrl) => { - const first = await fetch(`${baseUrl}/hook-replay`, { - method: "POST", - headers: { - "x-bot-api-secret-token": "secret", - "content-type": "application/json", - }, - body: JSON.stringify(payload), - }); - const second = await fetch(`${baseUrl}/hook-replay`, { - method: "POST", - headers: { - "x-bot-api-secret-token": "secret", - "content-type": "application/json", - }, - body: JSON.stringify(payload), - }); + await withServer(webhookRequestHandler, async (baseUrl) => { + const first = await fetch(`${baseUrl}/hook-replay`, { + method: "POST", + headers: { + "x-bot-api-secret-token": "secret", + "content-type": "application/json", + }, + body: JSON.stringify(payload), + }); + const second = await fetch(`${baseUrl}/hook-replay`, { + method: "POST", + headers: { + "x-bot-api-secret-token": "secret", + "content-type": "application/json", + }, + body: JSON.stringify(payload), + }); - expect(first.status).toBe(200); - expect(second.status).toBe(200); - expect(sink).toHaveBeenCalledTimes(1); - }, - ); + expect(first.status).toBe(200); + expect(second.status).toBe(200); + expect(sink).toHaveBeenCalledTimes(1); + }); } finally { unregister(); } }); it("returns 429 when per-path request rate exceeds threshold", async () => { - const core = {} as PluginRuntime; - const account: ResolvedZaloAccount = { - accountId: "default", - enabled: true, - token: "tok", - tokenSource: "config", - config: {}, - }; - const unregister = registerZaloWebhookTarget({ - token: "tok", - account, - config: {} as OpenClawConfig, - runtime: {}, - core, - secret: "secret", - path: "/hook-rate", - mediaMaxMb: 5, - }); + const unregister = registerTarget({ path: "/hook-rate" }); try { - await withServer( - async (req, res) => { - const handled = await handleZaloWebhookRequest(req, res); - if (!handled) { - res.statusCode = 404; - res.end("not found"); - } - }, - async (baseUrl) => { - let saw429 = false; - for (let i = 0; i < 130; i += 1) { - const response = await fetch(`${baseUrl}/hook-rate`, { - method: "POST", - headers: { - "x-bot-api-secret-token": "secret", - "content-type": "application/json", - }, - body: "{}", - }); - if (response.status === 429) { - saw429 = true; - break; - } + await withServer(webhookRequestHandler, async (baseUrl) => { + let saw429 = false; + for (let i = 0; i < 130; i += 1) { + const response = await fetch(`${baseUrl}/hook-rate`, { + method: "POST", + headers: { + "x-bot-api-secret-token": "secret", + "content-type": "application/json", + }, + body: "{}", + }); + if (response.status === 429) { + saw429 = true; + break; } + } - expect(saw429).toBe(true); - }, - ); + expect(saw429).toBe(true); + }); } finally { unregister(); }