build: Node.js@12.3
[express.git] / test / express.json.js
blob907fa0cfeb82a98aaf566f09d701e1fdbd3fe9ba
2 var assert = require('assert')
3 var Buffer = require('safe-buffer').Buffer
4 var express = require('..')
5 var request = require('supertest')
7 describe('express.json()', function () {
8   it('should parse JSON', function (done) {
9     request(createApp())
10       .post('/')
11       .set('Content-Type', 'application/json')
12       .send('{"user":"tobi"}')
13       .expect(200, '{"user":"tobi"}', done)
14   })
16   it('should handle Content-Length: 0', function (done) {
17     request(createApp())
18       .post('/')
19       .set('Content-Type', 'application/json')
20       .set('Content-Length', '0')
21       .expect(200, '{}', done)
22   })
24   it('should handle empty message-body', function (done) {
25     request(createApp())
26       .post('/')
27       .set('Content-Type', 'application/json')
28       .set('Transfer-Encoding', 'chunked')
29       .expect(200, '{}', done)
30   })
32   it('should handle no message-body', function (done) {
33     request(createApp())
34       .post('/')
35       .set('Content-Type', 'application/json')
36       .unset('Transfer-Encoding')
37       .expect(200, '{}', done)
38   })
40   it('should 400 when invalid content-length', function (done) {
41     var app = express()
43     app.use(function (req, res, next) {
44       req.headers['content-length'] = '20' // bad length
45       next()
46     })
48     app.use(express.json())
50     app.post('/', function (req, res) {
51       res.json(req.body)
52     })
54     request(app)
55       .post('/')
56       .set('Content-Type', 'application/json')
57       .send('{"str":')
58       .expect(400, /content length/, done)
59   })
61   it('should handle duplicated middleware', function (done) {
62     var app = express()
64     app.use(express.json())
65     app.use(express.json())
67     app.post('/', function (req, res) {
68       res.json(req.body)
69     })
71     request(app)
72       .post('/')
73       .set('Content-Type', 'application/json')
74       .send('{"user":"tobi"}')
75       .expect(200, '{"user":"tobi"}', done)
76   })
78   describe('when JSON is invalid', function () {
79     before(function () {
80       this.app = createApp()
81     })
83     it('should 400 for bad token', function (done) {
84       request(this.app)
85         .post('/')
86         .set('Content-Type', 'application/json')
87         .send('{:')
88         .expect(400, parseError('{:'), done)
89     })
91     it('should 400 for incomplete', function (done) {
92       request(this.app)
93         .post('/')
94         .set('Content-Type', 'application/json')
95         .send('{"user"')
96         .expect(400, parseError('{"user"'), done)
97     })
99     it('should error with type = "entity.parse.failed"', function (done) {
100       request(this.app)
101         .post('/')
102         .set('Content-Type', 'application/json')
103         .set('X-Error-Property', 'type')
104         .send(' {"user"')
105         .expect(400, 'entity.parse.failed', done)
106     })
108     it('should include original body on error object', function (done) {
109       request(this.app)
110         .post('/')
111         .set('Content-Type', 'application/json')
112         .set('X-Error-Property', 'body')
113         .send(' {"user"')
114         .expect(400, ' {"user"', done)
115     })
116   })
118   describe('with limit option', function () {
119     it('should 413 when over limit with Content-Length', function (done) {
120       var buf = Buffer.alloc(1024, '.')
121       request(createApp({ limit: '1kb' }))
122         .post('/')
123         .set('Content-Type', 'application/json')
124         .set('Content-Length', '1034')
125         .send(JSON.stringify({ str: buf.toString() }))
126         .expect(413, done)
127     })
129     it('should error with type = "entity.too.large"', function (done) {
130       var buf = Buffer.alloc(1024, '.')
131       request(createApp({ limit: '1kb' }))
132         .post('/')
133         .set('Content-Type', 'application/json')
134         .set('Content-Length', '1034')
135         .set('X-Error-Property', 'type')
136         .send(JSON.stringify({ str: buf.toString() }))
137         .expect(413, 'entity.too.large', done)
138     })
140     it('should 413 when over limit with chunked encoding', function (done) {
141       var buf = Buffer.alloc(1024, '.')
142       var server = createApp({ limit: '1kb' })
143       var test = request(server).post('/')
144       test.set('Content-Type', 'application/json')
145       test.set('Transfer-Encoding', 'chunked')
146       test.write('{"str":')
147       test.write('"' + buf.toString() + '"}')
148       test.expect(413, done)
149     })
151     it('should accept number of bytes', function (done) {
152       var buf = Buffer.alloc(1024, '.')
153       request(createApp({ limit: 1024 }))
154         .post('/')
155         .set('Content-Type', 'application/json')
156         .send(JSON.stringify({ str: buf.toString() }))
157         .expect(413, done)
158     })
160     it('should not change when options altered', function (done) {
161       var buf = Buffer.alloc(1024, '.')
162       var options = { limit: '1kb' }
163       var server = createApp(options)
165       options.limit = '100kb'
167       request(server)
168         .post('/')
169         .set('Content-Type', 'application/json')
170         .send(JSON.stringify({ str: buf.toString() }))
171         .expect(413, done)
172     })
174     it('should not hang response', function (done) {
175       var buf = Buffer.alloc(10240, '.')
176       var server = createApp({ limit: '8kb' })
177       var test = request(server).post('/')
178       test.set('Content-Type', 'application/json')
179       test.write(buf)
180       test.write(buf)
181       test.write(buf)
182       test.expect(413, done)
183     })
184   })
186   describe('with inflate option', function () {
187     describe('when false', function () {
188       before(function () {
189         this.app = createApp({ inflate: false })
190       })
192       it('should not accept content-encoding', function (done) {
193         var test = request(this.app).post('/')
194         test.set('Content-Encoding', 'gzip')
195         test.set('Content-Type', 'application/json')
196         test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
197         test.expect(415, 'content encoding unsupported', done)
198       })
199     })
201     describe('when true', function () {
202       before(function () {
203         this.app = createApp({ inflate: true })
204       })
206       it('should accept content-encoding', function (done) {
207         var test = request(this.app).post('/')
208         test.set('Content-Encoding', 'gzip')
209         test.set('Content-Type', 'application/json')
210         test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
211         test.expect(200, '{"name":"论"}', done)
212       })
213     })
214   })
216   describe('with strict option', function () {
217     describe('when undefined', function () {
218       before(function () {
219         this.app = createApp()
220       })
222       it('should 400 on primitives', function (done) {
223         request(this.app)
224           .post('/')
225           .set('Content-Type', 'application/json')
226           .send('true')
227           .expect(400, parseError('#rue').replace('#', 't'), done)
228       })
229     })
231     describe('when false', function () {
232       before(function () {
233         this.app = createApp({ strict: false })
234       })
236       it('should parse primitives', function (done) {
237         request(this.app)
238           .post('/')
239           .set('Content-Type', 'application/json')
240           .send('true')
241           .expect(200, 'true', done)
242       })
243     })
245     describe('when true', function () {
246       before(function () {
247         this.app = createApp({ strict: true })
248       })
250       it('should not parse primitives', function (done) {
251         request(this.app)
252           .post('/')
253           .set('Content-Type', 'application/json')
254           .send('true')
255           .expect(400, parseError('#rue').replace('#', 't'), done)
256       })
258       it('should not parse primitives with leading whitespaces', function (done) {
259         request(this.app)
260           .post('/')
261           .set('Content-Type', 'application/json')
262           .send('    true')
263           .expect(400, parseError('    #rue').replace('#', 't'), done)
264       })
266       it('should allow leading whitespaces in JSON', function (done) {
267         request(this.app)
268           .post('/')
269           .set('Content-Type', 'application/json')
270           .send('   { "user": "tobi" }')
271           .expect(200, '{"user":"tobi"}', done)
272       })
274       it('should error with type = "entity.parse.failed"', function (done) {
275         request(this.app)
276           .post('/')
277           .set('Content-Type', 'application/json')
278           .set('X-Error-Property', 'type')
279           .send('true')
280           .expect(400, 'entity.parse.failed', done)
281       })
283       it('should include correct message in stack trace', function (done) {
284         request(this.app)
285           .post('/')
286           .set('Content-Type', 'application/json')
287           .set('X-Error-Property', 'stack')
288           .send('true')
289           .expect(400)
290           .expect(shouldContainInBody(parseError('#rue').replace('#', 't')))
291           .end(done)
292       })
293     })
294   })
296   describe('with type option', function () {
297     describe('when "application/vnd.api+json"', function () {
298       before(function () {
299         this.app = createApp({ type: 'application/vnd.api+json' })
300       })
302       it('should parse JSON for custom type', function (done) {
303         request(this.app)
304           .post('/')
305           .set('Content-Type', 'application/vnd.api+json')
306           .send('{"user":"tobi"}')
307           .expect(200, '{"user":"tobi"}', done)
308       })
310       it('should ignore standard type', function (done) {
311         request(this.app)
312           .post('/')
313           .set('Content-Type', 'application/json')
314           .send('{"user":"tobi"}')
315           .expect(200, '{}', done)
316       })
317     })
319     describe('when ["application/json", "application/vnd.api+json"]', function () {
320       before(function () {
321         this.app = createApp({
322           type: ['application/json', 'application/vnd.api+json']
323         })
324       })
326       it('should parse JSON for "application/json"', function (done) {
327         request(this.app)
328           .post('/')
329           .set('Content-Type', 'application/json')
330           .send('{"user":"tobi"}')
331           .expect(200, '{"user":"tobi"}', done)
332       })
334       it('should parse JSON for "application/vnd.api+json"', function (done) {
335         request(this.app)
336           .post('/')
337           .set('Content-Type', 'application/vnd.api+json')
338           .send('{"user":"tobi"}')
339           .expect(200, '{"user":"tobi"}', done)
340       })
342       it('should ignore "application/x-json"', function (done) {
343         request(this.app)
344           .post('/')
345           .set('Content-Type', 'application/x-json')
346           .send('{"user":"tobi"}')
347           .expect(200, '{}', done)
348       })
349     })
351     describe('when a function', function () {
352       it('should parse when truthy value returned', function (done) {
353         var app = createApp({ type: accept })
355         function accept (req) {
356           return req.headers['content-type'] === 'application/vnd.api+json'
357         }
359         request(app)
360           .post('/')
361           .set('Content-Type', 'application/vnd.api+json')
362           .send('{"user":"tobi"}')
363           .expect(200, '{"user":"tobi"}', done)
364       })
366       it('should work without content-type', function (done) {
367         var app = createApp({ type: accept })
369         function accept (req) {
370           return true
371         }
373         var test = request(app).post('/')
374         test.write('{"user":"tobi"}')
375         test.expect(200, '{"user":"tobi"}', done)
376       })
378       it('should not invoke without a body', function (done) {
379         var app = createApp({ type: accept })
381         function accept (req) {
382           throw new Error('oops!')
383         }
385         request(app)
386           .get('/')
387           .expect(404, done)
388       })
389     })
390   })
392   describe('with verify option', function () {
393     it('should assert value if function', function () {
394       assert.throws(createApp.bind(null, { verify: 'lol' }),
395         /TypeError: option verify must be function/)
396     })
398     it('should error from verify', function (done) {
399       var app = createApp({ verify: function (req, res, buf) {
400         if (buf[0] === 0x5b) throw new Error('no arrays')
401       } })
403       request(app)
404         .post('/')
405         .set('Content-Type', 'application/json')
406         .send('["tobi"]')
407         .expect(403, 'no arrays', done)
408     })
410     it('should error with type = "entity.verify.failed"', function (done) {
411       var app = createApp({ verify: function (req, res, buf) {
412         if (buf[0] === 0x5b) throw new Error('no arrays')
413       } })
415       request(app)
416         .post('/')
417         .set('Content-Type', 'application/json')
418         .set('X-Error-Property', 'type')
419         .send('["tobi"]')
420         .expect(403, 'entity.verify.failed', done)
421     })
423     it('should allow custom codes', function (done) {
424       var app = createApp({ verify: function (req, res, buf) {
425         if (buf[0] !== 0x5b) return
426         var err = new Error('no arrays')
427         err.status = 400
428         throw err
429       } })
431       request(app)
432         .post('/')
433         .set('Content-Type', 'application/json')
434         .send('["tobi"]')
435         .expect(400, 'no arrays', done)
436     })
438     it('should allow custom type', function (done) {
439       var app = createApp({ verify: function (req, res, buf) {
440         if (buf[0] !== 0x5b) return
441         var err = new Error('no arrays')
442         err.type = 'foo.bar'
443         throw err
444       } })
446       request(app)
447         .post('/')
448         .set('Content-Type', 'application/json')
449         .set('X-Error-Property', 'type')
450         .send('["tobi"]')
451         .expect(403, 'foo.bar', done)
452     })
454     it('should include original body on error object', function (done) {
455       var app = createApp({ verify: function (req, res, buf) {
456         if (buf[0] === 0x5b) throw new Error('no arrays')
457       } })
459       request(app)
460         .post('/')
461         .set('Content-Type', 'application/json')
462         .set('X-Error-Property', 'body')
463         .send('["tobi"]')
464         .expect(403, '["tobi"]', done)
465     })
467     it('should allow pass-through', function (done) {
468       var app = createApp({ verify: function (req, res, buf) {
469         if (buf[0] === 0x5b) throw new Error('no arrays')
470       } })
472       request(app)
473         .post('/')
474         .set('Content-Type', 'application/json')
475         .send('{"user":"tobi"}')
476         .expect(200, '{"user":"tobi"}', done)
477     })
479     it('should work with different charsets', function (done) {
480       var app = createApp({ verify: function (req, res, buf) {
481         if (buf[0] === 0x5b) throw new Error('no arrays')
482       } })
484       var test = request(app).post('/')
485       test.set('Content-Type', 'application/json; charset=utf-16')
486       test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
487       test.expect(200, '{"name":"论"}', done)
488     })
490     it('should 415 on unknown charset prior to verify', function (done) {
491       var app = createApp({ verify: function (req, res, buf) {
492         throw new Error('unexpected verify call')
493       } })
495       var test = request(app).post('/')
496       test.set('Content-Type', 'application/json; charset=x-bogus')
497       test.write(Buffer.from('00000000', 'hex'))
498       test.expect(415, 'unsupported charset "X-BOGUS"', done)
499     })
500   })
502   describe('charset', function () {
503     before(function () {
504       this.app = createApp()
505     })
507     it('should parse utf-8', function (done) {
508       var test = request(this.app).post('/')
509       test.set('Content-Type', 'application/json; charset=utf-8')
510       test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
511       test.expect(200, '{"name":"论"}', done)
512     })
514     it('should parse utf-16', function (done) {
515       var test = request(this.app).post('/')
516       test.set('Content-Type', 'application/json; charset=utf-16')
517       test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex'))
518       test.expect(200, '{"name":"论"}', done)
519     })
521     it('should parse when content-length != char length', function (done) {
522       var test = request(this.app).post('/')
523       test.set('Content-Type', 'application/json; charset=utf-8')
524       test.set('Content-Length', '13')
525       test.write(Buffer.from('7b2274657374223a22c3a5227d', 'hex'))
526       test.expect(200, '{"test":"å"}', done)
527     })
529     it('should default to utf-8', function (done) {
530       var test = request(this.app).post('/')
531       test.set('Content-Type', 'application/json')
532       test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
533       test.expect(200, '{"name":"论"}', done)
534     })
536     it('should fail on unknown charset', function (done) {
537       var test = request(this.app).post('/')
538       test.set('Content-Type', 'application/json; charset=koi8-r')
539       test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex'))
540       test.expect(415, 'unsupported charset "KOI8-R"', done)
541     })
543     it('should error with type = "charset.unsupported"', function (done) {
544       var test = request(this.app).post('/')
545       test.set('Content-Type', 'application/json; charset=koi8-r')
546       test.set('X-Error-Property', 'type')
547       test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex'))
548       test.expect(415, 'charset.unsupported', done)
549     })
550   })
552   describe('encoding', function () {
553     before(function () {
554       this.app = createApp({ limit: '1kb' })
555     })
557     it('should parse without encoding', function (done) {
558       var test = request(this.app).post('/')
559       test.set('Content-Type', 'application/json')
560       test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
561       test.expect(200, '{"name":"论"}', done)
562     })
564     it('should support identity encoding', function (done) {
565       var test = request(this.app).post('/')
566       test.set('Content-Encoding', 'identity')
567       test.set('Content-Type', 'application/json')
568       test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex'))
569       test.expect(200, '{"name":"论"}', done)
570     })
572     it('should support gzip encoding', function (done) {
573       var test = request(this.app).post('/')
574       test.set('Content-Encoding', 'gzip')
575       test.set('Content-Type', 'application/json')
576       test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
577       test.expect(200, '{"name":"论"}', done)
578     })
580     it('should support deflate encoding', function (done) {
581       var test = request(this.app).post('/')
582       test.set('Content-Encoding', 'deflate')
583       test.set('Content-Type', 'application/json')
584       test.write(Buffer.from('789cab56ca4bcc4d55b2527ab16e97522d00274505ac', 'hex'))
585       test.expect(200, '{"name":"论"}', done)
586     })
588     it('should be case-insensitive', function (done) {
589       var test = request(this.app).post('/')
590       test.set('Content-Encoding', 'GZIP')
591       test.set('Content-Type', 'application/json')
592       test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
593       test.expect(200, '{"name":"论"}', done)
594     })
596     it('should 415 on unknown encoding', function (done) {
597       var test = request(this.app).post('/')
598       test.set('Content-Encoding', 'nulls')
599       test.set('Content-Type', 'application/json')
600       test.write(Buffer.from('000000000000', 'hex'))
601       test.expect(415, 'unsupported content encoding "nulls"', done)
602     })
604     it('should error with type = "encoding.unsupported"', function (done) {
605       var test = request(this.app).post('/')
606       test.set('Content-Encoding', 'nulls')
607       test.set('Content-Type', 'application/json')
608       test.set('X-Error-Property', 'type')
609       test.write(Buffer.from('000000000000', 'hex'))
610       test.expect(415, 'encoding.unsupported', done)
611     })
613     it('should 400 on malformed encoding', function (done) {
614       var test = request(this.app).post('/')
615       test.set('Content-Encoding', 'gzip')
616       test.set('Content-Type', 'application/json')
617       test.write(Buffer.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
618       test.expect(400, done)
619     })
621     it('should 413 when inflated value exceeds limit', function (done) {
622       // gzip'd data exceeds 1kb, but deflated below 1kb
623       var test = request(this.app).post('/')
624       test.set('Content-Encoding', 'gzip')
625       test.set('Content-Type', 'application/json')
626       test.write(Buffer.from('1f8b080000000000000bedc1010d000000c2a0f74f6d0f071400000000000000', 'hex'))
627       test.write(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'))
628       test.write(Buffer.from('0000000000000000004f0625b3b71650c30000', 'hex'))
629       test.expect(413, done)
630     })
631   })
634 function createApp (options) {
635   var app = express()
637   app.use(express.json(options))
639   app.use(function (err, req, res, next) {
640     res.status(err.status || 500)
641     res.send(String(err[req.headers['x-error-property'] || 'message']))
642   })
644   app.post('/', function (req, res) {
645     res.json(req.body)
646   })
648   return app
651 function parseError (str) {
652   try {
653     JSON.parse(str); throw new SyntaxError('strict violation')
654   } catch (e) {
655     return e.message
656   }
659 function shouldContainInBody (str) {
660   return function (res) {
661     assert.ok(res.text.indexOf(str) !== -1,
662       'expected \'' + res.text + '\' to contain \'' + str + '\'')
663   }