3 var assert
= require('assert')
4 var asyncHooks
= tryRequire('async_hooks')
5 var Buffer
= require('safe-buffer').Buffer
6 var express
= require('..')
7 var request
= require('supertest')
9 var describeAsyncHooks
= typeof asyncHooks
.AsyncLocalStorage
=== 'function'
13 describe('express.json()', function () {
14 it('should parse JSON', function (done
) {
17 .set('Content-Type', 'application/json')
18 .send('{"user":"tobi"}')
19 .expect(200, '{"user":"tobi"}', done
)
22 it('should handle Content-Length: 0', function (done
) {
25 .set('Content-Type', 'application/json')
26 .set('Content-Length', '0')
27 .expect(200, '{}', done
)
30 it('should handle empty message-body', function (done
) {
33 .set('Content-Type', 'application/json')
34 .set('Transfer-Encoding', 'chunked')
35 .expect(200, '{}', done
)
38 it('should handle no message-body', function (done
) {
41 .set('Content-Type', 'application/json')
42 .unset('Transfer-Encoding')
43 .expect(200, '{}', done
)
46 // The old node error message modification in body parser is catching this
47 it('should 400 when only whitespace', function (done
) {
50 .set('Content-Type', 'application/json')
52 .expect(400, '[entity.parse.failed] ' + parseError(' \n'), done
)
55 it('should 400 when invalid content-length', function (done
) {
58 app
.use(function (req
, res
, next
) {
59 req
.headers
['content-length'] = '20' // bad length
63 app
.use(express
.json())
65 app
.post('/', function (req
, res
) {
71 .set('Content-Type', 'application/json')
73 .expect(400, /content length/, done
)
76 it('should handle duplicated middleware', function (done
) {
79 app
.use(express
.json())
80 app
.use(express
.json())
82 app
.post('/', function (req
, res
) {
88 .set('Content-Type', 'application/json')
89 .send('{"user":"tobi"}')
90 .expect(200, '{"user":"tobi"}', done
)
93 describe('when JSON is invalid', function () {
95 this.app
= createApp()
98 it('should 400 for bad token', function (done
) {
101 .set('Content-Type', 'application/json')
103 .expect(400, '[entity.parse.failed] ' + parseError('{:'), done
)
106 it('should 400 for incomplete', function (done
) {
109 .set('Content-Type', 'application/json')
111 .expect(400, '[entity.parse.failed] ' + parseError('{"user"'), done
)
114 it('should include original body on error object', function (done
) {
117 .set('Content-Type', 'application/json')
118 .set('X-Error-Property', 'body')
120 .expect(400, ' {"user"', done
)
124 describe('with limit option', function () {
125 it('should 413 when over limit with Content-Length', function (done
) {
126 var buf
= Buffer
.alloc(1024, '.')
127 request(createApp({ limit
: '1kb' }))
129 .set('Content-Type', 'application/json')
130 .set('Content-Length', '1034')
131 .send(JSON
.stringify({ str
: buf
.toString() }))
132 .expect(413, '[entity.too.large] request entity too large', done
)
135 it('should 413 when over limit with chunked encoding', function (done
) {
136 var app
= createApp({ limit
: '1kb' })
137 var buf
= Buffer
.alloc(1024, '.')
138 var test
= request(app
).post('/')
139 test
.set('Content-Type', 'application/json')
140 test
.set('Transfer-Encoding', 'chunked')
141 test
.write('{"str":')
142 test
.write('"' + buf
.toString() + '"}')
143 test
.expect(413, done
)
146 it('should 413 when inflated body over limit', function (done
) {
147 var app
= createApp({ limit
: '1kb' })
148 var test
= request(app
).post('/')
149 test
.set('Content-Encoding', 'gzip')
150 test
.set('Content-Type', 'application/json')
151 test
.write(Buffer
.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a040000', 'hex'))
152 test
.expect(413, done
)
155 it('should accept number of bytes', function (done
) {
156 var buf
= Buffer
.alloc(1024, '.')
157 request(createApp({ limit
: 1024 }))
159 .set('Content-Type', 'application/json')
160 .send(JSON
.stringify({ str
: buf
.toString() }))
164 it('should not change when options altered', function (done
) {
165 var buf
= Buffer
.alloc(1024, '.')
166 var options
= { limit
: '1kb' }
167 var app
= createApp(options
)
169 options
.limit
= '100kb'
173 .set('Content-Type', 'application/json')
174 .send(JSON
.stringify({ str
: buf
.toString() }))
178 it('should not hang response', function (done
) {
179 var buf
= Buffer
.alloc(10240, '.')
180 var app
= createApp({ limit
: '8kb' })
181 var test
= request(app
).post('/')
182 test
.set('Content-Type', 'application/json')
186 test
.expect(413, done
)
189 it('should not error when inflating', function (done
) {
190 var app
= createApp({ limit
: '1kb' })
191 var test
= request(app
).post('/')
192 test
.set('Content-Encoding', 'gzip')
193 test
.set('Content-Type', 'application/json')
194 test
.write(Buffer
.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a0400', 'hex'))
195 test
.expect(413, done
)
199 describe('with inflate option', function () {
200 describe('when false', function () {
202 this.app
= createApp({ inflate
: false })
205 it('should not accept content-encoding', function (done
) {
206 var test
= request(this.app
).post('/')
207 test
.set('Content-Encoding', 'gzip')
208 test
.set('Content-Type', 'application/json')
209 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
210 test
.expect(415, '[encoding.unsupported] content encoding unsupported', done
)
214 describe('when true', function () {
216 this.app
= createApp({ inflate
: true })
219 it('should accept content-encoding', function (done
) {
220 var test
= request(this.app
).post('/')
221 test
.set('Content-Encoding', 'gzip')
222 test
.set('Content-Type', 'application/json')
223 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
224 test
.expect(200, '{"name":"论"}', done
)
229 describe('with strict option', function () {
230 describe('when undefined', function () {
232 this.app
= createApp()
235 it('should 400 on primitives', function (done
) {
238 .set('Content-Type', 'application/json')
240 .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace(/#/g
, 't'), done
)
244 describe('when false', function () {
246 this.app
= createApp({ strict
: false })
249 it('should parse primitives', function (done
) {
252 .set('Content-Type', 'application/json')
254 .expect(200, 'true', done
)
258 describe('when true', function () {
260 this.app
= createApp({ strict
: true })
263 it('should not parse primitives', function (done
) {
266 .set('Content-Type', 'application/json')
268 .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace(/#/g
, 't'), done
)
271 it('should not parse primitives with leading whitespaces', function (done
) {
274 .set('Content-Type', 'application/json')
276 .expect(400, '[entity.parse.failed] ' + parseError(' #rue').replace(/#/g
, 't'), done
)
279 it('should allow leading whitespaces in JSON', function (done
) {
282 .set('Content-Type', 'application/json')
283 .send(' { "user": "tobi" }')
284 .expect(200, '{"user":"tobi"}', done
)
287 it('should include correct message in stack trace', function (done
) {
290 .set('Content-Type', 'application/json')
291 .set('X-Error-Property', 'stack')
294 .expect(shouldContainInBody(parseError('#rue').replace(/#/g
, 't')))
300 describe('with type option', function () {
301 describe('when "application/vnd.api+json"', function () {
303 this.app
= createApp({ type
: 'application/vnd.api+json' })
306 it('should parse JSON for custom type', function (done
) {
309 .set('Content-Type', 'application/vnd.api+json')
310 .send('{"user":"tobi"}')
311 .expect(200, '{"user":"tobi"}', done
)
314 it('should ignore standard type', function (done
) {
317 .set('Content-Type', 'application/json')
318 .send('{"user":"tobi"}')
319 .expect(200, '', done
)
323 describe('when ["application/json", "application/vnd.api+json"]', function () {
325 this.app
= createApp({
326 type
: ['application/json', 'application/vnd.api+json']
330 it('should parse JSON for "application/json"', function (done
) {
333 .set('Content-Type', 'application/json')
334 .send('{"user":"tobi"}')
335 .expect(200, '{"user":"tobi"}', done
)
338 it('should parse JSON for "application/vnd.api+json"', function (done
) {
341 .set('Content-Type', 'application/vnd.api+json')
342 .send('{"user":"tobi"}')
343 .expect(200, '{"user":"tobi"}', done
)
346 it('should ignore "application/x-json"', function (done
) {
349 .set('Content-Type', 'application/x-json')
350 .send('{"user":"tobi"}')
351 .expect(200, '', done
)
355 describe('when a function', function () {
356 it('should parse when truthy value returned', function (done
) {
357 var app
= createApp({ type
: accept
})
359 function accept (req
) {
360 return req
.headers
['content-type'] === 'application/vnd.api+json'
365 .set('Content-Type', 'application/vnd.api+json')
366 .send('{"user":"tobi"}')
367 .expect(200, '{"user":"tobi"}', done
)
370 it('should work without content-type', function (done
) {
371 var app
= createApp({ type
: accept
})
373 function accept (req
) {
377 var test
= request(app
).post('/')
378 test
.write('{"user":"tobi"}')
379 test
.expect(200, '{"user":"tobi"}', done
)
382 it('should not invoke without a body', function (done
) {
383 var app
= createApp({ type
: accept
})
385 function accept (req
) {
386 throw new Error('oops!')
396 describe('with verify option', function () {
397 it('should assert value if function', function () {
398 assert
.throws(createApp
.bind(null, { verify
: 'lol' }),
399 /TypeError: option verify must be function/)
402 it('should error from verify', function (done
) {
403 var app
= createApp({
404 verify: function (req
, res
, buf
) {
405 if (buf
[0] === 0x5b) throw new Error('no arrays')
411 .set('Content-Type', 'application/json')
413 .expect(403, '[entity.verify.failed] no arrays', done
)
416 it('should allow custom codes', function (done
) {
417 var app
= createApp({
418 verify: function (req
, res
, buf
) {
419 if (buf
[0] !== 0x5b) return
420 var err
= new Error('no arrays')
428 .set('Content-Type', 'application/json')
430 .expect(400, '[entity.verify.failed] no arrays', done
)
433 it('should allow custom type', function (done
) {
434 var app
= createApp({
435 verify: function (req
, res
, buf
) {
436 if (buf
[0] !== 0x5b) return
437 var err
= new Error('no arrays')
445 .set('Content-Type', 'application/json')
447 .expect(403, '[foo.bar] no arrays', done
)
450 it('should include original body on error object', function (done
) {
451 var app
= createApp({
452 verify: function (req
, res
, buf
) {
453 if (buf
[0] === 0x5b) throw new Error('no arrays')
459 .set('Content-Type', 'application/json')
460 .set('X-Error-Property', 'body')
462 .expect(403, '["tobi"]', done
)
465 it('should allow pass-through', function (done
) {
466 var app
= createApp({
467 verify: function (req
, res
, buf
) {
468 if (buf
[0] === 0x5b) throw new Error('no arrays')
474 .set('Content-Type', 'application/json')
475 .send('{"user":"tobi"}')
476 .expect(200, '{"user":"tobi"}', done
)
479 it('should work with different charsets', function (done
) {
480 var app
= createApp({
481 verify: function (req
, res
, buf
) {
482 if (buf
[0] === 0x5b) throw new Error('no arrays')
486 var test
= request(app
).post('/')
487 test
.set('Content-Type', 'application/json; charset=utf-16')
488 test
.write(Buffer
.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
489 test
.expect(200, '{"name":"论"}', done
)
492 it('should 415 on unknown charset prior to verify', function (done
) {
493 var app
= createApp({
494 verify: function (req
, res
, buf
) {
495 throw new Error('unexpected verify call')
499 var test
= request(app
).post('/')
500 test
.set('Content-Type', 'application/json; charset=x-bogus')
501 test
.write(Buffer
.from('00000000', 'hex'))
502 test
.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done
)
506 describeAsyncHooks('async local storage', function () {
509 var store
= { foo
: 'bar' }
511 app
.use(function (req
, res
, next
) {
512 req
.asyncLocalStorage
= new asyncHooks
.AsyncLocalStorage()
513 req
.asyncLocalStorage
.run(store
, next
)
516 app
.use(express
.json())
518 app
.use(function (req
, res
, next
) {
519 var local
= req
.asyncLocalStorage
.getStore()
522 res
.setHeader('x-store-foo', String(local
.foo
))
528 app
.use(function (err
, req
, res
, next
) {
529 var local
= req
.asyncLocalStorage
.getStore()
532 res
.setHeader('x-store-foo', String(local
.foo
))
535 res
.status(err
.status
|| 500)
536 res
.send('[' + err
.type
+ '] ' + err
.message
)
539 app
.post('/', function (req
, res
) {
546 it('should presist store', function (done
) {
549 .set('Content-Type', 'application/json')
550 .send('{"user":"tobi"}')
552 .expect('x-store-foo', 'bar')
553 .expect('{"user":"tobi"}')
557 it('should persist store when unmatched content-type', function (done
) {
560 .set('Content-Type', 'application/fizzbuzz')
563 .expect('x-store-foo', 'bar')
568 it('should presist store when inflated', function (done
) {
569 var test
= request(this.app
).post('/')
570 test
.set('Content-Encoding', 'gzip')
571 test
.set('Content-Type', 'application/json')
572 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
574 test
.expect('x-store-foo', 'bar')
575 test
.expect('{"name":"论"}')
579 it('should presist store when inflate error', function (done
) {
580 var test
= request(this.app
).post('/')
581 test
.set('Content-Encoding', 'gzip')
582 test
.set('Content-Type', 'application/json')
583 test
.write(Buffer
.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
585 test
.expect('x-store-foo', 'bar')
589 it('should presist store when parse error', function (done
) {
592 .set('Content-Type', 'application/json')
595 .expect('x-store-foo', 'bar')
599 it('should presist store when limit exceeded', function (done
) {
602 .set('Content-Type', 'application/json')
603 .send('{"user":"' + Buffer
.alloc(1024 * 100, '.').toString() + '"}')
605 .expect('x-store-foo', 'bar')
610 describe('charset', function () {
612 this.app
= createApp()
615 it('should parse utf-8', function (done
) {
616 var test
= request(this.app
).post('/')
617 test
.set('Content-Type', 'application/json; charset=utf-8')
618 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
619 test
.expect(200, '{"name":"论"}', done
)
622 it('should parse utf-16', function (done
) {
623 var test
= request(this.app
).post('/')
624 test
.set('Content-Type', 'application/json; charset=utf-16')
625 test
.write(Buffer
.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
626 test
.expect(200, '{"name":"论"}', done
)
629 it('should parse when content-length != char length', function (done
) {
630 var test
= request(this.app
).post('/')
631 test
.set('Content-Type', 'application/json; charset=utf-8')
632 test
.set('Content-Length', '13')
633 test
.write(Buffer
.from('7b2274657374223a22c3a5227d', 'hex'))
634 test
.expect(200, '{"test":"å"}', done
)
637 it('should default to utf-8', function (done
) {
638 var test
= request(this.app
).post('/')
639 test
.set('Content-Type', 'application/json')
640 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
641 test
.expect(200, '{"name":"论"}', done
)
644 it('should fail on unknown charset', function (done
) {
645 var test
= request(this.app
).post('/')
646 test
.set('Content-Type', 'application/json; charset=koi8-r')
647 test
.write(Buffer
.from('7b226e616d65223a22cec5d4227d', 'hex'))
648 test
.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done
)
652 describe('encoding', function () {
654 this.app
= createApp({ limit
: '1kb' })
657 it('should parse without encoding', function (done
) {
658 var test
= request(this.app
).post('/')
659 test
.set('Content-Type', 'application/json')
660 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
661 test
.expect(200, '{"name":"论"}', done
)
664 it('should support identity encoding', function (done
) {
665 var test
= request(this.app
).post('/')
666 test
.set('Content-Encoding', 'identity')
667 test
.set('Content-Type', 'application/json')
668 test
.write(Buffer
.from('7b226e616d65223a22e8aeba227d', 'hex'))
669 test
.expect(200, '{"name":"论"}', done
)
672 it('should support gzip encoding', function (done
) {
673 var test
= request(this.app
).post('/')
674 test
.set('Content-Encoding', 'gzip')
675 test
.set('Content-Type', 'application/json')
676 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
677 test
.expect(200, '{"name":"论"}', done
)
680 it('should support deflate encoding', function (done
) {
681 var test
= request(this.app
).post('/')
682 test
.set('Content-Encoding', 'deflate')
683 test
.set('Content-Type', 'application/json')
684 test
.write(Buffer
.from('789cab56ca4bcc4d55b2527ab16e97522d00274505ac', 'hex'))
685 test
.expect(200, '{"name":"论"}', done
)
688 it('should be case-insensitive', function (done
) {
689 var test
= request(this.app
).post('/')
690 test
.set('Content-Encoding', 'GZIP')
691 test
.set('Content-Type', 'application/json')
692 test
.write(Buffer
.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
693 test
.expect(200, '{"name":"论"}', done
)
696 it('should 415 on unknown encoding', function (done
) {
697 var test
= request(this.app
).post('/')
698 test
.set('Content-Encoding', 'nulls')
699 test
.set('Content-Type', 'application/json')
700 test
.write(Buffer
.from('000000000000', 'hex'))
701 test
.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done
)
704 it('should 400 on malformed encoding', function (done
) {
705 var test
= request(this.app
).post('/')
706 test
.set('Content-Encoding', 'gzip')
707 test
.set('Content-Type', 'application/json')
708 test
.write(Buffer
.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
709 test
.expect(400, done
)
712 it('should 413 when inflated value exceeds limit', function (done
) {
713 // gzip'd data exceeds 1kb, but deflated below 1kb
714 var test
= request(this.app
).post('/')
715 test
.set('Content-Encoding', 'gzip')
716 test
.set('Content-Type', 'application/json')
717 test
.write(Buffer
.from('1f8b080000000000000bedc1010d000000c2a0f74f6d0f071400000000000000', 'hex'))
718 test
.write(Buffer
.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'))
719 test
.write(Buffer
.from('0000000000000000004f0625b3b71650c30000', 'hex'))
720 test
.expect(413, done
)
725 function createApp (options
) {
728 app
.use(express
.json(options
))
730 app
.use(function (err
, req
, res
, next
) {
732 res
.status(err
.status
|| 500)
733 res
.send(String(req
.headers
['x-error-property']
734 ? err
[req
.headers
['x-error-property']]
735 : ('[' + err
.type
+ '] ' + err
.message
)))
738 app
.post('/', function (req
, res
) {
745 function parseError (str
) {
747 JSON
.parse(str
); throw new SyntaxError('strict violation')
753 function shouldContainInBody (str
) {
754 return function (res
) {
755 assert
.ok(res
.text
.indexOf(str
) !== -1,
756 'expected \'' + res
.text
+ '\' to contain \'' + str
+ '\'')
760 function tryRequire (name
) {