fastify 5.11.3 → 5.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.pi/self-learning-memory/README.md +8 -0
  2. package/.pi/self-learning-memory/core/CORE.md +33 -0
  3. package/.pi/self-learning-memory/core/index.json +735 -0
  4. package/.pi/self-learning-memory/daily/2026-07-06.md +22 -0
  5. package/.pi/self-learning-memory/daily/2026-07-29.md +50 -0
  6. package/.pi/self-learning-memory/daily/2026-08-04.md +41 -0
  7. package/.pi/self-learning-memory/daily/2026-08-18.md +40 -0
  8. package/.pi/self-learning-memory/long-term-memory.md +89 -0
  9. package/AGENTS.md +290 -0
  10. package/SPONSORS.md +1 -0
  11. package/docs/Guides/Serverless.md +34 -2
  12. package/docs/Reference/Reply.md +14 -0
  13. package/docs/Reference/Server.md +8 -3
  14. package/fastify.d.ts +1 -1
  15. package/fastify.js +2 -2
  16. package/lib/content-type.js +4 -1
  17. package/lib/reply.js +11 -1
  18. package/lib/request.js +3 -2
  19. package/lib/route.js +2 -1
  20. package/lib/validation.js +10 -1
  21. package/package.json +2 -3
  22. package/test/close.test.js +34 -0
  23. package/test/content-parser.test.js +16 -18
  24. package/test/content-type.test.js +4 -0
  25. package/test/fix-root-primitive-coercion.test.js +35 -0
  26. package/test/helper.js +73 -1
  27. package/test/hooks-async.test.js +1 -2
  28. package/test/hooks.test.js +1 -2
  29. package/test/internals/reply.test.js +76 -1
  30. package/test/listen.1.test.js +17 -52
  31. package/test/listen.5.test.js +6 -9
  32. package/test/logger/instantiation.test.js +1 -1
  33. package/test/logger/logging.test.js +1 -1
  34. package/test/logger/request.test.js +1 -1
  35. package/test/logger/response.test.js +1 -1
  36. package/test/plugin.4.test.js +5 -4
  37. package/test/reply-media-type.test.js +131 -0
  38. package/test/route-prefix.test.js +33 -1
  39. package/test/route.6.test.js +4 -11
  40. package/test/schema-feature.test.js +36 -65
  41. package/test/schema-serialization.test.js +1 -1
  42. package/test/schema-special-usage.test.js +1 -1
  43. package/test/schema-validation.test.js +1 -1
  44. package/test/stream.2.test.js +1 -1
  45. package/test/trust-proxy.test.js +27 -21
  46. package/test/types/fastify.tst.ts +2 -0
  47. package/test/toolkit.js +0 -63
@@ -2,8 +2,9 @@
2
2
 
3
3
  const { test } = require('node:test')
4
4
  const net = require('node:net')
5
- const Fastify = require('../fastify')
6
5
  const { once } = require('node:events')
6
+ const { spyWarning } = require('process-warning')
7
+ const Fastify = require('../fastify')
7
8
  const { FSTWRN003 } = require('../lib/warnings.js')
8
9
 
9
10
  function createDeferredPromise () {
@@ -102,21 +103,17 @@ test('same port conflict and success should not fire callback multiple times - p
102
103
  test('should emit a warning when using async callback', (t, done) => {
103
104
  t.plan(2)
104
105
 
105
- process.on('warning', onWarning)
106
- function onWarning (warning) {
107
- t.assert.strictEqual(warning.name, 'FastifyWarning')
108
- t.assert.strictEqual(warning.code, FSTWRN003.code)
109
- }
110
-
106
+ const spyData = spyWarning(FSTWRN003)
111
107
  const fastify = Fastify()
112
108
 
113
109
  t.after(async () => {
114
110
  await fastify.close()
115
- process.removeListener('warning', onWarning)
116
- FSTWRN003.emitted = false
111
+ spyData.restore()
117
112
  })
118
113
 
119
114
  fastify.listen({ port: 0 }, async function doNotUseAsyncCallback () {
115
+ t.assert.deepStrictEqual(spyData.calls, [{ arguments: ['listen method'], result: true }])
116
+ t.assert.strictEqual(spyData.callCount(), 1)
120
117
  done()
121
118
  })
122
119
  })
@@ -14,7 +14,7 @@ const helper = require('../helper')
14
14
  const { FST_ERR_LOG_INVALID_LOGGER } = require('../../lib/errors')
15
15
  const { once, on } = stream
16
16
  const { createTempFile, request } = require('./logger-test-utils')
17
- const { partialDeepStrictEqual } = require('../toolkit')
17
+ const { partialDeepStrictEqual } = require('../helper')
18
18
 
19
19
  t.test('logger instantiation', { timeout: 60000 }, async (t) => {
20
20
  let localhost
@@ -11,7 +11,7 @@ const { LogController } = require('../../lib/log-controller')
11
11
  const helper = require('../helper')
12
12
  const { once, on } = stream
13
13
  const { request } = require('./logger-test-utils')
14
- const { partialDeepStrictEqual } = require('../toolkit')
14
+ const { partialDeepStrictEqual } = require('../helper')
15
15
 
16
16
  t.test('logging', { timeout: 60000 }, async (t) => {
17
17
  let localhost
@@ -9,7 +9,7 @@ const Fastify = require('../../fastify')
9
9
  const helper = require('../helper')
10
10
  const { on } = stream
11
11
  const { request } = require('./logger-test-utils')
12
- const { partialDeepStrictEqual } = require('../toolkit')
12
+ const { partialDeepStrictEqual } = require('../helper')
13
13
 
14
14
  t.test('request', { timeout: 60000 }, async (t) => {
15
15
  let localhost
@@ -7,7 +7,7 @@ const split = require('split2')
7
7
  const pino = require('pino')
8
8
 
9
9
  const Fastify = require('../../fastify')
10
- const { partialDeepStrictEqual } = require('../toolkit')
10
+ const { partialDeepStrictEqual } = require('../helper')
11
11
  const { on } = stream
12
12
 
13
13
  t.test('response serialization', { timeout: 60000 }, async (t) => {
@@ -3,7 +3,6 @@
3
3
  const { test, describe } = require('node:test')
4
4
  const Fastify = require('../fastify')
5
5
  const fp = require('fastify-plugin')
6
- const fakeTimer = require('@sinonjs/fake-timers')
7
6
  const { FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER } = require('../lib/errors')
8
7
 
9
8
  test('pluginTimeout', (t, testDone) => {
@@ -46,12 +45,14 @@ test('pluginTimeout - named function', (t, testDone) => {
46
45
 
47
46
  test('pluginTimeout default', (t, testDone) => {
48
47
  t.plan(5)
49
- const clock = fakeTimer.install({ shouldClearNativeTimers: true })
48
+ t.mock.timers.enable({
49
+ apis: ['setTimeout', 'Date']
50
+ })
50
51
 
51
52
  const fastify = Fastify()
52
53
  fastify.register(function (app, opts, done) {
53
54
  // default time elapsed without calling done
54
- clock.tick(10000)
55
+ t.mock.timers.tick(10000)
55
56
  })
56
57
 
57
58
  fastify.ready((err) => {
@@ -64,7 +65,7 @@ test('pluginTimeout default', (t, testDone) => {
64
65
  testDone()
65
66
  })
66
67
 
67
- t.after(clock.uninstall)
68
+ t.after(() => t.mock.timers.reset())
68
69
  })
69
70
 
70
71
  test('plugin metadata - version', (t, testDone) => {
@@ -0,0 +1,131 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+
6
+ test('reply.mediaType should match the content-type header', async (t) => {
7
+ t.plan(2)
8
+
9
+ const fastify = Fastify()
10
+
11
+ fastify.get('/', (request, reply) => {
12
+ reply.type('application/json')
13
+ t.assert.strictEqual(reply.mediaType, 'application/json')
14
+ reply.send({ mediaType: reply.mediaType })
15
+ })
16
+
17
+ const response = await fastify.inject({
18
+ method: 'GET',
19
+ url: '/'
20
+ })
21
+ const body = await response.json()
22
+ t.assert.strictEqual(body.mediaType, 'application/json')
23
+ })
24
+
25
+ test('reply.mediaType should strip the charset parameter', async (t) => {
26
+ t.plan(2)
27
+
28
+ const fastify = Fastify()
29
+
30
+ fastify.get('/', (request, reply) => {
31
+ reply.header('content-type', 'application/json; charset=utf-8')
32
+ t.assert.strictEqual(reply.mediaType, 'application/json')
33
+ reply.send({ mediaType: reply.mediaType })
34
+ })
35
+
36
+ const response = await fastify.inject({
37
+ method: 'GET',
38
+ url: '/'
39
+ })
40
+ const body = await response.json()
41
+ t.assert.strictEqual(body.mediaType, 'application/json')
42
+ })
43
+
44
+ test('reply.mediaType should strip the space', async (t) => {
45
+ t.plan(2)
46
+
47
+ const fastify = Fastify()
48
+
49
+ fastify.get('/', (request, reply) => {
50
+ reply.header('content-type', ' application/json ; charset=utf-8')
51
+ t.assert.strictEqual(reply.mediaType, 'application/json')
52
+ reply.send({ mediaType: reply.mediaType })
53
+ })
54
+
55
+ const response = await fastify.inject({
56
+ method: 'GET',
57
+ url: '/'
58
+ })
59
+ const body = await response.json()
60
+ t.assert.strictEqual(body.mediaType, 'application/json')
61
+ })
62
+
63
+ test('reply.mediaType is undefined when content-type is not set', async (t) => {
64
+ t.plan(2)
65
+
66
+ const fastify = Fastify()
67
+
68
+ fastify.get('/', (request, reply) => {
69
+ t.assert.strictEqual(reply.mediaType, undefined)
70
+ reply.send({ mediaType: reply.mediaType })
71
+ })
72
+
73
+ const response = await fastify.inject({
74
+ method: 'GET',
75
+ url: '/'
76
+ })
77
+ const body = await response.json()
78
+ t.assert.strictEqual(body.mediaType, undefined)
79
+ })
80
+
81
+ test('reply.mediaType supported in hooks', async (t) => {
82
+ t.plan(5)
83
+
84
+ const fastify = Fastify()
85
+
86
+ fastify.get('/', {
87
+ preHandler: (request, reply, done) => {
88
+ reply.type('application/json')
89
+ t.assert.strictEqual(reply.mediaType, 'application/json')
90
+ done()
91
+ },
92
+ preSerialization: (request, reply, payload, done) => {
93
+ t.assert.strictEqual(reply.mediaType, 'application/json')
94
+ done(null, payload)
95
+ },
96
+ onSend: (request, reply, payload, done) => {
97
+ t.assert.strictEqual(reply.mediaType, 'application/json')
98
+ done(null, payload)
99
+ }
100
+ }, (request, reply) => {
101
+ t.assert.strictEqual(reply.mediaType, 'application/json')
102
+ reply.send({ mediaType: reply.mediaType })
103
+ })
104
+
105
+ const response = await fastify.inject({
106
+ method: 'GET',
107
+ url: '/'
108
+ })
109
+ const body = await response.json()
110
+ t.assert.strictEqual(body.mediaType, 'application/json')
111
+ })
112
+
113
+ test('reply.mediaType should reflect the last type set', async (t) => {
114
+ t.plan(2)
115
+
116
+ const fastify = Fastify()
117
+
118
+ fastify.get('/', (request, reply) => {
119
+ reply.type('application/json')
120
+ reply.type('text/plain')
121
+ t.assert.strictEqual(reply.mediaType, 'text/plain')
122
+ reply.send(`mediaType = ${reply.mediaType}`)
123
+ })
124
+
125
+ const response = await fastify.inject({
126
+ method: 'GET',
127
+ url: '/'
128
+ })
129
+ const body = response.body
130
+ t.assert.strictEqual(body, 'mediaType = text/plain')
131
+ })
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { test } = require('node:test')
4
4
  const Fastify = require('..')
5
- const { waitForCb } = require('./toolkit')
5
+ const { waitForCb } = require('./helper')
6
6
 
7
7
  test('Prefix options should add a prefix for all the routes inside a register / 1', (t, testDone) => {
8
8
  t.plan(6)
@@ -563,6 +563,38 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "
563
563
  completion.patience.then(testDone)
564
564
  })
565
565
 
566
+ test('reports the canonical route url for hidden prefix trailing slash route', async t => {
567
+ t.plan(4)
568
+
569
+ const fastify = Fastify({
570
+ ignoreTrailingSlash: false,
571
+ exposeHeadRoutes: false
572
+ })
573
+
574
+ const onRouteUrls = []
575
+
576
+ fastify.addHook('onRoute', routeOptions => {
577
+ onRouteUrls.push(routeOptions.url)
578
+ })
579
+
580
+ fastify.register(async instance => {
581
+ instance.get('/', async request => {
582
+ return request.routeOptions.url
583
+ })
584
+
585
+ instance.get('/bar/', async request => {
586
+ return request.routeOptions.url
587
+ })
588
+ }, { prefix: '/prefix' })
589
+
590
+ t.assert.strictEqual((await fastify.inject('/prefix')).payload, '/prefix')
591
+ t.assert.strictEqual((await fastify.inject('/prefix/')).payload, '/prefix')
592
+ t.assert.strictEqual((await fastify.inject('/prefix/bar/')).payload, '/prefix/bar/')
593
+ t.assert.deepStrictEqual(onRouteUrls, ['/prefix', '/prefix/bar/'])
594
+
595
+ await fastify.close()
596
+ })
597
+
566
598
  test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: false', (t, testDone) => {
567
599
  t.plan(4)
568
600
  const fastify = Fastify({
@@ -4,6 +4,7 @@ const stream = require('node:stream')
4
4
  const { ReadableStream } = require('node:stream/web')
5
5
  const { test } = require('node:test')
6
6
  const Fastify = require('..')
7
+ const { assertNoWarning } = require('./helper')
7
8
 
8
9
  test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => {
9
10
  t.plan(1)
@@ -281,6 +282,8 @@ test('route onSend can be function or array of functions', async t => {
281
282
  })
282
283
 
283
284
  test('no warning for exposeHeadRoute', async t => {
285
+ assertNoWarning(t)
286
+
284
287
  const fastify = Fastify()
285
288
 
286
289
  fastify.route({
@@ -292,15 +295,5 @@ test('no warning for exposeHeadRoute', async t => {
292
295
  }
293
296
  })
294
297
 
295
- const listener = (w) => {
296
- t.assert.fail('no warning')
297
- }
298
-
299
- process.on('warning', listener)
300
-
301
- await fastify.listen({ port: 0 })
302
-
303
- process.removeListener('warning', listener)
304
-
305
- await fastify.close()
298
+ await fastify.ready()
306
299
  })
@@ -1,13 +1,14 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const Fastify = require('..')
5
4
  const fp = require('fastify-plugin')
5
+ const { spyWarning } = require('process-warning')
6
+ const Fastify = require('..')
6
7
  const deepClone = require('rfdc')({ circles: true, proto: false })
7
8
  const Ajv = require('ajv')
8
9
  const { kSchemaController } = require('../lib/symbols.js')
9
10
  const { FSTWRN001 } = require('../lib/warnings')
10
- const { waitForCb } = require('./toolkit')
11
+ const { waitForCb } = require('./helper')
11
12
 
12
13
  const echoParams = (req, reply) => { reply.send(req.params) }
13
14
  const echoBody = (req, reply) => { reply.send(req.body) }
@@ -320,18 +321,10 @@ test('Should not change the input schemas', (t, testDone) => {
320
321
 
321
322
  test('Should emit warning if the schema headers is undefined', (t, testDone) => {
322
323
  t.plan(4)
323
- const fastify = Fastify()
324
+ const spyData = spyWarning(FSTWRN001)
325
+ t.after(spyData.restore)
324
326
 
325
- process.on('warning', onWarning)
326
- function onWarning (warning) {
327
- t.assert.strictEqual(warning.name, 'FastifyWarning')
328
- t.assert.strictEqual(warning.code, FSTWRN001.code)
329
- }
330
-
331
- t.after(() => {
332
- process.removeListener('warning', onWarning)
333
- FSTWRN001.emitted = false
334
- })
327
+ const fastify = Fastify()
335
328
 
336
329
  fastify.post('/:id', {
337
330
  handler: echoParams,
@@ -346,24 +339,18 @@ test('Should emit warning if the schema headers is undefined', (t, testDone) =>
346
339
  }, (error, res) => {
347
340
  t.assert.ifError(error)
348
341
  t.assert.strictEqual(res.statusCode, 200)
342
+ t.assert.deepStrictEqual(spyData.calls, [{ arguments: ['headers', 'POST', '/:id'], result: true }])
343
+ t.assert.strictEqual(spyData.callCount(), 1)
349
344
  testDone()
350
345
  })
351
346
  })
352
347
 
353
348
  test('Should emit warning if the schema body is undefined', (t, testDone) => {
354
349
  t.plan(4)
355
- const fastify = Fastify()
356
-
357
- process.on('warning', onWarning)
358
- function onWarning (warning) {
359
- t.assert.strictEqual(warning.name, 'FastifyWarning')
360
- t.assert.strictEqual(warning.code, FSTWRN001.code)
361
- }
350
+ const spyData = spyWarning(FSTWRN001)
351
+ t.after(spyData.restore)
362
352
 
363
- t.after(() => {
364
- process.removeListener('warning', onWarning)
365
- FSTWRN001.emitted = false
366
- })
353
+ const fastify = Fastify()
367
354
 
368
355
  fastify.post('/:id', {
369
356
  handler: echoParams,
@@ -378,24 +365,18 @@ test('Should emit warning if the schema body is undefined', (t, testDone) => {
378
365
  }, (error, res) => {
379
366
  t.assert.ifError(error)
380
367
  t.assert.strictEqual(res.statusCode, 200)
368
+ t.assert.deepStrictEqual(spyData.calls, [{ arguments: ['body', 'POST', '/:id'], result: true }])
369
+ t.assert.strictEqual(spyData.callCount(), 1)
381
370
  testDone()
382
371
  })
383
372
  })
384
373
 
385
374
  test('Should emit warning if the schema query is undefined', (t, testDone) => {
386
375
  t.plan(4)
387
- const fastify = Fastify()
376
+ const spyData = spyWarning(FSTWRN001)
377
+ t.after(spyData.restore)
388
378
 
389
- process.on('warning', onWarning)
390
- function onWarning (warning) {
391
- t.assert.strictEqual(warning.name, 'FastifyWarning')
392
- t.assert.strictEqual(warning.code, FSTWRN001.code)
393
- }
394
-
395
- t.after(() => {
396
- process.removeListener('warning', onWarning)
397
- FSTWRN001.emitted = false
398
- })
379
+ const fastify = Fastify()
399
380
 
400
381
  fastify.post('/:id', {
401
382
  handler: echoParams,
@@ -410,24 +391,18 @@ test('Should emit warning if the schema query is undefined', (t, testDone) => {
410
391
  }, (error, res) => {
411
392
  t.assert.ifError(error)
412
393
  t.assert.strictEqual(res.statusCode, 200)
394
+ t.assert.deepStrictEqual(spyData.calls, [{ arguments: ['querystring', 'POST', '/:id'], result: true }])
395
+ t.assert.strictEqual(spyData.callCount(), 1)
413
396
  testDone()
414
397
  })
415
398
  })
416
399
 
417
400
  test('Should emit warning if the schema params is undefined', (t, testDone) => {
418
401
  t.plan(4)
419
- const fastify = Fastify()
402
+ const spyData = spyWarning(FSTWRN001)
403
+ t.after(spyData.restore)
420
404
 
421
- process.on('warning', onWarning)
422
- function onWarning (warning) {
423
- t.assert.strictEqual(warning.name, 'FastifyWarning')
424
- t.assert.strictEqual(warning.code, FSTWRN001.code)
425
- }
426
-
427
- t.after(() => {
428
- process.removeListener('warning', onWarning)
429
- FSTWRN001.emitted = false
430
- })
405
+ const fastify = Fastify()
431
406
 
432
407
  fastify.post('/:id', {
433
408
  handler: echoParams,
@@ -442,31 +417,18 @@ test('Should emit warning if the schema params is undefined', (t, testDone) => {
442
417
  }, (error, res) => {
443
418
  t.assert.ifError(error)
444
419
  t.assert.strictEqual(res.statusCode, 200)
420
+ t.assert.deepStrictEqual(spyData.calls, [{ arguments: ['params', 'POST', '/:id'], result: true }])
421
+ t.assert.strictEqual(spyData.callCount(), 1)
445
422
  testDone()
446
423
  })
447
424
  })
448
425
 
449
426
  test('Should emit a warning for every route with undefined schema', (t, testDone) => {
450
- t.plan(16)
451
- const fastify = Fastify()
452
-
453
- let runs = 0
454
- const expectedWarningEmitted = [0, 1, 2, 3]
455
- // It emits 4 warnings:
456
- // - 2 - GET and HEAD for /undefinedParams/:id
457
- // - 2 - GET and HEAD for /undefinedBody/:id
458
- // => 3 x 4 assertions = 12 assertions
459
- function onWarning (warning) {
460
- t.assert.strictEqual(warning.name, 'FastifyWarning')
461
- t.assert.strictEqual(warning.code, FSTWRN001.code)
462
- t.assert.strictEqual(runs++, expectedWarningEmitted.shift())
463
- }
427
+ t.plan(9)
428
+ const spyData = spyWarning(FSTWRN001)
429
+ t.after(spyData.restore)
464
430
 
465
- process.on('warning', onWarning)
466
- t.after(() => {
467
- process.removeListener('warning', onWarning)
468
- FSTWRN001.emitted = false
469
- })
431
+ const fastify = Fastify()
470
432
 
471
433
  fastify.get('/undefinedParams/:id', {
472
434
  handler: echoParams,
@@ -496,6 +458,15 @@ test('Should emit a warning for every route with undefined schema', (t, testDone
496
458
  }, (error, res) => {
497
459
  t.assert.ifError(error)
498
460
  t.assert.strictEqual(res.statusCode, 200)
461
+ // fastify.inject run in series
462
+ // last callback recieve all warnings at once
463
+ // GET /undefinedParams/123
464
+ t.assert.deepStrictEqual(spyData.calls[0], { arguments: ['params', 'GET', '/undefinedParams/:id'], result: true })
465
+ t.assert.deepStrictEqual(spyData.calls[1], { arguments: ['params', 'HEAD', '/undefinedParams/:id'], result: true })
466
+ // GET /undefinedBody/123
467
+ t.assert.deepStrictEqual(spyData.calls[2], { arguments: ['body', 'GET', '/undefinedBody/:id'], result: true })
468
+ t.assert.deepStrictEqual(spyData.calls[3], { arguments: ['body', 'HEAD', '/undefinedBody/:id'], result: true })
469
+ t.assert.strictEqual(spyData.callCount(), 4)
499
470
  testDone()
500
471
  })
501
472
  })
@@ -3,7 +3,7 @@
3
3
  const { test } = require('node:test')
4
4
  const Fastify = require('..')
5
5
  const ContentType = require('../lib/content-type')
6
- const { waitForCb } = require('./toolkit')
6
+ const { waitForCb } = require('./helper')
7
7
 
8
8
  const echoBody = (req, reply) => { reply.send(req.body) }
9
9
 
@@ -9,7 +9,7 @@ const Fastify = require('..')
9
9
  const ajvMergePatch = require('ajv-merge-patch')
10
10
  const ajvErrors = require('ajv-errors')
11
11
  const proxyquire = require('proxyquire')
12
- const { waitForCb } = require('./toolkit')
12
+ const { waitForCb } = require('./helper')
13
13
 
14
14
  test('Ajv plugins array parameter', (t, testDone) => {
15
15
  t.plan(3)
@@ -5,7 +5,7 @@ const Fastify = require('..')
5
5
 
6
6
  const AJV = require('ajv')
7
7
  const Schema = require('fluent-json-schema')
8
- const { waitForCb } = require('./toolkit')
8
+ const { waitForCb } = require('./helper')
9
9
  const { kRequestContentType } = require('../lib/symbols')
10
10
 
11
11
  const customSchemaCompilers = {
@@ -7,7 +7,7 @@ const resolve = require('node:path').resolve
7
7
  const zlib = require('node:zlib')
8
8
  const pipeline = require('node:stream').pipeline
9
9
  const Fastify = require('..')
10
- const { waitForCb } = require('./toolkit')
10
+ const { waitForCb } = require('./helper')
11
11
 
12
12
  test('onSend hook stream', t => {
13
13
  t.plan(4)
@@ -106,20 +106,33 @@ test('trust proxy function', async t => {
106
106
  await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyfunc', undefined, 'fastify.test:1234')
107
107
  })
108
108
 
109
- test('trust proxy number', async t => {
110
- t.plan(8)
109
+ test('trust proxy number ignores forwarded headers', async t => {
110
+ t.plan(5)
111
111
  const app = fastify({
112
112
  trustProxy: 1
113
113
  })
114
114
  t.after(() => app.close())
115
115
 
116
116
  app.get('/trustproxynumber', function (req, reply) {
117
- testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
117
+ t.assert.strictEqual(req.ip, '203.0.113.7', 'ip falls back to socket remote address')
118
+ t.assert.deepStrictEqual(req.ips, ['203.0.113.7'], 'ips falls back to socket remote address')
119
+ t.assert.strictEqual(req.host, 'app.example.com', 'host ignores x-forwarded-host')
120
+ t.assert.strictEqual(req.hostname, 'app.example.com', 'hostname ignores x-forwarded-host')
121
+ t.assert.strictEqual(req.protocol, 'http', 'protocol ignores x-forwarded-proto')
118
122
  reply.code(200).send({ ip: req.ip, host: req.host })
119
123
  })
120
124
 
121
- const fastifyServer = await app.listen({ port: 0 })
122
- await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined, 'fastify.test:1234')
125
+ await app.inject({
126
+ method: 'GET',
127
+ url: '/trustproxynumber',
128
+ remoteAddress: '203.0.113.7',
129
+ headers: {
130
+ host: 'app.example.com',
131
+ 'x-forwarded-for': '9.9.9.9, 8.8.8.8',
132
+ 'x-forwarded-host': 'evil.com',
133
+ 'x-forwarded-proto': 'https'
134
+ }
135
+ })
123
136
  })
124
137
 
125
138
  test('trust proxy IP addresses', async t => {
@@ -241,18 +254,15 @@ test('trust proxy reads forwarded headers from trusted connections', async t =>
241
254
  })
242
255
  })
243
256
 
244
- test('trust proxy with number and undefined socket remoteAddress', t => {
257
+ test('trust proxy with number and undefined socket remoteAddress ignores forwarded headers', t => {
245
258
  t.plan(3)
246
259
 
247
- // Test case for issue #6606: trustProxy: 1 with undefined/null socket.remoteAddress
248
- // This simulates IISNode on Windows where socket.remoteAddress may be undefined
249
260
  const headers = {
261
+ host: 'real.test',
250
262
  'x-forwarded-for': '2.2.2.2, 1.1.1.1',
251
263
  'x-forwarded-host': 'fastify.test',
252
264
  'x-forwarded-proto': 'https'
253
265
  }
254
- // socket must exist but remoteAddress can be undefined
255
- // This is what happens in IISNode with enableXFF="true"
256
266
  const req = {
257
267
  method: 'GET',
258
268
  url: '/',
@@ -262,20 +272,16 @@ test('trust proxy with number and undefined socket remoteAddress', t => {
262
272
 
263
273
  const TpRequest = buildRequest(Request, 1)
264
274
  const request = new TpRequest('id', 'params', req, 'query', 'log')
265
- // Even with undefined socket.remoteAddress, req.ip should be populated from X-Forwarded-For
266
- t.assert.ok(request.ip, 'ip is defined')
267
- // With trustProxy: 1, we trust 1 hop from socket. Since socket.remoteAddress is undefined,
268
- // the hop count check should skip it and we get 1.1.1.1 (the first trusted address from X-Forwarded-For)
269
- t.assert.strictEqual(request.ip, '1.1.1.1', 'gets ip from x-forwarded-for')
270
- // The host should also work correctly
271
- t.assert.strictEqual(request.host, 'fastify.test', 'gets host from x-forwarded-host')
275
+ t.assert.strictEqual(request.ip, undefined, 'ip falls back to undefined socket remote address')
276
+ t.assert.strictEqual(request.host, 'real.test', 'host ignores x-forwarded-host')
277
+ t.assert.strictEqual(request.protocol, 'http', 'protocol ignores x-forwarded-proto')
272
278
  })
273
279
 
274
- test('trust proxy with number and null socket remoteAddress', t => {
280
+ test('trust proxy with number and null socket remoteAddress ignores forwarded headers', t => {
275
281
  t.plan(2)
276
282
 
277
- // Test case for trustProxy: 1 with null socket.remoteAddress
278
283
  const headers = {
284
+ host: 'real.test',
279
285
  'x-forwarded-for': '2.2.2.2, 1.1.1.1',
280
286
  'x-forwarded-host': 'fastify.test'
281
287
  }
@@ -288,8 +294,8 @@ test('trust proxy with number and null socket remoteAddress', t => {
288
294
 
289
295
  const TpRequest = buildRequest(Request, 1)
290
296
  const request = new TpRequest('id', 'params', req, 'query', 'log')
291
- t.assert.ok(request.ip, 'ip is defined')
292
- t.assert.strictEqual(request.ip, '1.1.1.1', 'gets ip from x-forwarded-for')
297
+ t.assert.strictEqual(request.ip, null, 'ip falls back to null socket remote address')
298
+ t.assert.strictEqual(request.host, 'real.test', 'host ignores x-forwarded-host')
293
299
  })
294
300
 
295
301
  test('trust proxy does not trust x-forwarded-host/proto when socket is null', t => {
@@ -198,6 +198,8 @@ expect(fastify({
198
198
  }
199
199
  })).type.toBeAssignableTo<FastifyInstance>()
200
200
  expect(fastify({ trustProxy: true })).type.toBeAssignableTo<FastifyInstance>()
201
+ // @ts-expect-error No overload matches this call.
202
+ fastify({ trustProxy: 1 })
201
203
  expect(fastify({ querystringParser: () => ({ foo: 'bar' }) })).type.toBeAssignableTo<FastifyInstance>()
202
204
  expect(fastify({ querystringParser: () => ({ foo: { bar: 'fuzz' } }) })).type.toBeAssignableTo<FastifyInstance>()
203
205
  expect(fastify({ querystringParser: () => ({ foo: ['bar', 'fuzz'] }) })).type.toBeAssignableTo<FastifyInstance>()