deps: body-parser@1.20.0
[express.git] / test / express.json.js
bloba8cfebc41e2614b1d647ddce626f6355621d5782
1 'use strict'
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'
10   ? describe
11   : describe.skip
13 describe('express.json()', function () {
14   it('should parse JSON', function (done) {
15     request(createApp())
16       .post('/')
17       .set('Content-Type', 'application/json')
18       .send('{"user":"tobi"}')
19       .expect(200, '{"user":"tobi"}', done)
20   })
22   it('should handle Content-Length: 0', function (done) {
23     request(createApp())
24       .post('/')
25       .set('Content-Type', 'application/json')
26       .set('Content-Length', '0')
27       .expect(200, '{}', done)
28   })
30   it('should handle empty message-body', function (done) {
31     request(createApp())
32       .post('/')
33       .set('Content-Type', 'application/json')
34       .set('Transfer-Encoding', 'chunked')
35       .expect(200, '{}', done)
36   })
38   it('should handle no message-body', function (done) {
39     request(createApp())
40       .post('/')
41       .set('Content-Type', 'application/json')
42       .unset('Transfer-Encoding')
43       .expect(200, '{}', done)
44   })
46   it('should 400 when only whitespace', function (done) {
47     request(createApp())
48       .post('/')
49       .set('Content-Type', 'application/json')
50       .send('  \n')
51       .expect(400, '[entity.parse.failed] ' + parseError(' '), done)
52   })
54   it('should 400 when invalid content-length', function (done) {
55     var app = express()
57     app.use(function (req, res, next) {
58       req.headers['content-length'] = '20' // bad length
59       next()
60     })
62     app.use(express.json())
64     app.post('/', function (req, res) {
65       res.json(req.body)
66     })
68     request(app)
69       .post('/')
70       .set('Content-Type', 'application/json')
71       .send('{"str":')
72       .expect(400, /content length/, done)
73   })
75   it('should 500 if stream not readable', function (done) {
76     var app = express()
78     app.use(function (req, res, next) {
79       req.on('end', next)
80       req.resume()
81     })
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)
88     })
90     app.post('/', function (req, res) {
91       res.json(req.body)
92     })
94     request(app)
95       .post('/')
96       .set('Content-Type', 'application/json')
97       .send('{"user":"tobi"}')
98       .expect(500, '[stream.not.readable] stream is not readable', done)
99   })
101   it('should handle duplicated middleware', function (done) {
102     var app = express()
104     app.use(express.json())
105     app.use(express.json())
107     app.post('/', function (req, res) {
108       res.json(req.body)
109     })
111     request(app)
112       .post('/')
113       .set('Content-Type', 'application/json')
114       .send('{"user":"tobi"}')
115       .expect(200, '{"user":"tobi"}', done)
116   })
118   describe('when JSON is invalid', function () {
119     before(function () {
120       this.app = createApp()
121     })
123     it('should 400 for bad token', function (done) {
124       request(this.app)
125         .post('/')
126         .set('Content-Type', 'application/json')
127         .send('{:')
128         .expect(400, '[entity.parse.failed] ' + parseError('{:'), done)
129     })
131     it('should 400 for incomplete', function (done) {
132       request(this.app)
133         .post('/')
134         .set('Content-Type', 'application/json')
135         .send('{"user"')
136         .expect(400, '[entity.parse.failed] ' + parseError('{"user"'), done)
137     })
139     it('should include original body on error object', function (done) {
140       request(this.app)
141         .post('/')
142         .set('Content-Type', 'application/json')
143         .set('X-Error-Property', 'body')
144         .send(' {"user"')
145         .expect(400, ' {"user"', done)
146     })
147   })
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' }))
153         .post('/')
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)
158     })
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)
169     })
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)
178     })
180     it('should accept number of bytes', function (done) {
181       var buf = Buffer.alloc(1024, '.')
182       request(createApp({ limit: 1024 }))
183         .post('/')
184         .set('Content-Type', 'application/json')
185         .send(JSON.stringify({ str: buf.toString() }))
186         .expect(413, done)
187     })
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'
196       request(app)
197         .post('/')
198         .set('Content-Type', 'application/json')
199         .send(JSON.stringify({ str: buf.toString() }))
200         .expect(413, done)
201     })
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')
208       test.write(buf)
209       test.write(buf)
210       test.write(buf)
211       test.expect(413, done)
212     })
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)
221     })
222   })
224   describe('with inflate option', function () {
225     describe('when false', function () {
226       before(function () {
227         this.app = createApp({ inflate: false })
228       })
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)
236       })
237     })
239     describe('when true', function () {
240       before(function () {
241         this.app = createApp({ inflate: true })
242       })
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)
250       })
251     })
252   })
254   describe('with strict option', function () {
255     describe('when undefined', function () {
256       before(function () {
257         this.app = createApp()
258       })
260       it('should 400 on primitives', function (done) {
261         request(this.app)
262           .post('/')
263           .set('Content-Type', 'application/json')
264           .send('true')
265           .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace('#', 't'), done)
266       })
267     })
269     describe('when false', function () {
270       before(function () {
271         this.app = createApp({ strict: false })
272       })
274       it('should parse primitives', function (done) {
275         request(this.app)
276           .post('/')
277           .set('Content-Type', 'application/json')
278           .send('true')
279           .expect(200, 'true', done)
280       })
281     })
283     describe('when true', function () {
284       before(function () {
285         this.app = createApp({ strict: true })
286       })
288       it('should not parse primitives', function (done) {
289         request(this.app)
290           .post('/')
291           .set('Content-Type', 'application/json')
292           .send('true')
293           .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace('#', 't'), done)
294       })
296       it('should not parse primitives with leading whitespaces', function (done) {
297         request(this.app)
298           .post('/')
299           .set('Content-Type', 'application/json')
300           .send('    true')
301           .expect(400, '[entity.parse.failed] ' + parseError('    #rue').replace('#', 't'), done)
302       })
304       it('should allow leading whitespaces in JSON', function (done) {
305         request(this.app)
306           .post('/')
307           .set('Content-Type', 'application/json')
308           .send('   { "user": "tobi" }')
309           .expect(200, '{"user":"tobi"}', done)
310       })
312       it('should include correct message in stack trace', function (done) {
313         request(this.app)
314           .post('/')
315           .set('Content-Type', 'application/json')
316           .set('X-Error-Property', 'stack')
317           .send('true')
318           .expect(400)
319           .expect(shouldContainInBody(parseError('#rue').replace('#', 't')))
320           .end(done)
321       })
322     })
323   })
325   describe('with type option', function () {
326     describe('when "application/vnd.api+json"', function () {
327       before(function () {
328         this.app = createApp({ type: 'application/vnd.api+json' })
329       })
331       it('should parse JSON for custom type', function (done) {
332         request(this.app)
333           .post('/')
334           .set('Content-Type', 'application/vnd.api+json')
335           .send('{"user":"tobi"}')
336           .expect(200, '{"user":"tobi"}', done)
337       })
339       it('should ignore standard type', function (done) {
340         request(this.app)
341           .post('/')
342           .set('Content-Type', 'application/json')
343           .send('{"user":"tobi"}')
344           .expect(200, '{}', done)
345       })
346     })
348     describe('when ["application/json", "application/vnd.api+json"]', function () {
349       before(function () {
350         this.app = createApp({
351           type: ['application/json', 'application/vnd.api+json']
352         })
353       })
355       it('should parse JSON for "application/json"', function (done) {
356         request(this.app)
357           .post('/')
358           .set('Content-Type', 'application/json')
359           .send('{"user":"tobi"}')
360           .expect(200, '{"user":"tobi"}', done)
361       })
363       it('should parse JSON for "application/vnd.api+json"', function (done) {
364         request(this.app)
365           .post('/')
366           .set('Content-Type', 'application/vnd.api+json')
367           .send('{"user":"tobi"}')
368           .expect(200, '{"user":"tobi"}', done)
369       })
371       it('should ignore "application/x-json"', function (done) {
372         request(this.app)
373           .post('/')
374           .set('Content-Type', 'application/x-json')
375           .send('{"user":"tobi"}')
376           .expect(200, '{}', done)
377       })
378     })
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'
386         }
388         request(app)
389           .post('/')
390           .set('Content-Type', 'application/vnd.api+json')
391           .send('{"user":"tobi"}')
392           .expect(200, '{"user":"tobi"}', done)
393       })
395       it('should work without content-type', function (done) {
396         var app = createApp({ type: accept })
398         function accept (req) {
399           return true
400         }
402         var test = request(app).post('/')
403         test.write('{"user":"tobi"}')
404         test.expect(200, '{"user":"tobi"}', done)
405       })
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!')
412         }
414         request(app)
415           .get('/')
416           .expect(404, done)
417       })
418     })
419   })
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/)
425     })
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')
431         }
432       })
434       request(app)
435         .post('/')
436         .set('Content-Type', 'application/json')
437         .send('["tobi"]')
438         .expect(403, '[entity.verify.failed] no arrays', done)
439     })
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')
446           err.status = 400
447           throw err
448         }
449       })
451       request(app)
452         .post('/')
453         .set('Content-Type', 'application/json')
454         .send('["tobi"]')
455         .expect(400, '[entity.verify.failed] no arrays', done)
456     })
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')
463           err.type = 'foo.bar'
464           throw err
465         }
466       })
468       request(app)
469         .post('/')
470         .set('Content-Type', 'application/json')
471         .send('["tobi"]')
472         .expect(403, '[foo.bar] no arrays', done)
473     })
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')
479         }
480       })
482       request(app)
483         .post('/')
484         .set('Content-Type', 'application/json')
485         .set('X-Error-Property', 'body')
486         .send('["tobi"]')
487         .expect(403, '["tobi"]', done)
488     })
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')
494         }
495       })
497       request(app)
498         .post('/')
499         .set('Content-Type', 'application/json')
500         .send('{"user":"tobi"}')
501         .expect(200, '{"user":"tobi"}', done)
502     })
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')
508         }
509       })
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)
515     })
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')
521         }
522       })
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)
528     })
529   })
531   describeAsyncHooks('async local storage', function () {
532     before(function () {
533       var app = express()
534       var store = { foo: 'bar' }
536       app.use(function (req, res, next) {
537         req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
538         req.asyncLocalStorage.run(store, next)
539       })
541       app.use(express.json())
543       app.use(function (req, res, next) {
544         var local = req.asyncLocalStorage.getStore()
546         if (local) {
547           res.setHeader('x-store-foo', String(local.foo))
548         }
550         next()
551       })
553       app.use(function (err, req, res, next) {
554         var local = req.asyncLocalStorage.getStore()
556         if (local) {
557           res.setHeader('x-store-foo', String(local.foo))
558         }
560         res.status(err.status || 500)
561         res.send('[' + err.type + '] ' + err.message)
562       })
564       app.post('/', function (req, res) {
565         res.json(req.body)
566       })
568       this.app = app
569     })
571     it('should presist store', function (done) {
572       request(this.app)
573         .post('/')
574         .set('Content-Type', 'application/json')
575         .send('{"user":"tobi"}')
576         .expect(200)
577         .expect('x-store-foo', 'bar')
578         .expect('{"user":"tobi"}')
579         .end(done)
580     })
582     it('should presist store when unmatched content-type', function (done) {
583       request(this.app)
584         .post('/')
585         .set('Content-Type', 'application/fizzbuzz')
586         .send('buzz')
587         .expect(200)
588         .expect('x-store-foo', 'bar')
589         .expect('{}')
590         .end(done)
591     })
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'))
598       test.expect(200)
599       test.expect('x-store-foo', 'bar')
600       test.expect('{"name":"论"}')
601       test.end(done)
602     })
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'))
609       test.expect(400)
610       test.expect('x-store-foo', 'bar')
611       test.end(done)
612     })
614     it('should presist store when parse error', function (done) {
615       request(this.app)
616         .post('/')
617         .set('Content-Type', 'application/json')
618         .send('{"user":')
619         .expect(400)
620         .expect('x-store-foo', 'bar')
621         .end(done)
622     })
624     it('should presist store when limit exceeded', function (done) {
625       request(this.app)
626         .post('/')
627         .set('Content-Type', 'application/json')
628         .send('{"user":"' + Buffer.alloc(1024 * 100, '.').toString() + '"}')
629         .expect(413)
630         .expect('x-store-foo', 'bar')
631         .end(done)
632     })
633   })
635   describe('charset', function () {
636     before(function () {
637       this.app = createApp()
638     })
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)
645     })
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)
652     })
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)
660     })
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)
667     })
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)
674     })
675   })
677   describe('encoding', function () {
678     before(function () {
679       this.app = createApp({ limit: '1kb' })
680     })
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)
687     })
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)
695     })
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)
703     })
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)
711     })
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)
719     })
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)
727     })
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)
735     })
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)
746     })
747   })
750 function createApp (options) {
751   var app = express()
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)))
760   })
762   app.post('/', function (req, res) {
763     res.json(req.body)
764   })
766   return app
769 function parseError (str) {
770   try {
771     JSON.parse(str); throw new SyntaxError('strict violation')
772   } catch (e) {
773     return e.message
774   }
777 function shouldContainInBody (str) {
778   return function (res) {
779     assert.ok(res.text.indexOf(str) !== -1,
780       'expected \'' + res.text + '\' to contain \'' + str + '\'')
781   }
784 function tryRequire (name) {
785   try {
786     return require(name)
787   } catch (e) {
788     return {}
789   }