import { describe, it, before, after } from "node:test"; import assert from "node:assert/strict"; import Koa from "koa"; import KoaRouter from "@koa/router"; import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; import { jsonBody } from "../../src/webapi/middleware/jsonBody.js"; async function request( url: string, opts: { method?: string; body?: unknown; contentType?: string } = {}, ): Promise<{ status: number; body: unknown }> { const method = opts.method ?? "POST"; const bodyStr = opts.body !== undefined ? JSON.stringify(opts.body) : undefined; const headers: Record = {}; if (bodyStr !== undefined) { headers["Content-Type"] = opts.contentType ?? "application/json"; headers["Content-Length"] = String(bodyStr.length); } const res = await fetch(url, { method, headers, body: bodyStr }); const text = await res.text(); let parsed: unknown; try { parsed = JSON.parse(text); } catch { parsed = text; } return { status: res.status, body: parsed }; } async function makeTestServer() { const failValidator = Object.assign((_obj: unknown): _obj is never => false, { errors: [{ message: "required" }] as unknown[], schema: { $id: "test-schema", title: "Test", description: "" }, }); const app = new Koa(); const router = new KoaRouter(); router.post("/echo", jsonBody(), async (ctx) => { ctx.status = 200; ctx.body = { ok: true, received: ctx.request.body }; }); router.post( "/fail-validate", jsonBody({ validate: failValidator as any }), async (ctx) => { ctx.status = 200; ctx.body = { ok: true }; }, ); router.post( "/with-params/:uid", jsonBody({ includeParams: ["uid"] }), async (ctx) => { ctx.status = 200; ctx.body = { ok: true, body: ctx.request.body }; }, ); app.use(router.routes()); app.use(router.allowedMethods()); const server = createServer(app.callback()); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const { port } = server.address() as AddressInfo; const url = `http://127.0.0.1:${port}`; const close = () => new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())), ); return { url, close }; } describe("jsonBody middleware", () => { let url: string; let closeServer: () => Promise; before(async () => { ({ url, close: closeServer } = await makeTestServer()); }); after(() => closeServer()); it("no Content-Type → 200 with empty body (bodyparser sets body to {})", async () => { const res = await fetch(url + "/echo", { method: "POST" }); const json = await res.json(); assert.equal(res.status, 200); assert.deepEqual(json.received, {}); }); it("invalid JSON with Content-Type: application/json → 400 from bodyparser", async () => { const res = await fetch(url + "/echo", { method: "POST", headers: { "Content-Type": "application/json" }, body: "not-valid-json", }); assert.equal(res.status, 400); }); it("valid JSON body → 200 with echoed body", async () => { const res = await request(url + "/echo", { body: { hello: "world" } }); assert.equal(res.status, 200); assert.deepEqual((res.body as any).received, { hello: "world" }); }); it("failing validator → 400 jsonchema_validation_failed with schema and errors", async () => { const res = await request(url + "/fail-validate", { body: { any: "thing" }, }); assert.equal(res.status, 400); assert.equal((res.body as any).error, "jsonchema_validation_failed"); assert.ok((res.body as any).schema, "response includes schema"); assert.ok( Array.isArray((res.body as any).errors), "response includes errors", ); }); it("includeParams: param absent from body → merged in", async () => { const res = await request(url + "/with-params/user-42", { body: { other: "field" }, }); assert.equal(res.status, 200); assert.equal((res.body as any).body?.uid, "user-42"); }); it("includeParams: param present with matching value → ok", async () => { const res = await request(url + "/with-params/user-42", { body: { uid: "user-42" }, }); assert.equal(res.status, 200); }); it("includeParams: mismatching param value → 400 mismatch_params", async () => { const res = await request(url + "/with-params/user-42", { body: { uid: "different-uid" }, }); assert.equal(res.status, 400); assert.equal((res.body as any).error, "mismatch_params"); assert.equal((res.body as any).param, "uid"); }); });