fastify 5.3.2 → 5.4.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 (103) hide show
  1. package/README.md +2 -0
  2. package/build/build-validation.js +2 -1
  3. package/docs/Guides/Delay-Accepting-Requests.md +3 -3
  4. package/docs/Guides/Ecosystem.md +16 -7
  5. package/docs/Guides/Serverless.md +28 -69
  6. package/docs/Reference/ContentTypeParser.md +1 -1
  7. package/docs/Reference/Errors.md +2 -4
  8. package/docs/Reference/Hooks.md +14 -14
  9. package/docs/Reference/Logging.md +3 -3
  10. package/docs/Reference/Middleware.md +1 -1
  11. package/docs/Reference/Reply.md +8 -8
  12. package/docs/Reference/Request.md +1 -1
  13. package/docs/Reference/Routes.md +3 -3
  14. package/docs/Reference/Server.md +40 -10
  15. package/docs/Reference/Validation-and-Serialization.md +1 -1
  16. package/eslint.config.js +17 -9
  17. package/fastify.d.ts +2 -1
  18. package/fastify.js +20 -4
  19. package/lib/configValidator.js +1 -1
  20. package/lib/decorate.js +2 -2
  21. package/lib/errors.js +6 -8
  22. package/lib/logger-factory.js +1 -1
  23. package/lib/logger-pino.js +2 -2
  24. package/lib/pluginOverride.js +3 -1
  25. package/lib/reply.js +9 -13
  26. package/lib/request.js +4 -11
  27. package/lib/server.js +30 -51
  28. package/lib/symbols.js +1 -0
  29. package/lib/warnings.js +8 -0
  30. package/package.json +11 -7
  31. package/test/404s.test.js +226 -325
  32. package/test/allow-unsafe-regex.test.js +19 -48
  33. package/test/als.test.js +28 -40
  34. package/test/async-await.test.js +11 -2
  35. package/test/body-limit.test.js +41 -65
  36. package/test/build-certificate.js +1 -1
  37. package/test/close-pipelining.test.js +5 -4
  38. package/test/custom-parser-async.test.js +17 -22
  39. package/test/decorator-namespace.test._js_ +3 -4
  40. package/test/decorator.test.js +422 -341
  41. package/test/diagnostics-channel/async-delay-request.test.js +7 -16
  42. package/test/diagnostics-channel/sync-delay-request.test.js +7 -16
  43. package/test/helper.js +108 -70
  44. package/test/hooks-async.test.js +248 -218
  45. package/test/hooks.on-listen.test.js +255 -239
  46. package/test/hooks.on-ready.test.js +110 -92
  47. package/test/hooks.test.js +910 -769
  48. package/test/http-methods/lock.test.js +31 -31
  49. package/test/http-methods/mkcol.test.js +5 -9
  50. package/test/http-methods/proppatch.test.js +23 -29
  51. package/test/http-methods/report.test.js +44 -69
  52. package/test/http-methods/search.test.js +67 -82
  53. package/test/http2/closing.test.js +38 -20
  54. package/test/http2/secure-with-fallback.test.js +28 -27
  55. package/test/https/https.test.js +56 -53
  56. package/test/inject.test.js +114 -97
  57. package/test/input-validation.js +63 -53
  58. package/test/internals/errors.test.js +0 -10
  59. package/test/internals/handle-request.test.js +49 -66
  60. package/test/internals/hooks.test.js +17 -0
  61. package/test/issue-4959.test.js +14 -5
  62. package/test/listen.4.test.js +31 -43
  63. package/test/logger/response.test.js +19 -20
  64. package/test/nullable-validation.test.js +33 -46
  65. package/test/options.error-handler.test.js +1 -1
  66. package/test/options.test.js +1 -1
  67. package/test/output-validation.test.js +49 -72
  68. package/test/patch.error-handler.test.js +1 -1
  69. package/test/patch.test.js +1 -1
  70. package/test/plugin.1.test.js +71 -60
  71. package/test/plugin.2.test.js +104 -86
  72. package/test/plugin.3.test.js +56 -35
  73. package/test/plugin.4.test.js +124 -119
  74. package/test/promises.test.js +36 -30
  75. package/test/proto-poisoning.test.js +78 -97
  76. package/test/put.error-handler.test.js +1 -1
  77. package/test/put.test.js +1 -1
  78. package/test/reply-error.test.js +169 -148
  79. package/test/reply-trailers.test.js +119 -108
  80. package/test/request-error.test.js +0 -46
  81. package/test/route-hooks.test.js +112 -92
  82. package/test/route-prefix.test.js +194 -133
  83. package/test/schema-feature.test.js +309 -238
  84. package/test/schema-serialization.test.js +177 -154
  85. package/test/schema-special-usage.test.js +165 -132
  86. package/test/schema-validation.test.js +278 -199
  87. package/test/set-error-handler.test.js +58 -1
  88. package/test/skip-reply-send.test.js +64 -69
  89. package/test/stream.1.test.js +30 -27
  90. package/test/stream.2.test.js +20 -10
  91. package/test/stream.3.test.js +37 -31
  92. package/test/trust-proxy.test.js +32 -58
  93. package/test/types/errors.test-d.ts +0 -1
  94. package/test/types/fastify.test-d.ts +3 -0
  95. package/test/types/plugin.test-d.ts +1 -1
  96. package/test/types/register.test-d.ts +1 -1
  97. package/test/types/request.test-d.ts +1 -0
  98. package/test/url-rewriting.test.js +45 -62
  99. package/test/use-semicolon-delimiter.test.js +1 -1
  100. package/types/errors.d.ts +0 -1
  101. package/types/request.d.ts +1 -0
  102. package/.taprc +0 -7
  103. package/test/http2/missing-http2-module.test.js +0 -17
@@ -4,6 +4,7 @@ const sget = require('simple-get').concat
4
4
  const Ajv = require('ajv')
5
5
  const Joi = require('joi')
6
6
  const yup = require('yup')
7
+ const assert = require('node:assert')
7
8
 
8
9
  module.exports.payloadMethod = function (method, t) {
9
10
  const test = t.test
@@ -127,28 +128,27 @@ module.exports.payloadMethod = function (method, t) {
127
128
 
128
129
  done()
129
130
  })
130
- t.pass()
131
+ t.assert.ok(true)
131
132
  } catch (e) {
132
- t.fail()
133
+ t.assert.fail()
133
134
  }
134
135
  })
135
136
 
136
137
  fastify.listen({ port: 0 }, function (err) {
137
- if (err) {
138
- t.error(err)
139
- }
138
+ assert.ifError(err)
140
139
 
141
- t.teardown(() => { fastify.close() })
140
+ t.after(() => { fastify.close() })
142
141
 
143
- test(`${upMethod} - correctly replies`, t => {
142
+ test(`${upMethod} - correctly replies`, (t, testDone) => {
144
143
  if (upMethod === 'HEAD') {
145
144
  t.plan(2)
146
145
  sget({
147
146
  method: upMethod,
148
147
  url: 'http://localhost:' + fastify.server.address().port
149
148
  }, (err, response) => {
150
- t.error(err)
151
- t.equal(response.statusCode, 200)
149
+ t.assert.ifError(err)
150
+ t.assert.strictEqual(response.statusCode, 200)
151
+ testDone()
152
152
  })
153
153
  } else {
154
154
  t.plan(3)
@@ -160,14 +160,15 @@ module.exports.payloadMethod = function (method, t) {
160
160
  },
161
161
  json: true
162
162
  }, (err, response, body) => {
163
- t.error(err)
164
- t.equal(response.statusCode, 200)
165
- t.same(body, { hello: 42 })
163
+ t.assert.ifError(err)
164
+ t.assert.strictEqual(response.statusCode, 200)
165
+ t.assert.deepStrictEqual(body, { hello: 42 })
166
+ testDone()
166
167
  })
167
168
  }
168
169
  })
169
170
 
170
- test(`${upMethod} - 400 on bad parameters`, t => {
171
+ test(`${upMethod} - 400 on bad parameters`, (t, testDone) => {
171
172
  t.plan(3)
172
173
  sget({
173
174
  method: upMethod,
@@ -177,18 +178,19 @@ module.exports.payloadMethod = function (method, t) {
177
178
  },
178
179
  json: true
179
180
  }, (err, response, body) => {
180
- t.error(err)
181
- t.equal(response.statusCode, 400)
182
- t.same(body, {
181
+ t.assert.ifError(err)
182
+ t.assert.strictEqual(response.statusCode, 400)
183
+ t.assert.deepStrictEqual(body, {
183
184
  error: 'Bad Request',
184
185
  message: 'body/hello must be integer',
185
186
  statusCode: 400,
186
187
  code: 'FST_ERR_VALIDATION'
187
188
  })
189
+ testDone()
188
190
  })
189
191
  })
190
192
 
191
- test(`${upMethod} - input-validation coerce`, t => {
193
+ test(`${upMethod} - input-validation coerce`, (t, testDone) => {
192
194
  t.plan(3)
193
195
  sget({
194
196
  method: upMethod,
@@ -198,13 +200,14 @@ module.exports.payloadMethod = function (method, t) {
198
200
  },
199
201
  json: true
200
202
  }, (err, response, body) => {
201
- t.error(err)
202
- t.equal(response.statusCode, 200)
203
- t.same(body, { hello: 42 })
203
+ t.assert.ifError(err)
204
+ t.assert.strictEqual(response.statusCode, 200)
205
+ t.assert.deepStrictEqual(body, { hello: 42 })
206
+ testDone()
204
207
  })
205
208
  })
206
209
 
207
- test(`${upMethod} - input-validation custom schema compiler`, t => {
210
+ test(`${upMethod} - input-validation custom schema compiler`, (t, testDone) => {
208
211
  t.plan(3)
209
212
  sget({
210
213
  method: upMethod,
@@ -215,13 +218,14 @@ module.exports.payloadMethod = function (method, t) {
215
218
  },
216
219
  json: true
217
220
  }, (err, response, body) => {
218
- t.error(err)
219
- t.equal(response.statusCode, 200)
220
- t.same(body, { hello: 42 })
221
+ t.assert.ifError(err)
222
+ t.assert.strictEqual(response.statusCode, 200)
223
+ t.assert.deepStrictEqual(body, { hello: 42 })
224
+ testDone()
221
225
  })
222
226
  })
223
227
 
224
- test(`${upMethod} - input-validation joi schema compiler ok`, t => {
228
+ test(`${upMethod} - input-validation joi schema compiler ok`, (t, testDone) => {
225
229
  t.plan(3)
226
230
  sget({
227
231
  method: upMethod,
@@ -231,13 +235,14 @@ module.exports.payloadMethod = function (method, t) {
231
235
  },
232
236
  json: true
233
237
  }, (err, response, body) => {
234
- t.error(err)
235
- t.equal(response.statusCode, 200)
236
- t.same(body, { hello: 42 })
238
+ t.assert.ifError(err)
239
+ t.assert.strictEqual(response.statusCode, 200)
240
+ t.assert.deepStrictEqual(body, { hello: '42' })
241
+ testDone()
237
242
  })
238
243
  })
239
244
 
240
- test(`${upMethod} - input-validation joi schema compiler ko`, t => {
245
+ test(`${upMethod} - input-validation joi schema compiler ko`, (t, testDone) => {
241
246
  t.plan(3)
242
247
  sget({
243
248
  method: upMethod,
@@ -247,18 +252,19 @@ module.exports.payloadMethod = function (method, t) {
247
252
  },
248
253
  json: true
249
254
  }, (err, response, body) => {
250
- t.error(err)
251
- t.equal(response.statusCode, 400)
252
- t.same(body, {
255
+ t.assert.ifError(err)
256
+ t.assert.strictEqual(response.statusCode, 400)
257
+ t.assert.deepStrictEqual(body, {
253
258
  error: 'Bad Request',
254
259
  message: '"hello" must be a string',
255
260
  statusCode: 400,
256
261
  code: 'FST_ERR_VALIDATION'
257
262
  })
263
+ testDone()
258
264
  })
259
265
  })
260
266
 
261
- test(`${upMethod} - input-validation yup schema compiler ok`, t => {
267
+ test(`${upMethod} - input-validation yup schema compiler ok`, (t, testDone) => {
262
268
  t.plan(3)
263
269
  sget({
264
270
  method: upMethod,
@@ -268,13 +274,14 @@ module.exports.payloadMethod = function (method, t) {
268
274
  },
269
275
  json: true
270
276
  }, (err, response, body) => {
271
- t.error(err)
272
- t.equal(response.statusCode, 200)
273
- t.same(body, { hello: 42 })
277
+ t.assert.ifError(err)
278
+ t.assert.strictEqual(response.statusCode, 200)
279
+ t.assert.deepStrictEqual(body, { hello: '42' })
280
+ testDone()
274
281
  })
275
282
  })
276
283
 
277
- test(`${upMethod} - input-validation yup schema compiler ko`, t => {
284
+ test(`${upMethod} - input-validation yup schema compiler ko`, (t, testDone) => {
278
285
  t.plan(3)
279
286
  sget({
280
287
  method: upMethod,
@@ -284,52 +291,55 @@ module.exports.payloadMethod = function (method, t) {
284
291
  },
285
292
  json: true
286
293
  }, (err, response, body) => {
287
- t.error(err)
288
- t.equal(response.statusCode, 400)
289
- t.match(body, {
294
+ t.assert.ifError(err)
295
+ t.assert.strictEqual(response.statusCode, 400)
296
+ t.assert.deepStrictEqual(body, {
290
297
  error: 'Bad Request',
291
- message: /body hello must be a `string` type, but the final value was: `44`./,
298
+ message: 'body hello must be a `string` type, but the final value was: `44`.',
292
299
  statusCode: 400,
293
300
  code: 'FST_ERR_VALIDATION'
294
301
  })
302
+ testDone()
295
303
  })
296
304
  })
297
305
 
298
- test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, t => {
306
+ test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, (t, testDone) => {
299
307
  t.plan(3)
300
308
  sget({
301
309
  method: upMethod,
302
310
  url: 'http://localhost:' + fastify.server.address().port + '/plugin',
303
- body: { },
311
+ body: {},
304
312
  json: true
305
313
  }, (err, response, body) => {
306
- t.error(err)
307
- t.equal(response.statusCode, 400)
308
- t.same(body, {
314
+ t.assert.ifError(err)
315
+ t.assert.strictEqual(response.statusCode, 400)
316
+ t.assert.deepStrictEqual(body, {
309
317
  error: 'Bad Request',
310
318
  message: 'From custom schema compiler!',
311
- statusCode: '400',
319
+ statusCode: 400,
312
320
  code: 'FST_ERR_VALIDATION'
313
321
  })
322
+ testDone()
314
323
  })
315
324
  })
316
325
 
317
- test(`${upMethod} - input-validation custom schema compiler encapsulated`, t => {
326
+ test(`${upMethod} - input-validation custom schema compiler encapsulated`, (t, testDone) => {
318
327
  t.plan(3)
319
328
  sget({
320
329
  method: upMethod,
321
330
  url: 'http://localhost:' + fastify.server.address().port + '/plugin/custom',
322
- body: { },
331
+ body: {},
323
332
  json: true
324
333
  }, (err, response, body) => {
325
- t.error(err)
326
- t.equal(response.statusCode, 400)
327
- t.same(body, {
334
+ t.assert.ifError(err)
335
+ t.assert.strictEqual(response.statusCode, 400)
336
+ t.assert.deepStrictEqual(body, {
328
337
  error: 'Bad Request',
329
338
  message: 'Always fail!',
330
- statusCode: '400',
339
+ statusCode: 400,
331
340
  code: 'FST_ERR_VALIDATION'
332
341
  })
342
+ testDone()
333
343
  })
334
344
  })
335
345
  })
@@ -580,16 +580,6 @@ test('FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX', t => {
580
580
  t.assert.ok(error instanceof Error)
581
581
  })
582
582
 
583
- test('FST_ERR_HTTP2_INVALID_VERSION', t => {
584
- t.plan(5)
585
- const error = new errors.FST_ERR_HTTP2_INVALID_VERSION()
586
- t.assert.strictEqual(error.name, 'FastifyError')
587
- t.assert.strictEqual(error.code, 'FST_ERR_HTTP2_INVALID_VERSION')
588
- t.assert.strictEqual(error.message, 'HTTP2 is available only from node >= 8.8.1')
589
- t.assert.strictEqual(error.statusCode, 500)
590
- t.assert.ok(error instanceof Error)
591
- })
592
-
593
583
  test('FST_ERR_INIT_OPTS_INVALID', t => {
594
584
  t.plan(5)
595
585
  const error = new errors.FST_ERR_INIT_OPTS_INVALID()
@@ -7,7 +7,6 @@ const Request = require('../../lib/request')
7
7
  const Reply = require('../../lib/reply')
8
8
  const { kRouteContext } = require('../../lib/symbols')
9
9
  const buildSchema = require('../../lib/validation').compileSchemasForValidation
10
- const sget = require('simple-get').concat
11
10
 
12
11
  const Ajv = require('ajv')
13
12
  const ajv = new Ajv({ coerceTypes: true })
@@ -129,8 +128,8 @@ test('handler function - preValidationCallback with finished response', t => {
129
128
  internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context }))
130
129
  })
131
130
 
132
- test('request should be defined in onSend Hook on post request with content type application/json', (t, done) => {
133
- t.plan(8)
131
+ test('request should be defined in onSend Hook on post request with content type application/json', async t => {
132
+ t.plan(6)
134
133
  const fastify = require('../..')()
135
134
 
136
135
  t.after(() => {
@@ -149,28 +148,24 @@ test('request should be defined in onSend Hook on post request with content type
149
148
  reply.send(200)
150
149
  })
151
150
 
152
- fastify.listen({ port: 0 }, err => {
153
- t.assert.ifError(err)
154
- sget({
155
- method: 'POST',
156
- url: 'http://localhost:' + fastify.server.address().port,
157
- headers: {
158
- 'content-type': 'application/json'
159
- }
160
- }, (err, response, body) => {
161
- t.assert.ifError(err)
162
- // a 400 error is expected because of no body
163
- t.assert.strictEqual(response.statusCode, 400)
164
- done()
165
- })
151
+ const fastifyServer = await fastify.listen({ port: 0 })
152
+ const result = await fetch(fastifyServer, {
153
+ method: 'POST',
154
+ headers: {
155
+ 'content-type': 'application/json'
156
+ }
166
157
  })
158
+
159
+ t.assert.strictEqual(result.status, 400)
167
160
  })
168
161
 
169
- test('request should be defined in onSend Hook on post request with content type application/x-www-form-urlencoded', (t, done) => {
170
- t.plan(7)
162
+ test('request should be defined in onSend Hook on post request with content type application/x-www-form-urlencoded', async t => {
163
+ t.plan(5)
171
164
  const fastify = require('../..')()
172
165
 
173
- t.after(() => { fastify.close() })
166
+ t.after(() => {
167
+ fastify.close()
168
+ })
174
169
 
175
170
  fastify.addHook('onSend', (request, reply, payload, done) => {
176
171
  t.assert.ok(request)
@@ -183,29 +178,25 @@ test('request should be defined in onSend Hook on post request with content type
183
178
  reply.send(200)
184
179
  })
185
180
 
186
- fastify.listen({ port: 0 }, err => {
187
- t.assert.ifError(err)
188
-
189
- sget({
190
- method: 'POST',
191
- url: 'http://localhost:' + fastify.server.address().port,
192
- headers: {
193
- 'content-type': 'application/x-www-form-urlencoded'
194
- }
195
- }, (err, response, body) => {
196
- t.assert.ifError(err)
197
- // a 415 error is expected because of missing content type parser
198
- t.assert.strictEqual(response.statusCode, 415)
199
- done()
200
- })
181
+ const fastifyServer = await fastify.listen({ port: 0 })
182
+ const result = await fetch(fastifyServer, {
183
+ method: 'POST',
184
+ headers: {
185
+ 'content-type': 'application/x-www-form-urlencoded'
186
+ }
201
187
  })
188
+
189
+ // a 415 error is expected because of missing content type parser
190
+ t.assert.strictEqual(result.status, 415)
202
191
  })
203
192
 
204
- test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', (t, done) => {
205
- t.plan(7)
193
+ test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', async t => {
194
+ t.plan(5)
206
195
  const fastify = require('../..')()
207
196
 
208
- t.after(() => { fastify.close() })
197
+ t.after(() => {
198
+ fastify.close()
199
+ })
209
200
 
210
201
  fastify.addHook('onSend', (request, reply, payload, done) => {
211
202
  t.assert.ok(request)
@@ -218,26 +209,20 @@ test('request should be defined in onSend Hook on options request with content t
218
209
  reply.send(200)
219
210
  })
220
211
 
221
- fastify.listen({ port: 0 }, err => {
222
- t.assert.ifError(err)
223
-
224
- sget({
225
- method: 'OPTIONS',
226
- url: 'http://localhost:' + fastify.server.address().port,
227
- headers: {
228
- 'content-type': 'application/x-www-form-urlencoded'
229
- }
230
- }, (err, response, body) => {
231
- t.assert.ifError(err)
232
- // Body parsing skipped, so no body sent
233
- t.assert.strictEqual(response.statusCode, 200)
234
- done()
235
- })
212
+ const fastifyServer = await fastify.listen({ port: 0 })
213
+ const result = await fetch(fastifyServer, {
214
+ method: 'OPTIONS',
215
+ headers: {
216
+ 'content-type': 'application/x-www-form-urlencoded'
217
+ }
236
218
  })
219
+
220
+ // Body parsing skipped, so no body sent
221
+ t.assert.strictEqual(result.status, 200)
237
222
  })
238
223
 
239
- test('request should respond with an error if an unserialized payload is sent inside an async handler', (t, done) => {
240
- t.plan(3)
224
+ test('request should respond with an error if an unserialized payload is sent inside an async handler', async t => {
225
+ t.plan(2)
241
226
 
242
227
  const fastify = require('../..')()
243
228
 
@@ -246,18 +231,16 @@ test('request should respond with an error if an unserialized payload is sent in
246
231
  return Promise.resolve(request.headers)
247
232
  })
248
233
 
249
- fastify.inject({
234
+ const res = await fastify.inject({
250
235
  method: 'GET',
251
236
  url: '/'
252
- }, (err, res) => {
253
- t.assert.ifError(err)
254
- t.assert.strictEqual(res.statusCode, 500)
255
- t.assert.deepStrictEqual(JSON.parse(res.payload), {
256
- error: 'Internal Server Error',
257
- code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE',
258
- message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.',
259
- statusCode: 500
260
- })
261
- done()
237
+ })
238
+
239
+ t.assert.strictEqual(res.statusCode, 500)
240
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
241
+ error: 'Internal Server Error',
242
+ code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE',
243
+ message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.',
244
+ statusCode: 500
262
245
  })
263
246
  })
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { test } = require('node:test')
4
4
  const { Hooks } = require('../../lib/hooks')
5
+ const { default: fastify } = require('../../fastify')
5
6
  const noop = () => {}
6
7
 
7
8
  test('hooks should have 4 array with the registered hooks', t => {
@@ -77,3 +78,19 @@ test('should throw on wrong parameters', t => {
77
78
  t.assert.strictEqual(e.message, 'onSend hook should be a function, instead got [object Null]')
78
79
  }
79
80
  })
81
+
82
+ test('Integration test: internal function _addHook should be turned into app.ready() rejection', async (t) => {
83
+ const app = fastify()
84
+
85
+ app.register(async function () {
86
+ app.addHook('notRealHook', async () => {})
87
+ })
88
+
89
+ try {
90
+ await app.ready()
91
+ t.assert.fail('Expected ready() to throw')
92
+ } catch (err) {
93
+ t.assert.strictEqual(err.code, 'FST_ERR_HOOK_NOT_SUPPORTED')
94
+ t.assert.match(err.message, /hook not supported/i)
95
+ }
96
+ })
@@ -3,7 +3,14 @@
3
3
  const { test } = require('node:test')
4
4
  const http = require('node:http')
5
5
  const Fastify = require('../fastify')
6
-
6
+ const { setTimeout } = require('node:timers')
7
+
8
+ /*
9
+ * Ensure that a socket error during the request does not cause the
10
+ * onSend hook to be called multiple times.
11
+ *
12
+ * @see https://github.com/fastify/fastify/issues/4959
13
+ */
7
14
  function runBadClientCall (reqOptions, payload) {
8
15
  let innerResolve, innerReject
9
16
  const promise = new Promise((resolve, reject) => {
@@ -17,7 +24,7 @@ function runBadClientCall (reqOptions, payload) {
17
24
  ...reqOptions,
18
25
  headers: {
19
26
  'Content-Type': 'application/json',
20
- 'Content-Length': Buffer.byteLength(postData),
27
+ 'Content-Length': Buffer.byteLength(postData)
21
28
  }
22
29
  }, () => {
23
30
  innerReject(new Error('Request should have failed'))
@@ -25,7 +32,9 @@ function runBadClientCall (reqOptions, payload) {
25
32
 
26
33
  // Kill the socket immediately (before sending data)
27
34
  req.on('socket', (socket) => {
28
- setTimeout(() => { socket.destroy() }, 5)
35
+ socket.on('connect', () => {
36
+ setTimeout(() => { socket.destroy() }, 0)
37
+ })
29
38
  })
30
39
  req.on('error', innerResolve)
31
40
  req.write(postData)
@@ -34,7 +43,7 @@ function runBadClientCall (reqOptions, payload) {
34
43
  return promise
35
44
  }
36
45
 
37
- test('should handle a soket error', async (t) => {
46
+ test('should handle a socket error', async (t) => {
38
47
  t.plan(4)
39
48
  const fastify = Fastify()
40
49
 
@@ -78,7 +87,7 @@ test('should handle a soket error', async (t) => {
78
87
  hostname: 'localhost',
79
88
  port: fastify.server.address().port,
80
89
  path: '/',
81
- method: 'PUT',
90
+ method: 'PUT'
82
91
  }, { test: 'me' })
83
92
  t.assert.equal(err.code, 'ECONNRESET')
84
93
  })
@@ -3,10 +3,8 @@
3
3
  const { test, before } = require('node:test')
4
4
  const dns = require('node:dns').promises
5
5
  const dnsCb = require('node:dns')
6
- const sget = require('simple-get').concat
7
6
  const Fastify = require('../fastify')
8
7
  const helper = require('./helper')
9
- const { waitForCb } = require('./toolkit')
10
8
 
11
9
  let localhostForURL
12
10
 
@@ -90,7 +88,7 @@ test('listen logs the port as info', async t => {
90
88
 
91
89
  test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => {
92
90
  const localAddresses = await dns.lookup('localhost', { all: true })
93
- t.plan(2 * localAddresses.length)
91
+ t.plan(3 * localAddresses.length)
94
92
 
95
93
  const app = Fastify()
96
94
  app.get('/', async () => 'hello localhost')
@@ -98,52 +96,42 @@ test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => {
98
96
  await app.listen({ port: 0, host: 'localhost' })
99
97
 
100
98
  for (const lookup of localAddresses) {
101
- await new Promise((resolve, reject) => {
102
- sget({
103
- method: 'GET',
104
- url: getUrl(app, lookup)
105
- }, (err, response, body) => {
106
- if (err) { return reject(err) }
107
- t.assert.strictEqual(response.statusCode, 200)
108
- t.assert.deepStrictEqual(body.toString(), 'hello localhost')
109
- resolve()
110
- })
99
+ const result = await fetch(getUrl(app, lookup), {
100
+ method: 'GET'
111
101
  })
102
+
103
+ t.assert.ok(result.ok)
104
+ t.assert.deepEqual(result.status, 200)
105
+ t.assert.deepStrictEqual(await result.text(), 'hello localhost')
112
106
  }
113
107
  })
114
108
 
115
- test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', (t, done) => {
116
- dnsCb.lookup('localhost', { all: true }, (err, lookups) => {
117
- t.plan(2 + (3 * lookups.length))
118
- t.assert.ifError(err)
119
-
120
- const app = Fastify()
121
- app.get('/', async () => 'hello localhost')
122
- app.listen({ port: 0, host: 'localhost' }, (err) => {
123
- t.assert.ifError(err)
124
- t.after(() => app.close())
125
-
126
- const { stepIn, patience } = waitForCb({ steps: lookups.length })
127
-
128
- // Loop over each lookup and perform the assertions
129
- if (lookups.length > 0) {
130
- for (const lookup of lookups) {
131
- sget({
132
- method: 'GET',
133
- url: getUrl(app, lookup)
134
- }, (err, response, body) => {
135
- t.assert.ifError(err)
136
- t.assert.strictEqual(response.statusCode, 200)
137
- t.assert.deepStrictEqual(body.toString(), 'hello localhost')
138
- // Call stepIn to report that a request has been completed
139
- stepIn()
140
- })
141
- }
142
- // When all requests have been completed, call done
143
- patience.then(() => done())
144
- }
109
+ test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', async (t) => {
110
+ const lookups = await new Promise((resolve, reject) => {
111
+ dnsCb.lookup('localhost', { all: true }, (err, lookups) => {
112
+ if (err) return reject(err)
113
+ resolve(lookups)
145
114
  })
146
115
  })
116
+
117
+ t.plan(3 * lookups.length)
118
+
119
+ const app = Fastify()
120
+ app.get('/', async () => 'hello localhost')
121
+ t.after(() => app.close())
122
+
123
+ await app.listen({ port: 0, host: 'localhost' })
124
+
125
+ // Loop over each lookup and perform the assertions
126
+ for (const lookup of lookups) {
127
+ const result = await fetch(getUrl(app, lookup), {
128
+ method: 'GET'
129
+ })
130
+
131
+ t.assert.ok(result.ok)
132
+ t.assert.deepEqual(result.status, 200)
133
+ t.assert.deepStrictEqual(await result.text(), 'hello localhost')
134
+ }
147
135
  })
148
136
 
149
137
  test('addresses getter', async t => {