ITADN

Fastify rate limit doesn't seem to work properly with async security prehandler hook and jest

#427Closedtedmiddleton 创建于 2025-12-07
T
tedmiddletoncommented
### Prerequisites - [x] I have written a descriptive issue title - [x] I have searched existing issues to ensure the bug has not already been reported ### Fastify version 5.6.2 ### Plugin version 10.3.0 ### Node.js version 24.9.0 ### Operating system macOS ### Operating system version (i.e. 20.04, 11.3, 10) 15.6.1 ### Description Hey folks - I'm puzzled by how to set up an authentication preHandler hook when fastify rate limit is being used: ``` // prehandler-ratelimit.test.ts import Fastify, { FastifyInstance, FastifyReply, FastifyRequest, } from 'fastify'; import rateLimit from '@fastify/rate-limit'; describe('preHandler + rateLimit', () => { let app: FastifyInstance; const handlerSpy = jest.fn(); beforeAll(async () => { app = Fastify({ logger: false }); // Uncomment this and we fail //await app.register(rateLimit, { // max: 10, // timeWindow: '1 minute', //}); // Route under test app.post( '/test', { preHandler: async (req: FastifyRequest, reply: FastifyReply) => { // Simulate auth failure reply.code(401); await reply.send({ error: 'unauthorized from preHandler' }); // Fastify should stop here - we should NOT enter the POST handler }, }, async (req: FastifyRequest, reply: FastifyReply) => { // This must NOT run if preHandler already sent the response handlerSpy(); return { ok: true }; } ); await app.ready(); }); afterAll(async () => { await app.close(); }); it('does not call handler when preHandler sends 401', async () => { const res = await app.inject({ method: 'POST', url: '/test', }); expect(res.statusCode).toBe(401); expect(res.json()).toEqual({ error: 'unauthorized from preHandler' }); // Critical assertion: handler should never be invoked expect(handlerSpy).not.toHaveBeenCalled(); }); }); ``` When I run this code as is, it passes the test. When I uncomment out the fastify rate limit registration, I get this on the console: ``` bun test v1.3.0 (b0a6feca) test/ratelimiter.integration.test.ts: Debugger attached. 91 | this.on('timeout', callback) 92 | return this 93 | } 94 | 95 | Response.prototype.writeHead = function () { 96 | const result = http.ServerResponse.prototype.writeHead.apply(this, arguments) ^ error: Cannot writeHead headers after they are sent to the client code: "ERR_HTTP_HEADERS_SENT" at <anonymous> (node:_http_server:917:29) at <anonymous> (/Users/ted/Projects/web/coin-server-be/node_modules/light-my-request/lib/response.js:96:58) at <anonymous> (/Users/ted/Projects/web/coin-server-be/node_modules/fastify/lib/error-handler.js:45:19) at fallbackErrorHandler (/Users/ted/Projects/web/coin-server-be/node_modules/fastify/lib/error-handler.js:142:3) at handleError (/Users/ted/Projects/web/coin-server-be/node_modules/fastify/lib/error-handler.js:35:5) at onErrorHook (/Users/ted/Projects/web/coin-server-be/node_modules/fastify/lib/reply.js:817:5) at <anonymous> (/Users/ted/Projects/web/coin-server-be/node_modules/fastify/lib/reply.js:140:5) at handleError (/Users/ted/Projects/web/coin-server-be/node_modules/fastify/lib/error-handler.js:78:11) at onErrorHook (/Users/ted/Projects/web/coin-server-be/node_modules/fastify/lib/reply.js:817:5) ✗ preHandler + rateLimit > does not call handler when preHandler sends 401 [6.95ms] 0 pass 1 fail 3 expect() calls Ran 1 test across 1 file. [93.00ms] Debugger detached. ``` When I change the preHandler hook to be non-async it works perfectly. Am I just doing something crazy wrong here, or is this a defect? ### Link to code that reproduces the bug https://github.com/tedmiddleton/fastify-rate-limiter-issue ### Expected Behavior ``` bun test v1.3.0 (b0a6feca) test/ratelimiter.integration.test.ts: Debugger attached. ✓ preHandler + rateLimit > does not call handler when preHandler sends 401 [6.54ms] 1 pass 0 fail 3 expect() calls Ran 1 test across 1 file. [100.00ms] Debugger detached. ```
关闭于 2025-12-13 2 条评论