fastify 3.27.4 → 4.0.0-alpha.3

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 (168) 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/Ecosystem.md +9 -0
  7. package/docs/Guides/Getting-Started.md +7 -7
  8. package/docs/Guides/Plugins-Guide.md +1 -1
  9. package/docs/Guides/Serverless.md +3 -3
  10. package/docs/Guides/Testing.md +2 -2
  11. package/docs/Migration-Guide-V4.md +12 -0
  12. package/docs/Reference/ContentTypeParser.md +4 -0
  13. package/docs/Reference/Decorators.md +2 -2
  14. package/docs/Reference/Encapsulation.md +2 -2
  15. package/docs/Reference/Errors.md +51 -6
  16. package/docs/Reference/HTTP2.md +3 -3
  17. package/docs/Reference/Hooks.md +4 -7
  18. package/docs/Reference/LTS.md +5 -4
  19. package/docs/Reference/Plugins.md +3 -3
  20. package/docs/Reference/Reply.md +73 -22
  21. package/docs/Reference/Request.md +1 -3
  22. package/docs/Reference/Routes.md +22 -15
  23. package/docs/Reference/Server.md +69 -119
  24. package/docs/Reference/TypeScript.md +20 -22
  25. package/docs/Reference/Validation-and-Serialization.md +30 -55
  26. package/docs/Type-Providers.md +257 -0
  27. package/examples/asyncawait.js +1 -1
  28. package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
  29. package/examples/benchmark/hooks-benchmark.js +1 -1
  30. package/examples/benchmark/simple.js +1 -1
  31. package/examples/hooks.js +2 -2
  32. package/examples/http2.js +1 -1
  33. package/examples/https.js +1 -1
  34. package/examples/parser.js +13 -3
  35. package/examples/route-prefix.js +1 -1
  36. package/examples/shared-schema.js +1 -1
  37. package/examples/simple-stream.js +18 -0
  38. package/examples/simple.js +1 -1
  39. package/examples/simple.mjs +1 -1
  40. package/examples/typescript-server.ts +1 -1
  41. package/examples/use-plugin.js +1 -1
  42. package/fastify.d.ts +34 -22
  43. package/fastify.js +40 -36
  44. package/lib/configValidator.js +902 -1023
  45. package/lib/contentTypeParser.js +6 -16
  46. package/lib/context.js +36 -10
  47. package/lib/decorate.js +3 -1
  48. package/lib/error-handler.js +158 -0
  49. package/lib/error-serializer.js +257 -0
  50. package/lib/errors.js +51 -9
  51. package/lib/fourOhFour.js +31 -20
  52. package/lib/handleRequest.js +10 -13
  53. package/lib/hooks.js +14 -9
  54. package/lib/pluginOverride.js +0 -3
  55. package/lib/pluginUtils.js +3 -2
  56. package/lib/reply.js +121 -175
  57. package/lib/request.js +13 -10
  58. package/lib/route.js +131 -138
  59. package/lib/schema-controller.js +2 -2
  60. package/lib/schemas.js +27 -1
  61. package/lib/server.js +242 -116
  62. package/lib/symbols.js +5 -3
  63. package/lib/validation.js +11 -9
  64. package/lib/warnings.js +4 -12
  65. package/lib/wrapThenable.js +4 -11
  66. package/package.json +37 -39
  67. package/test/404s.test.js +258 -125
  68. package/test/500s.test.js +3 -3
  69. package/test/als.test.js +1 -1
  70. package/test/async-await.test.js +20 -76
  71. package/test/bodyLimit.test.js +1 -1
  72. package/test/build-certificate.js +6 -7
  73. package/test/case-insensitive.test.js +4 -4
  74. package/test/close-pipelining.test.js +2 -2
  75. package/test/close.test.js +11 -11
  76. package/test/content-parser.test.js +32 -0
  77. package/test/context-config.test.js +52 -0
  78. package/test/custom-http-server.test.js +14 -7
  79. package/test/custom-parser-async.test.js +1 -66
  80. package/test/custom-parser.test.js +92 -159
  81. package/test/custom-querystring-parser.test.js +3 -3
  82. package/test/decorator.test.js +11 -13
  83. package/test/delete.test.js +6 -6
  84. package/test/encapsulated-error-handler.test.js +50 -0
  85. package/test/esm/index.test.js +0 -14
  86. package/test/fastify-instance.test.js +4 -4
  87. package/test/fluent-schema.test.js +4 -4
  88. package/test/genReqId.test.js +1 -1
  89. package/test/get.test.js +4 -4
  90. package/test/handler-context.test.js +2 -2
  91. package/test/head.test.js +1 -1
  92. package/test/helper.js +19 -4
  93. package/test/hooks-async.test.js +15 -48
  94. package/test/hooks.on-ready.test.js +10 -5
  95. package/test/hooks.test.js +78 -119
  96. package/test/http2/closing.test.js +10 -16
  97. package/test/http2/constraint.test.js +1 -1
  98. package/test/http2/head.test.js +1 -1
  99. package/test/http2/plain.test.js +1 -1
  100. package/test/http2/secure-with-fallback.test.js +1 -1
  101. package/test/http2/secure.test.js +1 -1
  102. package/test/http2/unknown-http-method.test.js +4 -10
  103. package/test/https/custom-https-server.test.js +12 -6
  104. package/test/https/https.test.js +1 -1
  105. package/test/input-validation.js +3 -3
  106. package/test/internals/handleRequest.test.js +6 -43
  107. package/test/internals/initialConfig.test.js +41 -12
  108. package/test/internals/logger.test.js +2 -2
  109. package/test/internals/reply.test.js +317 -48
  110. package/test/internals/request.test.js +13 -7
  111. package/test/internals/server.test.js +88 -0
  112. package/test/listen.deprecated.test.js +202 -0
  113. package/test/listen.test.js +140 -145
  114. package/test/logger.test.js +82 -42
  115. package/test/maxRequestsPerSocket.test.js +8 -6
  116. package/test/middleware.test.js +2 -25
  117. package/test/nullable-validation.test.js +53 -16
  118. package/test/output-validation.test.js +1 -1
  119. package/test/plugin.test.js +47 -21
  120. package/test/pretty-print.test.js +22 -10
  121. package/test/promises.test.js +1 -1
  122. package/test/proto-poisoning.test.js +6 -6
  123. package/test/register.test.js +3 -3
  124. package/test/reply-error.test.js +126 -15
  125. package/test/reply-trailers.test.js +270 -0
  126. package/test/request-error.test.js +3 -6
  127. package/test/route-hooks.test.js +18 -18
  128. package/test/route-prefix.test.js +2 -1
  129. package/test/route.test.js +206 -22
  130. package/test/router-options.test.js +2 -2
  131. package/test/schema-examples.test.js +11 -5
  132. package/test/schema-feature.test.js +25 -20
  133. package/test/schema-serialization.test.js +9 -9
  134. package/test/schema-special-usage.test.js +5 -153
  135. package/test/schema-validation.test.js +9 -9
  136. package/test/skip-reply-send.test.js +2 -2
  137. package/test/stream.test.js +82 -23
  138. package/test/throw.test.js +8 -5
  139. package/test/trust-proxy.test.js +6 -6
  140. package/test/type-provider.test.js +20 -0
  141. package/test/types/fastify.test-d.ts +10 -18
  142. package/test/types/hooks.test-d.ts +61 -5
  143. package/test/types/import.js +2 -0
  144. package/test/types/import.ts +1 -0
  145. package/test/types/instance.test-d.ts +68 -17
  146. package/test/types/logger.test-d.ts +44 -15
  147. package/test/types/reply.test-d.ts +2 -1
  148. package/test/types/request.test-d.ts +71 -1
  149. package/test/types/route.test-d.ts +8 -2
  150. package/test/types/schema.test-d.ts +2 -39
  151. package/test/types/type-provider.test-d.ts +424 -0
  152. package/test/url-rewriting.test.js +3 -3
  153. package/test/validation-error-handling.test.js +8 -8
  154. package/test/versioned-routes.test.js +30 -18
  155. package/test/wrapThenable.test.js +7 -6
  156. package/types/content-type-parser.d.ts +17 -8
  157. package/types/hooks.d.ts +182 -85
  158. package/types/instance.d.ts +286 -118
  159. package/types/logger.d.ts +18 -104
  160. package/types/plugin.d.ts +10 -4
  161. package/types/reply.d.ts +18 -12
  162. package/types/request.d.ts +13 -8
  163. package/types/route.d.ts +62 -34
  164. package/types/schema.d.ts +1 -1
  165. package/types/type-provider.d.ts +99 -0
  166. package/types/utils.d.ts +1 -1
  167. package/lib/schema-compilers.js +0 -12
  168. package/test/emit-warning.test.js +0 -166
@@ -14,6 +14,21 @@ const {
14
14
  kReplyIsError,
15
15
  kReplySerializerDefault
16
16
  } = require('../../lib/symbols')
17
+ const fs = require('fs')
18
+ const path = require('path')
19
+ const warning = require('../../lib/warnings')
20
+
21
+ const doGet = function (url) {
22
+ return new Promise((resolve, reject) => {
23
+ sget({ method: 'GET', url, followRedirects: false }, (err, response, body) => {
24
+ if (err) {
25
+ reject(err)
26
+ } else {
27
+ resolve({ response, body })
28
+ }
29
+ })
30
+ })
31
+ }
17
32
 
18
33
  test('Once called, Reply should return an object with methods', t => {
19
34
  t.plan(13)
@@ -36,7 +51,7 @@ test('Once called, Reply should return an object with methods', t => {
36
51
  t.equal(reply.request, request)
37
52
  })
38
53
 
39
- test('reply.send will logStream error and destroy the stream', { only: true }, t => {
54
+ test('reply.send will logStream error and destroy the stream', t => {
40
55
  t.plan(1)
41
56
  let destroyCalled
42
57
  const payload = new Readable({
@@ -53,6 +68,7 @@ test('reply.send will logStream error and destroy the stream', { only: true }, t
53
68
  hasHeader: () => false,
54
69
  getHeader: () => undefined,
55
70
  writeHead: () => {},
71
+ write: () => {},
56
72
  headersSent: true
57
73
  })
58
74
 
@@ -74,6 +90,7 @@ test('reply.send throw with circular JSON', t => {
74
90
  hasHeader: () => false,
75
91
  getHeader: () => undefined,
76
92
  writeHead: () => {},
93
+ write: () => {},
77
94
  end: () => {}
78
95
  }
79
96
  const reply = new Reply(response, { context: { onSend: [] } })
@@ -91,6 +108,7 @@ test('reply.send returns itself', t => {
91
108
  hasHeader: () => false,
92
109
  getHeader: () => undefined,
93
110
  writeHead: () => {},
111
+ write: () => {},
94
112
  end: () => {}
95
113
  }
96
114
  const reply = new Reply(response, { context: { onSend: [] } })
@@ -247,9 +265,9 @@ test('within an instance', t => {
247
265
  done()
248
266
  })
249
267
 
250
- fastify.listen(0, err => {
268
+ fastify.listen({ port: 0 }, err => {
251
269
  t.error(err)
252
- fastify.server.unref()
270
+ t.teardown(fastify.close.bind(fastify))
253
271
 
254
272
  test('custom serializer should be used', t => {
255
273
  t.plan(3)
@@ -407,9 +425,9 @@ test('buffer without content type should send a application/octet-stream and raw
407
425
  reply.send(Buffer.alloc(1024))
408
426
  })
409
427
 
410
- fastify.listen(0, err => {
428
+ fastify.listen({ port: 0 }, err => {
411
429
  t.error(err)
412
- fastify.server.unref()
430
+ t.teardown(fastify.close.bind(fastify))
413
431
 
414
432
  sget({
415
433
  method: 'GET',
@@ -432,9 +450,9 @@ test('buffer with content type should not send application/octet-stream', t => {
432
450
  reply.send(Buffer.alloc(1024))
433
451
  })
434
452
 
435
- fastify.listen(0, err => {
453
+ fastify.listen({ port: 0 }, err => {
436
454
  t.error(err)
437
- fastify.server.unref()
455
+ t.teardown(fastify.close.bind(fastify))
438
456
 
439
457
  sget({
440
458
  method: 'GET',
@@ -451,8 +469,6 @@ test('stream with content type should not send application/octet-stream', t => {
451
469
  t.plan(4)
452
470
 
453
471
  const fastify = require('../..')()
454
- const fs = require('fs')
455
- const path = require('path')
456
472
 
457
473
  const streamPath = path.join(__dirname, '..', '..', 'package.json')
458
474
  const stream = fs.createReadStream(streamPath)
@@ -462,9 +478,9 @@ test('stream with content type should not send application/octet-stream', t => {
462
478
  reply.header('Content-Type', 'text/plain').send(stream)
463
479
  })
464
480
 
465
- fastify.listen(0, err => {
481
+ fastify.listen({ port: 0 }, err => {
466
482
  t.error(err)
467
- fastify.server.unref()
483
+ t.teardown(fastify.close.bind(fastify))
468
484
  sget({
469
485
  method: 'GET',
470
486
  url: 'http://localhost:' + fastify.server.address().port
@@ -476,6 +492,32 @@ test('stream with content type should not send application/octet-stream', t => {
476
492
  })
477
493
  })
478
494
 
495
+ test('stream without content type should not send application/octet-stream', t => {
496
+ t.plan(4)
497
+
498
+ const fastify = require('../..')()
499
+
500
+ const stream = fs.createReadStream(__filename)
501
+ const buf = fs.readFileSync(__filename)
502
+
503
+ fastify.get('/', function (req, reply) {
504
+ reply.send(stream)
505
+ })
506
+
507
+ fastify.listen({ port: 0 }, err => {
508
+ t.error(err)
509
+ t.teardown(fastify.close.bind(fastify))
510
+ sget({
511
+ method: 'GET',
512
+ url: 'http://localhost:' + fastify.server.address().port
513
+ }, (err, response, body) => {
514
+ t.error(err)
515
+ t.equal(response.headers['content-type'], undefined)
516
+ t.same(body, buf)
517
+ })
518
+ })
519
+ })
520
+
479
521
  test('stream using reply.raw.writeHead should return customize headers', t => {
480
522
  t.plan(6)
481
523
 
@@ -497,9 +539,9 @@ test('stream using reply.raw.writeHead should return customize headers', t => {
497
539
  reply.send(stream)
498
540
  })
499
541
 
500
- fastify.listen(0, err => {
542
+ fastify.listen({ port: 0 }, err => {
501
543
  t.error(err)
502
- fastify.server.unref()
544
+ t.teardown(fastify.close.bind(fastify))
503
545
  sget({
504
546
  method: 'GET',
505
547
  url: 'http://localhost:' + fastify.server.address().port
@@ -521,9 +563,9 @@ test('plain string without content type should send a text/plain', t => {
521
563
  reply.send('hello world!')
522
564
  })
523
565
 
524
- fastify.listen(0, err => {
566
+ fastify.listen({ port: 0 }, err => {
525
567
  t.error(err)
526
- fastify.server.unref()
568
+ t.teardown(fastify.close.bind(fastify))
527
569
 
528
570
  sget({
529
571
  method: 'GET',
@@ -545,9 +587,9 @@ test('plain string with content type should be sent unmodified', t => {
545
587
  reply.type('text/css').send('hello world!')
546
588
  })
547
589
 
548
- fastify.listen(0, err => {
590
+ fastify.listen({ port: 0 }, err => {
549
591
  t.error(err)
550
- fastify.server.unref()
592
+ t.teardown(fastify.close.bind(fastify))
551
593
 
552
594
  sget({
553
595
  method: 'GET',
@@ -572,9 +614,9 @@ test('plain string with content type and custom serializer should be serialized'
572
614
  .send('hello world!')
573
615
  })
574
616
 
575
- fastify.listen(0, err => {
617
+ fastify.listen({ port: 0 }, err => {
576
618
  t.error(err)
577
- fastify.server.unref()
619
+ t.teardown(fastify.close.bind(fastify))
578
620
 
579
621
  sget({
580
622
  method: 'GET',
@@ -596,9 +638,9 @@ test('plain string with content type application/json should NOT be serialized a
596
638
  reply.type('application/json').send('{"key": "hello world!"}')
597
639
  })
598
640
 
599
- fastify.listen(0, err => {
641
+ fastify.listen({ port: 0 }, err => {
600
642
  t.error(err)
601
- fastify.server.unref()
643
+ t.teardown(fastify.close.bind(fastify))
602
644
 
603
645
  sget({
604
646
  method: 'GET',
@@ -649,9 +691,9 @@ test('plain string with custom json content type should NOT be serialized as jso
649
691
  })
650
692
  })
651
693
 
652
- fastify.listen(0, err => {
694
+ fastify.listen({ port: 0 }, err => {
653
695
  t.error(err)
654
- fastify.server.unref()
696
+ t.teardown(fastify.close.bind(fastify))
655
697
 
656
698
  Object.keys(customSamples).forEach((path) => {
657
699
  sget({
@@ -675,9 +717,9 @@ test('non-string with content type application/json SHOULD be serialized as json
675
717
  reply.type('application/json').send({ key: 'hello world!' })
676
718
  })
677
719
 
678
- fastify.listen(0, err => {
720
+ fastify.listen({ port: 0 }, err => {
679
721
  t.error(err)
680
- fastify.server.unref()
722
+ t.teardown(fastify.close.bind(fastify))
681
723
 
682
724
  sget({
683
725
  method: 'GET',
@@ -690,6 +732,30 @@ test('non-string with content type application/json SHOULD be serialized as json
690
732
  })
691
733
  })
692
734
 
735
+ test('non-string with custom json\'s content-type SHOULD be serialized as json', t => {
736
+ t.plan(4)
737
+
738
+ const fastify = require('../..')()
739
+
740
+ fastify.get('/', function (req, reply) {
741
+ reply.type('application/json; version=2; ').send({ key: 'hello world!' })
742
+ })
743
+
744
+ fastify.listen({ port: 0 }, err => {
745
+ t.error(err)
746
+ t.teardown(fastify.close.bind(fastify))
747
+
748
+ sget({
749
+ method: 'GET',
750
+ url: 'http://localhost:' + fastify.server.address().port
751
+ }, (err, response, body) => {
752
+ t.error(err)
753
+ t.equal(response.headers['content-type'], 'application/json; version=2; charset=utf-8')
754
+ t.same(body.toString(), JSON.stringify({ key: 'hello world!' }))
755
+ })
756
+ })
757
+ })
758
+
693
759
  test('non-string with custom json content type SHOULD be serialized as json', t => {
694
760
  t.plan(16)
695
761
 
@@ -724,9 +790,9 @@ test('non-string with custom json content type SHOULD be serialized as json', t
724
790
  })
725
791
  })
726
792
 
727
- fastify.listen(0, err => {
793
+ fastify.listen({ port: 0 }, err => {
728
794
  t.error(err)
729
- fastify.server.unref()
795
+ t.teardown(fastify.close.bind(fastify))
730
796
 
731
797
  Object.keys(customSamples).forEach((path) => {
732
798
  sget({
@@ -789,9 +855,9 @@ test('undefined payload should be sent as-is', t => {
789
855
  reply.code(204).send()
790
856
  })
791
857
 
792
- fastify.listen(0, err => {
858
+ fastify.listen({ port: 0 }, err => {
793
859
  t.error(err)
794
- fastify.server.unref()
860
+ t.teardown(fastify.close.bind(fastify))
795
861
 
796
862
  sget({
797
863
  method: 'GET',
@@ -834,9 +900,9 @@ test('for HEAD method, no body should be sent but content-length should be', t =
834
900
  reply.code(200).send(null)
835
901
  })
836
902
 
837
- fastify.listen(0, err => {
903
+ fastify.listen({ port: 0 }, err => {
838
904
  t.error(err)
839
- fastify.server.unref()
905
+ t.teardown(fastify.close.bind(fastify))
840
906
 
841
907
  sget({
842
908
  method: 'HEAD',
@@ -881,10 +947,10 @@ test('reply.send(new NotFound()) should not invoke the 404 handler', t => {
881
947
  done()
882
948
  }, { prefix: '/prefixed' })
883
949
 
884
- fastify.listen(0, err => {
950
+ fastify.listen({ port: 0 }, err => {
885
951
  t.error(err)
886
952
 
887
- fastify.server.unref()
953
+ t.teardown(fastify.close.bind(fastify))
888
954
 
889
955
  sget({
890
956
  method: 'GET',
@@ -928,9 +994,9 @@ test('reply can set multiple instances of same header', t => {
928
994
  .send({})
929
995
  })
930
996
 
931
- fastify.listen(0, err => {
997
+ fastify.listen({ port: 0 }, err => {
932
998
  t.error(err)
933
- fastify.server.unref()
999
+ t.teardown(fastify.close.bind(fastify))
934
1000
 
935
1001
  sget({
936
1002
  method: 'GET',
@@ -955,9 +1021,9 @@ test('reply.hasHeader returns correct values', t => {
955
1021
  reply.send()
956
1022
  })
957
1023
 
958
- fastify.listen(0, err => {
1024
+ fastify.listen({ port: 0 }, err => {
959
1025
  t.error(err)
960
- fastify.server.unref()
1026
+ t.teardown(fastify.close.bind(fastify))
961
1027
  sget({
962
1028
  method: 'GET',
963
1029
  url: 'http://localhost:' + fastify.server.address().port + '/headers'
@@ -984,9 +1050,9 @@ test('reply.getHeader returns correct values', t => {
984
1050
  reply.send()
985
1051
  })
986
1052
 
987
- fastify.listen(0, err => {
1053
+ fastify.listen({ port: 0 }, err => {
988
1054
  t.error(err)
989
- fastify.server.unref()
1055
+ t.teardown(fastify.close.bind(fastify))
990
1056
  sget({
991
1057
  method: 'GET',
992
1058
  url: 'http://localhost:' + fastify.server.address().port + '/headers'
@@ -1054,9 +1120,9 @@ test('reply.removeHeader can remove the value', t => {
1054
1120
  reply.send()
1055
1121
  })
1056
1122
 
1057
- fastify.listen(0, err => {
1123
+ fastify.listen({ port: 0 }, err => {
1058
1124
  t.error(err)
1059
- fastify.server.unref()
1125
+ t.teardown(fastify.close.bind(fastify))
1060
1126
  sget({
1061
1127
  method: 'GET',
1062
1128
  url: 'http://localhost:' + fastify.server.address().port + '/headers'
@@ -1081,9 +1147,9 @@ test('reply.header can reset the value', t => {
1081
1147
  reply.send()
1082
1148
  })
1083
1149
 
1084
- fastify.listen(0, err => {
1150
+ fastify.listen({ port: 0 }, err => {
1085
1151
  t.error(err)
1086
- fastify.server.unref()
1152
+ t.teardown(fastify.close.bind(fastify))
1087
1153
  sget({
1088
1154
  method: 'GET',
1089
1155
  url: 'http://localhost:' + fastify.server.address().port + '/headers'
@@ -1110,9 +1176,9 @@ test('reply.hasHeader computes raw and fastify headers', t => {
1110
1176
  reply.send()
1111
1177
  })
1112
1178
 
1113
- fastify.listen(0, err => {
1179
+ fastify.listen({ port: 0 }, err => {
1114
1180
  t.error(err)
1115
- fastify.server.unref()
1181
+ t.teardown(fastify.close.bind(fastify))
1116
1182
  sget({
1117
1183
  method: 'GET',
1118
1184
  url: 'http://localhost:' + fastify.server.address().port + '/headers'
@@ -1279,9 +1345,9 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t =
1279
1345
  .send({})
1280
1346
  })
1281
1347
 
1282
- fastify.listen(0, err => {
1348
+ fastify.listen({ port: 0 }, err => {
1283
1349
  t.error(err)
1284
- fastify.server.unref()
1350
+ t.teardown(fastify.close.bind(fastify))
1285
1351
 
1286
1352
  sget({
1287
1353
  method: 'GET',
@@ -1300,6 +1366,34 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t =
1300
1366
  })
1301
1367
  })
1302
1368
 
1369
+ test('should emit deprecation warning when trying to modify the reply.sent property', t => {
1370
+ t.plan(4)
1371
+ const fastify = require('../..')()
1372
+
1373
+ const deprecationCode = 'FSTDEP010'
1374
+ warning.emitted.delete(deprecationCode)
1375
+
1376
+ process.removeAllListeners('warning')
1377
+ process.on('warning', onWarning)
1378
+ function onWarning (warning) {
1379
+ t.equal(warning.name, 'FastifyDeprecation')
1380
+ t.equal(warning.code, deprecationCode)
1381
+ }
1382
+
1383
+ fastify.get('/', (req, reply) => {
1384
+ reply.sent = true
1385
+
1386
+ reply.raw.end()
1387
+ })
1388
+
1389
+ fastify.inject('/', (err, res) => {
1390
+ t.error(err)
1391
+ t.pass()
1392
+
1393
+ process.removeListener('warning', onWarning)
1394
+ })
1395
+ })
1396
+
1303
1397
  test('should throw error when passing falsy value to reply.sent', t => {
1304
1398
  t.plan(4)
1305
1399
  const fastify = require('../..')()
@@ -1328,6 +1422,7 @@ test('should throw error when attempting to set reply.sent more than once', t =>
1328
1422
  reply.sent = true
1329
1423
  try {
1330
1424
  reply.sent = true
1425
+ t.fail('must throw')
1331
1426
  } catch (err) {
1332
1427
  t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT')
1333
1428
  t.equal(err.message, 'Reply was already sent.')
@@ -1341,6 +1436,23 @@ test('should throw error when attempting to set reply.sent more than once', t =>
1341
1436
  })
1342
1437
  })
1343
1438
 
1439
+ test('should not throw error when attempting to set reply.sent if the underlining request was sent', t => {
1440
+ t.plan(3)
1441
+ const fastify = require('../..')()
1442
+
1443
+ fastify.get('/', function (req, reply) {
1444
+ reply.raw.end()
1445
+ t.doesNotThrow(() => {
1446
+ reply.sent = true
1447
+ })
1448
+ })
1449
+
1450
+ fastify.inject('/', (err, res) => {
1451
+ t.error(err)
1452
+ t.pass()
1453
+ })
1454
+ })
1455
+
1344
1456
  test('reply.getResponseTime() should return 0 before the timer is initialised on the reply by setting up response listeners', t => {
1345
1457
  t.plan(1)
1346
1458
  const response = { statusCode: 200 }
@@ -1606,7 +1718,7 @@ test('cannot set the replySerializer when the server is running', t => {
1606
1718
  const fastify = require('../..')()
1607
1719
  t.teardown(fastify.close.bind(fastify))
1608
1720
 
1609
- fastify.listen(err => {
1721
+ fastify.listen({ port: 0 }, err => {
1610
1722
  t.error(err)
1611
1723
  try {
1612
1724
  fastify.setReplySerializer(() => {})
@@ -1726,3 +1838,160 @@ test('reply.then', t => {
1726
1838
  response.destroy(_err)
1727
1839
  })
1728
1840
  })
1841
+
1842
+ test('reply.sent should read from response.writableEnded if it is defined', t => {
1843
+ t.plan(1)
1844
+
1845
+ const reply = new Reply({ writableEnded: true }, {}, {})
1846
+
1847
+ t.equal(reply.sent, true)
1848
+ })
1849
+
1850
+ test('redirect to an invalid URL should not crash the server', async t => {
1851
+ const fastify = require('../..')()
1852
+ fastify.route({
1853
+ method: 'GET',
1854
+ url: '/redirect',
1855
+ handler: (req, reply) => {
1856
+ reply.log.warn = function mockWarn (obj, message) {
1857
+ t.equal(message, 'Invalid character in header content ["location"]')
1858
+ }
1859
+
1860
+ switch (req.query.useCase) {
1861
+ case '1':
1862
+ reply.redirect('/?key=a’b')
1863
+ break
1864
+
1865
+ case '2':
1866
+ reply.redirect(encodeURI('/?key=a’b'))
1867
+ break
1868
+
1869
+ default:
1870
+ reply.redirect('/?key=ab')
1871
+ break
1872
+ }
1873
+ }
1874
+ })
1875
+
1876
+ await fastify.listen({ port: 0 })
1877
+
1878
+ {
1879
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=1`)
1880
+ t.equal(response.statusCode, 500)
1881
+ t.same(JSON.parse(body), {
1882
+ statusCode: 500,
1883
+ code: 'ERR_INVALID_CHAR',
1884
+ error: 'Internal Server Error',
1885
+ message: 'Invalid character in header content ["location"]'
1886
+ })
1887
+ }
1888
+ {
1889
+ const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=2`)
1890
+ t.equal(response.statusCode, 302)
1891
+ t.equal(response.headers.location, '/?key=a%E2%80%99b')
1892
+ }
1893
+
1894
+ {
1895
+ const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=3`)
1896
+ t.equal(response.statusCode, 302)
1897
+ t.equal(response.headers.location, '/?key=ab')
1898
+ }
1899
+
1900
+ await fastify.close()
1901
+ })
1902
+
1903
+ test('invalid response headers should not crash the server', async t => {
1904
+ const fastify = require('../..')()
1905
+ fastify.route({
1906
+ method: 'GET',
1907
+ url: '/bad-headers',
1908
+ handler: (req, reply) => {
1909
+ reply.log.warn = function mockWarn (obj, message) {
1910
+ t.equal(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged')
1911
+ }
1912
+
1913
+ reply.header('foo', '$')
1914
+ reply.header('smile-encoded', '\uD83D\uDE00')
1915
+ reply.header('smile', '😄')
1916
+ reply.header('bar', 'ƒ∂å')
1917
+
1918
+ reply.send({})
1919
+ }
1920
+ })
1921
+
1922
+ await fastify.listen({ port: 0 })
1923
+
1924
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1925
+ t.equal(response.statusCode, 500)
1926
+ t.same(JSON.parse(body), {
1927
+ statusCode: 500,
1928
+ code: 'ERR_INVALID_CHAR',
1929
+ error: 'Internal Server Error',
1930
+ message: 'Invalid character in header content ["smile-encoded"]'
1931
+ })
1932
+
1933
+ await fastify.close()
1934
+ })
1935
+
1936
+ test('invalid response headers when sending back an error', async t => {
1937
+ const fastify = require('../..')()
1938
+ fastify.route({
1939
+ method: 'GET',
1940
+ url: '/bad-headers',
1941
+ handler: (req, reply) => {
1942
+ reply.log.warn = function mockWarn (obj, message) {
1943
+ t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
1944
+ }
1945
+
1946
+ reply.header('smile', '😄')
1947
+ reply.send(new Error('user land error'))
1948
+ }
1949
+ })
1950
+
1951
+ await fastify.listen({ port: 0 })
1952
+
1953
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1954
+ t.equal(response.statusCode, 500)
1955
+ t.same(JSON.parse(body), {
1956
+ statusCode: 500,
1957
+ code: 'ERR_INVALID_CHAR',
1958
+ error: 'Internal Server Error',
1959
+ message: 'Invalid character in header content ["smile"]'
1960
+ })
1961
+
1962
+ await fastify.close()
1963
+ })
1964
+
1965
+ test('invalid response headers and custom error handler', async t => {
1966
+ const fastify = require('../..')()
1967
+ fastify.route({
1968
+ method: 'GET',
1969
+ url: '/bad-headers',
1970
+ handler: (req, reply) => {
1971
+ reply.log.warn = function mockWarn (obj, message) {
1972
+ t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
1973
+ }
1974
+
1975
+ reply.header('smile', '😄')
1976
+ reply.send(new Error('user land error'))
1977
+ }
1978
+ })
1979
+
1980
+ fastify.setErrorHandler(function (error, request, reply) {
1981
+ t.equal(error.message, 'user land error', 'custom error handler receives the error')
1982
+ reply.status(500).send({ ops: true })
1983
+ })
1984
+
1985
+ await fastify.listen({ port: 0 })
1986
+
1987
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1988
+ t.equal(response.statusCode, 500)
1989
+ t.same(JSON.parse(body), {
1990
+ statusCode: 500,
1991
+ code: 'ERR_INVALID_CHAR',
1992
+ error: 'Internal Server Error',
1993
+ message: 'Invalid character in header content ["smile"]'
1994
+ })
1995
+
1996
+ await fastify.close()
1997
+ })
@@ -4,8 +4,9 @@ const { test } = require('tap')
4
4
 
5
5
  const Request = require('../../lib/request')
6
6
 
7
+ process.removeAllListeners('warning')
8
+
7
9
  test('Regular request', t => {
8
- t.plan(15)
9
10
  const headers = {
10
11
  host: 'hostname'
11
12
  }
@@ -15,22 +16,27 @@ test('Regular request', t => {
15
16
  socket: { remoteAddress: 'ip' },
16
17
  headers
17
18
  }
19
+ req.connection = req.socket
18
20
  const request = new Request('id', 'params', req, 'query', 'log')
19
21
  t.type(request, Request)
20
22
  t.equal(request.id, 'id')
21
23
  t.equal(request.params, 'params')
22
- t.same(request.raw, req)
24
+ t.equal(request.raw, req)
23
25
  t.equal(request.query, 'query')
24
26
  t.equal(request.headers, headers)
25
27
  t.equal(request.log, 'log')
26
28
  t.equal(request.ip, 'ip')
27
29
  t.equal(request.ips, undefined)
28
30
  t.equal(request.hostname, 'hostname')
29
- t.equal(request.body, null)
31
+ t.equal(request.body, undefined)
30
32
  t.equal(request.method, 'GET')
31
33
  t.equal(request.url, '/')
34
+ t.equal(request.socket, req.socket)
32
35
  t.equal(request.protocol, 'http')
33
- t.same(request.socket, req.socket)
36
+
37
+ // This will be removed, it's deprecated
38
+ t.equal(request.connection, req.connection)
39
+ t.end()
34
40
  })
35
41
 
36
42
  test('Regular request - hostname from authority', t => {
@@ -92,11 +98,11 @@ test('Request with trust proxy', t => {
92
98
  t.equal(request.ip, '2.2.2.2')
93
99
  t.same(request.ips, ['ip', '1.1.1.1', '2.2.2.2'])
94
100
  t.equal(request.hostname, 'example.com')
95
- t.equal(request.body, null)
101
+ t.equal(request.body, undefined)
96
102
  t.equal(request.method, 'GET')
97
103
  t.equal(request.url, '/')
104
+ t.equal(request.socket, req.socket)
98
105
  t.equal(request.protocol, 'http')
99
- t.same(request.socket, req.socket)
100
106
  })
101
107
 
102
108
  test('Request with trust proxy, encrypted', t => {
@@ -236,7 +242,7 @@ test('Request with undefined socket', t => {
236
242
  t.equal(request.ip, undefined)
237
243
  t.equal(request.ips, undefined)
238
244
  t.equal(request.hostname, 'hostname')
239
- t.equal(request.body, null)
245
+ t.same(request.body, null)
240
246
  t.equal(request.method, 'GET')
241
247
  t.equal(request.url, '/')
242
248
  t.equal(request.protocol, undefined)