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 it('should 400 when only whitespace', function (done) {
49 .set('Content-Type', 'application/json')
51 .expect(400, '[entity.parse.failed] ' + parseError(' '), 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 500 if stream not readable', function (done) {
78 app.use(function (req, res, next) {
83 app.use(express.json())
85 app.use(function (err, req, res, next) {
86 res.status(err.status || 500)
87 res.send('[' + err.type + '] ' + err.message)
90 app.post('/', function (req, res) {
96 .set('Content-Type', 'application/json')
97 .send('{"user":"tobi"}')
98 .expect(500, '[stream.not.readable] stream is not readable', done)
101 it('should handle duplicated middleware', function (done) {
104 app.use(express.json())
105 app.use(express.json())
107 app.post('/', function (req, res) {
113 .set('Content-Type', 'application/json')
114 .send('{"user":"tobi"}')
115 .expect(200, '{"user":"tobi"}', done)
118 describe('when JSON is invalid', function () {
120 this.app = createApp()
123 it('should 400 for bad token', function (done) {
126 .set('Content-Type', 'application/json')
128 .expect(400, '[entity.parse.failed] ' + parseError('{:'), done)
131 it('should 400 for incomplete', function (done) {
134 .set('Content-Type', 'application/json')
136 .expect(400, '[entity.parse.failed] ' + parseError('{"user"'), done)
139 it('should include original body on error object', function (done) {
142 .set('Content-Type', 'application/json')
143 .set('X-Error-Property', 'body')
145 .expect(400, ' {"user"', done)
149 describe('with limit option', function () {
150 it('should 413 when over limit with Content-Length', function (done) {
151 var buf = Buffer.alloc(1024, '.')
152 request(createApp({ limit: '1kb' }))
154 .set('Content-Type', 'application/json')
155 .set('Content-Length', '1034')
156 .send(JSON.stringify({ str: buf.toString() }))
157 .expect(413, '[entity.too.large] request entity too large', done)
160 it('should 413 when over limit with chunked encoding', function (done) {
161 var app = createApp({ limit: '1kb' })
162 var buf = Buffer.alloc(1024, '.')
163 var test = request(app).post('/')
164 test.set('Content-Type', 'application/json')
165 test.set('Transfer-Encoding', 'chunked')
166 test.write('{"str":')
167 test.write('"' + buf.toString() + '"}')
168 test.expect(413, done)
171 it('should 413 when inflated body over limit', function (done) {
172 var app = createApp({ limit: '1kb' })
173 var test = request(app).post('/')
174 test.set('Content-Encoding', 'gzip')
175 test.set('Content-Type', 'application/json')
176 test.write(Buffer.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a040000', 'hex'))
177 test.expect(413, done)
180 it('should accept number of bytes', function (done) {
181 var buf = Buffer.alloc(1024, '.')
182 request(createApp({ limit: 1024 }))
184 .set('Content-Type', 'application/json')
185 .send(JSON.stringify({ str: buf.toString() }))
189 it('should not change when options altered', function (done) {
190 var buf = Buffer.alloc(1024, '.')
191 var options = { limit: '1kb' }
192 var app = createApp(options)
194 options.limit = '100kb'
198 .set('Content-Type', 'application/json')
199 .send(JSON.stringify({ str: buf.toString() }))
203 it('should not hang response', function (done) {
204 var buf = Buffer.alloc(10240, '.')
205 var app = createApp({ limit: '8kb' })
206 var test = request(app).post('/')
207 test.set('Content-Type', 'application/json')
211 test.expect(413, done)
214 it('should not error when inflating', function (done) {
215 var app = createApp({ limit: '1kb' })
216 var test = request(app).post('/')
217 test.set('Content-Encoding', 'gzip')
218 test.set('Content-Type', 'application/json')
219 test.write(Buffer.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a0400', 'hex'))
220 test.expect(413, done)
224 describe('with inflate option', function () {
225 describe('when false', function () {
227 this.app = createApp({ inflate: false })
230 it('should not accept content-encoding', function (done) {
231 var test = request(this.app).post('/')
232 test.set('Content-Encoding', 'gzip')
233 test.set('Content-Type', 'application/json')
234 test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
235 test.expect(415, '[encoding.unsupported] content encoding unsupported', done)
239 describe('when true', function () {
241 this.app = createApp({ inflate: true })
244 it('should accept content-encoding', function (done) {
245 var test = request(this.app).post('/')
246 test.set('Content-Encoding', 'gzip')
247 test.set('Content-Type', 'application/json')
248 test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
249 test.expect(200, '{"name":"论"}', done)
254 describe('with strict option', function () {
255 describe('when undefined', function () {
257 this.app = createApp()
260 it('should 400 on primitives', function (done) {
263 .set('Content-Type', 'application/json')
265 .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace('#', 't'), done)
269 describe('when false', function () {
271 this.app = createApp({ strict: false })
274 it('should parse primitives', function (done) {
277 .set('Content-Type', 'application/json')
279 .expect(200, 'true', done)
283 describe('when true', function () {
285 this.app = createApp({ strict: true })
288 it('should not parse primitives', function (done) {
291 .set('Content-Type', 'application/json')
293 .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace('#', 't'), done)
296 it('should not parse primitives with leading whitespaces', function (done) {
299 .set('Content-Type', 'application/json')
301 .expect(400, '[entity.parse.failed] ' + parseError(' #rue').replace('#', 't'), done)
304 it('should allow leading whitespaces in JSON', function (done) {
307 .set('Content-Type', 'application/json')
308 .send(' { "user": "tobi" }')
309 .expect(200, '{"user":"tobi"}', done)
312 it('should include correct message in stack trace', function (done) {
315 .set('Content-Type', 'application/json')
316 .set('X-Error-Property', 'stack')
319 .expect(shouldContainInBody(parseError('#rue').replace('#', 't')))
325 describe('with type option', function () {
326 describe('when "application/vnd.api+json"', function () {
328 this.app = createApp({ type: 'application/vnd.api+json' })
331 it('should parse JSON for custom type', function (done) {
334 .set('Content-Type', 'application/vnd.api+json')
335 .send('{"user":"tobi"}')
336 .expect(200, '{"user":"tobi"}', done)
339 it('should ignore standard type', function (done) {
342 .set('Content-Type', 'application/json')
343 .send('{"user":"tobi"}')
344 .expect(200, '{}', done)
348 describe('when ["application/json", "application/vnd.api+json"]', function () {
350 this.app = createApp({
351 type: ['application/json', 'application/vnd.api+json']
355 it('should parse JSON for "application/json"', function (done) {
358 .set('Content-Type', 'application/json')
359 .send('{"user":"tobi"}')
360 .expect(200, '{"user":"tobi"}', done)
363 it('should parse JSON for "application/vnd.api+json"', function (done) {
366 .set('Content-Type', 'application/vnd.api+json')
367 .send('{"user":"tobi"}')
368 .expect(200, '{"user":"tobi"}', done)
371 it('should ignore "application/x-json"', function (done) {
374 .set('Content-Type', 'application/x-json')
375 .send('{"user":"tobi"}')
376 .expect(200, '{}', done)
380 describe('when a function', function () {
381 it('should parse when truthy value returned', function (done) {
382 var app = createApp({ type: accept })
384 function accept (req) {
385 return req.headers['content-type'] === 'application/vnd.api+json'
390 .set('Content-Type', 'application/vnd.api+json')
391 .send('{"user":"tobi"}')
392 .expect(200, '{"user":"tobi"}', done)
395 it('should work without content-type', function (done) {
396 var app = createApp({ type: accept })
398 function accept (req) {
402 var test = request(app).post('/')
403 test.write('{"user":"tobi"}')
404 test.expect(200, '{"user":"tobi"}', done)
407 it('should not invoke without a body', function (done) {
408 var app = createApp({ type: accept })
410 function accept (req) {
411 throw new Error('oops!')
421 describe('with verify option', function () {
422 it('should assert value if function', function () {
423 assert.throws(createApp.bind(null, { verify: 'lol' }),
424 /TypeError: option verify must be function/)
427 it('should error from verify', function (done) {
428 var app = createApp({
429 verify: function (req, res, buf) {
430 if (buf[0] === 0x5b) throw new Error('no arrays')
436 .set('Content-Type', 'application/json')
438 .expect(403, '[entity.verify.failed] no arrays', done)
441 it('should allow custom codes', function (done) {
442 var app = createApp({
443 verify: function (req, res, buf) {
444 if (buf[0] !== 0x5b) return
445 var err = new Error('no arrays')
453 .set('Content-Type', 'application/json')
455 .expect(400, '[entity.verify.failed] no arrays', done)
458 it('should allow custom type', function (done) {
459 var app = createApp({
460 verify: function (req, res, buf) {
461 if (buf[0] !== 0x5b) return
462 var err = new Error('no arrays')
470 .set('Content-Type', 'application/json')
472 .expect(403, '[foo.bar] no arrays', done)
475 it('should include original body on error object', function (done) {
476 var app = createApp({
477 verify: function (req, res, buf) {
478 if (buf[0] === 0x5b) throw new Error('no arrays')
484 .set('Content-Type', 'application/json')
485 .set('X-Error-Property', 'body')
487 .expect(403, '["tobi"]', done)
490 it('should allow pass-through', function (done) {
491 var app = createApp({
492 verify: function (req, res, buf) {
493 if (buf[0] === 0x5b) throw new Error('no arrays')
499 .set('Content-Type', 'application/json')
500 .send('{"user":"tobi"}')
501 .expect(200, '{"user":"tobi"}', done)
504 it('should work with different charsets', function (done) {
505 var app = createApp({
506 verify: function (req, res, buf) {
507 if (buf[0] === 0x5b) throw new Error('no arrays')
511 var test = request(app).post('/')
512 test.set('Content-Type', 'application/json; charset=utf-16')
513 test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
514 test.expect(200, '{"name":"论"}', done)
517 it('should 415 on unknown charset prior to verify', function (done) {
518 var app = createApp({
519 verify: function (req, res, buf) {
520 throw new Error('unexpected verify call')
524 var test = request(app).post('/')
525 test.set('Content-Type', 'application/json; charset=x-bogus')
526 test.write(Buffer.from('00000000', 'hex'))
527 test.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done)
531 describeAsyncHooks('async local storage', function () {
534 var store = { foo: 'bar' }
536 app.use(function (req, res, next) {
537 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
538 req.asyncLocalStorage.run(store, next)
541 app.use(express.json())
543 app.use(function (req, res, next) {
544 var local = req.asyncLocalStorage.getStore()
547 res.setHeader('x-store-foo', String(local.foo))
553 app.use(function (err, req, res, next) {
554 var local = req.asyncLocalStorage.getStore()
557 res.setHeader('x-store-foo', String(local.foo))
560 res.status(err.status || 500)
561 res.send('[' + err.type + '] ' + err.message)
564 app.post('/', function (req, res) {
571 it('should presist store', function (done) {
574 .set('Content-Type', 'application/json')
575 .send('{"user":"tobi"}')
577 .expect('x-store-foo', 'bar')
578 .expect('{"user":"tobi"}')
582 it('should presist store when unmatched content-type', function (done) {
585 .set('Content-Type', 'application/fizzbuzz')
588 .expect('x-store-foo', 'bar')
593 it('should presist store when inflated', function (done) {
594 var test = request(this.app).post('/')
595 test.set('Content-Encoding', 'gzip')
596 test.set('Content-Type', 'application/json')
597 test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
599 test.expect('x-store-foo', 'bar')
600 test.expect('{"name":"论"}')
604 it('should presist store when inflate error', function (done) {
605 var test = request(this.app).post('/')
606 test.set('Content-Encoding', 'gzip')
607 test.set('Content-Type', 'application/json')
608 test.write(Buffer.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
610 test.expect('x-store-foo', 'bar')
614 it('should presist store when parse error', function (done) {
617 .set('Content-Type', 'application/json')
620 .expect('x-store-foo', 'bar')
624 it('should presist store when limit exceeded', function (done) {
627 .set('Content-Type', 'application/json')
628 .send('{"user":"' + Buffer.alloc(1024 * 100, '.').toString() + '"}')
630 .expect('x-store-foo', 'bar')
635 describe('charset', function () {
637 this.app = createApp()
640 it('should parse utf-8', function (done) {
641 var test = request(this.app).post('/')
642 test.set('Content-Type', 'application/json; charset=utf-8')
643 test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
644 test.expect(200, '{"name":"论"}', done)
647 it('should parse utf-16', function (done) {
648 var test = request(this.app).post('/')
649 test.set('Content-Type', 'application/json; charset=utf-16')
650 test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
651 test.expect(200, '{"name":"论"}', done)
654 it('should parse when content-length != char length', function (done) {
655 var test = request(this.app).post('/')
656 test.set('Content-Type', 'application/json; charset=utf-8')
657 test.set('Content-Length', '13')
658 test.write(Buffer.from('7b2274657374223a22c3a5227d', 'hex'))
659 test.expect(200, '{"test":"å"}', done)
662 it('should default to utf-8', function (done) {
663 var test = request(this.app).post('/')
664 test.set('Content-Type', 'application/json')
665 test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
666 test.expect(200, '{"name":"论"}', done)
669 it('should fail on unknown charset', function (done) {
670 var test = request(this.app).post('/')
671 test.set('Content-Type', 'application/json; charset=koi8-r')
672 test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex'))
673 test.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done)
677 describe('encoding', function () {
679 this.app = createApp({ limit: '1kb' })
682 it('should parse without encoding', function (done) {
683 var test = request(this.app).post('/')
684 test.set('Content-Type', 'application/json')
685 test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
686 test.expect(200, '{"name":"论"}', done)
689 it('should support identity encoding', function (done) {
690 var test = request(this.app).post('/')
691 test.set('Content-Encoding', 'identity')
692 test.set('Content-Type', 'application/json')
693 test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
694 test.expect(200, '{"name":"论"}', done)
697 it('should support gzip encoding', function (done) {
698 var test = request(this.app).post('/')
699 test.set('Content-Encoding', 'gzip')
700 test.set('Content-Type', 'application/json')
701 test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
702 test.expect(200, '{"name":"论"}', done)
705 it('should support deflate encoding', function (done) {
706 var test = request(this.app).post('/')
707 test.set('Content-Encoding', 'deflate')
708 test.set('Content-Type', 'application/json')
709 test.write(Buffer.from('789cab56ca4bcc4d55b2527ab16e97522d00274505ac', 'hex'))
710 test.expect(200, '{"name":"论"}', done)
713 it('should be case-insensitive', function (done) {
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('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
718 test.expect(200, '{"name":"论"}', done)
721 it('should 415 on unknown encoding', function (done) {
722 var test = request(this.app).post('/')
723 test.set('Content-Encoding', 'nulls')
724 test.set('Content-Type', 'application/json')
725 test.write(Buffer.from('000000000000', 'hex'))
726 test.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done)
729 it('should 400 on malformed encoding', function (done) {
730 var test = request(this.app).post('/')
731 test.set('Content-Encoding', 'gzip')
732 test.set('Content-Type', 'application/json')
733 test.write(Buffer.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
734 test.expect(400, done)
737 it('should 413 when inflated value exceeds limit', function (done) {
738 // gzip'd data exceeds 1kb, but deflated below 1kb
739 var test = request(this.app).post('/')
740 test.set('Content-Encoding', 'gzip')
741 test.set('Content-Type', 'application/json')
742 test.write(Buffer.from('1f8b080000000000000bedc1010d000000c2a0f74f6d0f071400000000000000', 'hex'))
743 test.write(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'))
744 test.write(Buffer.from('0000000000000000004f0625b3b71650c30000', 'hex'))
745 test.expect(413, done)
750 function createApp (options) {
753 app.use(express.json(options))
755 app.use(function (err, req, res, next) {
756 res.status(err.status || 500)
757 res.send(String(req.headers['x-error-property']
758 ? err[req.headers['x-error-property']]
759 : ('[' + err.type + '] ' + err.message)))
762 app.post('/', function (req, res) {
769 function parseError (str) {
771 JSON.parse(str); throw new SyntaxError('strict violation')
777 function shouldContainInBody (str) {
778 return function (res) {
779 assert.ok(res.text.indexOf(str) !== -1,
780 'expected \'' + res.text + '\' to contain \'' + str + '\'')
784 function tryRequire (name) {