3 var assert
= require('assert')
4 var asyncHooks
= tryRequire('async_hooks')
5 var express
= require('..')
6 var request
= require('supertest')
8 var describeAsyncHooks
= typeof asyncHooks
.AsyncLocalStorage
=== 'function'
12 describe('express.json()', function () {
13 it('should parse JSON', function (done
) {
16 .set('Content-Type', 'application/json')
17 .send('{"user":"tobi"}')
18 .expect(200, '{"user":"tobi"}', done
)
21 it('should handle Content-Length: 0', function (done
) {
24 .set('Content-Type', 'application/json')
25 .set('Content-Length', '0')
26 .expect(200, '{}', done
)
29 it('should handle empty message-body', function (done
) {
32 .set('Content-Type', 'application/json')
33 .set('Transfer-Encoding', 'chunked')
34 .expect(200, '{}', done
)
37 it('should handle no message-body', function (done
) {
40 .set('Content-Type', 'application/json')
41 .unset('Transfer-Encoding')
42 .expect(200, '{}', done
)
45 // The old node error message modification in body parser is catching this
46 it('should 400 when only whitespace', function (done
) {
49 .set('Content-Type', 'application/json')
51 .expect(400, '[entity.parse.failed] ' + parseError(' \n'), done
)
54 it('should 400 when invalid content-length', function (done
) {
57 app
.use(function (req
, res
, next
) {
58 req
.headers
['content-length'] = '20' // bad length
62 app
.use(express
.json())
64 app
.post('/', function (req
, res
) {
70 .set('Content-Type', 'application/json')
72 .expect(400, /content length/, done
)
75 it('should handle duplicated middleware', function (done
) {
78 app
.use(express
.json())
79 app
.use(express
.json())
81 app
.post('/', function (req
, res
) {
87 .set('Content-Type', 'application/json')
88 .send('{"user":"tobi"}')
89 .expect(200, '{"user":"tobi"}', done
)
92 describe('when JSON is invalid', function () {
94 this.app
= createApp()
97 it('should 400 for bad token', function (done
) {
100 .set('Content-Type', 'application/json')
102 .expect(400, '[entity.parse.failed] ' + parseError('{:'), done
)
105 it('should 400 for incomplete', function (done
) {
108 .set('Content-Type', 'application/json')
110 .expect(400, '[entity.parse.failed] ' + parseError('{"user"'), done
)
113 it('should include original body on error object', function (done
) {
116 .set('Content-Type', 'application/json')
117 .set('X-Error-Property', 'body')
119 .expect(400, ' {"user"', done
)
123 describe('with limit option', function () {
124 it('should 413 when over limit with Content-Length', function (done
) {
125 var buf
= Buffer
.alloc(1024, '.')
126 request(createApp({ limit
: '1kb' }))
128 .set('Content-Type', 'application/json')
129 .set('Content-Length', '1034')
130 .send(JSON
.stringify({ str
: buf
.toString() }))
131 .expect(413, '[entity.too.large] request entity too large', done
)
134 it('should 413 when over limit with chunked encoding', function (done
) {
135 var app
= createApp({ limit
: '1kb' })
136 var buf
= Buffer
.alloc(1024, '.')
137 var test
= request(app
).post('/')
138 test
.set('Content-Type', 'application/json')
139 test
.set('Transfer-Encoding', 'chunked')
140 test
.write('{"str":')
141 test
.write('"' + buf
.toString() + '"}')
142 test
.expect(413, done
)
145 it('should 413 when inflated body over limit', function (done
) {
146 var app
= createApp({ limit
: '1kb' })
147 var test
= request(app
).post('/')
148 test
.set('Content-Encoding', 'gzip')
149 test
.set('Content-Type', 'application/json')
150 test
.write(Buffer
.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a040000', 'hex'))
151 test
.expect(413, done
)
154 it('should accept number of bytes', function (done
) {
155 var buf
= Buffer
.alloc(1024, '.')
156 request(createApp({ limit
: 1024 }))
158 .set('Content-Type', 'application/json')
159 .send(JSON
.stringify({ str
: buf
.toString() }))
163 it('should not change when options altered', function (done
) {
164 var buf
= Buffer
.alloc(1024, '.')
165 var options
= { limit
: '1kb' }
166 var app
= createApp(options
)
168 options
.limit
= '100kb'
172 .set('Content-Type', 'application/json')
173 .send(JSON
.stringify({ str
: buf
.toString() }))
177 it('should not hang response', function (done
) {
178 var buf
= Buffer
.alloc(10240, '.')
179 var app
= createApp({ limit
: '8kb' })
180 var test
= request(app
).post('/')
181 test
.set('Content-Type', 'application/json')
185 test
.expect(413, done
)
188 it('should not error when inflating', function (done
) {
189 var app
= createApp({ limit
: '1kb' })
190 var test
= request(app
).post('/')
191 test
.set('Content-Encoding', 'gzip')
192 test
.set('Content-Type', 'application/json')
193 test
.write(Buffer
.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a0400', 'hex'))
194 test
.expect(413, done
)
198 describe('with inflate option', function () {
199 describe('when false', function () {
201 this.app
= createApp({ inflate
: false })
204 it('should not accept content-encoding', function (done
) {
205 var test
= request(this.app
).post('/')
206 test
.set('Content-Encoding', 'gzip')
207 test
.set('Content-Type', 'application/json')
208 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
209 test
.expect(415, '[encoding.unsupported] content encoding unsupported', done
)
213 describe('when true', function () {
215 this.app
= createApp({ inflate
: true })
218 it('should accept content-encoding', function (done
) {
219 var test
= request(this.app
).post('/')
220 test
.set('Content-Encoding', 'gzip')
221 test
.set('Content-Type', 'application/json')
222 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
223 test
.expect(200, '{"name":"论"}', done
)
228 describe('with strict option', function () {
229 describe('when undefined', function () {
231 this.app
= createApp()
234 it('should 400 on primitives', function (done
) {
237 .set('Content-Type', 'application/json')
239 .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace(/#/g
, 't'), done
)
243 describe('when false', function () {
245 this.app
= createApp({ strict
: false })
248 it('should parse primitives', function (done
) {
251 .set('Content-Type', 'application/json')
253 .expect(200, 'true', done
)
257 describe('when true', function () {
259 this.app
= createApp({ strict
: true })
262 it('should not parse primitives', function (done
) {
265 .set('Content-Type', 'application/json')
267 .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace(/#/g
, 't'), done
)
270 it('should not parse primitives with leading whitespaces', function (done
) {
273 .set('Content-Type', 'application/json')
275 .expect(400, '[entity.parse.failed] ' + parseError(' #rue').replace(/#/g
, 't'), done
)
278 it('should allow leading whitespaces in JSON', function (done
) {
281 .set('Content-Type', 'application/json')
282 .send(' { "user": "tobi" }')
283 .expect(200, '{"user":"tobi"}', done
)
286 it('should include correct message in stack trace', function (done
) {
289 .set('Content-Type', 'application/json')
290 .set('X-Error-Property', 'stack')
293 .expect(shouldContainInBody(parseError('#rue').replace(/#/g
, 't')))
299 describe('with type option', function () {
300 describe('when "application/vnd.api+json"', function () {
302 this.app
= createApp({ type
: 'application/vnd.api+json' })
305 it('should parse JSON for custom type', function (done
) {
308 .set('Content-Type', 'application/vnd.api+json')
309 .send('{"user":"tobi"}')
310 .expect(200, '{"user":"tobi"}', done
)
313 it('should ignore standard type', function (done
) {
316 .set('Content-Type', 'application/json')
317 .send('{"user":"tobi"}')
318 .expect(200, '', done
)
322 describe('when ["application/json", "application/vnd.api+json"]', function () {
324 this.app
= createApp({
325 type
: ['application/json', 'application/vnd.api+json']
329 it('should parse JSON for "application/json"', function (done
) {
332 .set('Content-Type', 'application/json')
333 .send('{"user":"tobi"}')
334 .expect(200, '{"user":"tobi"}', done
)
337 it('should parse JSON for "application/vnd.api+json"', function (done
) {
340 .set('Content-Type', 'application/vnd.api+json')
341 .send('{"user":"tobi"}')
342 .expect(200, '{"user":"tobi"}', done
)
345 it('should ignore "application/x-json"', function (done
) {
348 .set('Content-Type', 'application/x-json')
349 .send('{"user":"tobi"}')
350 .expect(200, '', done
)
354 describe('when a function', function () {
355 it('should parse when truthy value returned', function (done
) {
356 var app
= createApp({ type
: accept
})
358 function accept (req
) {
359 return req
.headers
['content-type'] === 'application/vnd.api+json'
364 .set('Content-Type', 'application/vnd.api+json')
365 .send('{"user":"tobi"}')
366 .expect(200, '{"user":"tobi"}', done
)
369 it('should work without content-type', function (done
) {
370 var app
= createApp({ type
: accept
})
372 function accept (req
) {
376 var test
= request(app
).post('/')
377 test
.write('{"user":"tobi"}')
378 test
.expect(200, '{"user":"tobi"}', done
)
381 it('should not invoke without a body', function (done
) {
382 var app
= createApp({ type
: accept
})
384 function accept (req
) {
385 throw new Error('oops!')
395 describe('with verify option', function () {
396 it('should assert value if function', function () {
397 assert
.throws(createApp
.bind(null, { verify
: 'lol' }),
398 /TypeError: option verify must be function/)
401 it('should error from verify', function (done
) {
402 var app
= createApp({
403 verify: function (req
, res
, buf
) {
404 if (buf
[0] === 0x5b) throw new Error('no arrays')
410 .set('Content-Type', 'application/json')
412 .expect(403, '[entity.verify.failed] no arrays', done
)
415 it('should allow custom codes', function (done
) {
416 var app
= createApp({
417 verify: function (req
, res
, buf
) {
418 if (buf
[0] !== 0x5b) return
419 var err
= new Error('no arrays')
427 .set('Content-Type', 'application/json')
429 .expect(400, '[entity.verify.failed] no arrays', done
)
432 it('should allow custom type', function (done
) {
433 var app
= createApp({
434 verify: function (req
, res
, buf
) {
435 if (buf
[0] !== 0x5b) return
436 var err
= new Error('no arrays')
444 .set('Content-Type', 'application/json')
446 .expect(403, '[foo.bar] no arrays', done
)
449 it('should include original body on error object', function (done
) {
450 var app
= createApp({
451 verify: function (req
, res
, buf
) {
452 if (buf
[0] === 0x5b) throw new Error('no arrays')
458 .set('Content-Type', 'application/json')
459 .set('X-Error-Property', 'body')
461 .expect(403, '["tobi"]', done
)
464 it('should allow pass-through', function (done
) {
465 var app
= createApp({
466 verify: function (req
, res
, buf
) {
467 if (buf
[0] === 0x5b) throw new Error('no arrays')
473 .set('Content-Type', 'application/json')
474 .send('{"user":"tobi"}')
475 .expect(200, '{"user":"tobi"}', done
)
478 it('should work with different charsets', function (done
) {
479 var app
= createApp({
480 verify: function (req
, res
, buf
) {
481 if (buf
[0] === 0x5b) throw new Error('no arrays')
485 var test
= request(app
).post('/')
486 test
.set('Content-Type', 'application/json; charset=utf-16')
487 test
.write(Buffer
.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
488 test
.expect(200, '{"name":"论"}', done
)
491 it('should 415 on unknown charset prior to verify', function (done
) {
492 var app
= createApp({
493 verify: function (req
, res
, buf
) {
494 throw new Error('unexpected verify call')
498 var test
= request(app
).post('/')
499 test
.set('Content-Type', 'application/json; charset=x-bogus')
500 test
.write(Buffer
.from('00000000', 'hex'))
501 test
.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done
)
505 describeAsyncHooks('async local storage', function () {
508 var store
= { foo
: 'bar' }
510 app
.use(function (req
, res
, next
) {
511 req
.asyncLocalStorage
= new asyncHooks
.AsyncLocalStorage()
512 req
.asyncLocalStorage
.run(store
, next
)
515 app
.use(express
.json())
517 app
.use(function (req
, res
, next
) {
518 var local
= req
.asyncLocalStorage
.getStore()
521 res
.setHeader('x-store-foo', String(local
.foo
))
527 app
.use(function (err
, req
, res
, next
) {
528 var local
= req
.asyncLocalStorage
.getStore()
531 res
.setHeader('x-store-foo', String(local
.foo
))
534 res
.status(err
.status
|| 500)
535 res
.send('[' + err
.type
+ '] ' + err
.message
)
538 app
.post('/', function (req
, res
) {
545 it('should presist store', function (done
) {
548 .set('Content-Type', 'application/json')
549 .send('{"user":"tobi"}')
551 .expect('x-store-foo', 'bar')
552 .expect('{"user":"tobi"}')
556 it('should persist store when unmatched content-type', function (done
) {
559 .set('Content-Type', 'application/fizzbuzz')
562 .expect('x-store-foo', 'bar')
567 it('should presist store when inflated', function (done
) {
568 var test
= request(this.app
).post('/')
569 test
.set('Content-Encoding', 'gzip')
570 test
.set('Content-Type', 'application/json')
571 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
573 test
.expect('x-store-foo', 'bar')
574 test
.expect('{"name":"论"}')
578 it('should presist store when inflate error', function (done
) {
579 var test
= request(this.app
).post('/')
580 test
.set('Content-Encoding', 'gzip')
581 test
.set('Content-Type', 'application/json')
582 test
.write(Buffer
.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
584 test
.expect('x-store-foo', 'bar')
588 it('should presist store when parse error', function (done
) {
591 .set('Content-Type', 'application/json')
594 .expect('x-store-foo', 'bar')
598 it('should presist store when limit exceeded', function (done
) {
601 .set('Content-Type', 'application/json')
602 .send('{"user":"' + Buffer
.alloc(1024 * 100, '.').toString() + '"}')
604 .expect('x-store-foo', 'bar')
609 describe('charset', function () {
611 this.app
= createApp()
614 it('should parse utf-8', function (done
) {
615 var test
= request(this.app
).post('/')
616 test
.set('Content-Type', 'application/json; charset=utf-8')
617 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
618 test
.expect(200, '{"name":"论"}', done
)
621 it('should parse utf-16', function (done
) {
622 var test
= request(this.app
).post('/')
623 test
.set('Content-Type', 'application/json; charset=utf-16')
624 test
.write(Buffer
.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
625 test
.expect(200, '{"name":"论"}', done
)
628 it('should parse when content-length != char length', function (done
) {
629 var test
= request(this.app
).post('/')
630 test
.set('Content-Type', 'application/json; charset=utf-8')
631 test
.set('Content-Length', '13')
632 test
.write(Buffer
.from('7b2274657374223a22c3a5227d', 'hex'))
633 test
.expect(200, '{"test":"å"}', done
)
636 it('should default to utf-8', function (done
) {
637 var test
= request(this.app
).post('/')
638 test
.set('Content-Type', 'application/json')
639 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
640 test
.expect(200, '{"name":"论"}', done
)
643 it('should fail on unknown charset', function (done
) {
644 var test
= request(this.app
).post('/')
645 test
.set('Content-Type', 'application/json; charset=koi8-r')
646 test
.write(Buffer
.from('7b226e616d65223a22cec5d4227d', 'hex'))
647 test
.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done
)
651 describe('encoding', function () {
653 this.app
= createApp({ limit
: '1kb' })
656 it('should parse without encoding', function (done
) {
657 var test
= request(this.app
).post('/')
658 test
.set('Content-Type', 'application/json')
659 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
660 test
.expect(200, '{"name":"论"}', done
)
663 it('should support identity encoding', function (done
) {
664 var test
= request(this.app
).post('/')
665 test
.set('Content-Encoding', 'identity')
666 test
.set('Content-Type', 'application/json')
667 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
668 test
.expect(200, '{"name":"论"}', done
)
671 it('should support gzip encoding', function (done
) {
672 var test
= request(this.app
).post('/')
673 test
.set('Content-Encoding', 'gzip')
674 test
.set('Content-Type', 'application/json')
675 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
676 test
.expect(200, '{"name":"论"}', done
)
679 it('should support deflate encoding', function (done
) {
680 var test
= request(this.app
).post('/')
681 test
.set('Content-Encoding', 'deflate')
682 test
.set('Content-Type', 'application/json')
683 test
.write(Buffer
.from('789cab56ca4bcc4d55b2527ab16e97522d00274505ac', 'hex'))
684 test
.expect(200, '{"name":"论"}', done
)
687 it('should be case-insensitive', function (done
) {
688 var test
= request(this.app
).post('/')
689 test
.set('Content-Encoding', 'GZIP')
690 test
.set('Content-Type', 'application/json')
691 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
692 test
.expect(200, '{"name":"论"}', done
)
695 it('should 415 on unknown encoding', function (done
) {
696 var test
= request(this.app
).post('/')
697 test
.set('Content-Encoding', 'nulls')
698 test
.set('Content-Type', 'application/json')
699 test
.write(Buffer
.from('000000000000', 'hex'))
700 test
.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done
)
703 it('should 400 on malformed encoding', function (done
) {
704 var test
= request(this.app
).post('/')
705 test
.set('Content-Encoding', 'gzip')
706 test
.set('Content-Type', 'application/json')
707 test
.write(Buffer
.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
708 test
.expect(400, done
)
711 it('should 413 when inflated value exceeds limit', function (done
) {
712 // gzip'd data exceeds 1kb, but deflated below 1kb
713 var test
= request(this.app
).post('/')
714 test
.set('Content-Encoding', 'gzip')
715 test
.set('Content-Type', 'application/json')
716 test
.write(Buffer
.from('1f8b080000000000000bedc1010d000000c2a0f74f6d0f071400000000000000', 'hex'))
717 test
.write(Buffer
.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'))
718 test
.write(Buffer
.from('0000000000000000004f0625b3b71650c30000', 'hex'))
719 test
.expect(413, done
)
724 function createApp (options
) {
727 app
.use(express
.json(options
))
729 app
.use(function (err
, req
, res
, next
) {
731 res
.status(err
.status
|| 500)
732 res
.send(String(req
.headers
['x-error-property']
733 ? err
[req
.headers
['x-error-property']]
734 : ('[' + err
.type
+ '] ' + err
.message
)))
737 app
.post('/', function (req
, res
) {
744 function parseError (str
) {
746 JSON
.parse(str
); throw new SyntaxError('strict violation')
752 function shouldContainInBody (str
) {
753 return function (res
) {
754 assert
.ok(res
.text
.indexOf(str
) !== -1,
755 'expected \'' + res
.text
+ '\' to contain \'' + str
+ '\'')
759 function tryRequire (name
) {