fastify 4.24.0 → 4.24.2

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 (54) hide show
  1. package/fastify.js +1 -1
  2. package/lib/route.js +10 -4
  3. package/package.json +32 -32
  4. package/test/404s.test.js +31 -39
  5. package/test/async-await.test.js +1 -1
  6. package/test/build-certificate.js +90 -1
  7. package/test/close-pipelining.test.js +5 -5
  8. package/test/close.test.js +1 -5
  9. package/test/custom-http-server.test.js +94 -91
  10. package/test/custom-parser.0.test.js +21 -47
  11. package/test/custom-parser.1.test.js +10 -732
  12. package/test/custom-parser.2.test.js +102 -0
  13. package/test/custom-parser.3.test.js +245 -0
  14. package/test/custom-parser.4.test.js +239 -0
  15. package/test/custom-parser.5.test.js +149 -0
  16. package/test/head.test.js +204 -0
  17. package/test/helper.js +30 -8
  18. package/test/hooks-async.test.js +3 -3
  19. package/test/hooks.on-listen.test.js +7 -6
  20. package/test/hooks.test.js +4 -15
  21. package/test/http2/closing.test.js +7 -15
  22. package/test/https/custom-https-server.test.js +43 -40
  23. package/test/listen.1.test.js +101 -0
  24. package/test/listen.2.test.js +103 -0
  25. package/test/listen.3.test.js +87 -0
  26. package/test/listen.4.test.js +164 -0
  27. package/test/listen.deprecated.test.js +3 -9
  28. package/test/logger/instantiation.test.js +25 -16
  29. package/test/logger/logger-test-utils.js +1 -1
  30. package/test/plugin.1.test.js +249 -0
  31. package/test/plugin.2.test.js +328 -0
  32. package/test/plugin.3.test.js +311 -0
  33. package/test/plugin.4.test.js +416 -0
  34. package/test/reply-trailers.test.js +1 -2
  35. package/test/route.1.test.js +309 -0
  36. package/test/route.2.test.js +99 -0
  37. package/test/route.3.test.js +205 -0
  38. package/test/route.4.test.js +131 -0
  39. package/test/route.5.test.js +230 -0
  40. package/test/route.6.test.js +306 -0
  41. package/test/route.7.test.js +370 -0
  42. package/test/route.8.test.js +142 -0
  43. package/test/stream.1.test.js +108 -0
  44. package/test/stream.2.test.js +119 -0
  45. package/test/stream.3.test.js +192 -0
  46. package/test/stream.4.test.js +223 -0
  47. package/test/stream.5.test.js +194 -0
  48. package/test/trust-proxy.test.js +2 -4
  49. package/test/upgrade.test.js +3 -3
  50. package/types/instance.d.ts +2 -0
  51. package/test/listen.test.js +0 -427
  52. package/test/plugin.test.js +0 -1275
  53. package/test/route.test.js +0 -1762
  54. package/test/stream.test.js +0 -816
@@ -0,0 +1,131 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const Fastify = require('..')
6
+
7
+ test('route error handler overrides global custom error handler', t => {
8
+ t.plan(4)
9
+
10
+ const fastify = Fastify()
11
+
12
+ const customGlobalErrorHandler = (error, request, reply) => {
13
+ t.error(error)
14
+ reply.code(429).send({ message: 'Too much coffee' })
15
+ }
16
+
17
+ const customRouteErrorHandler = (error, request, reply) => {
18
+ t.equal(error.message, 'Wrong Pot Error')
19
+ reply.code(418).send({
20
+ message: 'Make a brew',
21
+ statusCode: 418,
22
+ error: 'Wrong Pot Error'
23
+ })
24
+ }
25
+
26
+ fastify.setErrorHandler(customGlobalErrorHandler)
27
+
28
+ fastify.route({
29
+ method: 'GET',
30
+ path: '/more-coffee',
31
+ handler: (req, res) => {
32
+ res.send(new Error('Wrong Pot Error'))
33
+ },
34
+ errorHandler: customRouteErrorHandler
35
+ })
36
+
37
+ fastify.inject({
38
+ method: 'GET',
39
+ url: '/more-coffee'
40
+ }, (error, res) => {
41
+ t.error(error)
42
+ t.equal(res.statusCode, 418)
43
+ t.same(JSON.parse(res.payload), {
44
+ message: 'Make a brew',
45
+ statusCode: 418,
46
+ error: 'Wrong Pot Error'
47
+ })
48
+ })
49
+ })
50
+
51
+ test('throws when route with empty url', async t => {
52
+ t.plan(1)
53
+
54
+ const fastify = Fastify()
55
+ try {
56
+ fastify.route({
57
+ method: 'GET',
58
+ url: '',
59
+ handler: (req, res) => {
60
+ res.send('hi!')
61
+ }
62
+ })
63
+ } catch (err) {
64
+ t.equal(err.message, 'The path could not be empty')
65
+ }
66
+ })
67
+
68
+ test('throws when route with empty url in shorthand declaration', async t => {
69
+ t.plan(1)
70
+
71
+ const fastify = Fastify()
72
+ try {
73
+ fastify.get(
74
+ '',
75
+ async function handler () { return {} }
76
+ )
77
+ } catch (err) {
78
+ t.equal(err.message, 'The path could not be empty')
79
+ }
80
+ })
81
+
82
+ test('throws when route-level error handler is not a function', t => {
83
+ t.plan(1)
84
+
85
+ const fastify = Fastify()
86
+
87
+ try {
88
+ fastify.route({
89
+ method: 'GET',
90
+ url: '/tea',
91
+ handler: (req, res) => {
92
+ res.send('hi!')
93
+ },
94
+ errorHandler: 'teapot'
95
+ })
96
+ } catch (err) {
97
+ t.equal(err.message, 'Error Handler for GET:/tea route, if defined, must be a function')
98
+ }
99
+ })
100
+
101
+ test('route child logger factory overrides default child logger factory', t => {
102
+ t.plan(3)
103
+
104
+ const fastify = Fastify()
105
+
106
+ const customRouteChildLogger = (logger, bindings, opts, req) => {
107
+ const child = logger.child(bindings, opts)
108
+ child.customLog = function (message) {
109
+ t.equal(message, 'custom')
110
+ }
111
+ return child
112
+ }
113
+
114
+ fastify.route({
115
+ method: 'GET',
116
+ path: '/coffee',
117
+ handler: (req, res) => {
118
+ req.log.customLog('custom')
119
+ res.send()
120
+ },
121
+ childLoggerFactory: customRouteChildLogger
122
+ })
123
+
124
+ fastify.inject({
125
+ method: 'GET',
126
+ url: '/coffee'
127
+ }, (error, res) => {
128
+ t.error(error)
129
+ t.equal(res.statusCode, 200)
130
+ })
131
+ })
@@ -0,0 +1,230 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const Fastify = require('..')
6
+
7
+ test('route child logger factory does not affect other routes', t => {
8
+ t.plan(6)
9
+
10
+ const fastify = Fastify()
11
+
12
+ const customRouteChildLogger = (logger, bindings, opts, req) => {
13
+ const child = logger.child(bindings, opts)
14
+ child.customLog = function (message) {
15
+ t.equal(message, 'custom')
16
+ }
17
+ return child
18
+ }
19
+
20
+ fastify.route({
21
+ method: 'GET',
22
+ path: '/coffee',
23
+ handler: (req, res) => {
24
+ req.log.customLog('custom')
25
+ res.send()
26
+ },
27
+ childLoggerFactory: customRouteChildLogger
28
+ })
29
+
30
+ fastify.route({
31
+ method: 'GET',
32
+ path: '/tea',
33
+ handler: (req, res) => {
34
+ t.notMatch(req.log.customLog instanceof Function)
35
+ res.send()
36
+ }
37
+ })
38
+
39
+ fastify.inject({
40
+ method: 'GET',
41
+ url: '/coffee'
42
+ }, (error, res) => {
43
+ t.error(error)
44
+ t.equal(res.statusCode, 200)
45
+ })
46
+ fastify.inject({
47
+ method: 'GET',
48
+ url: '/tea'
49
+ }, (error, res) => {
50
+ t.error(error)
51
+ t.equal(res.statusCode, 200)
52
+ })
53
+ })
54
+ test('route child logger factory overrides global custom error handler', t => {
55
+ t.plan(6)
56
+
57
+ const fastify = Fastify()
58
+
59
+ const customGlobalChildLogger = (logger, bindings, opts, req) => {
60
+ const child = logger.child(bindings, opts)
61
+ child.globalLog = function (message) {
62
+ t.equal(message, 'global')
63
+ }
64
+ return child
65
+ }
66
+ const customRouteChildLogger = (logger, bindings, opts, req) => {
67
+ const child = logger.child(bindings, opts)
68
+ child.customLog = function (message) {
69
+ t.equal(message, 'custom')
70
+ }
71
+ return child
72
+ }
73
+
74
+ fastify.setChildLoggerFactory(customGlobalChildLogger)
75
+
76
+ fastify.route({
77
+ method: 'GET',
78
+ path: '/coffee',
79
+ handler: (req, res) => {
80
+ req.log.customLog('custom')
81
+ res.send()
82
+ },
83
+ childLoggerFactory: customRouteChildLogger
84
+ })
85
+ fastify.route({
86
+ method: 'GET',
87
+ path: '/more-coffee',
88
+ handler: (req, res) => {
89
+ req.log.globalLog('global')
90
+ res.send()
91
+ }
92
+ })
93
+
94
+ fastify.inject({
95
+ method: 'GET',
96
+ url: '/coffee'
97
+ }, (error, res) => {
98
+ t.error(error)
99
+ t.equal(res.statusCode, 200)
100
+ })
101
+ fastify.inject({
102
+ method: 'GET',
103
+ url: '/more-coffee'
104
+ }, (error, res) => {
105
+ t.error(error)
106
+ t.equal(res.statusCode, 200)
107
+ })
108
+ })
109
+
110
+ test('Creates a HEAD route for each GET one (default)', t => {
111
+ t.plan(8)
112
+
113
+ const fastify = Fastify()
114
+
115
+ fastify.route({
116
+ method: 'GET',
117
+ path: '/more-coffee',
118
+ handler: (req, reply) => {
119
+ reply.send({ here: 'is coffee' })
120
+ }
121
+ })
122
+
123
+ fastify.route({
124
+ method: 'GET',
125
+ path: '/some-light',
126
+ handler: (req, reply) => {
127
+ reply.send('Get some light!')
128
+ }
129
+ })
130
+
131
+ fastify.inject({
132
+ method: 'HEAD',
133
+ url: '/more-coffee'
134
+ }, (error, res) => {
135
+ t.error(error)
136
+ t.equal(res.statusCode, 200)
137
+ t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
138
+ t.same(res.body, '')
139
+ })
140
+
141
+ fastify.inject({
142
+ method: 'HEAD',
143
+ url: '/some-light'
144
+ }, (error, res) => {
145
+ t.error(error)
146
+ t.equal(res.statusCode, 200)
147
+ t.equal(res.headers['content-type'], 'text/plain; charset=utf-8')
148
+ t.equal(res.body, '')
149
+ })
150
+ })
151
+
152
+ test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', t => {
153
+ t.plan(4)
154
+
155
+ const fastify = Fastify({ exposeHeadRoutes: false })
156
+
157
+ fastify.route({
158
+ method: 'GET',
159
+ path: '/more-coffee',
160
+ handler: (req, reply) => {
161
+ reply.send({ here: 'is coffee' })
162
+ }
163
+ })
164
+
165
+ fastify.route({
166
+ method: 'GET',
167
+ path: '/some-light',
168
+ handler: (req, reply) => {
169
+ reply.send('Get some light!')
170
+ }
171
+ })
172
+
173
+ fastify.inject({
174
+ method: 'HEAD',
175
+ url: '/more-coffee'
176
+ }, (error, res) => {
177
+ t.error(error)
178
+ t.equal(res.statusCode, 404)
179
+ })
180
+
181
+ fastify.inject({
182
+ method: 'HEAD',
183
+ url: '/some-light'
184
+ }, (error, res) => {
185
+ t.error(error)
186
+ t.equal(res.statusCode, 404)
187
+ })
188
+ })
189
+
190
+ test('Creates a HEAD route for each GET one', t => {
191
+ t.plan(8)
192
+
193
+ const fastify = Fastify({ exposeHeadRoutes: true })
194
+
195
+ fastify.route({
196
+ method: 'GET',
197
+ path: '/more-coffee',
198
+ handler: (req, reply) => {
199
+ reply.send({ here: 'is coffee' })
200
+ }
201
+ })
202
+
203
+ fastify.route({
204
+ method: 'GET',
205
+ path: '/some-light',
206
+ handler: (req, reply) => {
207
+ reply.send('Get some light!')
208
+ }
209
+ })
210
+
211
+ fastify.inject({
212
+ method: 'HEAD',
213
+ url: '/more-coffee'
214
+ }, (error, res) => {
215
+ t.error(error)
216
+ t.equal(res.statusCode, 200)
217
+ t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
218
+ t.same(res.body, '')
219
+ })
220
+
221
+ fastify.inject({
222
+ method: 'HEAD',
223
+ url: '/some-light'
224
+ }, (error, res) => {
225
+ t.error(error)
226
+ t.equal(res.statusCode, 200)
227
+ t.equal(res.headers['content-type'], 'text/plain; charset=utf-8')
228
+ t.equal(res.body, '')
229
+ })
230
+ })
@@ -0,0 +1,306 @@
1
+ 'use strict'
2
+
3
+ const stream = require('node:stream')
4
+ const t = require('tap')
5
+ const test = t.test
6
+ const Fastify = require('..')
7
+
8
+ test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => {
9
+ t.plan(1)
10
+
11
+ const fastify = Fastify()
12
+
13
+ const arr = []
14
+ fastify.register((instance, opts, next) => {
15
+ instance.addHook('onRoute', (routeOptions) => {
16
+ arr.push(`${routeOptions.method} ${routeOptions.url}`)
17
+ })
18
+
19
+ instance.route({
20
+ method: 'GET',
21
+ path: '/',
22
+ exposeHeadRoute: true,
23
+ prefixTrailingSlash: 'both',
24
+ handler: (req, reply) => {
25
+ reply.send({ here: 'is coffee' })
26
+ }
27
+ })
28
+
29
+ next()
30
+ }, { prefix: '/v1' })
31
+
32
+ await fastify.ready()
33
+
34
+ t.ok(true)
35
+ })
36
+
37
+ test('Will not create a HEAD route that is not GET', t => {
38
+ t.plan(11)
39
+
40
+ const fastify = Fastify({ exposeHeadRoutes: true })
41
+
42
+ fastify.route({
43
+ method: 'GET',
44
+ path: '/more-coffee',
45
+ handler: (req, reply) => {
46
+ reply.send({ here: 'is coffee' })
47
+ }
48
+ })
49
+
50
+ fastify.route({
51
+ method: 'GET',
52
+ path: '/some-light',
53
+ handler: (req, reply) => {
54
+ reply.send()
55
+ }
56
+ })
57
+
58
+ fastify.route({
59
+ method: 'POST',
60
+ path: '/something',
61
+ handler: (req, reply) => {
62
+ reply.send({ look: 'It is something!' })
63
+ }
64
+ })
65
+
66
+ fastify.inject({
67
+ method: 'HEAD',
68
+ url: '/more-coffee'
69
+ }, (error, res) => {
70
+ t.error(error)
71
+ t.equal(res.statusCode, 200)
72
+ t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
73
+ t.same(res.body, '')
74
+ })
75
+
76
+ fastify.inject({
77
+ method: 'HEAD',
78
+ url: '/some-light'
79
+ }, (error, res) => {
80
+ t.error(error)
81
+ t.equal(res.statusCode, 200)
82
+ t.equal(res.headers['content-type'], undefined)
83
+ t.equal(res.headers['content-length'], '0')
84
+ t.equal(res.body, '')
85
+ })
86
+
87
+ fastify.inject({
88
+ method: 'HEAD',
89
+ url: '/something'
90
+ }, (error, res) => {
91
+ t.error(error)
92
+ t.equal(res.statusCode, 404)
93
+ })
94
+ })
95
+
96
+ test('HEAD route should handle properly each response type', t => {
97
+ t.plan(25)
98
+
99
+ const fastify = Fastify({ exposeHeadRoutes: true })
100
+ const resString = 'Found me!'
101
+ const resJSON = { here: 'is Johnny' }
102
+ const resBuffer = Buffer.from('I am a buffer!')
103
+ const resStream = stream.Readable.from('I am a stream!')
104
+
105
+ fastify.route({
106
+ method: 'GET',
107
+ path: '/json',
108
+ handler: (req, reply) => {
109
+ reply.send(resJSON)
110
+ }
111
+ })
112
+
113
+ fastify.route({
114
+ method: 'GET',
115
+ path: '/string',
116
+ handler: (req, reply) => {
117
+ reply.send(resString)
118
+ }
119
+ })
120
+
121
+ fastify.route({
122
+ method: 'GET',
123
+ path: '/buffer',
124
+ handler: (req, reply) => {
125
+ reply.send(resBuffer)
126
+ }
127
+ })
128
+
129
+ fastify.route({
130
+ method: 'GET',
131
+ path: '/buffer-with-content-type',
132
+ handler: (req, reply) => {
133
+ reply.headers({ 'content-type': 'image/jpeg' })
134
+ reply.send(resBuffer)
135
+ }
136
+ })
137
+
138
+ fastify.route({
139
+ method: 'GET',
140
+ path: '/stream',
141
+ handler: (req, reply) => {
142
+ return resStream
143
+ }
144
+ })
145
+
146
+ fastify.inject({
147
+ method: 'HEAD',
148
+ url: '/json'
149
+ }, (error, res) => {
150
+ t.error(error)
151
+ t.equal(res.statusCode, 200)
152
+ t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
153
+ t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJSON))}`)
154
+ t.same(res.body, '')
155
+ })
156
+
157
+ fastify.inject({
158
+ method: 'HEAD',
159
+ url: '/string'
160
+ }, (error, res) => {
161
+ t.error(error)
162
+ t.equal(res.statusCode, 200)
163
+ t.equal(res.headers['content-type'], 'text/plain; charset=utf-8')
164
+ t.equal(res.headers['content-length'], `${Buffer.byteLength(resString)}`)
165
+ t.equal(res.body, '')
166
+ })
167
+
168
+ fastify.inject({
169
+ method: 'HEAD',
170
+ url: '/buffer'
171
+ }, (error, res) => {
172
+ t.error(error)
173
+ t.equal(res.statusCode, 200)
174
+ t.equal(res.headers['content-type'], 'application/octet-stream')
175
+ t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
176
+ t.equal(res.body, '')
177
+ })
178
+
179
+ fastify.inject({
180
+ method: 'HEAD',
181
+ url: '/buffer-with-content-type'
182
+ }, (error, res) => {
183
+ t.error(error)
184
+ t.equal(res.statusCode, 200)
185
+ t.equal(res.headers['content-type'], 'image/jpeg')
186
+ t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
187
+ t.equal(res.body, '')
188
+ })
189
+
190
+ fastify.inject({
191
+ method: 'HEAD',
192
+ url: '/stream'
193
+ }, (error, res) => {
194
+ t.error(error)
195
+ t.equal(res.statusCode, 200)
196
+ t.equal(res.headers['content-type'], undefined)
197
+ t.equal(res.headers['content-length'], undefined)
198
+ t.equal(res.body, '')
199
+ })
200
+ })
201
+
202
+ test('HEAD route should respect custom onSend handlers', t => {
203
+ t.plan(6)
204
+
205
+ let counter = 0
206
+ const resBuffer = Buffer.from('I am a coffee!')
207
+ const fastify = Fastify({ exposeHeadRoutes: true })
208
+ const customOnSend = (res, reply, payload, done) => {
209
+ counter = counter + 1
210
+ done(null, payload)
211
+ }
212
+
213
+ fastify.route({
214
+ method: 'GET',
215
+ path: '/more-coffee',
216
+ handler: (req, reply) => {
217
+ reply.send(resBuffer)
218
+ },
219
+ onSend: [customOnSend, customOnSend]
220
+ })
221
+
222
+ fastify.inject({
223
+ method: 'HEAD',
224
+ url: '/more-coffee'
225
+ }, (error, res) => {
226
+ t.error(error)
227
+ t.equal(res.statusCode, 200)
228
+ t.equal(res.headers['content-type'], 'application/octet-stream')
229
+ t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
230
+ t.equal(res.body, '')
231
+ t.equal(counter, 2)
232
+ })
233
+ })
234
+
235
+ test('route onSend can be function or array of functions', t => {
236
+ t.plan(12)
237
+ const counters = { single: 0, multiple: 0 }
238
+
239
+ const resBuffer = Buffer.from('I am a coffee!')
240
+ const fastify = Fastify({ exposeHeadRoutes: true })
241
+
242
+ fastify.route({
243
+ method: 'GET',
244
+ path: '/coffee',
245
+ handler: () => resBuffer,
246
+ onSend: (res, reply, payload, done) => {
247
+ counters.single += 1
248
+ done(null, payload)
249
+ }
250
+ })
251
+
252
+ const customOnSend = (res, reply, payload, done) => {
253
+ counters.multiple += 1
254
+ done(null, payload)
255
+ }
256
+
257
+ fastify.route({
258
+ method: 'GET',
259
+ path: '/more-coffee',
260
+ handler: () => resBuffer,
261
+ onSend: [customOnSend, customOnSend]
262
+ })
263
+
264
+ fastify.inject({ method: 'HEAD', url: '/coffee' }, (error, res) => {
265
+ t.error(error)
266
+ t.equal(res.statusCode, 200)
267
+ t.equal(res.headers['content-type'], 'application/octet-stream')
268
+ t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
269
+ t.equal(res.body, '')
270
+ t.equal(counters.single, 1)
271
+ })
272
+
273
+ fastify.inject({ method: 'HEAD', url: '/more-coffee' }, (error, res) => {
274
+ t.error(error)
275
+ t.equal(res.statusCode, 200)
276
+ t.equal(res.headers['content-type'], 'application/octet-stream')
277
+ t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
278
+ t.equal(res.body, '')
279
+ t.equal(counters.multiple, 2)
280
+ })
281
+ })
282
+
283
+ test('no warning for exposeHeadRoute', async t => {
284
+ const fastify = Fastify()
285
+
286
+ fastify.route({
287
+ method: 'GET',
288
+ path: '/more-coffee',
289
+ exposeHeadRoute: true,
290
+ async handler () {
291
+ return 'hello world'
292
+ }
293
+ })
294
+
295
+ const listener = (w) => {
296
+ t.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()
306
+ })