fastify 3.27.3 → 4.0.0-alpha.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 (164) hide show
  1. package/.taprc +3 -0
  2. package/README.md +7 -7
  3. package/build/build-error-serializer.js +27 -0
  4. package/build/build-validation.js +47 -35
  5. package/docs/Guides/Database.md +320 -0
  6. package/docs/Guides/Getting-Started.md +7 -7
  7. package/docs/Guides/Plugins-Guide.md +1 -1
  8. package/docs/Guides/Serverless.md +3 -3
  9. package/docs/Guides/Testing.md +2 -2
  10. package/docs/Migration-Guide-V4.md +12 -0
  11. package/docs/Reference/ContentTypeParser.md +4 -0
  12. package/docs/Reference/Decorators.md +2 -2
  13. package/docs/Reference/Encapsulation.md +2 -2
  14. package/docs/Reference/Errors.md +51 -6
  15. package/docs/Reference/HTTP2.md +3 -3
  16. package/docs/Reference/Hooks.md +4 -7
  17. package/docs/Reference/LTS.md +5 -4
  18. package/docs/Reference/Plugins.md +3 -3
  19. package/docs/Reference/Reply.md +23 -22
  20. package/docs/Reference/Request.md +1 -3
  21. package/docs/Reference/Routes.md +22 -15
  22. package/docs/Reference/Server.md +69 -119
  23. package/docs/Reference/TypeScript.md +20 -22
  24. package/docs/Reference/Validation-and-Serialization.md +30 -55
  25. package/docs/Type-Providers.md +257 -0
  26. package/examples/asyncawait.js +1 -1
  27. package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
  28. package/examples/benchmark/hooks-benchmark.js +1 -1
  29. package/examples/benchmark/simple.js +1 -1
  30. package/examples/hooks.js +2 -2
  31. package/examples/http2.js +1 -1
  32. package/examples/https.js +1 -1
  33. package/examples/parser.js +1 -1
  34. package/examples/route-prefix.js +1 -1
  35. package/examples/shared-schema.js +1 -1
  36. package/examples/simple-stream.js +18 -0
  37. package/examples/simple.js +1 -1
  38. package/examples/simple.mjs +1 -1
  39. package/examples/typescript-server.ts +1 -1
  40. package/examples/use-plugin.js +1 -1
  41. package/fastify.d.ts +34 -22
  42. package/fastify.js +40 -36
  43. package/lib/configValidator.js +902 -1023
  44. package/lib/contentTypeParser.js +6 -16
  45. package/lib/context.js +36 -10
  46. package/lib/decorate.js +3 -1
  47. package/lib/error-handler.js +158 -0
  48. package/lib/error-serializer.js +257 -0
  49. package/lib/errors.js +43 -9
  50. package/lib/fourOhFour.js +31 -20
  51. package/lib/handleRequest.js +10 -13
  52. package/lib/hooks.js +14 -9
  53. package/lib/pluginOverride.js +0 -3
  54. package/lib/pluginUtils.js +3 -2
  55. package/lib/reply.js +29 -158
  56. package/lib/request.js +13 -10
  57. package/lib/route.js +131 -138
  58. package/lib/schema-controller.js +2 -2
  59. package/lib/schemas.js +27 -1
  60. package/lib/server.js +241 -116
  61. package/lib/symbols.js +4 -3
  62. package/lib/validation.js +2 -1
  63. package/lib/warnings.js +4 -12
  64. package/lib/wrapThenable.js +4 -11
  65. package/package.json +37 -39
  66. package/test/404s.test.js +258 -125
  67. package/test/500s.test.js +3 -3
  68. package/test/als.test.js +1 -1
  69. package/test/async-await.test.js +20 -76
  70. package/test/bodyLimit.test.js +1 -1
  71. package/test/build-certificate.js +6 -7
  72. package/test/case-insensitive.test.js +4 -4
  73. package/test/close-pipelining.test.js +2 -2
  74. package/test/close.test.js +11 -11
  75. package/test/content-parser.test.js +32 -0
  76. package/test/context-config.test.js +52 -0
  77. package/test/custom-http-server.test.js +14 -7
  78. package/test/custom-parser-async.test.js +1 -66
  79. package/test/custom-parser.test.js +92 -159
  80. package/test/custom-querystring-parser.test.js +3 -3
  81. package/test/decorator.test.js +11 -13
  82. package/test/delete.test.js +6 -6
  83. package/test/encapsulated-error-handler.test.js +50 -0
  84. package/test/esm/index.test.js +0 -14
  85. package/test/fastify-instance.test.js +4 -4
  86. package/test/fluent-schema.test.js +4 -4
  87. package/test/genReqId.test.js +1 -1
  88. package/test/get.test.js +4 -4
  89. package/test/handler-context.test.js +2 -2
  90. package/test/head.test.js +1 -1
  91. package/test/helper.js +19 -4
  92. package/test/hooks-async.test.js +15 -48
  93. package/test/hooks.on-ready.test.js +10 -5
  94. package/test/hooks.test.js +78 -119
  95. package/test/http2/closing.test.js +10 -16
  96. package/test/http2/constraint.test.js +1 -1
  97. package/test/http2/head.test.js +1 -1
  98. package/test/http2/plain.test.js +1 -1
  99. package/test/http2/secure-with-fallback.test.js +1 -1
  100. package/test/http2/secure.test.js +1 -1
  101. package/test/http2/unknown-http-method.test.js +4 -10
  102. package/test/https/custom-https-server.test.js +12 -6
  103. package/test/https/https.test.js +1 -1
  104. package/test/input-validation.js +3 -3
  105. package/test/internals/handleRequest.test.js +6 -43
  106. package/test/internals/initialConfig.test.js +41 -12
  107. package/test/internals/logger.test.js +2 -2
  108. package/test/internals/reply.test.js +281 -40
  109. package/test/internals/request.test.js +13 -7
  110. package/test/internals/server.test.js +88 -0
  111. package/test/listen.deprecated.test.js +202 -0
  112. package/test/listen.test.js +118 -150
  113. package/test/logger.test.js +82 -42
  114. package/test/maxRequestsPerSocket.test.js +8 -6
  115. package/test/middleware.test.js +2 -25
  116. package/test/nullable-validation.test.js +53 -16
  117. package/test/output-validation.test.js +1 -1
  118. package/test/plugin.test.js +47 -21
  119. package/test/pretty-print.test.js +22 -10
  120. package/test/promises.test.js +1 -1
  121. package/test/proto-poisoning.test.js +6 -6
  122. package/test/register.test.js +3 -3
  123. package/test/reply-error.test.js +126 -15
  124. package/test/request-error.test.js +3 -6
  125. package/test/route-hooks.test.js +18 -18
  126. package/test/route-prefix.test.js +2 -1
  127. package/test/route.test.js +206 -22
  128. package/test/router-options.test.js +2 -2
  129. package/test/schema-examples.test.js +11 -5
  130. package/test/schema-feature.test.js +25 -20
  131. package/test/schema-serialization.test.js +9 -9
  132. package/test/schema-special-usage.test.js +5 -153
  133. package/test/schema-validation.test.js +9 -9
  134. package/test/skip-reply-send.test.js +2 -2
  135. package/test/stream.test.js +82 -23
  136. package/test/throw.test.js +8 -5
  137. package/test/trust-proxy.test.js +6 -6
  138. package/test/type-provider.test.js +20 -0
  139. package/test/types/fastify.test-d.ts +10 -18
  140. package/test/types/import.js +2 -0
  141. package/test/types/import.ts +1 -0
  142. package/test/types/instance.test-d.ts +68 -17
  143. package/test/types/logger.test-d.ts +44 -15
  144. package/test/types/reply.test-d.ts +2 -1
  145. package/test/types/route.test-d.ts +8 -2
  146. package/test/types/schema.test-d.ts +2 -39
  147. package/test/types/type-provider.test-d.ts +417 -0
  148. package/test/url-rewriting.test.js +3 -3
  149. package/test/validation-error-handling.test.js +8 -8
  150. package/test/versioned-routes.test.js +30 -18
  151. package/test/wrapThenable.test.js +7 -6
  152. package/types/content-type-parser.d.ts +17 -8
  153. package/types/hooks.d.ts +102 -59
  154. package/types/instance.d.ts +244 -118
  155. package/types/logger.d.ts +18 -104
  156. package/types/plugin.d.ts +10 -4
  157. package/types/reply.d.ts +18 -12
  158. package/types/request.d.ts +10 -5
  159. package/types/route.d.ts +42 -31
  160. package/types/schema.d.ts +1 -1
  161. package/types/type-provider.d.ts +99 -0
  162. package/types/utils.d.ts +1 -1
  163. package/lib/schema-compilers.js +0 -12
  164. package/test/emit-warning.test.js +0 -166
@@ -163,7 +163,7 @@ test('Use shared schema and $ref with $id in response ($ref to $id)', t => {
163
163
  t.equal(res.statusCode, 400)
164
164
  t.same(res.json(), {
165
165
  error: 'Bad Request',
166
- message: "body should have required property 'address'",
166
+ message: "body must have required property 'address'",
167
167
  statusCode: 400
168
168
  })
169
169
  })
@@ -236,8 +236,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s
236
236
  $schema: 'http://json-schema.org/draft-07/schema#',
237
237
  title: 'List of Asset locations',
238
238
  type: 'array',
239
- items: { $ref: 'http://example.com/asset.json#' },
240
- default: []
239
+ items: { $ref: 'http://example.com/asset.json#' }
241
240
  }
242
241
 
243
242
  fastify.post('/', {
@@ -273,7 +272,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s
273
272
  t.equal(res.statusCode, 400)
274
273
  t.same(res.json(), {
275
274
  error: 'Bad Request',
276
- message: 'body[0].location.email should match format "email"',
275
+ message: 'body/0/location/email must match format "email"',
277
276
  statusCode: 400
278
277
  })
279
278
  })
@@ -282,7 +281,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s
282
281
 
283
282
  test('Custom setSerializerCompiler', t => {
284
283
  t.plan(7)
285
- const fastify = Fastify()
284
+ const fastify = Fastify({ exposeHeadRoutes: false })
286
285
 
287
286
  const outSchema = {
288
287
  $id: 'test',
@@ -355,7 +354,6 @@ test('Custom setSerializerCompiler returns bad serialized output', t => {
355
354
  t.error(err)
356
355
  t.equal(res.statusCode, 500)
357
356
  t.strictSame(res.json(), {
358
- error: 'Internal Server Error',
359
357
  code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE',
360
358
  message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.',
361
359
  statusCode: 500
@@ -410,11 +408,11 @@ test('Custom serializer per route', async t => {
410
408
  res = await fastify.inject('/route')
411
409
  t.equal(res.json().mean, 'route')
412
410
 
413
- t.equal(hit, 2, 'the custom and route serializer has been called')
411
+ t.equal(hit, 4, 'the custom and route serializer has been called')
414
412
  })
415
413
 
416
414
  test('Reply serializer win over serializer ', t => {
417
- t.plan(5)
415
+ t.plan(6)
418
416
 
419
417
  const fastify = Fastify()
420
418
  fastify.setReplySerializer(function (payload, statusCode) {
@@ -453,7 +451,7 @@ test('Reply serializer win over serializer ', t => {
453
451
  })
454
452
 
455
453
  test('Reply serializer win over serializer ', t => {
456
- t.plan(5)
454
+ t.plan(6)
457
455
 
458
456
  const fastify = Fastify()
459
457
  fastify.setReplySerializer(function (payload, statusCode) {
@@ -570,11 +568,13 @@ test('do not crash if status code serializer errors', async t => {
570
568
  const fastify = Fastify()
571
569
 
572
570
  const requiresFoo = {
571
+ type: 'object',
573
572
  properties: { foo: { type: 'string' } },
574
573
  required: ['foo']
575
574
  }
576
575
 
577
576
  const someUserErrorType2 = {
577
+ type: 'object',
578
578
  properties: {
579
579
  code: { type: 'number' }
580
580
  },
@@ -1,167 +1,18 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('tap')
4
- const Joi = require('@hapi/joi')
4
+ const Joi = require('joi')
5
5
  const AJV = require('ajv')
6
6
  const S = require('fluent-json-schema')
7
7
  const Fastify = require('..')
8
8
  const ajvMergePatch = require('ajv-merge-patch')
9
9
  const ajvErrors = require('ajv-errors')
10
10
 
11
- const buildValidatorAJV8 = require('@fastify/ajv-compiler-8')
12
-
13
- test('Ajv8 usage instead of the bundle one', t => {
14
- t.plan(2)
15
-
16
- t.test('use new ajv8 option', t => {
17
- t.plan(2)
18
- const fastify = Fastify({
19
- ajv: {
20
- customOptions: { strictRequired: true }
21
- },
22
- schemaController: {
23
- compilersFactory: {
24
- buildValidator: buildValidatorAJV8()
25
- }
26
- }
27
- })
28
-
29
- fastify.post('/', {
30
- schema: {
31
- body: {
32
- type: 'object',
33
- required: ['missing'],
34
- properties: {
35
- foo: {
36
- type: 'string'
37
- }
38
- }
39
- }
40
- },
41
- handler (req, reply) { reply.send({ ok: 1 }) }
42
- })
43
-
44
- fastify.ready(err => {
45
- t.ok(err)
46
- t.match(err.message, 'strictRequired', 'the new ajv8 option trigger a startup error')
47
- })
48
- })
49
-
50
- t.test('use new ajv8 option within a response schema', t => {
51
- t.plan(2)
52
- const fastify = Fastify({
53
- schemaController: {
54
- compilersFactory: {
55
- buildValidator: buildValidatorAJV8()
56
- }
57
- }
58
- })
59
-
60
- fastify.post('/', {
61
- schema: {
62
- body: {
63
- type: 'object',
64
- required: ['missing'],
65
- properties: {
66
- foo: {
67
- type: 'string'
68
- }
69
- }
70
- },
71
- response: {
72
- '2xx': {
73
- type: 'object',
74
- properties: {
75
- ok: {
76
- type: 'integer'
77
- }
78
- }
79
- }
80
- }
81
- },
82
- handler (req, reply) { reply.send({ ok: 1 }) }
83
- })
84
-
85
- fastify.ready(err => {
86
- t.error(err)
87
- t.pass('startup successful')
88
- })
89
- })
90
- })
91
-
92
- test('Ajv8 usage with plugins', t => {
93
- t.plan(2)
94
-
95
- t.test('use new ajv8 option', t => {
96
- t.plan(3)
97
- const fastify = Fastify({
98
- ajv: {
99
- customOptions: { validateFormats: true },
100
- plugins: [require('ajv-formats')]
101
- },
102
- schemaController: {
103
- compilersFactory: {
104
- buildValidator: buildValidatorAJV8()
105
- }
106
- }
107
- })
108
-
109
- callIt(fastify, (err, res) => {
110
- t.error(err)
111
- t.equal(res.statusCode, 400)
112
- t.equal(res.json().message, 'body must match format "date"')
113
- })
114
- })
115
-
116
- t.test('use new ajv8 option - avoid check', t => {
117
- t.plan(2)
118
- const fastify = Fastify({
119
- ajv: {
120
- customOptions: { validateFormats: false }
121
- },
122
- schemaController: {
123
- compilersFactory: {
124
- buildValidator: buildValidatorAJV8()
125
- }
126
- }
127
- })
128
-
129
- callIt(fastify, (err, res) => {
130
- t.error(err)
131
- t.equal(res.statusCode, 200)
132
- })
133
- })
134
-
135
- function callIt (fastify, cb) {
136
- fastify.post('/', {
137
- schema: {
138
- body: {
139
- type: 'object',
140
- properties: {
141
- foo: {
142
- type: 'string',
143
- format: 'date'
144
- }
145
- }
146
- }
147
- },
148
- handler (req, reply) { reply.send({ ok: 1 }) }
149
- })
150
-
151
- fastify.inject({
152
- method: 'POST',
153
- url: '/',
154
- payload: { foo: '99' }
155
- }, cb)
156
- }
157
- })
158
-
159
11
  test('Ajv plugins array parameter', t => {
160
12
  t.plan(3)
161
13
  const fastify = Fastify({
162
14
  ajv: {
163
15
  customOptions: {
164
- jsonPointers: true,
165
16
  allErrors: true
166
17
  },
167
18
  plugins: [
@@ -509,7 +360,7 @@ test('setSchemaController in a plugin', t => {
509
360
  ajvInstance.addSchema(baseSchema)
510
361
  ajvInstance.addSchema(refSchema)
511
362
 
512
- const fastify = Fastify()
363
+ const fastify = Fastify({ exposeHeadRoutes: false })
513
364
  fastify.register(schemaPlugin)
514
365
  fastify.get('/', {
515
366
  schema: {
@@ -760,7 +611,8 @@ test('multiple refs with the same ids', t => {
760
611
 
761
612
  fastify.addSchema(baseSchema)
762
613
  fastify.addSchema(refSchema)
763
- fastify.get('/', {
614
+
615
+ fastify.head('/', {
764
616
  schema: {
765
617
  query: refSchema,
766
618
  response: {
@@ -772,7 +624,7 @@ test('multiple refs with the same ids', t => {
772
624
  }
773
625
  })
774
626
 
775
- fastify.head('/', {
627
+ fastify.get('/', {
776
628
  schema: {
777
629
  query: refSchema,
778
630
  response: {
@@ -99,7 +99,7 @@ test('Basic validation test', t => {
99
99
  url: '/'
100
100
  }, (err, res) => {
101
101
  t.error(err)
102
- t.same(res.json(), { statusCode: 400, error: 'Bad Request', message: "body should have required property 'work'" })
102
+ t.same(res.json(), { statusCode: 400, error: 'Bad Request', message: "body must have required property 'work'" })
103
103
  t.equal(res.statusCode, 400)
104
104
  })
105
105
  })
@@ -314,7 +314,7 @@ test('Triple $ref with a simple $id', t => {
314
314
  }, (err, res) => {
315
315
  t.error(err)
316
316
  t.equal(res.statusCode, 400)
317
- t.same(res.json().message, "body should have required property 'foo'")
317
+ t.same(res.json().message, "body must have required property 'foo'")
318
318
  })
319
319
  })
320
320
 
@@ -348,6 +348,7 @@ test('Extending schema', t => {
348
348
  allOf: [
349
349
  { $ref: 'address.id#/definitions/address' },
350
350
  {
351
+ type: 'object',
351
352
  properties: { type: { enum: ['residential', 'business'] } },
352
353
  required: ['type']
353
354
  }
@@ -439,7 +440,7 @@ test('Should work with nested ids', t => {
439
440
  }, (err, res) => {
440
441
  t.error(err)
441
442
  t.equal(res.statusCode, 400)
442
- t.equal(res.json().message, 'params.id should be number')
443
+ t.equal(res.json().message, 'params/id must be number')
443
444
  })
444
445
  })
445
446
 
@@ -536,7 +537,7 @@ test('JSON Schema validation keywords', t => {
536
537
  t.same(res.json(), {
537
538
  statusCode: 400,
538
539
  error: 'Bad Request',
539
- message: 'params.ip should match format "ipv4"'
540
+ message: 'params/ip must match format "ipv4"'
540
541
  })
541
542
  })
542
543
  })
@@ -590,7 +591,7 @@ test('Nested id calls', t => {
590
591
  t.equal(res.statusCode, 400)
591
592
  t.same(res.json(), {
592
593
  error: 'Bad Request',
593
- message: 'body.host.ip should match format "ipv4"',
594
+ message: 'body/host/ip must match format "ipv4"',
594
595
  statusCode: 400
595
596
  })
596
597
  })
@@ -692,7 +693,7 @@ test('Use shared schema and $ref with $id ($ref to $id)', t => {
692
693
  t.equal(res.statusCode, 400)
693
694
  t.same(res.json(), {
694
695
  error: 'Bad Request',
695
- message: "body should have required property 'address'",
696
+ message: "body must have required property 'address'",
696
697
  statusCode: 400
697
698
  })
698
699
  })
@@ -712,8 +713,7 @@ test('Use items with $ref', t => {
712
713
 
713
714
  const body = {
714
715
  type: 'array',
715
- items: { $ref: 'http://example.com/ref-to-external-validator.json#' },
716
- default: []
716
+ items: { $ref: 'http://example.com/ref-to-external-validator.json#' }
717
717
  }
718
718
 
719
719
  fastify.post('/', {
@@ -809,7 +809,7 @@ test('Use $ref to /definitions', t => {
809
809
  t.equal(res.statusCode, 400)
810
810
  t.same(res.json(), {
811
811
  error: 'Bad Request',
812
- message: 'body.test.id should be number',
812
+ message: 'body/test/id must be number',
813
813
  statusCode: 400
814
814
  })
815
815
  })
@@ -201,7 +201,7 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {
201
201
 
202
202
  nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
203
203
 
204
- app.listen(0, err => {
204
+ app.listen({ port: 0 }, err => {
205
205
  t.error(err)
206
206
  const client = net.createConnection({ port: (app.server.address()).port }, () => {
207
207
  client.write('GET / HTTP/1.1\r\n\r\n')
@@ -297,7 +297,7 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {
297
297
  } else {
298
298
  app.addHook(hookOrHandler, async (req, reply) => {
299
299
  reply.hijack()
300
- reply.send('hello from reply.send()')
300
+ return reply.send('hello from reply.send()')
301
301
  })
302
302
  app.get('/', (req, reply) => t.fail('Handler should not be called'))
303
303
  }
@@ -14,7 +14,8 @@ const JSONStream = require('JSONStream')
14
14
  const send = require('send')
15
15
  const Readable = require('stream').Readable
16
16
  const split = require('split2')
17
- const { kDisableRequestLogging, kReplySent } = require('../lib/symbols.js')
17
+ const semver = require('semver')
18
+ const { kDisableRequestLogging } = require('../lib/symbols.js')
18
19
 
19
20
  function getUrl (app) {
20
21
  const { address, port } = app.server.address()
@@ -26,7 +27,7 @@ function getUrl (app) {
26
27
  }
27
28
 
28
29
  test('should respond with a stream', t => {
29
- t.plan(8)
30
+ t.plan(6)
30
31
  const fastify = Fastify()
31
32
 
32
33
  fastify.get('/', function (req, reply) {
@@ -34,18 +35,13 @@ test('should respond with a stream', t => {
34
35
  reply.code(200).send(stream)
35
36
  })
36
37
 
37
- fastify.get('/error', function (req, reply) {
38
- const stream = fs.createReadStream('not-existing-file', 'utf8')
39
- reply.code(200).send(stream)
40
- })
41
-
42
- fastify.listen(0, err => {
38
+ fastify.listen({ port: 0 }, err => {
43
39
  t.error(err)
44
40
  fastify.server.unref()
45
41
 
46
42
  sget(`http://localhost:${fastify.server.address().port}`, function (err, response, data) {
47
43
  t.error(err)
48
- t.equal(response.headers['content-type'], 'application/octet-stream')
44
+ t.equal(response.headers['content-type'], undefined)
49
45
  t.equal(response.statusCode, 200)
50
46
 
51
47
  fs.readFile(__filename, (err, expected) => {
@@ -53,6 +49,21 @@ test('should respond with a stream', t => {
53
49
  t.equal(expected.toString(), data.toString())
54
50
  })
55
51
  })
52
+ })
53
+ })
54
+
55
+ test('should respond with a stream (error)', t => {
56
+ t.plan(3)
57
+ const fastify = Fastify()
58
+
59
+ fastify.get('/error', function (req, reply) {
60
+ const stream = fs.createReadStream('not-existing-file', 'utf8')
61
+ reply.code(200).send(stream)
62
+ })
63
+
64
+ fastify.listen({ port: 0 }, err => {
65
+ t.error(err)
66
+ fastify.server.unref()
56
67
 
57
68
  sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) {
58
69
  t.error(err)
@@ -105,7 +116,7 @@ test('should trigger the onSend hook only twice if pumping the stream fails, fir
105
116
  done()
106
117
  })
107
118
 
108
- fastify.listen(0, err => {
119
+ fastify.listen({ port: 0 }, err => {
109
120
  t.error(err)
110
121
 
111
122
  fastify.server.unref()
@@ -268,7 +279,7 @@ test('Destroying streams prematurely', t => {
268
279
  reply.send(reallyLongStream)
269
280
  })
270
281
 
271
- fastify.listen(0, err => {
282
+ fastify.listen({ port: 0 }, err => {
272
283
  t.error(err)
273
284
  fastify.server.unref()
274
285
 
@@ -331,7 +342,7 @@ test('Destroying streams prematurely should call close method', t => {
331
342
  reply.send(reallyLongStream)
332
343
  })
333
344
 
334
- fastify.listen(0, err => {
345
+ fastify.listen({ port: 0 }, err => {
335
346
  t.error(err)
336
347
  fastify.server.unref()
337
348
 
@@ -393,7 +404,7 @@ test('Destroying streams prematurely should call close method when destroy is no
393
404
  reply.send(reallyLongStream)
394
405
  })
395
406
 
396
- fastify.listen(0, err => {
407
+ fastify.listen({ port: 0 }, err => {
397
408
  t.error(err)
398
409
  fastify.server.unref()
399
410
 
@@ -456,7 +467,7 @@ test('Destroying streams prematurely should call abort method', t => {
456
467
  reply.send(reallyLongStream)
457
468
  })
458
469
 
459
- fastify.listen(0, err => {
470
+ fastify.listen({ port: 0 }, err => {
460
471
  t.error(err)
461
472
  fastify.server.unref()
462
473
 
@@ -506,7 +517,7 @@ test('Destroying streams prematurely, log is disabled', t => {
506
517
  reply.send(reallyLongStream)
507
518
  })
508
519
 
509
- fastify.listen(0, err => {
520
+ fastify.listen({ port: 0 }, err => {
510
521
  t.error(err)
511
522
  fastify.server.unref()
512
523
 
@@ -536,7 +547,7 @@ test('should respond with a stream1', t => {
536
547
  stream.end({ a: 42 })
537
548
  })
538
549
 
539
- fastify.listen(0, err => {
550
+ fastify.listen({ port: 0 }, err => {
540
551
  t.error(err)
541
552
  fastify.server.unref()
542
553
 
@@ -568,7 +579,7 @@ test('return a 404 if the stream emits a 404 error', t => {
568
579
  reply.send(reallyLongStream)
569
580
  })
570
581
 
571
- fastify.listen(0, err => {
582
+ fastify.listen({ port: 0 }, err => {
572
583
  t.error(err)
573
584
  fastify.server.unref()
574
585
 
@@ -582,7 +593,7 @@ test('return a 404 if the stream emits a 404 error', t => {
582
593
  })
583
594
  })
584
595
 
585
- test('should support send module 200 and 404', { only: true }, t => {
596
+ test('should support send module 200 and 404', { skip: semver.gte(process.versions.node, '17.0.0') }, t => {
586
597
  t.plan(8)
587
598
  const fastify = Fastify()
588
599
 
@@ -596,7 +607,7 @@ test('should support send module 200 and 404', { only: true }, t => {
596
607
  reply.code(200).send(stream)
597
608
  })
598
609
 
599
- fastify.listen(0, err => {
610
+ fastify.listen({ port: 0 }, err => {
600
611
  t.error(err)
601
612
  fastify.server.unref()
602
613
 
@@ -604,7 +615,7 @@ test('should support send module 200 and 404', { only: true }, t => {
604
615
 
605
616
  sget(url, function (err, response, data) {
606
617
  t.error(err)
607
- t.equal(response.headers['content-type'], 'application/octet-stream')
618
+ t.equal(response.headers['content-type'], 'application/javascript; charset=UTF-8')
608
619
  t.equal(response.statusCode, 200)
609
620
 
610
621
  fs.readFile(__filename, (err, expected) => {
@@ -637,7 +648,7 @@ test('should destroy stream when response is ended', t => {
637
648
  reply.raw.end(Buffer.from('hello\n'))
638
649
  })
639
650
 
640
- fastify.listen(0, err => {
651
+ fastify.listen({ port: 0 }, err => {
641
652
  t.error(err)
642
653
  fastify.server.unref()
643
654
 
@@ -654,7 +665,7 @@ test('should mark reply as sent before pumping the payload stream into response
654
665
  const handleRequest = proxyquire('../lib/handleRequest', {
655
666
  './wrapThenable': (thenable, reply) => {
656
667
  thenable.then(function (payload) {
657
- t.equal(reply[kReplySent], true)
668
+ t.equal(reply.sent, true)
658
669
  })
659
670
  }
660
671
  })
@@ -671,7 +682,7 @@ test('should mark reply as sent before pumping the payload stream into response
671
682
 
672
683
  fastify.get('/', async function (req, reply) {
673
684
  const stream = fs.createReadStream(__filename, 'utf8')
674
- reply.code(200).send(stream)
685
+ return reply.code(200).send(stream)
675
686
  })
676
687
 
677
688
  fastify.inject({
@@ -683,3 +694,51 @@ test('should mark reply as sent before pumping the payload stream into response
683
694
  fastify.close()
684
695
  })
685
696
  })
697
+
698
+ test('reply.send handles aborted requests', t => {
699
+ t.plan(2)
700
+
701
+ const spyLogger = {
702
+ level: 'error',
703
+ fatal: () => { },
704
+ error: () => {
705
+ t.fail('should not log an error')
706
+ },
707
+ warn: () => { },
708
+ info: () => { },
709
+ debug: () => { },
710
+ trace: () => { },
711
+ child: () => { return spyLogger }
712
+ }
713
+ const fastify = Fastify({
714
+ logger: spyLogger
715
+ })
716
+
717
+ fastify.get('/', (req, reply) => {
718
+ setTimeout(() => {
719
+ const stream = new Readable({
720
+ read: function () {
721
+ this.push(null)
722
+ }
723
+ })
724
+ reply.send(stream)
725
+ }, 6)
726
+ })
727
+
728
+ fastify.listen({ port: 0 }, err => {
729
+ t.error(err)
730
+ fastify.server.unref()
731
+
732
+ const port = fastify.server.address().port
733
+ const http = require('http')
734
+ const req = http.get(`http://localhost:${port}`)
735
+ .on('error', (err) => {
736
+ t.equal(err.code, 'ECONNRESET')
737
+ fastify.close()
738
+ })
739
+
740
+ setTimeout(() => {
741
+ req.abort()
742
+ }, 1)
743
+ })
744
+ })
@@ -17,12 +17,15 @@ test('Fastify should throw on wrong options', t => {
17
17
  test('Fastify should throw on multiple assignment to the same route', t => {
18
18
  t.plan(1)
19
19
  const fastify = Fastify()
20
- fastify.get('/', () => {})
20
+
21
21
  fastify.get('/', () => {})
22
22
 
23
- fastify.ready(err => {
24
- t.equal(err.message, "Method 'GET' already declared for route '/' with constraints '{}'")
25
- })
23
+ try {
24
+ fastify.get('/', () => {})
25
+ t.fail('Should throw on duplicated route declaration')
26
+ } catch (error) {
27
+ t.equal(error.message, "Method 'GET' already declared for route '/'")
28
+ }
26
29
  })
27
30
 
28
31
  test('Fastify should throw for an invalid schema, printing the error route - headers', t => {
@@ -78,7 +81,7 @@ test('Fastify should throw for an invalid shorthand option type', t => {
78
81
  t.fail()
79
82
  } catch (e) {
80
83
  t.equal(e.code, 'FST_ERR_INIT_OPTS_INVALID')
81
- t.match(e.message, /should be boolean/)
84
+ t.match(e.message, /must be boolean/)
82
85
  t.pass()
83
86
  }
84
87
  })
@@ -63,7 +63,7 @@ test('trust proxy, not add properties to node req', (t) => {
63
63
 
64
64
  t.teardown(app.close.bind(app))
65
65
 
66
- app.listen(0, (err) => {
66
+ app.listen({ port: 0 }, (err) => {
67
67
  app.server.unref()
68
68
  t.error(err)
69
69
  sgetForwardedRequest(app, '1.1.1.1', '/trustproxy')
@@ -84,7 +84,7 @@ test('trust proxy chain', (t) => {
84
84
 
85
85
  t.teardown(app.close.bind(app))
86
86
 
87
- app.listen(0, (err) => {
87
+ app.listen({ port: 0 }, (err) => {
88
88
  app.server.unref()
89
89
  t.error(err)
90
90
  sgetForwardedRequest(app, '192.168.1.1, 1.1.1.1', '/trustproxychain')
@@ -103,7 +103,7 @@ test('trust proxy function', (t) => {
103
103
 
104
104
  t.teardown(app.close.bind(app))
105
105
 
106
- app.listen(0, (err) => {
106
+ app.listen({ port: 0 }, (err) => {
107
107
  app.server.unref()
108
108
  t.error(err)
109
109
  sgetForwardedRequest(app, '1.1.1.1', '/trustproxyfunc')
@@ -122,7 +122,7 @@ test('trust proxy number', (t) => {
122
122
 
123
123
  t.teardown(app.close.bind(app))
124
124
 
125
- app.listen(0, (err) => {
125
+ app.listen({ port: 0 }, (err) => {
126
126
  app.server.unref()
127
127
  t.error(err)
128
128
  sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxynumber')
@@ -141,7 +141,7 @@ test('trust proxy IP addresses', (t) => {
141
141
 
142
142
  t.teardown(app.close.bind(app))
143
143
 
144
- app.listen(0, (err) => {
144
+ app.listen({ port: 0 }, (err) => {
145
145
  app.server.unref()
146
146
  t.error(err)
147
147
  sgetForwardedRequest(app, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs')
@@ -168,7 +168,7 @@ test('trust proxy protocol', (t) => {
168
168
 
169
169
  t.teardown(app.close.bind(app))
170
170
 
171
- app.listen(0, (err) => {
171
+ app.listen({ port: 0 }, (err) => {
172
172
  app.server.unref()
173
173
  t.error(err)
174
174
  sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocol', 'lorem')