fastify 5.8.5 → 5.10.0

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 (126) hide show
  1. package/PROJECT_CHARTER.md +1 -1
  2. package/README.md +7 -10
  3. package/SECURITY.md +1 -1
  4. package/SPONSORS.md +6 -4
  5. package/build/build-validation.js +2 -2
  6. package/docs/Guides/Database.md +0 -28
  7. package/docs/Guides/Delay-Accepting-Requests.md +1 -1
  8. package/docs/Guides/Ecosystem.md +23 -9
  9. package/docs/Guides/Getting-Started.md +2 -2
  10. package/docs/Guides/Migration-Guide-V4.md +2 -2
  11. package/docs/Guides/Migration-Guide-V5.md +1 -1
  12. package/docs/Guides/Plugins-Guide.md +1 -1
  13. package/docs/Guides/Prototype-Poisoning.md +3 -3
  14. package/docs/Guides/Serverless.md +8 -15
  15. package/docs/Guides/Style-Guide.md +1 -1
  16. package/docs/Guides/Write-Plugin.md +1 -1
  17. package/docs/Reference/Encapsulation.md +27 -26
  18. package/docs/Reference/Errors.md +12 -4
  19. package/docs/Reference/HTTP2.md +10 -10
  20. package/docs/Reference/Hooks.md +5 -5
  21. package/docs/Reference/Index.md +14 -16
  22. package/docs/Reference/LTS.md +12 -13
  23. package/docs/Reference/Lifecycle.md +9 -8
  24. package/docs/Reference/Logging.md +47 -39
  25. package/docs/Reference/Middleware.md +21 -25
  26. package/docs/Reference/Principles.md +2 -2
  27. package/docs/Reference/Reply.md +6 -1
  28. package/docs/Reference/Request.md +29 -18
  29. package/docs/Reference/Routes.md +5 -2
  30. package/docs/Reference/Server.md +138 -11
  31. package/docs/Reference/Type-Providers.md +53 -9
  32. package/docs/Reference/TypeScript.md +3 -3
  33. package/docs/Reference/Validation-and-Serialization.md +15 -2
  34. package/docs/Reference/Warnings.md +11 -6
  35. package/eslint.config.js +7 -2
  36. package/fastify.d.ts +13 -3
  37. package/fastify.js +60 -31
  38. package/lib/content-type-parser.js +2 -0
  39. package/lib/content-type.js +34 -1
  40. package/lib/context.js +0 -3
  41. package/lib/decorate.js +11 -3
  42. package/lib/error-handler.js +10 -28
  43. package/lib/error-serializer.js +59 -59
  44. package/lib/errors.js +23 -1
  45. package/lib/four-oh-four.js +22 -19
  46. package/lib/handle-request.js +12 -5
  47. package/lib/log-controller.js +169 -0
  48. package/lib/logger-factory.js +25 -4
  49. package/lib/plugin-override.js +2 -1
  50. package/lib/plugin-utils.js +5 -5
  51. package/lib/reply.js +96 -50
  52. package/lib/req-id-gen-factory.js +4 -1
  53. package/lib/request.js +16 -6
  54. package/lib/route.js +47 -41
  55. package/lib/schema-controller.js +1 -1
  56. package/lib/schemas.js +37 -30
  57. package/lib/symbols.js +4 -2
  58. package/lib/validation.js +10 -13
  59. package/lib/warnings.js +22 -4
  60. package/package.json +15 -17
  61. package/scripts/validate-ecosystem-links.js +1 -0
  62. package/test/bundler/esbuild/package.json +1 -1
  63. package/test/close-pipelining.test.js +1 -2
  64. package/test/content-type.test.js +20 -0
  65. package/test/custom-http-server.test.js +38 -0
  66. package/test/decorator-instance-properties.test.js +63 -0
  67. package/test/diagnostics-channel/async-error-handler.test.js +74 -0
  68. package/test/genReqId.test.js +24 -0
  69. package/test/hooks.test.js +94 -0
  70. package/test/http-methods/get.test.js +1 -1
  71. package/test/http2/plain.test.js +135 -0
  72. package/test/http2/secure-with-fallback.test.js +1 -1
  73. package/test/https/https.test.js +1 -2
  74. package/test/internals/errors.test.js +31 -1
  75. package/test/internals/logger.test.js +322 -0
  76. package/test/internals/plugin.test.js +3 -1
  77. package/test/internals/reply.test.js +35 -4
  78. package/test/internals/request.test.js +37 -10
  79. package/test/internals/schema-controller-perf.test.js +33 -0
  80. package/test/logger/logging.test.js +57 -1
  81. package/test/logger/options.test.js +38 -1
  82. package/test/reply-error.test.js +1 -1
  83. package/test/reply-trailers.test.js +70 -0
  84. package/test/request-media-type.test.js +105 -0
  85. package/test/route-prefix.test.js +34 -0
  86. package/test/router-options.test.js +222 -11
  87. package/test/schema-serialization.test.js +108 -0
  88. package/test/schema-validation.test.js +24 -0
  89. package/test/scripts/validate-ecosystem-links.test.js +40 -57
  90. package/test/stream.4.test.js +2 -2
  91. package/test/throw.test.js +14 -0
  92. package/test/trust-proxy.test.js +70 -30
  93. package/test/types/content-type-parser.tst.ts +70 -0
  94. package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
  95. package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
  96. package/test/types/errors.tst.ts +91 -0
  97. package/test/types/fastify.tst.ts +351 -0
  98. package/test/types/hooks.tst.ts +578 -0
  99. package/test/types/instance.tst.ts +597 -0
  100. package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
  101. package/test/types/plugin.tst.ts +96 -0
  102. package/test/types/register.tst.ts +245 -0
  103. package/test/types/reply.tst.ts +297 -0
  104. package/test/types/request.tst.ts +199 -0
  105. package/test/types/route.tst.ts +576 -0
  106. package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
  107. package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
  108. package/test/types/tsconfig.json +9 -0
  109. package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
  110. package/test/types/using.tst.ts +14 -0
  111. package/types/errors.d.ts +4 -0
  112. package/types/instance.d.ts +2 -0
  113. package/types/logger.d.ts +22 -0
  114. package/types/request.d.ts +23 -2
  115. package/test/types/content-type-parser.test-d.ts +0 -72
  116. package/test/types/errors.test-d.ts +0 -90
  117. package/test/types/fastify.test-d.ts +0 -352
  118. package/test/types/hooks.test-d.ts +0 -550
  119. package/test/types/import.ts +0 -2
  120. package/test/types/instance.test-d.ts +0 -588
  121. package/test/types/plugin.test-d.ts +0 -97
  122. package/test/types/register.test-d.ts +0 -237
  123. package/test/types/reply.test-d.ts +0 -254
  124. package/test/types/request.test-d.ts +0 -188
  125. package/test/types/route.test-d.ts +0 -553
  126. package/test/types/using.test-d.ts +0 -17
@@ -331,10 +331,10 @@ test('Request with trust proxy - no x-forwarded-host header', t => {
331
331
  })
332
332
 
333
333
  test('Request with trust proxy - no x-forwarded-host header and fallback to authority', t => {
334
- t.plan(2)
334
+ t.plan(3)
335
335
  const headers = {
336
336
  'x-forwarded-for': '2.2.2.2, 1.1.1.1',
337
- ':authority': 'authority'
337
+ ':authority': 'authority:4321'
338
338
  }
339
339
  const req = {
340
340
  method: 'GET',
@@ -370,15 +370,16 @@ test('Request with trust proxy - no x-forwarded-host header and fallback to auth
370
370
  const TpRequest = Request.buildRequest(Request, true)
371
371
  const request = new TpRequest('id', 'params', req, 'query', 'log', context)
372
372
  t.assert.ok(request instanceof TpRequest)
373
- t.assert.strictEqual(request.host, 'authority')
373
+ t.assert.strictEqual(request.host, 'authority:4321')
374
+ t.assert.strictEqual(request.port, 4321)
374
375
  })
375
376
 
376
377
  test('Request with trust proxy - x-forwarded-host header has precedence over host', t => {
377
- t.plan(2)
378
+ t.plan(4)
378
379
  const headers = {
379
380
  'x-forwarded-for': ' 2.2.2.2, 1.1.1.1',
380
- 'x-forwarded-host': 'fastify.test',
381
- host: 'hostname'
381
+ 'x-forwarded-host': 'fastify.test:1234',
382
+ host: 'hostname:5678'
382
383
  }
383
384
  const req = {
384
385
  method: 'GET',
@@ -390,7 +391,9 @@ test('Request with trust proxy - x-forwarded-host header has precedence over hos
390
391
  const TpRequest = Request.buildRequest(Request, true)
391
392
  const request = new TpRequest('id', 'params', req, 'query', 'log')
392
393
  t.assert.ok(request instanceof TpRequest)
393
- t.assert.strictEqual(request.host, 'fastify.test')
394
+ t.assert.strictEqual(request.host, 'fastify.test:1234')
395
+ t.assert.strictEqual(request.hostname, 'fastify.test')
396
+ t.assert.strictEqual(request.port, 1234)
394
397
  })
395
398
 
396
399
  test('Request with trust proxy - handles multiple entries in x-forwarded-host/proto', t => {
@@ -487,11 +490,13 @@ test('Request with undefined socket', t => {
487
490
  t.assert.ok(request.compileValidationSchema instanceof Function)
488
491
  })
489
492
 
490
- test('Request with trust proxy and undefined socket', t => {
491
- t.plan(1)
493
+ test('Request with trust proxy and undefined socket does not trust x-forwarded-host/proto', t => {
494
+ t.plan(2)
492
495
  const headers = {
496
+ host: 'hostname',
493
497
  'x-forwarded-for': '2.2.2.2, 1.1.1.1',
494
- 'x-forwarded-host': 'fastify.test'
498
+ 'x-forwarded-host': 'fastify.test',
499
+ 'x-forwarded-proto': 'https'
495
500
  }
496
501
  const req = {
497
502
  method: 'GET',
@@ -502,5 +507,27 @@ test('Request with trust proxy and undefined socket', t => {
502
507
 
503
508
  const TpRequest = Request.buildRequest(Request, true)
504
509
  const request = new TpRequest('id', 'params', req, 'query', 'log')
510
+ t.assert.strictEqual(request.host, 'hostname')
511
+ t.assert.deepStrictEqual(request.protocol, undefined)
512
+ })
513
+
514
+ test('Request with trust proxy and null socket does not trust x-forwarded-host/proto', t => {
515
+ t.plan(2)
516
+ const headers = {
517
+ host: 'hostname',
518
+ 'x-forwarded-for': '2.2.2.2, 1.1.1.1',
519
+ 'x-forwarded-host': 'fastify.test',
520
+ 'x-forwarded-proto': 'https'
521
+ }
522
+ const req = {
523
+ method: 'GET',
524
+ url: '/',
525
+ socket: null,
526
+ headers
527
+ }
528
+
529
+ const TpRequest = Request.buildRequest(Request, true)
530
+ const request = new TpRequest('id', 'params', req, 'query', 'log')
531
+ t.assert.strictEqual(request.host, 'hostname')
505
532
  t.assert.deepStrictEqual(request.protocol, undefined)
506
533
  })
@@ -1,6 +1,7 @@
1
1
  const { sep } = require('node:path')
2
2
  const { test } = require('node:test')
3
3
  const Fastify = require('../../fastify')
4
+ const { kSchemaController } = require('../../lib/symbols')
4
5
 
5
6
  test('SchemaController are NOT loaded when the controllers are custom', async t => {
6
7
  const app = Fastify({
@@ -22,6 +23,38 @@ test('SchemaController are NOT loaded when the controllers are custom', async t
22
23
  t.assert.equal(stringifyModule, undefined, 'Stringify compiler is loaded')
23
24
  })
24
25
 
26
+ test('isCustomSerializerCompiler flag is set correctly when only buildSerializer is provided', async t => {
27
+ const app = Fastify({
28
+ schemaController: {
29
+ compilersFactory: {
30
+ buildSerializer: () => () => { }
31
+ }
32
+ }
33
+ })
34
+
35
+ await app.ready()
36
+
37
+ const schemaController = app[kSchemaController]
38
+ t.assert.equal(schemaController.isCustomValidatorCompiler, false, 'isCustomValidatorCompiler should be false')
39
+ t.assert.equal(schemaController.isCustomSerializerCompiler, true, 'isCustomSerializerCompiler should be true')
40
+ })
41
+
42
+ test('isCustomValidatorCompiler flag is set correctly when only buildValidator is provided', async t => {
43
+ const app = Fastify({
44
+ schemaController: {
45
+ compilersFactory: {
46
+ buildValidator: () => () => { }
47
+ }
48
+ }
49
+ })
50
+
51
+ await app.ready()
52
+
53
+ const schemaController = app[kSchemaController]
54
+ t.assert.equal(schemaController.isCustomValidatorCompiler, true, 'isCustomValidatorCompiler should be true')
55
+ t.assert.equal(schemaController.isCustomSerializerCompiler, false, 'isCustomSerializerCompiler should be false')
56
+ })
57
+
25
58
  test('SchemaController are loaded when the controllers are not custom', async t => {
26
59
  const app = Fastify()
27
60
  await app.ready()
@@ -7,6 +7,7 @@ const split = require('split2')
7
7
  const pino = require('pino')
8
8
 
9
9
  const Fastify = require('../../fastify')
10
+ const { LogController } = require('../../lib/log-controller')
10
11
  const helper = require('../helper')
11
12
  const { once, on } = stream
12
13
  const { request } = require('./logger-test-utils')
@@ -16,7 +17,7 @@ t.test('logging', { timeout: 60000 }, async (t) => {
16
17
  let localhost
17
18
  let localhostForURL
18
19
 
19
- t.plan(14)
20
+ t.plan(16)
20
21
 
21
22
  t.before(async function () {
22
23
  [localhost, localhostForURL] = await helper.getLoopbackHost()
@@ -80,6 +81,44 @@ t.test('logging', { timeout: 60000 }, async (t) => {
80
81
  }
81
82
  })
82
83
 
84
+ await t.test('should not log if logController option disables logging', async (t) => {
85
+ const stream = split(JSON.parse)
86
+ const fastify = Fastify({
87
+ logger: {
88
+ stream,
89
+ level: 'info'
90
+ },
91
+ logController: new class extends LogController {
92
+ isLogDisabled () {
93
+ t.assert.ok(true, 'isLogDisabled should be called')
94
+ return true // disable logging to test that incomingRequest is not called
95
+ }
96
+ }()
97
+ })
98
+ t.after(() => fastify.close())
99
+
100
+ fastify.get('/info', function (req, reply) {
101
+ reply.send({ hello: 'world' })
102
+ })
103
+
104
+ await fastify.ready()
105
+ const server = await fastify.listen({ port: 0, host: localhost })
106
+ const lines = [
107
+ { msg: `Server listening at ${server}` }
108
+ ]
109
+ // 2:
110
+ // - incoming request
111
+ // - request completed
112
+ t.plan(lines.length + 2)
113
+
114
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/info')
115
+
116
+ for await (const [line] of on(stream, 'data')) {
117
+ t.assert.ok(partialDeepStrictEqual(line, lines.shift()))
118
+ if (lines.length === 0) break
119
+ }
120
+ })
121
+
83
122
  await t.test('should log the error if no error handler is defined', async (t) => {
84
123
  const stream = split(JSON.parse)
85
124
  const fastify = Fastify({
@@ -282,6 +321,23 @@ t.test('logging', { timeout: 60000 }, async (t) => {
282
321
  t.assert.strictEqual(stream.readableLength, 0)
283
322
  })
284
323
 
324
+ await t.test('should not log incoming request, outgoing response and route not found for 414 onMaxParamLength when disabled', async (t) => {
325
+ t.plan(1)
326
+ const stream = split(JSON.parse)
327
+ const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } })
328
+ t.after(() => fastify.close())
329
+
330
+ await fastify.ready()
331
+
332
+ await fastify.inject({
333
+ method: 'GET',
334
+ url: `/${'1234567890'.repeat(12)}`
335
+ })
336
+
337
+ // no more readable data
338
+ t.assert.strictEqual(stream.readableLength, 0)
339
+ })
340
+
285
341
  await t.test('should log incoming request and outgoing response based on disableRequestLogging function', async (t) => {
286
342
  const lines = [
287
343
  'incoming request',
@@ -10,7 +10,7 @@ const Fastify = require('../../fastify')
10
10
  const { on } = stream
11
11
 
12
12
  t.test('logger options', { timeout: 60000 }, async (t) => {
13
- t.plan(16)
13
+ t.plan(18)
14
14
 
15
15
  await t.test('logger can be silenced', (t) => {
16
16
  t.plan(17)
@@ -479,6 +479,43 @@ t.test('logger options', { timeout: 60000 }, async (t) => {
479
479
  }
480
480
  })
481
481
 
482
+ await t.test('Should throw when custom log level for a route is invalid', async (t) => {
483
+ t.plan(4)
484
+
485
+ const fastify = Fastify({
486
+ logger: true
487
+ })
488
+ t.after(() => fastify.close())
489
+
490
+ try {
491
+ fastify.get('/log', { logLevel: 'invalid' }, (req, reply) => {
492
+ reply.send({ hello: 'world' })
493
+ })
494
+ t.assert.fail('fastify.get should throw')
495
+ } catch (err) {
496
+ t.assert.ok(err)
497
+ t.assert.strictEqual(err.code, 'FST_ERR_ROUTE_LOG_LEVEL_INVALID')
498
+ t.assert.strictEqual(err.statusCode, 500)
499
+ t.assert.strictEqual(err.message, "Log level for 'GET:/log' route must be a valid logger level. Received: 'invalid'")
500
+ }
501
+ })
502
+
503
+ await t.test('Should allow null custom log level for a route', async (t) => {
504
+ t.plan(1)
505
+
506
+ const fastify = Fastify({
507
+ logger: true
508
+ })
509
+ t.after(() => fastify.close())
510
+
511
+ fastify.get('/log', { logLevel: null }, (req, reply) => {
512
+ reply.send({ hello: 'world' })
513
+ })
514
+
515
+ const response = await fastify.inject({ method: 'GET', url: '/log' })
516
+ t.assert.deepEqual(await response.json(), { hello: 'world' })
517
+ })
518
+
482
519
  await t.test('should pass when using unWritable props in the logger option', (t) => {
483
520
  t.plan(8)
484
521
  const fastify = Fastify({
@@ -575,7 +575,7 @@ test('allow re-thrown error to default error handler when route handler is async
575
575
  })
576
576
 
577
577
  // Issue 2078 https://github.com/fastify/fastify/issues/2078
578
- // Supported error code list: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
578
+ // Supported error code list: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
579
579
  const invalidErrorCodes = [
580
580
  undefined,
581
581
  null,
@@ -192,6 +192,46 @@ test('error in trailers should be ignored', (t, testDone) => {
192
192
  })
193
193
  })
194
194
 
195
+ test('send is called once when multiple trailer callbacks run synchronously', (t, testDone) => {
196
+ t.plan(6)
197
+ const fastify = Fastify()
198
+ let endCalls = 0
199
+ let addTrailersCalls = 0
200
+
201
+ fastify.get('/', function (request, reply) {
202
+ const originalEnd = reply.raw.end.bind(reply.raw)
203
+ reply.raw.end = function (...args) {
204
+ endCalls++
205
+ return originalEnd(...args)
206
+ }
207
+ const originalAddTrailers = reply.raw.addTrailers.bind(reply.raw)
208
+ reply.raw.addTrailers = function (...args) {
209
+ addTrailersCalls++
210
+ return originalAddTrailers(...args)
211
+ }
212
+ reply.trailer('Return-Early', function (reply, payload, done) {
213
+ done(null, 'a')
214
+ })
215
+ reply.trailer('Content-MD5', function (reply, payload, done) {
216
+ done(null, 'b')
217
+ })
218
+ reply.send('hello')
219
+ })
220
+
221
+ fastify.inject({
222
+ method: 'GET',
223
+ url: '/'
224
+ }, (error, res) => {
225
+ t.assert.ifError(error)
226
+ t.assert.strictEqual(res.statusCode, 200)
227
+ t.assert.strictEqual(res.trailers['return-early'], 'a')
228
+ t.assert.strictEqual(res.trailers['content-md5'], 'b')
229
+ t.assert.strictEqual(endCalls, 1)
230
+ t.assert.strictEqual(addTrailersCalls, 1)
231
+ testDone()
232
+ })
233
+ })
234
+
195
235
  describe('trailer handler counter', () => {
196
236
  const data = JSON.stringify({ hello: 'world' })
197
237
  const hash = createHash('md5')
@@ -266,6 +306,36 @@ describe('trailer handler counter', () => {
266
306
  testDone()
267
307
  })
268
308
  })
309
+
310
+ test('mixed callback and promise trailers only use the first completion', (t, testDone) => {
311
+ t.plan(7)
312
+ const fastify = Fastify()
313
+
314
+ fastify.get('/', function (request, reply) {
315
+ reply.trailer('Async', function (reply, payload, done) {
316
+ setTimeout(() => done(null, 'async'), 10)
317
+ })
318
+ reply.trailer('Mixed', function (reply, payload, done) {
319
+ done(null, 'correct')
320
+ return Promise.resolve('corrupted')
321
+ })
322
+ reply.send('hello')
323
+ })
324
+
325
+ fastify.inject({
326
+ method: 'GET',
327
+ url: '/'
328
+ }, (error, res) => {
329
+ t.assert.ifError(error)
330
+ t.assert.strictEqual(res.statusCode, 200)
331
+ t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked')
332
+ t.assert.strictEqual(res.headers.trailer, 'async mixed')
333
+ t.assert.strictEqual(res.trailers.async, 'async')
334
+ t.assert.strictEqual(res.trailers.mixed, 'correct')
335
+ t.assert.ok(!res.headers['content-length'])
336
+ testDone()
337
+ })
338
+ })
269
339
  })
270
340
 
271
341
  test('removeTrailer', (t, testDone) => {
@@ -0,0 +1,105 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+
6
+ test('request.mediaType should match the content-type header', async (t) => {
7
+ t.plan(2)
8
+
9
+ const fastify = Fastify()
10
+
11
+ fastify.post('/', (request, reply) => {
12
+ t.assert.strictEqual(request.mediaType, 'application/json')
13
+ reply.send({ mediaType: request.mediaType })
14
+ })
15
+
16
+ const response = await fastify.inject({
17
+ method: 'POST',
18
+ url: '/',
19
+ body: JSON.stringify({ hello: 'world' }),
20
+ headers: {
21
+ 'content-type': 'application/json'
22
+ }
23
+ })
24
+ const body = await response.json()
25
+ t.assert.strictEqual(body.mediaType, 'application/json')
26
+ })
27
+
28
+ test('request.mediaType should strip the charset parameter', async (t) => {
29
+ t.plan(2)
30
+
31
+ const fastify = Fastify()
32
+
33
+ fastify.post('/', (request, reply) => {
34
+ t.assert.strictEqual(request.mediaType, 'application/json')
35
+ reply.send({ mediaType: request.mediaType })
36
+ })
37
+
38
+ const response = await fastify.inject({
39
+ method: 'POST',
40
+ url: '/',
41
+ body: JSON.stringify({ hello: 'world' }),
42
+ headers: {
43
+ 'content-type': 'application/json; charset=utf-8'
44
+ }
45
+ })
46
+ const body = await response.json()
47
+ t.assert.strictEqual(body.mediaType, 'application/json')
48
+ })
49
+
50
+ test('request.mediaType should strip the space', async (t) => {
51
+ t.plan(2)
52
+
53
+ const fastify = Fastify()
54
+
55
+ fastify.post('/', (request, reply) => {
56
+ t.assert.strictEqual(request.mediaType, 'application/json')
57
+ reply.send({ mediaType: request.mediaType })
58
+ })
59
+
60
+ const response = await fastify.inject({
61
+ method: 'POST',
62
+ url: '/',
63
+ body: JSON.stringify({ hello: 'world' }),
64
+ headers: {
65
+ 'content-type': ' application/json ; charset=utf-8'
66
+ }
67
+ })
68
+ const body = await response.json()
69
+ t.assert.strictEqual(body.mediaType, 'application/json')
70
+ })
71
+
72
+ test('request.mediaType supported in hooks', async (t) => {
73
+ t.plan(5)
74
+
75
+ const fastify = Fastify()
76
+
77
+ fastify.post('/', {
78
+ preParsing: (request, reply, payload, done) => {
79
+ t.assert.strictEqual(request.mediaType, 'application/json')
80
+ done(null, payload)
81
+ },
82
+ preValidation: (request, reply, done) => {
83
+ t.assert.strictEqual(request.mediaType, 'application/json')
84
+ done()
85
+ },
86
+ preHandler: (request, reply, done) => {
87
+ t.assert.strictEqual(request.mediaType, 'application/json')
88
+ done()
89
+ }
90
+ }, (request, reply) => {
91
+ t.assert.strictEqual(request.mediaType, 'application/json')
92
+ reply.send({ mediaType: request.mediaType })
93
+ })
94
+
95
+ const response = await fastify.inject({
96
+ method: 'POST',
97
+ url: '/',
98
+ body: JSON.stringify({ hello: 'world' }),
99
+ headers: {
100
+ 'content-type': 'application/json'
101
+ }
102
+ })
103
+ const body = await response.json()
104
+ t.assert.strictEqual(body.mediaType, 'application/json')
105
+ })
@@ -241,6 +241,40 @@ test('Prefix with trailing /', (t, testDone) => {
241
241
  completion.patience.then(testDone)
242
242
  })
243
243
 
244
+ test('Prefix with trailing / and nested prefix without leading /', (t, testDone) => {
245
+ t.plan(4)
246
+ const fastify = Fastify()
247
+
248
+ fastify.register(function (fastify, opts, done) {
249
+ fastify.register(function (fastify, opts, done) {
250
+ fastify.get('/route', (req, reply) => {
251
+ reply.send({ hello: 'world' })
252
+ })
253
+ done()
254
+ }, { prefix: 'inner' })
255
+ done()
256
+ }, { prefix: '/v1/' })
257
+
258
+ const completion = waitForCb({ steps: 2 })
259
+ fastify.inject({
260
+ method: 'GET',
261
+ url: '/v1/inner/route'
262
+ }, (err, res) => {
263
+ t.assert.ifError(err)
264
+ t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' })
265
+ completion.stepIn()
266
+ })
267
+ fastify.inject({
268
+ method: 'GET',
269
+ url: '/v1//inner/route'
270
+ }, (err, res) => {
271
+ t.assert.ifError(err)
272
+ t.assert.strictEqual(res.statusCode, 404)
273
+ completion.stepIn()
274
+ })
275
+ completion.patience.then(testDone)
276
+ })
277
+
244
278
  test('Prefix works multiple levels deep', (t, testDone) => {
245
279
  t.plan(2)
246
280
  const fastify = Fastify()