fastify 4.0.0-rc.3 → 4.0.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 (48) hide show
  1. package/README.md +2 -1
  2. package/build/build-validation.js +14 -2
  3. package/docs/Guides/Ecosystem.md +23 -11
  4. package/docs/Guides/Index.md +1 -1
  5. package/docs/Guides/Migration-Guide-V3.md +1 -1
  6. package/docs/Guides/Plugins-Guide.md +3 -3
  7. package/docs/Guides/Prototype-Poisoning.md +1 -1
  8. package/docs/Guides/Recommendations.md +2 -2
  9. package/docs/Guides/Serverless.md +5 -5
  10. package/docs/Guides/Testing.md +3 -1
  11. package/docs/Guides/Write-Plugin.md +3 -3
  12. package/docs/Migration-Guide-V4.md +1 -1
  13. package/docs/Reference/Logging.md +10 -5
  14. package/docs/Reference/Middleware.md +3 -3
  15. package/docs/Reference/Routes.md +2 -2
  16. package/docs/Reference/Server.md +59 -10
  17. package/docs/Reference/TypeScript.md +2 -2
  18. package/docs/Reference/Validation-and-Serialization.md +11 -1
  19. package/fastify.d.ts +2 -1
  20. package/fastify.js +38 -14
  21. package/lib/configValidator.js +456 -332
  22. package/lib/errors.js +4 -0
  23. package/lib/request.js +2 -2
  24. package/lib/route.js +5 -2
  25. package/lib/schemas.js +3 -0
  26. package/lib/validation.js +8 -2
  27. package/package.json +34 -34
  28. package/test/close-pipelining.test.js +4 -2
  29. package/test/close.test.js +93 -3
  30. package/test/custom-http-server.test.js +22 -0
  31. package/test/decorator.test.js +146 -0
  32. package/test/internals/initialConfig.test.js +5 -3
  33. package/test/output-validation.test.js +6 -2
  34. package/test/plugin.test.js +177 -14
  35. package/test/route-prefix.test.js +250 -0
  36. package/test/router-options.test.js +61 -0
  37. package/test/schema-feature.test.js +24 -0
  38. package/test/schema-serialization.test.js +57 -0
  39. package/test/types/fastify.test-d.ts +1 -0
  40. package/test/types/instance.test-d.ts +1 -0
  41. package/test/types/logger.test-d.ts +13 -1
  42. package/test/types/route.test-d.ts +14 -0
  43. package/test/types/type-provider.test-d.ts +6 -0
  44. package/types/instance.d.ts +1 -0
  45. package/types/logger.d.ts +3 -1
  46. package/types/route.d.ts +1 -1
  47. package/types/schema.d.ts +1 -1
  48. package/types/type-provider.d.ts +3 -4
@@ -184,11 +184,11 @@ test('fastify.register with fastify-plugin should provide access to external fas
184
184
  })
185
185
  })
186
186
 
187
- test('fastify.register with fastify-plugin registers root level plugins', t => {
187
+ test('fastify.register with fastify-plugin registers fastify level plugins', t => {
188
188
  t.plan(15)
189
189
  const fastify = Fastify()
190
190
 
191
- function rootPlugin (instance, opts, done) {
191
+ function fastifyPlugin (instance, opts, done) {
192
192
  instance.decorate('test', 'first')
193
193
  t.ok(instance.test)
194
194
  done()
@@ -199,7 +199,7 @@ test('fastify.register with fastify-plugin registers root level plugins', t => {
199
199
  done()
200
200
  }
201
201
 
202
- fastify.register(fp(rootPlugin))
202
+ fastify.register(fp(fastifyPlugin))
203
203
 
204
204
  fastify.register((instance, opts, done) => {
205
205
  t.ok(instance.test)
@@ -354,27 +354,29 @@ test('check dependencies - should throw', t => {
354
354
  })
355
355
 
356
356
  test('set the plugin name based on the plugin displayName symbol', t => {
357
- t.plan(5)
357
+ t.plan(6)
358
358
  const fastify = Fastify()
359
359
 
360
360
  fastify.register(fp((fastify, opts, done) => {
361
- t.equal(fastify.pluginName, 'plugin-A')
361
+ t.equal(fastify.pluginName, 'fastify -> plugin-A')
362
362
  fastify.register(fp((fastify, opts, done) => {
363
- t.equal(fastify.pluginName, 'plugin-A -> plugin-AB')
363
+ t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB')
364
364
  done()
365
365
  }, { name: 'plugin-AB' }))
366
366
  fastify.register(fp((fastify, opts, done) => {
367
- t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC')
367
+ t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC')
368
368
  done()
369
369
  }, { name: 'plugin-AC' }))
370
370
  done()
371
371
  }, { name: 'plugin-A' }))
372
372
 
373
373
  fastify.register(fp((fastify, opts, done) => {
374
- t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC -> plugin-B')
374
+ t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC -> plugin-B')
375
375
  done()
376
376
  }, { name: 'plugin-B' }))
377
377
 
378
+ t.equal(fastify.pluginName, 'fastify')
379
+
378
380
  fastify.listen({ port: 0 }, err => {
379
381
  t.error(err)
380
382
  fastify.close()
@@ -382,29 +384,31 @@ test('set the plugin name based on the plugin displayName symbol', t => {
382
384
  })
383
385
 
384
386
  test('plugin name will change when using no encapsulation', t => {
385
- t.plan(5)
387
+ t.plan(6)
386
388
  const fastify = Fastify()
387
389
 
388
390
  fastify.register(fp((fastify, opts, done) => {
389
391
  // store it in a different variable will hold the correct name
390
392
  const pluginName = fastify.pluginName
391
393
  fastify.register(fp((fastify, opts, done) => {
392
- t.equal(fastify.pluginName, 'plugin-A -> plugin-AB')
394
+ t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB')
393
395
  done()
394
396
  }, { name: 'plugin-AB' }))
395
397
  fastify.register(fp((fastify, opts, done) => {
396
- t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC')
398
+ t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC')
397
399
  done()
398
400
  }, { name: 'plugin-AC' }))
399
401
  setImmediate(() => {
400
402
  // normally we would expect the name plugin-A
401
403
  // but we operate on the same instance in each plugin
402
- t.equal(fastify.pluginName, 'plugin-A -> plugin-AB -> plugin-AC')
403
- t.equal(pluginName, 'plugin-A')
404
+ t.equal(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC')
405
+ t.equal(pluginName, 'fastify -> plugin-A')
404
406
  })
405
407
  done()
406
408
  }, { name: 'plugin-A' }))
407
409
 
410
+ t.equal(fastify.pluginName, 'fastify')
411
+
408
412
  fastify.listen({ port: 0 }, err => {
409
413
  t.error(err)
410
414
  fastify.close()
@@ -415,7 +419,7 @@ test('plugin name is undefined when accessing in no plugin context', t => {
415
419
  t.plan(2)
416
420
  const fastify = Fastify()
417
421
 
418
- t.equal(fastify.pluginName, undefined)
422
+ t.equal(fastify.pluginName, 'fastify')
419
423
 
420
424
  fastify.listen({ port: 0 }, err => {
421
425
  t.error(err)
@@ -1052,3 +1056,162 @@ test('plugin metadata - release candidate', t => {
1052
1056
  done()
1053
1057
  }
1054
1058
  })
1059
+
1060
+ test('hasPlugin method exists as a function', t => {
1061
+ t.plan(1)
1062
+
1063
+ const fastify = Fastify()
1064
+ t.equal(typeof fastify.hasPlugin, 'function')
1065
+ })
1066
+
1067
+ test('hasPlugin returns true if the specified plugin has been registered', async t => {
1068
+ t.plan(4)
1069
+
1070
+ const fastify = Fastify()
1071
+
1072
+ function pluginA (fastify, opts, done) {
1073
+ t.ok(fastify.hasPlugin('plugin-A'))
1074
+ done()
1075
+ }
1076
+ pluginA[Symbol.for('fastify.display-name')] = 'plugin-A'
1077
+ fastify.register(pluginA)
1078
+
1079
+ fastify.register(function pluginB (fastify, opts, done) {
1080
+ t.ok(fastify.hasPlugin('pluginB'))
1081
+ done()
1082
+ })
1083
+
1084
+ fastify.register(function (fastify, opts, done) {
1085
+ // one line
1086
+ t.ok(fastify.hasPlugin('function (fastify, opts, done) { -- // one line'))
1087
+ done()
1088
+ })
1089
+
1090
+ await fastify.ready()
1091
+
1092
+ t.ok(fastify.hasPlugin('fastify'))
1093
+ })
1094
+
1095
+ test('hasPlugin returns false if the specified plugin has not been registered', t => {
1096
+ t.plan(1)
1097
+
1098
+ const fastify = Fastify()
1099
+ t.notOk(fastify.hasPlugin('pluginFoo'))
1100
+ })
1101
+
1102
+ test('hasPlugin returns false when using encapsulation', async t => {
1103
+ t.plan(25)
1104
+
1105
+ const fastify = Fastify()
1106
+
1107
+ fastify.register(function pluginA (fastify, opts, done) {
1108
+ t.ok(fastify.hasPlugin('pluginA'))
1109
+ t.notOk(fastify.hasPlugin('pluginAA'))
1110
+ t.notOk(fastify.hasPlugin('pluginAAA'))
1111
+ t.notOk(fastify.hasPlugin('pluginAB'))
1112
+ t.notOk(fastify.hasPlugin('pluginB'))
1113
+
1114
+ fastify.register(function pluginAA (fastify, opts, done) {
1115
+ t.notOk(fastify.hasPlugin('pluginA'))
1116
+ t.ok(fastify.hasPlugin('pluginAA'))
1117
+ t.notOk(fastify.hasPlugin('pluginAAA'))
1118
+ t.notOk(fastify.hasPlugin('pluginAB'))
1119
+ t.notOk(fastify.hasPlugin('pluginB'))
1120
+
1121
+ fastify.register(function pluginAAA (fastify, opts, done) {
1122
+ t.notOk(fastify.hasPlugin('pluginA'))
1123
+ t.notOk(fastify.hasPlugin('pluginAA'))
1124
+ t.ok(fastify.hasPlugin('pluginAAA'))
1125
+ t.notOk(fastify.hasPlugin('pluginAB'))
1126
+ t.notOk(fastify.hasPlugin('pluginB'))
1127
+
1128
+ done()
1129
+ })
1130
+
1131
+ done()
1132
+ })
1133
+
1134
+ fastify.register(function pluginAB (fastify, opts, done) {
1135
+ t.notOk(fastify.hasPlugin('pluginA'))
1136
+ t.notOk(fastify.hasPlugin('pluginAA'))
1137
+ t.notOk(fastify.hasPlugin('pluginAAA'))
1138
+ t.ok(fastify.hasPlugin('pluginAB'))
1139
+ t.notOk(fastify.hasPlugin('pluginB'))
1140
+
1141
+ done()
1142
+ })
1143
+
1144
+ done()
1145
+ })
1146
+
1147
+ fastify.register(function pluginB (fastify, opts, done) {
1148
+ t.notOk(fastify.hasPlugin('pluginA'))
1149
+ t.notOk(fastify.hasPlugin('pluginAA'))
1150
+ t.notOk(fastify.hasPlugin('pluginAAA'))
1151
+ t.notOk(fastify.hasPlugin('pluginAB'))
1152
+ t.ok(fastify.hasPlugin('pluginB'))
1153
+
1154
+ done()
1155
+ })
1156
+
1157
+ await fastify.ready()
1158
+ })
1159
+
1160
+ test('hasPlugin returns true when using no encapsulation', async t => {
1161
+ t.plan(26)
1162
+
1163
+ const fastify = Fastify()
1164
+
1165
+ fastify.register(fp((fastify, opts, done) => {
1166
+ t.equal(fastify.pluginName, 'fastify -> plugin-AA')
1167
+ t.ok(fastify.hasPlugin('plugin-AA'))
1168
+ t.notOk(fastify.hasPlugin('plugin-A'))
1169
+ t.notOk(fastify.hasPlugin('plugin-AAA'))
1170
+ t.notOk(fastify.hasPlugin('plugin-AB'))
1171
+ t.notOk(fastify.hasPlugin('plugin-B'))
1172
+
1173
+ fastify.register(fp((fastify, opts, done) => {
1174
+ t.ok(fastify.hasPlugin('plugin-AA'))
1175
+ t.ok(fastify.hasPlugin('plugin-A'))
1176
+ t.notOk(fastify.hasPlugin('plugin-AAA'))
1177
+ t.notOk(fastify.hasPlugin('plugin-AB'))
1178
+ t.notOk(fastify.hasPlugin('plugin-B'))
1179
+
1180
+ fastify.register(fp((fastify, opts, done) => {
1181
+ t.ok(fastify.hasPlugin('plugin-AA'))
1182
+ t.ok(fastify.hasPlugin('plugin-A'))
1183
+ t.ok(fastify.hasPlugin('plugin-AAA'))
1184
+ t.notOk(fastify.hasPlugin('plugin-AB'))
1185
+ t.notOk(fastify.hasPlugin('plugin-B'))
1186
+
1187
+ done()
1188
+ }, { name: 'plugin-AAA' }))
1189
+
1190
+ done()
1191
+ }, { name: 'plugin-A' }))
1192
+
1193
+ fastify.register(fp((fastify, opts, done) => {
1194
+ t.ok(fastify.hasPlugin('plugin-AA'))
1195
+ t.ok(fastify.hasPlugin('plugin-A'))
1196
+ t.ok(fastify.hasPlugin('plugin-AAA'))
1197
+ t.ok(fastify.hasPlugin('plugin-AB'))
1198
+ t.notOk(fastify.hasPlugin('plugin-B'))
1199
+
1200
+ done()
1201
+ }, { name: 'plugin-AB' }))
1202
+
1203
+ done()
1204
+ }, { name: 'plugin-AA' }))
1205
+
1206
+ fastify.register(fp((fastify, opts, done) => {
1207
+ t.ok(fastify.hasPlugin('plugin-AA'))
1208
+ t.ok(fastify.hasPlugin('plugin-A'))
1209
+ t.ok(fastify.hasPlugin('plugin-AAA'))
1210
+ t.ok(fastify.hasPlugin('plugin-AB'))
1211
+ t.ok(fastify.hasPlugin('plugin-B'))
1212
+
1213
+ done()
1214
+ }, { name: 'plugin-B' }))
1215
+
1216
+ await fastify.ready()
1217
+ })
@@ -423,6 +423,37 @@ test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: tr
423
423
  })
424
424
  })
425
425
 
426
+ test('matches both /prefix and /prefix/ with a / route - ignoreDuplicateSlashes: true', t => {
427
+ t.plan(4)
428
+ const fastify = Fastify({
429
+ ignoreDuplicateSlashes: true
430
+ })
431
+
432
+ fastify.register(function (fastify, opts, done) {
433
+ fastify.get('/', (req, reply) => {
434
+ reply.send({ hello: 'world' })
435
+ })
436
+
437
+ done()
438
+ }, { prefix: '/prefix' })
439
+
440
+ fastify.inject({
441
+ method: 'GET',
442
+ url: '/prefix'
443
+ }, (err, res) => {
444
+ t.error(err)
445
+ t.same(JSON.parse(res.payload), { hello: 'world' })
446
+ })
447
+
448
+ fastify.inject({
449
+ method: 'GET',
450
+ url: '/prefix/'
451
+ }, (err, res) => {
452
+ t.error(err)
453
+ t.same(JSON.parse(res.payload), { hello: 'world' })
454
+ })
455
+ })
456
+
426
457
  test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: false', t => {
427
458
  t.plan(4)
428
459
  const fastify = Fastify({
@@ -459,6 +490,106 @@ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "
459
490
  })
460
491
  })
461
492
 
493
+ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: false', t => {
494
+ t.plan(4)
495
+ const fastify = Fastify({
496
+ ignoreDuplicateSlashes: false
497
+ })
498
+
499
+ fastify.register(function (fastify, opts, done) {
500
+ fastify.route({
501
+ method: 'GET',
502
+ url: '/',
503
+ prefixTrailingSlash: 'both',
504
+ handler: (req, reply) => {
505
+ reply.send({ hello: 'world' })
506
+ }
507
+ })
508
+
509
+ done()
510
+ }, { prefix: '/prefix' })
511
+
512
+ fastify.inject({
513
+ method: 'GET',
514
+ url: '/prefix'
515
+ }, (err, res) => {
516
+ t.error(err)
517
+ t.same(JSON.parse(res.payload), { hello: 'world' })
518
+ })
519
+
520
+ fastify.inject({
521
+ method: 'GET',
522
+ url: '/prefix/'
523
+ }, (err, res) => {
524
+ t.error(err)
525
+ t.same(JSON.parse(res.payload), { hello: 'world' })
526
+ })
527
+ })
528
+
529
+ test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', t => {
530
+ t.plan(4)
531
+ const fastify = Fastify({
532
+ ignoreTrailingSlash: true,
533
+ ignoreDuplicateSlashes: true
534
+ })
535
+
536
+ fastify.register(function (fastify, opts, done) {
537
+ fastify.get('/', (req, reply) => {
538
+ reply.send({ hello: 'world' })
539
+ })
540
+
541
+ done()
542
+ }, { prefix: '/prefix' })
543
+
544
+ fastify.inject({
545
+ method: 'GET',
546
+ url: '/prefix'
547
+ }, (err, res) => {
548
+ t.error(err)
549
+ t.same(JSON.parse(res.payload), { hello: 'world' })
550
+ })
551
+
552
+ fastify.inject({
553
+ method: 'GET',
554
+ url: '/prefix/'
555
+ }, (err, res) => {
556
+ t.error(err)
557
+ t.same(JSON.parse(res.payload), { hello: 'world' })
558
+ })
559
+ })
560
+
561
+ test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: false', t => {
562
+ t.plan(4)
563
+ const fastify = Fastify({
564
+ ignoreTrailingSlash: true,
565
+ ignoreDuplicateSlashes: false
566
+ })
567
+
568
+ fastify.register(function (fastify, opts, done) {
569
+ fastify.get('/', (req, reply) => {
570
+ reply.send({ hello: 'world' })
571
+ })
572
+
573
+ done()
574
+ }, { prefix: '/prefix' })
575
+
576
+ fastify.inject({
577
+ method: 'GET',
578
+ url: '/prefix'
579
+ }, (err, res) => {
580
+ t.error(err)
581
+ t.same(JSON.parse(res.payload), { hello: 'world' })
582
+ })
583
+
584
+ fastify.inject({
585
+ method: 'GET',
586
+ url: '/prefix/'
587
+ }, (err, res) => {
588
+ t.error(err)
589
+ t.same(JSON.parse(res.payload), { hello: 'world' })
590
+ })
591
+ })
592
+
462
593
  test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: "both" (default), ignoreTrailingSlash: true', t => {
463
594
  t.plan(2)
464
595
  const fastify = Fastify({
@@ -490,6 +621,89 @@ test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: "
490
621
  })
491
622
  })
492
623
 
624
+ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', t => {
625
+ t.plan(2)
626
+ const fastify = Fastify({
627
+ ignoreDuplicateSlashes: true
628
+ })
629
+
630
+ fastify.register(function (fastify, opts, done) {
631
+ fastify.route({
632
+ method: 'GET',
633
+ url: '/',
634
+ handler: (req, reply) => {
635
+ reply.send({ hello: 'world' })
636
+ }
637
+ })
638
+
639
+ done()
640
+ }, { prefix: '/prefix/' })
641
+
642
+ fastify.inject({
643
+ method: 'GET',
644
+ url: '/prefix//'
645
+ }, (err, res) => {
646
+ t.error(err)
647
+ t.same(JSON.parse(res.payload), { hello: 'world' })
648
+ })
649
+ })
650
+
651
+ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', t => {
652
+ t.plan(2)
653
+ const fastify = Fastify({
654
+ ignoreTrailingSlash: true,
655
+ ignoreDuplicateSlashes: true
656
+ })
657
+
658
+ fastify.register(function (fastify, opts, done) {
659
+ fastify.route({
660
+ method: 'GET',
661
+ url: '/',
662
+ handler: (req, reply) => {
663
+ reply.send({ hello: 'world' })
664
+ }
665
+ })
666
+
667
+ done()
668
+ }, { prefix: '/prefix/' })
669
+
670
+ fastify.inject({
671
+ method: 'GET',
672
+ url: '/prefix//'
673
+ }, (err, res) => {
674
+ t.error(err)
675
+ t.same(JSON.parse(res.payload), { hello: 'world' })
676
+ })
677
+ })
678
+
679
+ test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', t => {
680
+ t.plan(2)
681
+ const fastify = Fastify({
682
+ ignoreTrailingSlash: true,
683
+ ignoreDuplicateSlashes: true
684
+ })
685
+
686
+ fastify.register(function (fastify, opts, done) {
687
+ fastify.route({
688
+ method: 'GET',
689
+ url: '/',
690
+ handler: (req, reply) => {
691
+ reply.send({ hello: 'world' })
692
+ }
693
+ })
694
+
695
+ done()
696
+ }, { prefix: '/prefix/' })
697
+
698
+ fastify.inject({
699
+ method: 'GET',
700
+ url: '/prefix//'
701
+ }, (err, res) => {
702
+ t.error(err)
703
+ t.same(JSON.parse(res.payload), { hello: 'world' })
704
+ })
705
+ })
706
+
493
707
  test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreTrailingSlash: false', t => {
494
708
  t.plan(4)
495
709
  const fastify = Fastify({
@@ -526,6 +740,42 @@ test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ig
526
740
  })
527
741
  })
528
742
 
743
+ test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreDuplicateSlashes: false', t => {
744
+ t.plan(4)
745
+ const fastify = Fastify({
746
+ ignoreDuplicateSlashes: false
747
+ })
748
+
749
+ fastify.register(function (fastify, opts, done) {
750
+ fastify.route({
751
+ method: 'GET',
752
+ url: '/',
753
+ prefixTrailingSlash: 'no-slash',
754
+ handler: (req, reply) => {
755
+ reply.send({ hello: 'world' })
756
+ }
757
+ })
758
+
759
+ done()
760
+ }, { prefix: '/prefix' })
761
+
762
+ fastify.inject({
763
+ method: 'GET',
764
+ url: '/prefix'
765
+ }, (err, res) => {
766
+ t.error(err)
767
+ t.same(JSON.parse(res.payload), { hello: 'world' })
768
+ })
769
+
770
+ fastify.inject({
771
+ method: 'GET',
772
+ url: '/prefix/'
773
+ }, (err, res) => {
774
+ t.error(err)
775
+ t.equal(JSON.parse(res.payload).statusCode, 404)
776
+ })
777
+ })
778
+
529
779
  test('matches only /prefix/ with a / route - prefixTrailingSlash: "slash", ignoreTrailingSlash: false', t => {
530
780
  t.plan(4)
531
781
  const fastify = Fastify({
@@ -44,6 +44,67 @@ test('Should honor ignoreTrailingSlash option', t => {
44
44
  })
45
45
  })
46
46
 
47
+ test('Should honor ignoreDuplicateSlashes option', t => {
48
+ t.plan(4)
49
+ const fastify = Fastify({
50
+ ignoreDuplicateSlashes: true
51
+ })
52
+
53
+ fastify.get('/test//test///test', (req, res) => {
54
+ res.send('test')
55
+ })
56
+
57
+ fastify.listen({ port: 0 }, (err) => {
58
+ t.teardown(() => { fastify.close() })
59
+ if (err) t.threw(err)
60
+
61
+ const baseUrl = getUrl(fastify)
62
+
63
+ sget.concat(baseUrl + '/test/test/test', (err, res, data) => {
64
+ if (err) t.threw(err)
65
+ t.equal(res.statusCode, 200)
66
+ t.equal(data.toString(), 'test')
67
+ })
68
+
69
+ sget.concat(baseUrl + '/test//test///test', (err, res, data) => {
70
+ if (err) t.threw(err)
71
+ t.equal(res.statusCode, 200)
72
+ t.equal(data.toString(), 'test')
73
+ })
74
+ })
75
+ })
76
+
77
+ test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', t => {
78
+ t.plan(4)
79
+ const fastify = Fastify({
80
+ ignoreTrailingSlash: true,
81
+ ignoreDuplicateSlashes: true
82
+ })
83
+
84
+ fastify.get('/test//test///test', (req, res) => {
85
+ res.send('test')
86
+ })
87
+
88
+ fastify.listen({ port: 0 }, (err) => {
89
+ t.teardown(() => { fastify.close() })
90
+ if (err) t.threw(err)
91
+
92
+ const baseUrl = getUrl(fastify)
93
+
94
+ sget.concat(baseUrl + '/test/test/test/', (err, res, data) => {
95
+ if (err) t.threw(err)
96
+ t.equal(res.statusCode, 200)
97
+ t.equal(data.toString(), 'test')
98
+ })
99
+
100
+ sget.concat(baseUrl + '/test//test///test//', (err, res, data) => {
101
+ if (err) t.threw(err)
102
+ t.equal(res.statusCode, 200)
103
+ t.equal(data.toString(), 'test')
104
+ })
105
+ })
106
+ })
107
+
47
108
  test('Should honor maxParamLength option', t => {
48
109
  t.plan(4)
49
110
  const fastify = Fastify({ maxParamLength: 10 })
@@ -1755,3 +1755,27 @@ test('Should coerce the array if the default validator is used', async t => {
1755
1755
  t.error(err)
1756
1756
  }
1757
1757
  })
1758
+
1759
+ test('Should return a human-friendly error if response status codes are not specified', t => {
1760
+ t.plan(2)
1761
+ const fastify = Fastify()
1762
+
1763
+ fastify.route({
1764
+ url: '/',
1765
+ method: 'GET',
1766
+ schema: {
1767
+ response: {
1768
+ // This should be nested under a status code key, e.g { 200: { type: 'array' } }
1769
+ type: 'array'
1770
+ }
1771
+ },
1772
+ handler: (req, reply) => {
1773
+ reply.send([])
1774
+ }
1775
+ })
1776
+
1777
+ fastify.ready(err => {
1778
+ t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD')
1779
+ t.match(err.message, 'Failed building the serialization schema for GET: /, due to error response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }')
1780
+ })
1781
+ })
@@ -711,3 +711,60 @@ test('Errors in searilizer sended to errorHandler', async t => {
711
711
  t.ok(savedError.serialization, 'Serialization sign presents')
712
712
  t.end()
713
713
  })
714
+
715
+ test('capital X', t => {
716
+ t.plan(3)
717
+
718
+ const fastify = Fastify()
719
+ fastify.get('/', {
720
+ schema: {
721
+ response: {
722
+ '2XX': {
723
+ type: 'object',
724
+ properties: {
725
+ name: { type: 'string' },
726
+ work: { type: 'string' }
727
+ }
728
+ }
729
+ }
730
+ }
731
+ }, function (req, reply) {
732
+ reply.code(200).send({ name: 'Foo', work: 'Bar', nick: 'Boo' })
733
+ })
734
+
735
+ fastify.inject('/', (err, res) => {
736
+ t.error(err)
737
+ t.same(res.json(), { name: 'Foo', work: 'Bar' })
738
+ t.equal(res.statusCode, 200)
739
+ })
740
+ })
741
+
742
+ test('allow default as status code and used as last fallback', t => {
743
+ t.plan(3)
744
+ const fastify = Fastify()
745
+
746
+ fastify.route({
747
+ url: '/',
748
+ method: 'GET',
749
+ schema: {
750
+ response: {
751
+ default: {
752
+ type: 'object',
753
+ properties: {
754
+ name: { type: 'string' },
755
+ work: { type: 'string' }
756
+ }
757
+ }
758
+ }
759
+ },
760
+ handler: (req, reply) => {
761
+ reply.code(200).send({ name: 'Foo', work: 'Bar', nick: 'Boo' })
762
+ }
763
+ })
764
+
765
+ fastify.inject('/', (err, res) => {
766
+ t.error(err)
767
+ t.same(res.json(), { name: 'Foo', work: 'Bar' })
768
+ t.equal(res.statusCode, 200)
769
+ })
770
+ })
@@ -58,6 +58,7 @@ fastify({ http2: true, https: {} }).inject({}, lightMyRequestCallback)
58
58
  // server options
59
59
  expectAssignable<FastifyInstance<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse>>(fastify({ http2: true }))
60
60
  expectAssignable<FastifyInstance>(fastify({ ignoreTrailingSlash: true }))
61
+ expectAssignable<FastifyInstance>(fastify({ ignoreDuplicateSlashes: true }))
61
62
  expectAssignable<FastifyInstance>(fastify({ connectionTimeout: 1000 }))
62
63
  expectAssignable<FastifyInstance>(fastify({ forceCloseConnections: true }))
63
64
  expectAssignable<FastifyInstance>(fastify({ keepAliveTimeout: 1000 }))