fastify 2.7.1 → 2.11.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 (72) hide show
  1. package/README.md +15 -4
  2. package/build/build-validation.js +8 -0
  3. package/docs/Benchmarking.md +2 -2
  4. package/docs/ContentTypeParser.md +12 -10
  5. package/docs/Decorators.md +14 -14
  6. package/docs/Ecosystem.md +7 -1
  7. package/docs/Errors.md +13 -8
  8. package/docs/Fluent-Schema.md +9 -12
  9. package/docs/Getting-Started.md +29 -25
  10. package/docs/HTTP2.md +1 -1
  11. package/docs/Hooks.md +201 -186
  12. package/docs/LTS.md +6 -7
  13. package/docs/Logging.md +10 -10
  14. package/docs/Middleware.md +59 -0
  15. package/docs/Plugins-Guide.md +52 -52
  16. package/docs/Plugins.md +3 -0
  17. package/docs/Reply.md +47 -3
  18. package/docs/Routes.md +120 -8
  19. package/docs/Server.md +69 -3
  20. package/docs/Serverless.md +76 -4
  21. package/docs/TypeScript.md +33 -10
  22. package/docs/Validation-and-Serialization.md +137 -1
  23. package/examples/typescript-server.ts +1 -1
  24. package/fastify.d.ts +52 -13
  25. package/fastify.js +68 -7
  26. package/lib/configValidator.js +99 -52
  27. package/lib/contentTypeParser.js +4 -4
  28. package/lib/context.js +2 -1
  29. package/lib/errors.js +21 -18
  30. package/lib/fourOhFour.js +10 -10
  31. package/lib/handleRequest.js +1 -2
  32. package/lib/logger.js +2 -2
  33. package/lib/pluginUtils.js +32 -0
  34. package/lib/reply.js +41 -6
  35. package/lib/route.js +37 -9
  36. package/lib/schemas.js +23 -12
  37. package/lib/symbols.js +4 -1
  38. package/lib/validation.js +15 -9
  39. package/lib/wrapThenable.js +1 -1
  40. package/package.json +34 -26
  41. package/test/404s.test.js +41 -1
  42. package/test/async-await.js +66 -0
  43. package/test/custom-parser.test.js +1 -1
  44. package/test/custom-querystring-parser.test.js +1 -1
  45. package/test/decorator.test.js +48 -0
  46. package/test/emit-warning.test.js +3 -3
  47. package/test/fastify-instance.test.js +29 -0
  48. package/test/helper.js +7 -7
  49. package/test/hooks-async.js +4 -3
  50. package/test/hooks.test.js +27 -8
  51. package/test/input-validation.test.js +126 -0
  52. package/test/internals/errors.test.js +9 -1
  53. package/test/internals/initialConfig.test.js +4 -2
  54. package/test/internals/plugin.test.js +4 -4
  55. package/test/internals/reply.test.js +78 -6
  56. package/test/internals/schemas.test.js +30 -0
  57. package/test/internals/validation.test.js +18 -0
  58. package/test/listen.test.js +1 -1
  59. package/test/logger.test.js +314 -1
  60. package/test/plugin.test.js +171 -0
  61. package/test/promises.test.js +55 -0
  62. package/test/proto-poisoning.test.js +76 -0
  63. package/test/route-hooks.test.js +109 -91
  64. package/test/route-prefix.test.js +1 -1
  65. package/test/schemas.test.js +450 -0
  66. package/test/shared-schemas.test.js +2 -2
  67. package/test/stream.test.js +10 -6
  68. package/test/throw.test.js +48 -2
  69. package/test/types/index.ts +86 -1
  70. package/test/validation-error-handling.test.js +3 -3
  71. package/test/versioned-routes.test.js +1 -1
  72. package/docs/Middlewares.md +0 -59
@@ -3,8 +3,37 @@
3
3
  const t = require('tap')
4
4
  const test = t.test
5
5
  const Fastify = require('..')
6
+ const {
7
+ kOptions
8
+ } = require('../lib/symbols')
6
9
 
7
10
  test('root fastify instance is an object', t => {
8
11
  t.plan(1)
9
12
  t.type(Fastify(), 'object')
10
13
  })
14
+
15
+ test('fastify instance should contains ajv options', t => {
16
+ t.plan(1)
17
+ const fastify = Fastify({
18
+ ajv: {
19
+ customOptions: {
20
+ nullable: false
21
+ }
22
+ }
23
+ })
24
+ t.same(fastify[kOptions].ajv, {
25
+ customOptions: {
26
+ nullable: false
27
+ },
28
+ plugins: []
29
+ })
30
+ })
31
+
32
+ test('fastify instance get invalid ajv options', t => {
33
+ t.plan(1)
34
+ t.throw(() => Fastify({
35
+ ajv: {
36
+ customOptions: 8
37
+ }
38
+ }))
39
+ })
package/test/helper.js CHANGED
@@ -221,7 +221,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
221
221
  })
222
222
 
223
223
  if (loMethod === 'options') {
224
- test(`OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text`, t => {
224
+ test('OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text', t => {
225
225
  t.plan(2)
226
226
  sget({
227
227
  method: upMethod,
@@ -328,7 +328,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
328
328
  t.strictDeepEqual(JSON.parse(res.payload), {
329
329
  error: 'Bad Request',
330
330
  code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
331
- message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
331
+ message: "FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'",
332
332
  statusCode: 400
333
333
  })
334
334
  })
@@ -344,7 +344,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
344
344
  t.strictDeepEqual(JSON.parse(body.toString()), {
345
345
  error: 'Bad Request',
346
346
  code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
347
- message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
347
+ message: "FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'",
348
348
  statusCode: 400
349
349
  })
350
350
  })
@@ -361,7 +361,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
361
361
  t.strictDeepEqual(JSON.parse(res.payload), {
362
362
  error: 'Bad Request',
363
363
  code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
364
- message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
364
+ message: "FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'",
365
365
  statusCode: 400
366
366
  })
367
367
  })
@@ -378,7 +378,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
378
378
  t.strictDeepEqual(JSON.parse(body.toString()), {
379
379
  error: 'Bad Request',
380
380
  code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
381
- message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
381
+ message: "FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'",
382
382
  statusCode: 400
383
383
  })
384
384
  })
@@ -395,7 +395,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
395
395
  t.strictDeepEqual(JSON.parse(res.payload), {
396
396
  error: 'Bad Request',
397
397
  code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
398
- message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
398
+ message: "FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'",
399
399
  statusCode: 400
400
400
  })
401
401
  })
@@ -412,7 +412,7 @@ module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {
412
412
  t.strictDeepEqual(JSON.parse(body.toString()), {
413
413
  error: 'Bad Request',
414
414
  code: 'FST_ERR_CTP_EMPTY_JSON_BODY',
415
- message: `FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'`,
415
+ message: "FST_ERR_CTP_EMPTY_JSON_BODY: Body cannot be empty when content-type is set to 'application/json'",
416
416
  statusCode: 400
417
417
  })
418
418
  })
@@ -520,7 +520,7 @@ function asyncHookTest (t) {
520
520
 
521
521
  stream.on('data', line => {
522
522
  t.strictEqual(line.level, 40)
523
- t.true(line.msg.startsWith(`Async function has too many arguments. Async hooks should not use the 'next' argument.`))
523
+ t.true(line.msg.startsWith("Async function has too many arguments. Async hooks should not use the 'next' argument."))
524
524
  t.true(/test(\\|\/)hooks-async\.js/.test(line.msg))
525
525
  })
526
526
 
@@ -528,7 +528,7 @@ function asyncHookTest (t) {
528
528
  })
529
529
 
530
530
  t.test('4 arguments', t => {
531
- t.plan(6)
531
+ t.plan(9)
532
532
  const stream = split(JSON.parse)
533
533
  const fastify = Fastify({
534
534
  logger: { stream }
@@ -536,12 +536,13 @@ function asyncHookTest (t) {
536
536
 
537
537
  stream.on('data', line => {
538
538
  t.strictEqual(line.level, 40)
539
- t.true(line.msg.startsWith(`Async function has too many arguments. Async hooks should not use the 'next' argument.`))
539
+ t.true(line.msg.startsWith("Async function has too many arguments. Async hooks should not use the 'next' argument."))
540
540
  t.true(/test(\\|\/)hooks-async\.js/.test(line.msg))
541
541
  })
542
542
 
543
543
  fastify.addHook('onSend', async (req, reply, payload, next) => {})
544
544
  fastify.addHook('preSerialization', async (req, reply, payload, next) => {})
545
+ fastify.addHook('onError', async (req, reply, error, next) => {})
545
546
  })
546
547
 
547
548
  t.end()
@@ -14,7 +14,7 @@ const symbols = require('../lib/symbols.js')
14
14
  const payload = { hello: 'world' }
15
15
 
16
16
  test('hooks', t => {
17
- t.plan(37)
17
+ t.plan(38)
18
18
  const fastify = Fastify()
19
19
 
20
20
  try {
@@ -99,6 +99,9 @@ test('hooks', t => {
99
99
  t.is(reply.test, 'the reply has come')
100
100
  reply.code(200).send(payload)
101
101
  },
102
+ onResponse: function (req, reply, done) {
103
+ t.ok('onResponse inside hook')
104
+ },
102
105
  response: {
103
106
  200: {
104
107
  type: 'object'
@@ -485,7 +488,7 @@ test('onRoute hook should pass correct route with custom prefix', t => {
485
488
  })
486
489
 
487
490
  test('onRoute hook should pass correct route with custom options', t => {
488
- t.plan(5)
491
+ t.plan(6)
489
492
  const fastify = Fastify()
490
493
  fastify.register((instance, opts, next) => {
491
494
  instance.addHook('onRoute', function (route) {
@@ -493,8 +496,15 @@ test('onRoute hook should pass correct route with custom options', t => {
493
496
  t.strictEqual(route.url, '/foo')
494
497
  t.strictEqual(route.logLevel, 'info')
495
498
  t.strictEqual(route.bodyLimit, 100)
499
+ t.type(route.logSerializers.test, 'function')
496
500
  })
497
- instance.get('/foo', { logLevel: 'info', bodyLimit: 100 }, function (req, reply) {
501
+ instance.get('/foo', {
502
+ logLevel: 'info',
503
+ bodyLimit: 100,
504
+ logSerializers: {
505
+ test: value => value
506
+ }
507
+ }, function (req, reply) {
498
508
  reply.send()
499
509
  })
500
510
  next()
@@ -2581,16 +2591,24 @@ test('preSerialization hooks should support encapsulation', t => {
2581
2591
  })
2582
2592
 
2583
2593
  test('onRegister hook should be called / 1', t => {
2584
- t.plan(3)
2594
+ t.plan(4)
2585
2595
  const fastify = Fastify()
2586
2596
 
2597
+ const pluginOpts = { prefix: 'hello', custom: 'world' }
2587
2598
  fastify.register((instance, opts, next) => {
2588
2599
  next()
2589
- })
2600
+ }, pluginOpts)
2590
2601
 
2591
- fastify.addHook('onRegister', instance => {
2602
+ let first = true
2603
+ fastify.addHook('onRegister', (instance, opts) => {
2592
2604
  // duck typing for the win!
2593
2605
  t.ok(instance.addHook)
2606
+ if (first) {
2607
+ // the first call of the onRegister is the main fastify instance, not the registered ones
2608
+ first = false
2609
+ } else {
2610
+ t.deepEquals(opts, pluginOpts)
2611
+ }
2594
2612
  })
2595
2613
 
2596
2614
  fastify.ready(err => {
@@ -2655,7 +2673,7 @@ test('onRegister hook should be called / 3', t => {
2655
2673
  })
2656
2674
 
2657
2675
  test('onRegister hook should be called / 4', t => {
2658
- t.plan(2)
2676
+ t.plan(3)
2659
2677
  const fastify = Fastify()
2660
2678
 
2661
2679
  function plugin (instance, opts, next) {
@@ -2665,9 +2683,10 @@ test('onRegister hook should be called / 4', t => {
2665
2683
 
2666
2684
  fastify.register(plugin)
2667
2685
 
2668
- fastify.addHook('onRegister', instance => {
2686
+ fastify.addHook('onRegister', (instance, opts) => {
2669
2687
  // duck typing for the win!
2670
2688
  t.ok(instance.addHook)
2689
+ t.deepEquals(opts, {})
2671
2690
  })
2672
2691
 
2673
2692
  fastify.ready(err => {
@@ -3,6 +3,7 @@
3
3
  const t = require('tap')
4
4
  const test = t.test
5
5
  const Fastify = require('..')
6
+ const ajvMergePatch = require('ajv-merge-patch')
6
7
 
7
8
  test('case insensitive header validation', t => {
8
9
  t.plan(2)
@@ -67,3 +68,128 @@ test('not evaluate json-schema $schema keyword', t => {
67
68
  t.equal(res.payload, 'world')
68
69
  })
69
70
  })
71
+
72
+ test('Should handle $merge keywords in body', t => {
73
+ t.plan(5)
74
+ const fastify = Fastify({
75
+ ajv: {
76
+ plugins: [
77
+ ajvMergePatch
78
+ ]
79
+ }
80
+ })
81
+
82
+ fastify.route({
83
+ method: 'POST',
84
+ url: '/',
85
+ schema: {
86
+ body: {
87
+ $merge: {
88
+ source: {
89
+ type: 'object',
90
+ properties: {
91
+ q: {
92
+ type: 'string'
93
+ }
94
+ }
95
+ },
96
+ with: {
97
+ required: ['q']
98
+ }
99
+ }
100
+ }
101
+ },
102
+ handler (req, reply) {
103
+ reply.send({ ok: 1 })
104
+ }
105
+ })
106
+
107
+ fastify.ready(err => {
108
+ t.error(err)
109
+
110
+ fastify.inject({
111
+ method: 'POST',
112
+ url: '/'
113
+ }, (err, res) => {
114
+ t.error(err)
115
+ t.equals(res.statusCode, 400)
116
+ })
117
+
118
+ fastify.inject({
119
+ method: 'POST',
120
+ url: '/',
121
+ body: {
122
+ q: 'foo'
123
+ }
124
+ }, (err, res) => {
125
+ t.error(err)
126
+ t.equals(res.statusCode, 200)
127
+ })
128
+ })
129
+ })
130
+
131
+ test('Should handle $patch keywords in body', t => {
132
+ t.plan(5)
133
+ const fastify = Fastify({
134
+ ajv: {
135
+ plugins: [
136
+ ajvMergePatch
137
+ ]
138
+ }
139
+ })
140
+
141
+ fastify.route({
142
+ method: 'POST',
143
+ url: '/',
144
+ schema: {
145
+ body: {
146
+ $patch: {
147
+ source: {
148
+ type: 'object',
149
+ properties: {
150
+ q: {
151
+ type: 'string'
152
+ }
153
+ }
154
+ },
155
+ with: [
156
+ {
157
+ op: 'add',
158
+ path: '/properties/q',
159
+ value: { type: 'number' }
160
+ }
161
+ ]
162
+ }
163
+ }
164
+ },
165
+ handler (req, reply) {
166
+ reply.send({ ok: 1 })
167
+ }
168
+ })
169
+
170
+ fastify.ready(err => {
171
+ t.error(err)
172
+
173
+ fastify.inject({
174
+ method: 'POST',
175
+ url: '/',
176
+ body: {
177
+ q: 'foo'
178
+ }
179
+ }, (err, res) => {
180
+ t.error(err)
181
+ t.equals(res.statusCode, 400)
182
+ })
183
+
184
+ fastify.inject({
185
+ method: 'POST',
186
+ url: '/',
187
+ body: {
188
+ q: 10
189
+ }
190
+ }, (err, res) => {
191
+ t.error(err)
192
+ t.equals(res.statusCode, 200)
193
+ })
194
+ })
195
+ })
@@ -78,7 +78,7 @@ test('Should throw when error code has no message', t => {
78
78
  try {
79
79
  createError('code')
80
80
  } catch (err) {
81
- t.equal(err.message, `Fastify error message must not be empty`)
81
+ t.equal(err.message, 'Fastify error message must not be empty')
82
82
  }
83
83
  })
84
84
 
@@ -94,3 +94,11 @@ test('Create error with different base', t => {
94
94
  t.equal(err.statusCode, 500)
95
95
  t.ok(err.stack)
96
96
  })
97
+
98
+ test('Error has appropriate string tag', t => {
99
+ t.plan(1)
100
+ const NewError = createError('CODE', 'foo')
101
+ const err = new NewError()
102
+ const str = Object.prototype.toString.call(err)
103
+ t.equal(str, '[object Error]')
104
+ })
@@ -25,6 +25,7 @@ test('without options passed to Fastify, initialConfig should expose default val
25
25
  ignoreTrailingSlash: false,
26
26
  maxParamLength: 100,
27
27
  onProtoPoisoning: 'error',
28
+ onConstructorPoisoning: 'ignore',
28
29
  pluginTimeout: 10000,
29
30
  requestIdHeader: 'request-id',
30
31
  requestIdLogLabel: 'reqId'
@@ -55,7 +56,7 @@ test('Fastify.initialConfig should expose all options', t => {
55
56
  }
56
57
  },
57
58
  deriveVersion: (req, ctx) => {
58
- return req.headers['accept']
59
+ return req.headers.accept
59
60
  }
60
61
  }
61
62
 
@@ -153,7 +154,7 @@ test('Return an error if options do not match the validation schema', t => {
153
154
  } catch (error) {
154
155
  t.type(error, Error)
155
156
  t.equal(error.name, 'FastifyError [FST_ERR_INIT_OPTS_INVALID]')
156
- t.equal(error.message, `FST_ERR_INIT_OPTS_INVALID: Invalid initialization options: '["should be boolean"]'`)
157
+ t.equal(error.message, 'FST_ERR_INIT_OPTS_INVALID: Invalid initialization options: \'["should be boolean"]\'')
157
158
  t.equal(error.code, 'FST_ERR_INIT_OPTS_INVALID')
158
159
  t.ok(error.stack)
159
160
  t.pass()
@@ -228,6 +229,7 @@ test('Should not have issues when passing stream options to Pino.js', t => {
228
229
  ignoreTrailingSlash: true,
229
230
  maxParamLength: 100,
230
231
  onProtoPoisoning: 'error',
232
+ onConstructorPoisoning: 'ignore',
231
233
  pluginTimeout: 10000,
232
234
  requestIdHeader: 'request-id',
233
235
  requestIdLogLabel: 'reqId'
@@ -7,7 +7,7 @@ const pluginUtilsPublic = require('../../lib/pluginUtils.js')
7
7
  const pluginUtils = require('../../lib/pluginUtils')[Symbol.for('internals')]
8
8
  const symbols = require('../../lib/symbols.js')
9
9
 
10
- test(`shouldSkipOverride should check the 'skip-override' symbol`, t => {
10
+ test("shouldSkipOverride should check the 'skip-override' symbol", t => {
11
11
  t.plan(2)
12
12
 
13
13
  yes[Symbol.for('skip-override')] = true
@@ -19,7 +19,7 @@ test(`shouldSkipOverride should check the 'skip-override' symbol`, t => {
19
19
  function no () {}
20
20
  })
21
21
 
22
- test(`getMeta should return the object stored with the 'plugin-meta' symbol`, t => {
22
+ test("getMeta should return the object stored with the 'plugin-meta' symbol", t => {
23
23
  t.plan(1)
24
24
 
25
25
  const meta = { hello: 'world' }
@@ -76,7 +76,7 @@ test('checkDecorators should check if the given decorator is present in the inst
76
76
  pluginUtils.checkDecorators.call(context, fn)
77
77
  t.fail('should throw')
78
78
  } catch (err) {
79
- t.is(err.message, `The decorator 'plugin' is not present in Request`)
79
+ t.is(err.message, "The decorator 'plugin' is not present in Request")
80
80
  }
81
81
 
82
82
  function fn () {}
@@ -117,7 +117,7 @@ test('checkDependencies should check if the given dependency is present in the i
117
117
  pluginUtils.checkDependencies.call(context, fn)
118
118
  t.fail('should throw')
119
119
  } catch (err) {
120
- t.is(err.message, `The dependency 'plugin' of plugin 'test-plugin' is not registered`)
120
+ t.is(err.message, "The dependency 'plugin' of plugin 'test-plugin' is not registered")
121
121
  }
122
122
 
123
123
  function fn () {}
@@ -6,6 +6,7 @@ const sget = require('simple-get').concat
6
6
  const http = require('http')
7
7
  const NotFound = require('http-errors').NotFound
8
8
  const Reply = require('../../lib/reply')
9
+ const { Writable } = require('readable-stream')
9
10
  const {
10
11
  kReplyErrorHandlerCalled,
11
12
  kReplyHeaders,
@@ -36,14 +37,32 @@ test('Once called, Reply should return an object with methods', t => {
36
37
 
37
38
  test('reply.send throw with circular JSON', t => {
38
39
  t.plan(1)
39
- const request = {}
40
- const response = { setHeader: () => {} }
41
- const reply = new Reply(request, response, null)
40
+ const response = {
41
+ setHeader: () => {},
42
+ hasHeader: () => false,
43
+ getHeader: () => undefined,
44
+ writeHead: () => {},
45
+ end: () => {}
46
+ }
47
+ const reply = new Reply(response, { onSend: [] }, null)
42
48
  t.throws(() => {
43
49
  var obj = {}
44
50
  obj.obj = obj
45
51
  reply.send(JSON.stringify(obj))
46
- })
52
+ }, 'Converting circular structure to JSON')
53
+ })
54
+
55
+ test('reply.send returns itself', t => {
56
+ t.plan(1)
57
+ const response = {
58
+ setHeader: () => {},
59
+ hasHeader: () => false,
60
+ getHeader: () => undefined,
61
+ writeHead: () => {},
62
+ end: () => {}
63
+ }
64
+ const reply = new Reply(response, { onSend: [] }, null)
65
+ t.equal(reply.send('hello'), reply)
47
66
  })
48
67
 
49
68
  test('reply.serializer should set a custom serializer', t => {
@@ -262,7 +281,7 @@ test('within an instance', t => {
262
281
  http.get(url, (response) => {
263
282
  t.strictEqual(response.headers['x-onsend'], 'yes')
264
283
  t.strictEqual(response.headers['content-length'], '0')
265
- t.strictEqual(response.headers['location'], '/')
284
+ t.strictEqual(response.headers.location, '/')
266
285
  })
267
286
  })
268
287
 
@@ -419,7 +438,7 @@ test('stream using reply.res.writeHead should return customize headers', t => {
419
438
  url: 'http://localhost:' + fastify.server.address().port
420
439
  }, (err, response, body) => {
421
440
  t.error(err)
422
- t.strictEqual(response.headers['location'], '/')
441
+ t.strictEqual(response.headers.location, '/')
423
442
  t.strictEqual(response.headers['Content-Type'], undefined)
424
443
  t.deepEqual(body, buf)
425
444
  })
@@ -905,6 +924,23 @@ test('.status() is an alias for .code()', t => {
905
924
  })
906
925
  })
907
926
 
927
+ test('.statusCode is getter and setter', t => {
928
+ t.plan(4)
929
+ const fastify = require('../..')()
930
+
931
+ fastify.get('/', function (req, reply) {
932
+ t.ok(reply.statusCode, 200, 'default status value')
933
+ reply.statusCode = 418
934
+ t.ok(reply.statusCode, 418)
935
+ reply.send()
936
+ })
937
+
938
+ fastify.inject('/', (err, res) => {
939
+ t.error(err)
940
+ t.is(res.statusCode, 418)
941
+ })
942
+ })
943
+
908
944
  test('reply.header setting multiple cookies as multiple Set-Cookie headers', t => {
909
945
  t.plan(7)
910
946
 
@@ -1252,3 +1288,39 @@ test('reply should not call the custom serializer for errors and not found', t =
1252
1288
  t.strictEqual(res.statusCode, 404)
1253
1289
  })
1254
1290
  })
1291
+
1292
+ test('reply.then', t => {
1293
+ t.plan(2)
1294
+
1295
+ function context () {}
1296
+ function request () {}
1297
+
1298
+ t.test('without an error', t => {
1299
+ t.plan(1)
1300
+
1301
+ const response = new Writable()
1302
+ const reply = new Reply(response, context, request)
1303
+
1304
+ reply.then(function () {
1305
+ t.pass('fullfilled called')
1306
+ })
1307
+
1308
+ response.destroy()
1309
+ })
1310
+
1311
+ t.test('with an error', t => {
1312
+ t.plan(1)
1313
+
1314
+ const response = new Writable()
1315
+ const reply = new Reply(response, context, request)
1316
+ const _err = new Error('kaboom')
1317
+
1318
+ reply.then(function () {
1319
+ t.fail('fullfilled called')
1320
+ }, function (err) {
1321
+ t.equal(err, _err)
1322
+ })
1323
+
1324
+ response.destroy(_err)
1325
+ })
1326
+ })
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const test = require('tap').test
4
+ const { Schemas } = require('../../lib/schemas')
5
+
6
+ test('Should not change resolved schema', t => {
7
+ t.plan(4)
8
+
9
+ const schemas = new Schemas()
10
+ schemas.add({
11
+ $id: 'A',
12
+ field: 'value'
13
+ })
14
+ const schema = {
15
+ a: 'A#'
16
+ }
17
+ const resolvedSchema = schemas.resolveRefs(schema)
18
+
19
+ t.same(resolvedSchema.a, {
20
+ field: 'value'
21
+ })
22
+ t.same(resolvedSchema.$id, undefined)
23
+
24
+ schemas.getJsonSchemas()
25
+
26
+ t.same(resolvedSchema.a, {
27
+ field: 'value'
28
+ })
29
+ t.same(resolvedSchema.$id, undefined)
30
+ })
@@ -226,3 +226,21 @@ test('build schema - headers are not lowercased in case of custom object', t =>
226
226
  return () => {}
227
227
  }, new Schemas())
228
228
  })
229
+
230
+ test('build schema - uppercased headers are not included', t => {
231
+ t.plan(1)
232
+ const opts = {
233
+ schema: {
234
+ headers: {
235
+ type: 'object',
236
+ properties: {
237
+ 'Content-Type': { type: 'string' }
238
+ }
239
+ }
240
+ }
241
+ }
242
+ validation.build(opts, schema => {
243
+ t.notOk('Content-Type' in schema.properties, 'uppercase does not exist')
244
+ return () => {}
245
+ }, new Schemas())
246
+ })
@@ -190,7 +190,7 @@ if (os.platform() !== 'win32') {
190
190
  const fastify = Fastify()
191
191
  t.tearDown(fastify.close.bind(fastify))
192
192
 
193
- const sockFile = path.join(os.tmpdir(), 'server.sock')
193
+ const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').substr(2, 8)}-server.sock`)
194
194
  try {
195
195
  fs.unlinkSync(sockFile)
196
196
  } catch (e) { }