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.
- package/PROJECT_CHARTER.md +1 -1
- package/README.md +7 -10
- package/SECURITY.md +1 -1
- package/SPONSORS.md +6 -4
- package/build/build-validation.js +2 -2
- package/docs/Guides/Database.md +0 -28
- package/docs/Guides/Delay-Accepting-Requests.md +1 -1
- package/docs/Guides/Ecosystem.md +23 -9
- package/docs/Guides/Getting-Started.md +2 -2
- package/docs/Guides/Migration-Guide-V4.md +2 -2
- package/docs/Guides/Migration-Guide-V5.md +1 -1
- package/docs/Guides/Plugins-Guide.md +1 -1
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Guides/Serverless.md +8 -15
- package/docs/Guides/Style-Guide.md +1 -1
- package/docs/Guides/Write-Plugin.md +1 -1
- package/docs/Reference/Encapsulation.md +27 -26
- package/docs/Reference/Errors.md +12 -4
- package/docs/Reference/HTTP2.md +10 -10
- package/docs/Reference/Hooks.md +5 -5
- package/docs/Reference/Index.md +14 -16
- package/docs/Reference/LTS.md +12 -13
- package/docs/Reference/Lifecycle.md +9 -8
- package/docs/Reference/Logging.md +47 -39
- package/docs/Reference/Middleware.md +21 -25
- package/docs/Reference/Principles.md +2 -2
- package/docs/Reference/Reply.md +6 -1
- package/docs/Reference/Request.md +29 -18
- package/docs/Reference/Routes.md +5 -2
- package/docs/Reference/Server.md +138 -11
- package/docs/Reference/Type-Providers.md +53 -9
- package/docs/Reference/TypeScript.md +3 -3
- package/docs/Reference/Validation-and-Serialization.md +15 -2
- package/docs/Reference/Warnings.md +11 -6
- package/eslint.config.js +7 -2
- package/fastify.d.ts +13 -3
- package/fastify.js +60 -31
- package/lib/content-type-parser.js +2 -0
- package/lib/content-type.js +34 -1
- package/lib/context.js +0 -3
- package/lib/decorate.js +11 -3
- package/lib/error-handler.js +10 -28
- package/lib/error-serializer.js +59 -59
- package/lib/errors.js +23 -1
- package/lib/four-oh-four.js +22 -19
- package/lib/handle-request.js +12 -5
- package/lib/log-controller.js +169 -0
- package/lib/logger-factory.js +25 -4
- package/lib/plugin-override.js +2 -1
- package/lib/plugin-utils.js +5 -5
- package/lib/reply.js +96 -50
- package/lib/req-id-gen-factory.js +4 -1
- package/lib/request.js +16 -6
- package/lib/route.js +47 -41
- package/lib/schema-controller.js +1 -1
- package/lib/schemas.js +37 -30
- package/lib/symbols.js +4 -2
- package/lib/validation.js +10 -13
- package/lib/warnings.js +22 -4
- package/package.json +15 -17
- package/scripts/validate-ecosystem-links.js +1 -0
- package/test/bundler/esbuild/package.json +1 -1
- package/test/close-pipelining.test.js +1 -2
- package/test/content-type.test.js +20 -0
- package/test/custom-http-server.test.js +38 -0
- package/test/decorator-instance-properties.test.js +63 -0
- package/test/diagnostics-channel/async-error-handler.test.js +74 -0
- package/test/genReqId.test.js +24 -0
- package/test/hooks.test.js +94 -0
- package/test/http-methods/get.test.js +1 -1
- package/test/http2/plain.test.js +135 -0
- package/test/http2/secure-with-fallback.test.js +1 -1
- package/test/https/https.test.js +1 -2
- package/test/internals/errors.test.js +31 -1
- package/test/internals/logger.test.js +322 -0
- package/test/internals/plugin.test.js +3 -1
- package/test/internals/reply.test.js +35 -4
- package/test/internals/request.test.js +37 -10
- package/test/internals/schema-controller-perf.test.js +33 -0
- package/test/logger/logging.test.js +57 -1
- package/test/logger/options.test.js +38 -1
- package/test/reply-error.test.js +1 -1
- package/test/reply-trailers.test.js +70 -0
- package/test/request-media-type.test.js +105 -0
- package/test/route-prefix.test.js +34 -0
- package/test/router-options.test.js +222 -11
- package/test/schema-serialization.test.js +108 -0
- package/test/schema-validation.test.js +24 -0
- package/test/scripts/validate-ecosystem-links.test.js +40 -57
- package/test/stream.4.test.js +2 -2
- package/test/throw.test.js +14 -0
- package/test/trust-proxy.test.js +70 -30
- package/test/types/content-type-parser.tst.ts +70 -0
- package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
- package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
- package/test/types/errors.tst.ts +91 -0
- package/test/types/fastify.tst.ts +351 -0
- package/test/types/hooks.tst.ts +578 -0
- package/test/types/instance.tst.ts +597 -0
- package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
- package/test/types/plugin.tst.ts +96 -0
- package/test/types/register.tst.ts +245 -0
- package/test/types/reply.tst.ts +297 -0
- package/test/types/request.tst.ts +199 -0
- package/test/types/route.tst.ts +576 -0
- package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
- package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
- package/test/types/tsconfig.json +9 -0
- package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
- package/test/types/using.tst.ts +14 -0
- package/types/errors.d.ts +4 -0
- package/types/instance.d.ts +2 -0
- package/types/logger.d.ts +22 -0
- package/types/request.d.ts +23 -2
- package/test/types/content-type-parser.test-d.ts +0 -72
- package/test/types/errors.test-d.ts +0 -90
- package/test/types/fastify.test-d.ts +0 -352
- package/test/types/hooks.test-d.ts +0 -550
- package/test/types/import.ts +0 -2
- package/test/types/instance.test-d.ts +0 -588
- package/test/types/plugin.test-d.ts +0 -97
- package/test/types/register.test-d.ts +0 -237
- package/test/types/reply.test-d.ts +0 -254
- package/test/types/request.test-d.ts +0 -188
- package/test/types/route.test-d.ts +0 -553
- 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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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({
|
package/test/reply-error.test.js
CHANGED
|
@@ -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:
|
|
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()
|