From f36051c5c245fcf501bf618766cd763cc0202a21 Mon Sep 17 00:00:00 2001 From: Davi de Carvalho Date: Fri, 16 Oct 2026 10:32:48 -0300 Subject: [PATCH] feat: parse the body of QUERY requests by default The QUERY HTTP method (RFC 10008) carries the query in the request content, so request.body() must be populated for it just like POST. Add QUERY to the default allowedMethods list. Refs adonisjs/http-server#140 Co-Authored-By: Claude Opus 5.5 --- src/define_config.ts | 2 +- tests/body_parser.spec.ts | 111 +++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/define_config.ts b/src/define_config.ts index bdb13c8..64dbaba 100644 --- a/src/define_config.ts +++ b/src/define_config.ts @@ -32,7 +32,7 @@ import { type BodyParserConfig, type BodyParserOptionalConfig } from './types.ts */ export function defineConfig(config: BodyParserOptionalConfig): BodyParserConfig { return { - allowedMethods: config.allowedMethods || ['POST', 'PUT', 'PATCH', 'DELETE'], + allowedMethods: config.allowedMethods || ['POST', 'PUT', 'PATCH', 'DELETE', 'QUERY'], form: { encoding: 'utf-8', diff --git a/tests/body_parser.spec.ts b/tests/body_parser.spec.ts index 4ecfe5a..06ddc60 100644 --- a/tests/body_parser.spec.ts +++ b/tests/body_parser.spec.ts @@ -14,7 +14,8 @@ import { join } from 'node:path' import supertest from 'supertest' import { test } from '@japa/runner' import { pathExists } from 'fs-extra' -import { createServer } from 'node:http' +import type { AddressInfo } from 'node:net' +import { createServer, type Server } from 'node:http' import { readFile } from 'node:fs/promises' import { RequestFactory, @@ -24,6 +25,8 @@ import { import { AppFactory } from '@adonisjs/application/factories' import { Multipart } from '../src/multipart/main.ts' +import { defineConfig } from '../src/define_config.ts' +import { BodyParserMiddleware } from '../src/bodyparser_middleware.ts' import { type MultipartFile } from '../src/multipart/file.ts' import { BodyParserMiddlewareFactory } from '../factories/middleware_factory.ts' import { @@ -108,6 +111,112 @@ test.group('BodyParser Middleware', () => { }) }) +/** + * Sends a QUERY request to the given server and returns the parsed JSON + * response. Supertest does not expose a typed API for the QUERY method. + */ +async function sendQueryRequest(server: Server, contentType: string, body: string) { + await new Promise((resolve) => server.listen(0, 'localhost', () => resolve())) + try { + const { port } = server.address() as AddressInfo + const response = await fetch(`http://localhost:${port}`, { + method: 'QUERY', + headers: { 'content-type': contentType }, + body, + }) + return response.json() + } finally { + server.close() + } +} + +test.group('BodyParser Middleware | QUERY method', () => { + test('allow QUERY requests by default', ({ assert }) => { + assert.include(defineConfig({}).allowedMethods, 'QUERY') + }) + + test('parse JSON body of QUERY requests', async ({ assert }) => { + const server = createServer(async (req, res) => { + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + const middleware = new BodyParserMiddlewareFactory().create() + + await middleware.handle(ctx, async () => { + res.writeHead(200, { 'content-type': 'application/json' }) + res.end( + JSON.stringify({ + method: ctx.request.method(), + body: ctx.request.body(), + bodyType: ctx.request.bodyType, + }) + ) + }) + }) + + const body = await sendQueryRequest( + server, + 'application/json', + '{"username":"virk","limit":10}' + ) + + assert.deepEqual(body, { + method: 'QUERY', + body: { username: 'virk', limit: 10 }, + bodyType: 'json', + }) + }) + + test('parse urlencoded body of QUERY requests', async ({ assert }) => { + const server = createServer(async (req, res) => { + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + const middleware = new BodyParserMiddlewareFactory().create() + + await middleware.handle(ctx, async () => { + res.writeHead(200, { 'content-type': 'application/json' }) + res.end( + JSON.stringify({ + body: ctx.request.body(), + bodyType: ctx.request.bodyType, + }) + ) + }) + }) + + const body = await sendQueryRequest( + server, + 'application/x-www-form-urlencoded', + 'q=foo&limit=10&sort=-published' + ) + + assert.deepEqual(body, { + body: { q: 'foo', limit: '10', sort: '-published' }, + bodyType: 'urlencoded', + }) + }) + + test('do not parse QUERY requests when not part of allowedMethods', async ({ assert }) => { + const server = createServer(async (req, res) => { + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + const middleware = new BodyParserMiddleware( + defineConfig({ allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'] }) + ) + + await middleware.handle(ctx, async () => { + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(JSON.stringify({ body: ctx.request.body() })) + }) + }) + + const body = await sendQueryRequest(server, 'application/json', '{"username":"virk"}') + assert.deepEqual(body, { body: {} }) + }) +}) + test.group('BodyParser Middleware | form data', () => { test('handle request with form data', async ({ assert }) => { const server = createServer(async (req, res) => {