fastify 5.8.4 → 5.9.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 (105) hide show
  1. package/SECURITY.md +1 -1
  2. package/SPONSORS.md +6 -4
  3. package/docs/Guides/Database.md +0 -28
  4. package/docs/Guides/Ecosystem.md +17 -2
  5. package/docs/Guides/Serverless.md +2 -2
  6. package/docs/Guides/Write-Plugin.md +1 -1
  7. package/docs/Reference/Encapsulation.md +27 -26
  8. package/docs/Reference/Errors.md +10 -4
  9. package/docs/Reference/HTTP2.md +10 -10
  10. package/docs/Reference/Hooks.md +4 -4
  11. package/docs/Reference/Index.md +14 -16
  12. package/docs/Reference/LTS.md +12 -13
  13. package/docs/Reference/Lifecycle.md +9 -8
  14. package/docs/Reference/Logging.md +44 -39
  15. package/docs/Reference/Middleware.md +21 -25
  16. package/docs/Reference/Principles.md +2 -2
  17. package/docs/Reference/Reply.md +6 -1
  18. package/docs/Reference/Request.md +27 -16
  19. package/docs/Reference/Routes.md +5 -2
  20. package/docs/Reference/Server.md +31 -3
  21. package/docs/Reference/Type-Providers.md +29 -5
  22. package/docs/Reference/Validation-and-Serialization.md +15 -2
  23. package/docs/Reference/Warnings.md +7 -6
  24. package/eslint.config.js +7 -2
  25. package/fastify.d.ts +8 -3
  26. package/fastify.js +43 -14
  27. package/lib/content-type-parser.js +13 -1
  28. package/lib/decorate.js +11 -3
  29. package/lib/error-handler.js +4 -3
  30. package/lib/error-serializer.js +59 -59
  31. package/lib/errors.js +16 -1
  32. package/lib/four-oh-four.js +14 -9
  33. package/lib/handle-request.js +11 -5
  34. package/lib/plugin-override.js +2 -1
  35. package/lib/plugin-utils.js +5 -5
  36. package/lib/reply.js +63 -8
  37. package/lib/request.js +23 -19
  38. package/lib/route.js +20 -6
  39. package/lib/schema-controller.js +1 -1
  40. package/lib/schemas.js +37 -30
  41. package/lib/symbols.js +3 -1
  42. package/lib/validation.js +1 -13
  43. package/lib/warnings.js +3 -3
  44. package/package.json +16 -18
  45. package/scripts/validate-ecosystem-links.js +1 -0
  46. package/test/bundler/esbuild/package.json +1 -1
  47. package/test/close-pipelining.test.js +1 -2
  48. package/test/custom-http-server.test.js +38 -0
  49. package/test/decorator-instance-properties.test.js +63 -0
  50. package/test/diagnostics-channel/async-error-handler.test.js +74 -0
  51. package/test/hooks.test.js +23 -0
  52. package/test/http-methods/get.test.js +1 -1
  53. package/test/http2/plain.test.js +135 -0
  54. package/test/http2/secure-with-fallback.test.js +1 -1
  55. package/test/https/https.test.js +1 -2
  56. package/test/internals/errors.test.js +31 -1
  57. package/test/internals/plugin.test.js +3 -1
  58. package/test/internals/request.test.js +27 -3
  59. package/test/internals/schema-controller-perf.test.js +33 -0
  60. package/test/logger/logging.test.js +18 -1
  61. package/test/logger/options.test.js +38 -1
  62. package/test/reply-error.test.js +1 -1
  63. package/test/reply-trailers.test.js +70 -0
  64. package/test/request-media-type.test.js +105 -0
  65. package/test/request-port.test.js +72 -0
  66. package/test/route-prefix.test.js +34 -0
  67. package/test/router-options.test.js +222 -11
  68. package/test/schema-serialization.test.js +108 -0
  69. package/test/schema-validation.test.js +45 -0
  70. package/test/scripts/validate-ecosystem-links.test.js +40 -57
  71. package/test/throw.test.js +14 -0
  72. package/test/trust-proxy.test.js +74 -0
  73. package/test/types/content-type-parser.tst.ts +70 -0
  74. package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
  75. package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
  76. package/test/types/errors.tst.ts +91 -0
  77. package/test/types/fastify.tst.ts +351 -0
  78. package/test/types/hooks.tst.ts +578 -0
  79. package/test/types/instance.tst.ts +597 -0
  80. package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
  81. package/test/types/plugin.tst.ts +96 -0
  82. package/test/types/register.tst.ts +245 -0
  83. package/test/types/reply.tst.ts +297 -0
  84. package/test/types/request.tst.ts +199 -0
  85. package/test/types/route.tst.ts +576 -0
  86. package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
  87. package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
  88. package/test/types/tsconfig.json +9 -0
  89. package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
  90. package/test/types/using.tst.ts +14 -0
  91. package/types/errors.d.ts +3 -0
  92. package/types/request.d.ts +23 -2
  93. package/AGENTS.md +0 -290
  94. package/test/types/content-type-parser.test-d.ts +0 -72
  95. package/test/types/errors.test-d.ts +0 -90
  96. package/test/types/fastify.test-d.ts +0 -352
  97. package/test/types/hooks.test-d.ts +0 -550
  98. package/test/types/import.ts +0 -2
  99. package/test/types/instance.test-d.ts +0 -588
  100. package/test/types/plugin.test-d.ts +0 -97
  101. package/test/types/register.test-d.ts +0 -237
  102. package/test/types/reply.test-d.ts +0 -254
  103. package/test/types/request.test-d.ts +0 -188
  104. package/test/types/route.test-d.ts +0 -553
  105. package/test/types/using.test-d.ts +0 -17
@@ -0,0 +1,63 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+
6
+ test('hasRequestDecorator returns true for built-in constructor-assigned request properties', t => {
7
+ t.plan(6)
8
+ const fastify = Fastify()
9
+ t.assert.equal(fastify.hasRequestDecorator('id'), true)
10
+ t.assert.equal(fastify.hasRequestDecorator('params'), true)
11
+ t.assert.equal(fastify.hasRequestDecorator('raw'), true)
12
+ t.assert.equal(fastify.hasRequestDecorator('query'), true)
13
+ t.assert.equal(fastify.hasRequestDecorator('log'), true)
14
+ t.assert.equal(fastify.hasRequestDecorator('body'), true)
15
+ })
16
+
17
+ test('hasReplyDecorator returns true for built-in constructor-assigned reply properties', t => {
18
+ t.plan(3)
19
+ const fastify = Fastify()
20
+ t.assert.equal(fastify.hasReplyDecorator('raw'), true)
21
+ t.assert.equal(fastify.hasReplyDecorator('request'), true)
22
+ t.assert.equal(fastify.hasReplyDecorator('log'), true)
23
+ })
24
+
25
+ test('decorateRequest throws FST_ERR_DEC_ALREADY_PRESENT for built-in request properties', t => {
26
+ t.plan(4)
27
+ const fastify = Fastify()
28
+ for (const name of ['id', 'params', 'raw', 'body']) {
29
+ t.assert.throws(
30
+ () => fastify.decorateRequest(name, null),
31
+ (err) => err.code === 'FST_ERR_DEC_ALREADY_PRESENT'
32
+ )
33
+ }
34
+ })
35
+
36
+ test('decorateReply throws FST_ERR_DEC_ALREADY_PRESENT for built-in reply properties', t => {
37
+ t.plan(2)
38
+ const fastify = Fastify()
39
+ for (const name of ['raw', 'request']) {
40
+ t.assert.throws(
41
+ () => fastify.decorateReply(name, null),
42
+ (err) => err.code === 'FST_ERR_DEC_ALREADY_PRESENT'
43
+ )
44
+ }
45
+ })
46
+
47
+ test('hasRequestDecorator returns false for unknown properties', t => {
48
+ t.plan(1)
49
+ const fastify = Fastify()
50
+ t.assert.equal(fastify.hasRequestDecorator('nonExistent'), false)
51
+ })
52
+
53
+ test('decorateRequest still works for non-built-in names', (t, done) => {
54
+ t.plan(2)
55
+ const fastify = Fastify()
56
+ fastify.decorateRequest('myExtra', null)
57
+ t.assert.equal(fastify.hasRequestDecorator('myExtra'), true)
58
+ fastify.get('/', async (req) => req.myExtra)
59
+ fastify.ready((err) => {
60
+ t.assert.ifError(err)
61
+ done()
62
+ })
63
+ })
@@ -0,0 +1,74 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const diagnostics = require('node:diagnostics_channel')
5
+ const Fastify = require('../..')
6
+ const Request = require('../../lib/request')
7
+ const Reply = require('../../lib/reply')
8
+
9
+ test('diagnostics channel tracks async operations in async error handlers', async t => {
10
+ t.plan(17)
11
+ let callOrder = 0
12
+ let firstEncounteredMessage
13
+ let asyncStartFired = false
14
+ let asyncEndFired = false
15
+
16
+ const fastify = Fastify()
17
+
18
+ function subscribe (name, handler) {
19
+ const wrapped = (msg) => {
20
+ if (msg.request.server === fastify) handler(msg)
21
+ }
22
+ diagnostics.subscribe(name, wrapped)
23
+ t.after(() => diagnostics.unsubscribe(name, wrapped))
24
+ }
25
+
26
+ subscribe('tracing:fastify.request.handler:start', (msg) => {
27
+ t.assert.strictEqual(callOrder++, 0)
28
+ firstEncounteredMessage = msg
29
+ t.assert.ok(msg.request instanceof Request)
30
+ t.assert.ok(msg.reply instanceof Reply)
31
+ })
32
+
33
+ subscribe('tracing:fastify.request.handler:error', (msg) => {
34
+ t.assert.strictEqual(callOrder++, 1)
35
+ t.assert.ok(msg.error instanceof Error)
36
+ t.assert.strictEqual(msg.error.message, 'handler error')
37
+ })
38
+
39
+ subscribe('tracing:fastify.request.handler:end', (msg) => {
40
+ t.assert.strictEqual(callOrder++, 2)
41
+ t.assert.strictEqual(msg, firstEncounteredMessage)
42
+ t.assert.strictEqual(msg.async, true)
43
+ })
44
+
45
+ subscribe('tracing:fastify.request.handler:asyncStart', (msg) => {
46
+ t.assert.strictEqual(callOrder++, 3)
47
+ t.assert.strictEqual(msg, firstEncounteredMessage)
48
+ asyncStartFired = true
49
+ })
50
+
51
+ subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => {
52
+ t.assert.strictEqual(callOrder++, 4)
53
+ t.assert.strictEqual(msg, firstEncounteredMessage)
54
+ asyncEndFired = true
55
+ })
56
+
57
+ fastify.setErrorHandler(async (error, request, reply) => {
58
+ await new Promise(resolve => setImmediate(resolve))
59
+ reply.status(503).send({ error: error.message })
60
+ })
61
+
62
+ fastify.get('/', () => {
63
+ throw new Error('handler error')
64
+ })
65
+
66
+ const fastifyServer = await fastify.listen({ port: 0 })
67
+ t.after(() => fastify.close())
68
+
69
+ const response = await fetch(fastifyServer)
70
+ t.assert.ok(!response.ok)
71
+ t.assert.strictEqual(response.status, 503)
72
+ t.assert.ok(asyncStartFired, 'asyncStart should fire for async error handler')
73
+ t.assert.ok(asyncEndFired, 'asyncEnd should fire for async error handler')
74
+ })
@@ -12,6 +12,7 @@ const proxyquire = require('proxyquire')
12
12
  const { connect } = require('node:net')
13
13
  const { sleep } = require('./helper')
14
14
  const { waitForCb } = require('./toolkit.js')
15
+ const { fetch } = require('undici')
15
16
 
16
17
  process.removeAllListeners('warning')
17
18
 
@@ -3277,6 +3278,28 @@ test('onTimeout should be triggered and socket _meta is set', async t => {
3277
3278
  }
3278
3279
  })
3279
3280
 
3281
+ test('socket._meta is cleared after response to prevent keep-alive leaks', async t => {
3282
+ t.plan(3)
3283
+ const fastify = Fastify({ connectionTimeout: 500 })
3284
+ t.after(() => { fastify.close() })
3285
+
3286
+ fastify.addHook('onTimeout', function (req, res, done) { done() })
3287
+
3288
+ fastify.addHook('onResponse', function (req, reply, done) {
3289
+ t.assert.strictEqual(req.raw.socket._meta, null, 'socket._meta must be null after response')
3290
+ done()
3291
+ })
3292
+
3293
+ fastify.get('/', async (req, reply) => {
3294
+ return { hello: 'world' }
3295
+ })
3296
+
3297
+ const address = await fastify.listen({ port: 0 })
3298
+ const result = await fetch(address)
3299
+ t.assert.ok(result.ok)
3300
+ t.assert.strictEqual(result.status, 200)
3301
+ })
3302
+
3280
3303
  test('registering invalid hooks should throw an error', async t => {
3281
3304
  t.plan(3)
3282
3305
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const { Client } = require('undici')
4
+ const { Client, fetch } = require('undici')
5
5
  const fastify = require('../../fastify')()
6
6
 
7
7
  const schema = {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
+ const http2 = require('node:http2')
4
5
  const Fastify = require('../..')
5
6
  const h2url = require('h2url')
6
7
  const msg = { hello: 'world' }
@@ -66,3 +67,137 @@ test('http2 plain test', async t => {
66
67
  t.assert.strictEqual(JSON.parse(res.body).port, parseInt(host.split(':')[1]))
67
68
  })
68
69
  })
70
+
71
+ test('http2 large non-stream replies are sent completely', async t => {
72
+ const modes = ['buffer', 'string']
73
+
74
+ for (const mode of modes) {
75
+ await t.test(mode, async t => {
76
+ const fastify = Fastify({ http2: true })
77
+ const payload = mode === 'buffer'
78
+ ? Buffer.alloc((64 * 1024) + 1, 'a')
79
+ : 'a'.repeat((64 * 1024) + 1)
80
+ const contentLength = Buffer.byteLength(payload)
81
+
82
+ fastify.get('/large', async (req, reply) => {
83
+ reply.header('content-type', 'application/octet-stream')
84
+ reply.header('content-length', contentLength)
85
+
86
+ return payload
87
+ })
88
+
89
+ await fastify.listen({ port: 0, host: '127.0.0.1' })
90
+
91
+ const client = http2.connect(`http://127.0.0.1:${fastify.server.address().port}`)
92
+ t.after(() => {
93
+ client.close()
94
+ return fastify.close()
95
+ })
96
+
97
+ await new Promise((resolve, reject) => {
98
+ const large = client.request({
99
+ [http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_GET,
100
+ [http2.constants.HTTP2_HEADER_PATH]: '/large'
101
+ })
102
+ const chunks = []
103
+
104
+ large.on('error', reject)
105
+ large.on('data', chunk => {
106
+ chunks.push(chunk)
107
+ })
108
+ large.on('end', () => {
109
+ t.assert.strictEqual(Buffer.concat(chunks).length, contentLength)
110
+ resolve()
111
+ })
112
+ large.end()
113
+ })
114
+ })
115
+ }
116
+ })
117
+
118
+ test('http2 large buffer replies can be cancelled without rejecting the next stream', async t => {
119
+ const fastify = Fastify({ http2: true })
120
+ const payload = Buffer.alloc(32 * 1024 * 1024, 'a')
121
+ let smallHit = false
122
+
123
+ fastify.get('/large', async (req, reply) => {
124
+ reply.header('content-type', 'application/octet-stream')
125
+ reply.header('content-length', payload.length)
126
+
127
+ return payload
128
+ })
129
+
130
+ fastify.get('/small', async () => {
131
+ smallHit = true
132
+ return 'ok'
133
+ })
134
+
135
+ await fastify.listen({ port: 0, host: '127.0.0.1' })
136
+
137
+ const client = http2.connect(`http://127.0.0.1:${fastify.server.address().port}`)
138
+ t.after(() => {
139
+ client.close()
140
+ return fastify.close()
141
+ })
142
+
143
+ await new Promise((resolve, reject) => {
144
+ let cancelTimer
145
+ let requestTimer
146
+ let timeout
147
+
148
+ function cleanup () {
149
+ clearTimeout(cancelTimer)
150
+ clearTimeout(requestTimer)
151
+ clearTimeout(timeout)
152
+ }
153
+
154
+ const large = client.request({
155
+ [http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_GET,
156
+ [http2.constants.HTTP2_HEADER_PATH]: '/large'
157
+ })
158
+ let largeResponded = false
159
+
160
+ large.on('error', err => {
161
+ if (!largeResponded) {
162
+ cleanup()
163
+ reject(err)
164
+ }
165
+ })
166
+ large.on('response', () => {
167
+ largeResponded = true
168
+ large.pause()
169
+ cancelTimer = setTimeout(() => {
170
+ large.close(http2.constants.NGHTTP2_CANCEL)
171
+ }, 100)
172
+ requestTimer = setTimeout(() => {
173
+ const small = client.request({
174
+ [http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_GET,
175
+ [http2.constants.HTTP2_HEADER_PATH]: '/small'
176
+ })
177
+ let body = ''
178
+
179
+ timeout = setTimeout(() => {
180
+ cleanup()
181
+ reject(new Error('timed out waiting for /small response'))
182
+ }, 3000)
183
+
184
+ small.setEncoding('utf8')
185
+ small.on('error', err => {
186
+ cleanup()
187
+ reject(err)
188
+ })
189
+ small.on('data', chunk => {
190
+ body += chunk
191
+ })
192
+ small.on('end', () => {
193
+ cleanup()
194
+ t.assert.strictEqual(body, 'ok')
195
+ t.assert.strictEqual(smallHit, true)
196
+ resolve()
197
+ })
198
+ small.end()
199
+ }, 200)
200
+ })
201
+ large.end()
202
+ })
203
+ })
@@ -6,7 +6,7 @@ const h2url = require('h2url')
6
6
  const msg = { hello: 'world' }
7
7
 
8
8
  const { buildCertificate } = require('../build-certificate')
9
- const { Agent } = require('undici')
9
+ const { Agent, fetch } = require('undici')
10
10
  test.before(buildCertificate)
11
11
 
12
12
  test('secure with fallback', async (t) => {
@@ -1,11 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const { request } = require('undici')
4
+ const { request, Agent, fetch } = require('undici')
5
5
  const Fastify = require('../..')
6
6
 
7
7
  const { buildCertificate } = require('../build-certificate')
8
- const { Agent } = require('undici')
9
8
  test.before(buildCertificate)
10
9
 
11
10
  test('https', async (t) => {
@@ -5,7 +5,7 @@ const errors = require('../../lib/errors')
5
5
  const { readFileSync } = require('node:fs')
6
6
  const { resolve } = require('node:path')
7
7
 
8
- const expectedErrors = 88
8
+ const expectedErrors = 91
9
9
 
10
10
  test(`should expose ${expectedErrors} errors`, t => {
11
11
  t.plan(1)
@@ -630,6 +630,16 @@ test('FST_ERR_BAD_URL', t => {
630
630
  t.assert.ok(error instanceof Error)
631
631
  })
632
632
 
633
+ test('FST_ERR_MAX_PARAM_LENGTH', t => {
634
+ t.plan(5)
635
+ const error = new errors.FST_ERR_MAX_PARAM_LENGTH()
636
+ t.assert.strictEqual(error.name, 'FastifyError')
637
+ t.assert.strictEqual(error.code, 'FST_ERR_MAX_PARAM_LENGTH')
638
+ t.assert.strictEqual(error.message, "'%s' is exceeding the max param length")
639
+ t.assert.strictEqual(error.statusCode, 414)
640
+ t.assert.ok(error instanceof Error)
641
+ })
642
+
633
643
  test('FST_ERR_ASYNC_CONSTRAINT', t => {
634
644
  t.plan(5)
635
645
  const error = new errors.FST_ERR_ASYNC_CONSTRAINT()
@@ -710,6 +720,16 @@ test('FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', t => {
710
720
  t.assert.ok(error instanceof Error)
711
721
  })
712
722
 
723
+ test('FST_ERR_ROUTE_LOG_LEVEL_INVALID', t => {
724
+ t.plan(5)
725
+ const error = new errors.FST_ERR_ROUTE_LOG_LEVEL_INVALID()
726
+ t.assert.strictEqual(error.name, 'FastifyError')
727
+ t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_LOG_LEVEL_INVALID')
728
+ t.assert.strictEqual(error.message, "Log level for '%s:%s' route must be a valid logger level. Received: '%s'")
729
+ t.assert.strictEqual(error.statusCode, 500)
730
+ t.assert.ok(error instanceof TypeError)
731
+ })
732
+
713
733
  test('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', t => {
714
734
  t.plan(5)
715
735
  const error = new errors.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED()
@@ -810,6 +830,16 @@ test('FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER', t => {
810
830
  t.assert.ok(error instanceof TypeError)
811
831
  })
812
832
 
833
+ test('FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED', t => {
834
+ t.plan(5)
835
+ const error = new errors.FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED('my-dep', 'my-plugin')
836
+ t.assert.strictEqual(error.name, 'FastifyError')
837
+ t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED')
838
+ t.assert.strictEqual(error.message, "The dependency 'my-dep' of plugin 'my-plugin' is not registered")
839
+ t.assert.strictEqual(error.statusCode, 500)
840
+ t.assert.ok(error instanceof Error)
841
+ })
842
+
813
843
  test('FST_ERR_PLUGIN_CALLBACK_NOT_FN', t => {
814
844
  t.plan(5)
815
845
  const error = new errors.FST_ERR_PLUGIN_CALLBACK_NOT_FN()
@@ -149,7 +149,7 @@ test('checkDependencies should check if the given dependency is present in the i
149
149
  })
150
150
 
151
151
  test('checkDependencies should check if the given dependency is present in the instance (errored)', t => {
152
- t.plan(1)
152
+ t.plan(3)
153
153
 
154
154
  fn[Symbol.for('plugin-meta')] = {
155
155
  name: 'test-plugin',
@@ -164,6 +164,8 @@ test('checkDependencies should check if the given dependency is present in the i
164
164
  t.assert.fail('should throw')
165
165
  } catch (err) {
166
166
  t.assert.strictEqual(err.message, "The dependency 'plugin' of plugin 'test-plugin' is not registered")
167
+ t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED')
168
+ t.assert.strictEqual(err.name, 'FastifyError')
167
169
  }
168
170
 
169
171
  function fn () {}
@@ -487,11 +487,13 @@ test('Request with undefined socket', t => {
487
487
  t.assert.ok(request.compileValidationSchema instanceof Function)
488
488
  })
489
489
 
490
- test('Request with trust proxy and undefined socket', t => {
491
- t.plan(1)
490
+ test('Request with trust proxy and undefined socket does not trust x-forwarded-host/proto', t => {
491
+ t.plan(2)
492
492
  const headers = {
493
+ host: 'hostname',
493
494
  'x-forwarded-for': '2.2.2.2, 1.1.1.1',
494
- 'x-forwarded-host': 'fastify.test'
495
+ 'x-forwarded-host': 'fastify.test',
496
+ 'x-forwarded-proto': 'https'
495
497
  }
496
498
  const req = {
497
499
  method: 'GET',
@@ -502,5 +504,27 @@ test('Request with trust proxy and undefined socket', t => {
502
504
 
503
505
  const TpRequest = Request.buildRequest(Request, true)
504
506
  const request = new TpRequest('id', 'params', req, 'query', 'log')
507
+ t.assert.strictEqual(request.host, 'hostname')
508
+ t.assert.deepStrictEqual(request.protocol, undefined)
509
+ })
510
+
511
+ test('Request with trust proxy and null socket does not trust x-forwarded-host/proto', t => {
512
+ t.plan(2)
513
+ const headers = {
514
+ host: 'hostname',
515
+ 'x-forwarded-for': '2.2.2.2, 1.1.1.1',
516
+ 'x-forwarded-host': 'fastify.test',
517
+ 'x-forwarded-proto': 'https'
518
+ }
519
+ const req = {
520
+ method: 'GET',
521
+ url: '/',
522
+ socket: null,
523
+ headers
524
+ }
525
+
526
+ const TpRequest = Request.buildRequest(Request, true)
527
+ const request = new TpRequest('id', 'params', req, 'query', 'log')
528
+ t.assert.strictEqual(request.host, 'hostname')
505
529
  t.assert.deepStrictEqual(request.protocol, undefined)
506
530
  })
@@ -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()
@@ -16,7 +16,7 @@ t.test('logging', { timeout: 60000 }, async (t) => {
16
16
  let localhost
17
17
  let localhostForURL
18
18
 
19
- t.plan(14)
19
+ t.plan(15)
20
20
 
21
21
  t.before(async function () {
22
22
  [localhost, localhostForURL] = await helper.getLoopbackHost()
@@ -282,6 +282,23 @@ t.test('logging', { timeout: 60000 }, async (t) => {
282
282
  t.assert.strictEqual(stream.readableLength, 0)
283
283
  })
284
284
 
285
+ await t.test('should not log incoming request, outgoing response and route not found for 414 onMaxParamLength when disabled', async (t) => {
286
+ t.plan(1)
287
+ const stream = split(JSON.parse)
288
+ const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } })
289
+ t.after(() => fastify.close())
290
+
291
+ await fastify.ready()
292
+
293
+ await fastify.inject({
294
+ method: 'GET',
295
+ url: `/${'1234567890'.repeat(12)}`
296
+ })
297
+
298
+ // no more readable data
299
+ t.assert.strictEqual(stream.readableLength, 0)
300
+ })
301
+
285
302
  await t.test('should log incoming request and outgoing response based on disableRequestLogging function', async (t) => {
286
303
  const lines = [
287
304
  '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,