import { createServer, type Server } from "node:http"; import { OpenAPIHono } from "@hono/zod-openapi"; import { createOpenAPIRegistry } from "@suaveplan/api-openapi"; import { createPluginContext } from "@suaveplan/api-plugin"; import { EventBus } from "@suaveplan/events"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { createWebhookPlugin, resetWebhookInstruments, type WebhookPluginConfig, type WebhookService, } from "../../src/index.js"; interface Receiver { readonly url: string; readonly observed: { event: string; body: string }[]; close(): Promise; } async function startReceiver(): Promise { const observed: { event: string; body: string }[] = []; const server: Server = createServer((req, res) => { let body = ""; req.on("data", (c) => { body += c; }); req.on("end", () => { const evt = req.headers["x-webhook-event"]; observed.push({ event: Array.isArray(evt) ? (evt[0] ?? "") : (evt ?? ""), body, }); res.writeHead(200); res.end("ok"); }); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve) ); const address = server.address(); if (address === null || typeof address === "string") { throw new Error("failed to bind"); } return { url: `http://127.0.0.1:${address.port}/hooks`, observed, close: () => new Promise((resolve, reject) => { server.close((err) => (err ? reject(err) : resolve())); }), }; } const noopLogger = { debug: () => undefined, info: () => undefined, warn: () => undefined, error: () => undefined, }; describe("integration: webhook plugin full boot", () => { it("POST /webhooks/:id/test fires a synthetic webhook.ping", { timeout: 30_000, }, async () => { const receiver = await startReceiver(); try { resetWebhookInstruments(); const app = new OpenAPIHono(); app.onError((err, c) => { const ctx = (err as { context?: { statusCode?: number } }) .context; const status = (ctx?.statusCode ?? 500) as | 401 | 403 | 404 | 500; return c.json({ message: err?.message ?? "error" }, status); }); const registry = createOpenAPIRegistry({ info: { title: "test", version: "0.1.0" }, }); const services = new Map(); services.set("app", app); services.set("openAPIRegistry", registry); // Install a permissive auth middleware ABOVE the plugin's // /webhooks/* requireAuth so the integration test does not // need a real auth plugin. app.use("*", async (c, next) => { c.set("user", { id: "u", tenantId: null }); await next(); }); const descriptor = createWebhookPlugin(); const ctx = createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: descriptor.defaults as WebhookPluginConfig, services, }); await descriptor.onInit?.(ctx); // Register an endpoint via the service that the plugin // registered in ServiceRegistry. const service = ctx.getService("webhookService") as WebhookService; const endpoint = await service.register({ tenantId: null, url: receiver.url, secret: "shared-secret", events: ["webhook.ping"], }); // Fire via the test route. const res = await app.request(`/webhooks/${endpoint.id}/test`, { method: "POST", }); expect(res.status).toBe(200); const body = (await res.json()) as { status: string }; expect(body.status).toBe("delivered"); expect(receiver.observed).toHaveLength(1); expect(receiver.observed[0]?.event).toBe("webhook.ping"); } finally { await receiver.close(); } }); });