fastify 4.5.3 → 4.7.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 (60) hide show
  1. package/docs/Guides/Ecosystem.md +42 -16
  2. package/docs/Guides/Migration-Guide-V4.md +97 -48
  3. package/docs/Guides/Plugins-Guide.md +44 -0
  4. package/docs/Guides/Write-Plugin.md +1 -1
  5. package/docs/Reference/Logging.md +16 -13
  6. package/docs/Reference/Reply.md +5 -5
  7. package/docs/Reference/Request.md +19 -6
  8. package/docs/Reference/Routes.md +2 -2
  9. package/docs/Reference/Server.md +24 -2
  10. package/docs/Reference/Type-Providers.md +5 -19
  11. package/docs/Reference/TypeScript.md +7 -14
  12. package/examples/typescript-server.ts +1 -1
  13. package/fastify.d.ts +2 -2
  14. package/fastify.js +4 -1
  15. package/lib/contentTypeParser.js +10 -9
  16. package/lib/context.js +27 -3
  17. package/lib/error-handler.js +7 -3
  18. package/lib/error-serializer.js +13 -40
  19. package/lib/handleRequest.js +15 -13
  20. package/lib/reply.js +42 -34
  21. package/lib/request.js +36 -18
  22. package/lib/route.js +25 -10
  23. package/lib/schema-controller.js +7 -1
  24. package/lib/schemas.js +1 -0
  25. package/lib/server.js +6 -1
  26. package/lib/symbols.js +2 -0
  27. package/lib/validation.js +4 -3
  28. package/lib/warnings.js +2 -0
  29. package/package.json +26 -26
  30. package/test/404s.test.js +19 -1
  31. package/test/context-config.test.js +2 -1
  32. package/test/handler-context.test.js +12 -30
  33. package/test/has-route.test.js +77 -0
  34. package/test/internals/contentTypeParser.test.js +3 -3
  35. package/test/internals/handleRequest.test.js +4 -3
  36. package/test/internals/reply-serialize.test.js +9 -9
  37. package/test/internals/reply.test.js +10 -9
  38. package/test/internals/request-validate.test.js +97 -9
  39. package/test/internals/request.test.js +57 -3
  40. package/test/internals/validation.test.js +15 -0
  41. package/test/listen.deprecated.test.js +33 -0
  42. package/test/plugin.test.js +1 -1
  43. package/test/reply-code.test.js +59 -0
  44. package/test/route.test.js +29 -0
  45. package/test/router-options.test.js +33 -66
  46. package/test/schema-feature.test.js +25 -0
  47. package/test/schema-special-usage.test.js +29 -0
  48. package/test/schema-validation.test.js +19 -0
  49. package/test/search.test.js +169 -30
  50. package/test/server.test.js +54 -0
  51. package/test/types/fastify.test-d.ts +9 -0
  52. package/test/types/route.test-d.ts +11 -0
  53. package/test/unsupported-httpversion.test.js +0 -26
  54. package/types/hooks.d.ts +25 -25
  55. package/types/instance.d.ts +27 -21
  56. package/types/logger.d.ts +1 -1
  57. package/types/plugin.d.ts +3 -3
  58. package/types/reply.d.ts +2 -2
  59. package/types/request.d.ts +11 -5
  60. package/types/route.d.ts +9 -9
@@ -268,6 +268,21 @@ test('build schema - headers are not lowercased in case of custom object', t =>
268
268
  })
269
269
  })
270
270
 
271
+ test('build schema - headers are not lowercased in case of custom validator provided', t => {
272
+ t.plan(1)
273
+
274
+ class Headers {}
275
+ const opts = {
276
+ schema: {
277
+ headers: new Headers()
278
+ }
279
+ }
280
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {
281
+ t.type(schema, Headers)
282
+ return () => {}
283
+ }, true)
284
+ })
285
+
271
286
  test('build schema - uppercased headers are not included', t => {
272
287
  t.plan(1)
273
288
  const opts = {
@@ -200,3 +200,36 @@ test('listen when firstArg is { path: string(pipe) } and with backlog and callba
200
200
  t.equal(address, '\\\\.\\pipe\\testPipe3')
201
201
  })
202
202
  })
203
+
204
+ test('listen accepts a port as string, and callback', t => {
205
+ t.plan(2)
206
+ const fastify = Fastify()
207
+ t.teardown(fastify.close.bind(fastify))
208
+ const port = 3000
209
+ fastify.listen(port.toString(), localhost, (err) => {
210
+ t.equal(fastify.server.address().port, port)
211
+ t.error(err)
212
+ })
213
+ })
214
+
215
+ test('listen accepts a port as string, address and callback', t => {
216
+ t.plan(3)
217
+ const fastify = Fastify()
218
+ t.teardown(fastify.close.bind(fastify))
219
+ const port = 3000
220
+ fastify.listen(port.toString(), localhost, (err) => {
221
+ t.equal(fastify.server.address().port, port)
222
+ t.equal(fastify.server.address().address, localhost)
223
+ t.error(err)
224
+ })
225
+ })
226
+
227
+ test('listen with invalid port string without callback with (address)', t => {
228
+ t.plan(1)
229
+ const fastify = Fastify()
230
+ t.teardown(fastify.close.bind(fastify))
231
+ fastify.listen('-1')
232
+ .then(address => {
233
+ t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`)
234
+ })
235
+ })
@@ -885,7 +885,7 @@ test('pluginTimeout', t => {
885
885
  })
886
886
  })
887
887
 
888
- test('pluginTimeout - named function', { only: true }, t => {
888
+ test('pluginTimeout - named function', t => {
889
889
  t.plan(5)
890
890
  const fastify = Fastify({
891
891
  pluginTimeout: 10
@@ -0,0 +1,59 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const Fastify = require('..')
6
+
7
+ test('code should handle null/undefined/float', t => {
8
+ t.plan(8)
9
+
10
+ const fastify = Fastify()
11
+
12
+ fastify.get('/null', function (request, reply) {
13
+ reply.status(null).send()
14
+ })
15
+
16
+ fastify.get('/undefined', function (request, reply) {
17
+ reply.status(undefined).send()
18
+ })
19
+
20
+ fastify.get('/404.5', function (request, reply) {
21
+ reply.status(404.5).send()
22
+ })
23
+
24
+ fastify.inject({
25
+ method: 'GET',
26
+ url: '/null'
27
+ }, (error, res) => {
28
+ t.error(error)
29
+ t.equal(res.statusCode, 500)
30
+ t.same(res.json(), {
31
+ statusCode: 500,
32
+ code: 'FST_ERR_BAD_STATUS_CODE',
33
+ error: 'Internal Server Error',
34
+ message: 'Called reply with an invalid status code: null'
35
+ })
36
+ })
37
+
38
+ fastify.inject({
39
+ method: 'GET',
40
+ url: '/undefined'
41
+ }, (error, res) => {
42
+ t.error(error)
43
+ t.equal(res.statusCode, 500)
44
+ t.same(res.json(), {
45
+ statusCode: 500,
46
+ code: 'FST_ERR_BAD_STATUS_CODE',
47
+ error: 'Internal Server Error',
48
+ message: 'Called reply with an invalid status code: undefined'
49
+ })
50
+ })
51
+
52
+ fastify.inject({
53
+ method: 'GET',
54
+ url: '/404.5'
55
+ }, (error, res) => {
56
+ t.error(error)
57
+ t.equal(res.statusCode, 404)
58
+ })
59
+ })
@@ -1464,3 +1464,32 @@ test('invalid url attribute - non string URL', t => {
1464
1464
  t.equal(error.code, FST_ERR_INVALID_URL().code)
1465
1465
  }
1466
1466
  })
1467
+
1468
+ test('exposeHeadRoute should not reuse the same route option', async t => {
1469
+ t.plan(2)
1470
+
1471
+ const fastify = Fastify()
1472
+
1473
+ // we update the onRequest hook in onRoute hook
1474
+ // if we reuse the same route option
1475
+ // that means we will append another function inside the array
1476
+ fastify.addHook('onRoute', function (routeOption) {
1477
+ if (Array.isArray(routeOption.onRequest)) {
1478
+ routeOption.onRequest.push(() => {})
1479
+ } else {
1480
+ routeOption.onRequest = [() => {}]
1481
+ }
1482
+ })
1483
+
1484
+ fastify.addHook('onRoute', function (routeOption) {
1485
+ t.equal(routeOption.onRequest.length, 1)
1486
+ })
1487
+
1488
+ fastify.route({
1489
+ method: 'GET',
1490
+ path: '/more-coffee',
1491
+ async handler () {
1492
+ return 'hello world'
1493
+ }
1494
+ })
1495
+ })
@@ -1,20 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const test = require('tap').test
4
- const sget = require('simple-get')
5
4
  const Fastify = require('../')
6
5
  const { FST_ERR_BAD_URL } = require('../lib/errors')
7
6
 
8
- function getUrl (app) {
9
- const { address, port } = app.server.address()
10
- if (address === '::1') {
11
- return `http://[${address}]:${port}`
12
- } else {
13
- return `http://${address}:${port}`
14
- }
15
- }
16
-
17
- test('Should honor ignoreTrailingSlash option', t => {
7
+ test('Should honor ignoreTrailingSlash option', async t => {
18
8
  t.plan(4)
19
9
  const fastify = Fastify({
20
10
  ignoreTrailingSlash: true
@@ -24,27 +14,16 @@ test('Should honor ignoreTrailingSlash option', t => {
24
14
  res.send('test')
25
15
  })
26
16
 
27
- fastify.listen({ port: 0 }, (err) => {
28
- t.teardown(() => { fastify.close() })
29
- if (err) t.threw(err)
30
-
31
- const baseUrl = getUrl(fastify)
32
-
33
- sget.concat(baseUrl + '/test', (err, res, data) => {
34
- if (err) t.threw(err)
35
- t.equal(res.statusCode, 200)
36
- t.equal(data.toString(), 'test')
37
- })
17
+ let res = await fastify.inject('/test')
18
+ t.equal(res.statusCode, 200)
19
+ t.equal(res.payload.toString(), 'test')
38
20
 
39
- sget.concat(baseUrl + '/test/', (err, res, data) => {
40
- if (err) t.threw(err)
41
- t.equal(res.statusCode, 200)
42
- t.equal(data.toString(), 'test')
43
- })
44
- })
21
+ res = await fastify.inject('/test/')
22
+ t.equal(res.statusCode, 200)
23
+ t.equal(res.payload.toString(), 'test')
45
24
  })
46
25
 
47
- test('Should honor ignoreDuplicateSlashes option', t => {
26
+ test('Should honor ignoreDuplicateSlashes option', async t => {
48
27
  t.plan(4)
49
28
  const fastify = Fastify({
50
29
  ignoreDuplicateSlashes: true
@@ -54,27 +33,16 @@ test('Should honor ignoreDuplicateSlashes option', t => {
54
33
  res.send('test')
55
34
  })
56
35
 
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
- })
36
+ let res = await fastify.inject('/test/test/test')
37
+ t.equal(res.statusCode, 200)
38
+ t.equal(res.payload.toString(), 'test')
68
39
 
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
- })
40
+ res = await fastify.inject('/test//test///test')
41
+ t.equal(res.statusCode, 200)
42
+ t.equal(res.payload.toString(), 'test')
75
43
  })
76
44
 
77
- test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', t => {
45
+ test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', async t => {
78
46
  t.plan(4)
79
47
  const fastify = Fastify({
80
48
  ignoreTrailingSlash: true,
@@ -85,24 +53,13 @@ test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', t =>
85
53
  res.send('test')
86
54
  })
87
55
 
88
- fastify.listen({ port: 0 }, (err) => {
89
- t.teardown(() => { fastify.close() })
90
- if (err) t.threw(err)
91
-
92
- const baseUrl = getUrl(fastify)
56
+ let res = await fastify.inject('/test/test/test/')
57
+ t.equal(res.statusCode, 200)
58
+ t.equal(res.payload.toString(), 'test')
93
59
 
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
- })
60
+ res = await fastify.inject('/test//test///test//')
61
+ t.equal(res.statusCode, 200)
62
+ t.equal(res.payload.toString(), 'test')
106
63
  })
107
64
 
108
65
  test('Should honor maxParamLength option', t => {
@@ -131,12 +88,22 @@ test('Should honor maxParamLength option', t => {
131
88
  })
132
89
 
133
90
  test('Should expose router options via getters on request and reply', t => {
134
- t.plan(7)
91
+ t.plan(10)
135
92
  const fastify = Fastify()
93
+ const expectedSchema = {
94
+ params: {
95
+ id: { type: 'integer' }
96
+ }
97
+ }
136
98
 
137
- fastify.get('/test/:id', (req, reply) => {
99
+ fastify.get('/test/:id', {
100
+ schema: expectedSchema
101
+ }, (req, reply) => {
138
102
  t.equal(reply.context.config.url, '/test/:id')
139
103
  t.equal(reply.context.config.method, 'GET')
104
+ t.equal(req.routeConfig.url, '/test/:id')
105
+ t.equal(req.routeConfig.method, 'GET')
106
+ t.same(req.routeSchema, expectedSchema)
140
107
  t.equal(req.routerPath, '/test/:id')
141
108
  t.equal(req.routerMethod, 'GET')
142
109
  t.equal(req.is404, false)
@@ -1779,3 +1779,28 @@ test('Should return a human-friendly error if response status codes are not spec
1779
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
1780
  })
1781
1781
  })
1782
+
1783
+ test('setSchemaController: custom validator instance should not mutate headers schema', async t => {
1784
+ t.plan(2)
1785
+ class Headers {}
1786
+ const fastify = Fastify()
1787
+
1788
+ fastify.setSchemaController({
1789
+ compilersFactory: {
1790
+ buildValidator: function () {
1791
+ return ({ schema, method, url, httpPart }) => {
1792
+ t.type(schema, Headers)
1793
+ return () => {}
1794
+ }
1795
+ }
1796
+ }
1797
+ })
1798
+
1799
+ fastify.get('/', {
1800
+ schema: {
1801
+ headers: new Headers()
1802
+ }
1803
+ }, () => {})
1804
+
1805
+ await fastify.ready()
1806
+ })
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { test } = require('tap')
4
4
  const Joi = require('joi')
5
+ const yup = require('yup')
5
6
  const AJV = require('ajv')
6
7
  const S = require('fluent-json-schema')
7
8
  const Fastify = require('..')
@@ -673,3 +674,31 @@ test('JOI validation overwrite request headers', t => {
673
674
  })
674
675
  })
675
676
  })
677
+
678
+ test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => {
679
+ const fastify = Fastify()
680
+ const handler = () => { }
681
+
682
+ fastify.get('/the/url', {
683
+ schema: {
684
+ query: yup.object({
685
+ foo: yup.string()
686
+ })
687
+ },
688
+ validatorCompiler: ({ schema, method, url, httpPart }) => {
689
+ return function (data) {
690
+ // with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed
691
+ try {
692
+ const result = schema.validateSync(data, {})
693
+ return { value: result }
694
+ } catch (e) {
695
+ return { error: e }
696
+ }
697
+ }
698
+ },
699
+ handler
700
+ })
701
+
702
+ await fastify.ready()
703
+ t.pass('fastify is ready')
704
+ })
@@ -1017,3 +1017,22 @@ test("The same $id in route's schema must not overwrite others", t => {
1017
1017
  t.same(res.payload, 'ok')
1018
1018
  })
1019
1019
  })
1020
+
1021
+ test('Custom validator compiler should not mutate schema', async t => {
1022
+ t.plan(2)
1023
+ class Headers {}
1024
+ const fastify = Fastify()
1025
+
1026
+ fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
1027
+ t.type(schema, Headers)
1028
+ return () => {}
1029
+ })
1030
+
1031
+ fastify.get('/', {
1032
+ schema: {
1033
+ headers: new Headers()
1034
+ }
1035
+ }, () => {})
1036
+
1037
+ await fastify.ready()
1038
+ })
@@ -1,18 +1,17 @@
1
1
  'use strict'
2
2
 
3
3
  const t = require('tap')
4
+ const sget = require('simple-get').concat
4
5
  const test = t.test
5
6
  const fastify = require('..')()
6
7
 
7
8
  const schema = {
8
- schema: {
9
- response: {
10
- '2xx': {
11
- type: 'object',
12
- properties: {
13
- hello: {
14
- type: 'string'
15
- }
9
+ response: {
10
+ '2xx': {
11
+ type: 'object',
12
+ properties: {
13
+ hello: {
14
+ type: 'string'
16
15
  }
17
16
  }
18
17
  }
@@ -20,35 +19,45 @@ const schema = {
20
19
  }
21
20
 
22
21
  const querySchema = {
23
- schema: {
24
- querystring: {
25
- type: 'object',
26
- properties: {
27
- hello: {
28
- type: 'integer'
29
- }
22
+ querystring: {
23
+ type: 'object',
24
+ properties: {
25
+ hello: {
26
+ type: 'integer'
30
27
  }
31
28
  }
32
29
  }
33
30
  }
34
31
 
35
32
  const paramsSchema = {
36
- schema: {
37
- params: {
38
- type: 'object',
39
- properties: {
40
- foo: {
41
- type: 'string'
42
- },
43
- test: {
44
- type: 'integer'
45
- }
33
+ params: {
34
+ type: 'object',
35
+ properties: {
36
+ foo: {
37
+ type: 'string'
38
+ },
39
+ test: {
40
+ type: 'integer'
46
41
  }
47
42
  }
48
43
  }
49
44
  }
50
45
 
51
- test('shorthand - search', t => {
46
+ const bodySchema = {
47
+ body: {
48
+ type: 'object',
49
+ properties: {
50
+ foo: {
51
+ type: 'string'
52
+ },
53
+ test: {
54
+ type: 'integer'
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ test('search', t => {
52
61
  t.plan(1)
53
62
  try {
54
63
  fastify.route({
@@ -65,13 +74,13 @@ test('shorthand - search', t => {
65
74
  }
66
75
  })
67
76
 
68
- test('shorthand - search params', t => {
77
+ test('search, params schema', t => {
69
78
  t.plan(1)
70
79
  try {
71
80
  fastify.route({
72
81
  method: 'SEARCH',
73
82
  url: '/params/:foo/:test',
74
- paramsSchema,
83
+ schema: paramsSchema,
75
84
  handler: function (request, reply) {
76
85
  reply.code(200).send(request.params)
77
86
  }
@@ -82,13 +91,13 @@ test('shorthand - search params', t => {
82
91
  }
83
92
  })
84
93
 
85
- test('shorthand - get, querystring schema', t => {
94
+ test('search, querystring schema', t => {
86
95
  t.plan(1)
87
96
  try {
88
97
  fastify.route({
89
98
  method: 'SEARCH',
90
99
  url: '/query',
91
- querySchema,
100
+ schema: querySchema,
92
101
  handler: function (request, reply) {
93
102
  reply.code(200).send(request.query)
94
103
  }
@@ -98,3 +107,133 @@ test('shorthand - get, querystring schema', t => {
98
107
  t.fail()
99
108
  }
100
109
  })
110
+
111
+ test('search, body schema', t => {
112
+ t.plan(1)
113
+ try {
114
+ fastify.route({
115
+ method: 'SEARCH',
116
+ url: '/body',
117
+ schema: bodySchema,
118
+ handler: function (request, reply) {
119
+ reply.code(200).send(request.body)
120
+ }
121
+ })
122
+ t.pass()
123
+ } catch (e) {
124
+ t.fail()
125
+ }
126
+ })
127
+
128
+ fastify.listen({ port: 0 }, err => {
129
+ t.error(err)
130
+ t.teardown(() => { fastify.close() })
131
+
132
+ const url = `http://localhost:${fastify.server.address().port}`
133
+
134
+ test('request - search', t => {
135
+ t.plan(4)
136
+ sget({
137
+ method: 'SEARCH',
138
+ url
139
+ }, (err, response, body) => {
140
+ t.error(err)
141
+ t.equal(response.statusCode, 200)
142
+ t.equal(response.headers['content-length'], '' + body.length)
143
+ t.same(JSON.parse(body), { hello: 'world' })
144
+ })
145
+ })
146
+
147
+ test('request search params schema', t => {
148
+ t.plan(4)
149
+ sget({
150
+ method: 'SEARCH',
151
+ url: `${url}/params/world/123`
152
+ }, (err, response, body) => {
153
+ t.error(err)
154
+ t.equal(response.statusCode, 200)
155
+ t.equal(response.headers['content-length'], '' + body.length)
156
+ t.same(JSON.parse(body), { foo: 'world', test: 123 })
157
+ })
158
+ })
159
+
160
+ test('request search params schema error', t => {
161
+ t.plan(3)
162
+ sget({
163
+ method: 'SEARCH',
164
+ url: `${url}/params/world/string`
165
+ }, (err, response, body) => {
166
+ t.error(err)
167
+ t.equal(response.statusCode, 400)
168
+ t.same(JSON.parse(body), {
169
+ error: 'Bad Request',
170
+ message: 'params/test must be integer',
171
+ statusCode: 400
172
+ })
173
+ })
174
+ })
175
+
176
+ test('request search querystring schema', t => {
177
+ t.plan(4)
178
+ sget({
179
+ method: 'SEARCH',
180
+ url: `${url}/query?hello=123`
181
+ }, (err, response, body) => {
182
+ t.error(err)
183
+ t.equal(response.statusCode, 200)
184
+ t.equal(response.headers['content-length'], '' + body.length)
185
+ t.same(JSON.parse(body), { hello: 123 })
186
+ })
187
+ })
188
+
189
+ test('request search querystring schema error', t => {
190
+ t.plan(3)
191
+ sget({
192
+ method: 'SEARCH',
193
+ url: `${url}/query?hello=world`
194
+ }, (err, response, body) => {
195
+ t.error(err)
196
+ t.equal(response.statusCode, 400)
197
+ t.same(JSON.parse(body), {
198
+ error: 'Bad Request',
199
+ message: 'querystring/hello must be integer',
200
+ statusCode: 400
201
+ })
202
+ })
203
+ })
204
+
205
+ test('request search body schema', t => {
206
+ t.plan(4)
207
+ const replyBody = { foo: 'bar', test: 5 }
208
+ sget({
209
+ method: 'SEARCH',
210
+ url: `${url}/body`,
211
+ body: JSON.stringify(replyBody),
212
+ headers: { 'content-type': 'application/json' }
213
+ }, (err, response, body) => {
214
+ t.error(err)
215
+ t.equal(response.statusCode, 200)
216
+ t.equal(response.headers['content-length'], '' + body.length)
217
+ t.same(JSON.parse(body), replyBody)
218
+ })
219
+ })
220
+
221
+ test('request search body schema error', t => {
222
+ t.plan(4)
223
+ sget({
224
+ method: 'SEARCH',
225
+ url: `${url}/body`,
226
+ body: JSON.stringify({ foo: 'bar', test: 'test' }),
227
+ headers: { 'content-type': 'application/json' }
228
+ }, (err, response, body) => {
229
+ t.error(err)
230
+ t.equal(response.statusCode, 400)
231
+ t.equal(response.headers['content-length'], '' + body.length)
232
+ t.same(JSON.parse(body), {
233
+ error: 'Bad Request',
234
+ message: 'body/test must be integer',
235
+ statusCode: 400
236
+ })
237
+ })
238
+ })
239
+ })