docs: update homepage link http to https (#5920)
[express.git] / test / express.urlencoded.js
blob37afb07f38aa2e791822c205792ac2066a4e9ba8
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.urlencoded()', function () {
14 before(function () {
15 this.app = createApp()
18 it('should parse x-www-form-urlencoded', function (done) {
19 request(this.app)
20 .post('/')
21 .set('Content-Type', 'application/x-www-form-urlencoded')
22 .send('user=tobi')
23 .expect(200, '{"user":"tobi"}', done)
26 it('should 400 when invalid content-length', function (done) {
27 var app = express()
29 app.use(function (req, res, next) {
30 req.headers['content-length'] = '20' // bad length
31 next()
34 app.use(express.urlencoded())
36 app.post('/', function (req, res) {
37 res.json(req.body)
40 request(app)
41 .post('/')
42 .set('Content-Type', 'application/x-www-form-urlencoded')
43 .send('str=')
44 .expect(400, /content length/, done)
47 it('should handle Content-Length: 0', function (done) {
48 request(this.app)
49 .post('/')
50 .set('Content-Type', 'application/x-www-form-urlencoded')
51 .set('Content-Length', '0')
52 .send('')
53 .expect(200, '{}', done)
56 it('should handle empty message-body', function (done) {
57 request(createApp({ limit: '1kb' }))
58 .post('/')
59 .set('Content-Type', 'application/x-www-form-urlencoded')
60 .set('Transfer-Encoding', 'chunked')
61 .send('')
62 .expect(200, '{}', done)
65 it('should handle duplicated middleware', function (done) {
66 var app = express()
68 app.use(express.urlencoded())
69 app.use(express.urlencoded())
71 app.post('/', function (req, res) {
72 res.json(req.body)
75 request(app)
76 .post('/')
77 .set('Content-Type', 'application/x-www-form-urlencoded')
78 .send('user=tobi')
79 .expect(200, '{"user":"tobi"}', done)
82 it('should not parse extended syntax', function (done) {
83 request(this.app)
84 .post('/')
85 .set('Content-Type', 'application/x-www-form-urlencoded')
86 .send('user[name][first]=Tobi')
87 .expect(200, '{"user[name][first]":"Tobi"}', done)
90 describe('with extended option', function () {
91 describe('when false', function () {
92 before(function () {
93 this.app = createApp({ extended: false })
96 it('should not parse extended syntax', function (done) {
97 request(this.app)
98 .post('/')
99 .set('Content-Type', 'application/x-www-form-urlencoded')
100 .send('user[name][first]=Tobi')
101 .expect(200, '{"user[name][first]":"Tobi"}', done)
104 it('should parse multiple key instances', function (done) {
105 request(this.app)
106 .post('/')
107 .set('Content-Type', 'application/x-www-form-urlencoded')
108 .send('user=Tobi&user=Loki')
109 .expect(200, '{"user":["Tobi","Loki"]}', done)
113 describe('when true', function () {
114 before(function () {
115 this.app = createApp({ extended: true })
118 it('should parse multiple key instances', function (done) {
119 request(this.app)
120 .post('/')
121 .set('Content-Type', 'application/x-www-form-urlencoded')
122 .send('user=Tobi&user=Loki')
123 .expect(200, '{"user":["Tobi","Loki"]}', done)
126 it('should parse extended syntax', function (done) {
127 request(this.app)
128 .post('/')
129 .set('Content-Type', 'application/x-www-form-urlencoded')
130 .send('user[name][first]=Tobi')
131 .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done)
134 it('should parse parameters with dots', function (done) {
135 request(this.app)
136 .post('/')
137 .set('Content-Type', 'application/x-www-form-urlencoded')
138 .send('user.name=Tobi')
139 .expect(200, '{"user.name":"Tobi"}', done)
142 it('should parse fully-encoded extended syntax', function (done) {
143 request(this.app)
144 .post('/')
145 .set('Content-Type', 'application/x-www-form-urlencoded')
146 .send('user%5Bname%5D%5Bfirst%5D=Tobi')
147 .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done)
150 it('should parse array index notation', function (done) {
151 request(this.app)
152 .post('/')
153 .set('Content-Type', 'application/x-www-form-urlencoded')
154 .send('foo[0]=bar&foo[1]=baz')
155 .expect(200, '{"foo":["bar","baz"]}', done)
158 it('should parse array index notation with large array', function (done) {
159 var str = 'f[0]=0'
161 for (var i = 1; i < 500; i++) {
162 str += '&f[' + i + ']=' + i.toString(16)
165 request(this.app)
166 .post('/')
167 .set('Content-Type', 'application/x-www-form-urlencoded')
168 .send(str)
169 .expect(function (res) {
170 var obj = JSON.parse(res.text)
171 assert.strictEqual(Object.keys(obj).length, 1)
172 assert.strictEqual(Array.isArray(obj.f), true)
173 assert.strictEqual(obj.f.length, 500)
175 .expect(200, done)
178 it('should parse array of objects syntax', function (done) {
179 request(this.app)
180 .post('/')
181 .set('Content-Type', 'application/x-www-form-urlencoded')
182 .send('foo[0][bar]=baz&foo[0][fizz]=buzz&foo[]=done!')
183 .expect(200, '{"foo":[{"bar":"baz","fizz":"buzz"},"done!"]}', done)
186 it('should parse deep object', function (done) {
187 var str = 'foo'
189 for (var i = 0; i < 32; i++) {
190 str += '[p]'
193 str += '=bar'
195 request(this.app)
196 .post('/')
197 .set('Content-Type', 'application/x-www-form-urlencoded')
198 .send(str)
199 .expect(function (res) {
200 var obj = JSON.parse(res.text)
201 assert.strictEqual(Object.keys(obj).length, 1)
202 assert.strictEqual(typeof obj.foo, 'object')
204 var depth = 0
205 var ref = obj.foo
206 while ((ref = ref.p)) { depth++ }
207 assert.strictEqual(depth, 32)
209 .expect(200, done)
214 describe('with inflate option', function () {
215 describe('when false', function () {
216 before(function () {
217 this.app = createApp({ inflate: false })
220 it('should not accept content-encoding', function (done) {
221 var test = request(this.app).post('/')
222 test.set('Content-Encoding', 'gzip')
223 test.set('Content-Type', 'application/x-www-form-urlencoded')
224 test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
225 test.expect(415, '[encoding.unsupported] content encoding unsupported', done)
229 describe('when true', function () {
230 before(function () {
231 this.app = createApp({ inflate: true })
234 it('should accept content-encoding', function (done) {
235 var test = request(this.app).post('/')
236 test.set('Content-Encoding', 'gzip')
237 test.set('Content-Type', 'application/x-www-form-urlencoded')
238 test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
239 test.expect(200, '{"name":"论"}', done)
244 describe('with limit option', function () {
245 it('should 413 when over limit with Content-Length', function (done) {
246 var buf = Buffer.alloc(1024, '.')
247 request(createApp({ limit: '1kb' }))
248 .post('/')
249 .set('Content-Type', 'application/x-www-form-urlencoded')
250 .set('Content-Length', '1028')
251 .send('str=' + buf.toString())
252 .expect(413, done)
255 it('should 413 when over limit with chunked encoding', function (done) {
256 var app = createApp({ limit: '1kb' })
257 var buf = Buffer.alloc(1024, '.')
258 var test = request(app).post('/')
259 test.set('Content-Type', 'application/x-www-form-urlencoded')
260 test.set('Transfer-Encoding', 'chunked')
261 test.write('str=')
262 test.write(buf.toString())
263 test.expect(413, done)
266 it('should 413 when inflated body over limit', function (done) {
267 var app = createApp({ limit: '1kb' })
268 var test = request(app).post('/')
269 test.set('Content-Encoding', 'gzip')
270 test.set('Content-Type', 'application/x-www-form-urlencoded')
271 test.write(Buffer.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f9204040000', 'hex'))
272 test.expect(413, done)
275 it('should accept number of bytes', function (done) {
276 var buf = Buffer.alloc(1024, '.')
277 request(createApp({ limit: 1024 }))
278 .post('/')
279 .set('Content-Type', 'application/x-www-form-urlencoded')
280 .send('str=' + buf.toString())
281 .expect(413, done)
284 it('should not change when options altered', function (done) {
285 var buf = Buffer.alloc(1024, '.')
286 var options = { limit: '1kb' }
287 var app = createApp(options)
289 options.limit = '100kb'
291 request(app)
292 .post('/')
293 .set('Content-Type', 'application/x-www-form-urlencoded')
294 .send('str=' + buf.toString())
295 .expect(413, done)
298 it('should not hang response', function (done) {
299 var app = createApp({ limit: '8kb' })
300 var buf = Buffer.alloc(10240, '.')
301 var test = request(app).post('/')
302 test.set('Content-Type', 'application/x-www-form-urlencoded')
303 test.write(buf)
304 test.write(buf)
305 test.write(buf)
306 test.expect(413, done)
309 it('should not error when inflating', function (done) {
310 var app = createApp({ limit: '1kb' })
311 var test = request(app).post('/')
312 test.set('Content-Encoding', 'gzip')
313 test.set('Content-Type', 'application/x-www-form-urlencoded')
314 test.write(Buffer.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f92040400', 'hex'))
315 test.expect(413, done)
319 describe('with parameterLimit option', function () {
320 describe('with extended: false', function () {
321 it('should reject 0', function () {
322 assert.throws(createApp.bind(null, { extended: false, parameterLimit: 0 }),
323 /TypeError: option parameterLimit must be a positive number/)
326 it('should reject string', function () {
327 assert.throws(createApp.bind(null, { extended: false, parameterLimit: 'beep' }),
328 /TypeError: option parameterLimit must be a positive number/)
331 it('should 413 if over limit', function (done) {
332 request(createApp({ extended: false, parameterLimit: 10 }))
333 .post('/')
334 .set('Content-Type', 'application/x-www-form-urlencoded')
335 .send(createManyParams(11))
336 .expect(413, '[parameters.too.many] too many parameters', done)
339 it('should work when at the limit', function (done) {
340 request(createApp({ extended: false, parameterLimit: 10 }))
341 .post('/')
342 .set('Content-Type', 'application/x-www-form-urlencoded')
343 .send(createManyParams(10))
344 .expect(expectKeyCount(10))
345 .expect(200, done)
348 it('should work if number is floating point', function (done) {
349 request(createApp({ extended: false, parameterLimit: 10.1 }))
350 .post('/')
351 .set('Content-Type', 'application/x-www-form-urlencoded')
352 .send(createManyParams(11))
353 .expect(413, /too many parameters/, done)
356 it('should work with large limit', function (done) {
357 request(createApp({ extended: false, parameterLimit: 5000 }))
358 .post('/')
359 .set('Content-Type', 'application/x-www-form-urlencoded')
360 .send(createManyParams(5000))
361 .expect(expectKeyCount(5000))
362 .expect(200, done)
365 it('should work with Infinity limit', function (done) {
366 request(createApp({ extended: false, parameterLimit: Infinity }))
367 .post('/')
368 .set('Content-Type', 'application/x-www-form-urlencoded')
369 .send(createManyParams(10000))
370 .expect(expectKeyCount(10000))
371 .expect(200, done)
375 describe('with extended: true', function () {
376 it('should reject 0', function () {
377 assert.throws(createApp.bind(null, { extended: true, parameterLimit: 0 }),
378 /TypeError: option parameterLimit must be a positive number/)
381 it('should reject string', function () {
382 assert.throws(createApp.bind(null, { extended: true, parameterLimit: 'beep' }),
383 /TypeError: option parameterLimit must be a positive number/)
386 it('should 413 if over limit', function (done) {
387 request(createApp({ extended: true, parameterLimit: 10 }))
388 .post('/')
389 .set('Content-Type', 'application/x-www-form-urlencoded')
390 .send(createManyParams(11))
391 .expect(413, '[parameters.too.many] too many parameters', done)
394 it('should work when at the limit', function (done) {
395 request(createApp({ extended: true, parameterLimit: 10 }))
396 .post('/')
397 .set('Content-Type', 'application/x-www-form-urlencoded')
398 .send(createManyParams(10))
399 .expect(expectKeyCount(10))
400 .expect(200, done)
403 it('should work if number is floating point', function (done) {
404 request(createApp({ extended: true, parameterLimit: 10.1 }))
405 .post('/')
406 .set('Content-Type', 'application/x-www-form-urlencoded')
407 .send(createManyParams(11))
408 .expect(413, /too many parameters/, done)
411 it('should work with large limit', function (done) {
412 request(createApp({ extended: true, parameterLimit: 5000 }))
413 .post('/')
414 .set('Content-Type', 'application/x-www-form-urlencoded')
415 .send(createManyParams(5000))
416 .expect(expectKeyCount(5000))
417 .expect(200, done)
420 it('should work with Infinity limit', function (done) {
421 request(createApp({ extended: true, parameterLimit: Infinity }))
422 .post('/')
423 .set('Content-Type', 'application/x-www-form-urlencoded')
424 .send(createManyParams(10000))
425 .expect(expectKeyCount(10000))
426 .expect(200, done)
431 describe('with type option', function () {
432 describe('when "application/vnd.x-www-form-urlencoded"', function () {
433 before(function () {
434 this.app = createApp({ type: 'application/vnd.x-www-form-urlencoded' })
437 it('should parse for custom type', function (done) {
438 request(this.app)
439 .post('/')
440 .set('Content-Type', 'application/vnd.x-www-form-urlencoded')
441 .send('user=tobi')
442 .expect(200, '{"user":"tobi"}', done)
445 it('should ignore standard type', function (done) {
446 request(this.app)
447 .post('/')
448 .set('Content-Type', 'application/x-www-form-urlencoded')
449 .send('user=tobi')
450 .expect(200, '', done)
454 describe('when ["urlencoded", "application/x-pairs"]', function () {
455 before(function () {
456 this.app = createApp({
457 type: ['urlencoded', 'application/x-pairs']
461 it('should parse "application/x-www-form-urlencoded"', function (done) {
462 request(this.app)
463 .post('/')
464 .set('Content-Type', 'application/x-www-form-urlencoded')
465 .send('user=tobi')
466 .expect(200, '{"user":"tobi"}', done)
469 it('should parse "application/x-pairs"', function (done) {
470 request(this.app)
471 .post('/')
472 .set('Content-Type', 'application/x-pairs')
473 .send('user=tobi')
474 .expect(200, '{"user":"tobi"}', done)
477 it('should ignore application/x-foo', function (done) {
478 request(this.app)
479 .post('/')
480 .set('Content-Type', 'application/x-foo')
481 .send('user=tobi')
482 .expect(200, '', done)
486 describe('when a function', function () {
487 it('should parse when truthy value returned', function (done) {
488 var app = createApp({ type: accept })
490 function accept (req) {
491 return req.headers['content-type'] === 'application/vnd.something'
494 request(app)
495 .post('/')
496 .set('Content-Type', 'application/vnd.something')
497 .send('user=tobi')
498 .expect(200, '{"user":"tobi"}', done)
501 it('should work without content-type', function (done) {
502 var app = createApp({ type: accept })
504 function accept (req) {
505 return true
508 var test = request(app).post('/')
509 test.write('user=tobi')
510 test.expect(200, '{"user":"tobi"}', done)
513 it('should not invoke without a body', function (done) {
514 var app = createApp({ type: accept })
516 function accept (req) {
517 throw new Error('oops!')
520 request(app)
521 .get('/')
522 .expect(404, done)
527 describe('with verify option', function () {
528 it('should assert value if function', function () {
529 assert.throws(createApp.bind(null, { verify: 'lol' }),
530 /TypeError: option verify must be function/)
533 it('should error from verify', function (done) {
534 var app = createApp({
535 verify: function (req, res, buf) {
536 if (buf[0] === 0x20) throw new Error('no leading space')
540 request(app)
541 .post('/')
542 .set('Content-Type', 'application/x-www-form-urlencoded')
543 .send(' user=tobi')
544 .expect(403, '[entity.verify.failed] no leading space', done)
547 it('should allow custom codes', function (done) {
548 var app = createApp({
549 verify: function (req, res, buf) {
550 if (buf[0] !== 0x20) return
551 var err = new Error('no leading space')
552 err.status = 400
553 throw err
557 request(app)
558 .post('/')
559 .set('Content-Type', 'application/x-www-form-urlencoded')
560 .send(' user=tobi')
561 .expect(400, '[entity.verify.failed] no leading space', done)
564 it('should allow custom type', function (done) {
565 var app = createApp({
566 verify: function (req, res, buf) {
567 if (buf[0] !== 0x20) return
568 var err = new Error('no leading space')
569 err.type = 'foo.bar'
570 throw err
574 request(app)
575 .post('/')
576 .set('Content-Type', 'application/x-www-form-urlencoded')
577 .send(' user=tobi')
578 .expect(403, '[foo.bar] no leading space', done)
581 it('should allow pass-through', function (done) {
582 var app = createApp({
583 verify: function (req, res, buf) {
584 if (buf[0] === 0x5b) throw new Error('no arrays')
588 request(app)
589 .post('/')
590 .set('Content-Type', 'application/x-www-form-urlencoded')
591 .send('user=tobi')
592 .expect(200, '{"user":"tobi"}', done)
595 it('should 415 on unknown charset prior to verify', function (done) {
596 var app = createApp({
597 verify: function (req, res, buf) {
598 throw new Error('unexpected verify call')
602 var test = request(app).post('/')
603 test.set('Content-Type', 'application/x-www-form-urlencoded; charset=x-bogus')
604 test.write(Buffer.from('00000000', 'hex'))
605 test.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done)
609 describeAsyncHooks('async local storage', function () {
610 before(function () {
611 var app = express()
612 var store = { foo: 'bar' }
614 app.use(function (req, res, next) {
615 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
616 req.asyncLocalStorage.run(store, next)
619 app.use(express.urlencoded())
621 app.use(function (req, res, next) {
622 var local = req.asyncLocalStorage.getStore()
624 if (local) {
625 res.setHeader('x-store-foo', String(local.foo))
628 next()
631 app.use(function (err, req, res, next) {
632 var local = req.asyncLocalStorage.getStore()
634 if (local) {
635 res.setHeader('x-store-foo', String(local.foo))
638 res.status(err.status || 500)
639 res.send('[' + err.type + '] ' + err.message)
642 app.post('/', function (req, res) {
643 res.json(req.body)
646 this.app = app
649 it('should presist store', function (done) {
650 request(this.app)
651 .post('/')
652 .set('Content-Type', 'application/x-www-form-urlencoded')
653 .send('user=tobi')
654 .expect(200)
655 .expect('x-store-foo', 'bar')
656 .expect('{"user":"tobi"}')
657 .end(done)
660 it('should presist store when unmatched content-type', function (done) {
661 request(this.app)
662 .post('/')
663 .set('Content-Type', 'application/fizzbuzz')
664 .send('buzz')
665 .expect(200)
666 .expect('x-store-foo', 'bar')
667 .end(done)
670 it('should presist store when inflated', function (done) {
671 var test = request(this.app).post('/')
672 test.set('Content-Encoding', 'gzip')
673 test.set('Content-Type', 'application/x-www-form-urlencoded')
674 test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
675 test.expect(200)
676 test.expect('x-store-foo', 'bar')
677 test.expect('{"name":"论"}')
678 test.end(done)
681 it('should presist store when inflate error', function (done) {
682 var test = request(this.app).post('/')
683 test.set('Content-Encoding', 'gzip')
684 test.set('Content-Type', 'application/x-www-form-urlencoded')
685 test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad6080000', 'hex'))
686 test.expect(400)
687 test.expect('x-store-foo', 'bar')
688 test.end(done)
691 it('should presist store when limit exceeded', function (done) {
692 request(this.app)
693 .post('/')
694 .set('Content-Type', 'application/x-www-form-urlencoded')
695 .send('user=' + Buffer.alloc(1024 * 100, '.').toString())
696 .expect(413)
697 .expect('x-store-foo', 'bar')
698 .end(done)
702 describe('charset', function () {
703 before(function () {
704 this.app = createApp()
707 it('should parse utf-8', function (done) {
708 var test = request(this.app).post('/')
709 test.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
710 test.write(Buffer.from('6e616d653de8aeba', 'hex'))
711 test.expect(200, '{"name":"论"}', done)
714 it('should parse when content-length != char length', function (done) {
715 var test = request(this.app).post('/')
716 test.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8')
717 test.set('Content-Length', '7')
718 test.write(Buffer.from('746573743dc3a5', 'hex'))
719 test.expect(200, '{"test":"å"}', done)
722 it('should default to utf-8', function (done) {
723 var test = request(this.app).post('/')
724 test.set('Content-Type', 'application/x-www-form-urlencoded')
725 test.write(Buffer.from('6e616d653de8aeba', 'hex'))
726 test.expect(200, '{"name":"论"}', done)
729 it('should fail on unknown charset', function (done) {
730 var test = request(this.app).post('/')
731 test.set('Content-Type', 'application/x-www-form-urlencoded; charset=koi8-r')
732 test.write(Buffer.from('6e616d653dcec5d4', 'hex'))
733 test.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done)
737 describe('encoding', function () {
738 before(function () {
739 this.app = createApp({ limit: '10kb' })
742 it('should parse without encoding', function (done) {
743 var test = request(this.app).post('/')
744 test.set('Content-Type', 'application/x-www-form-urlencoded')
745 test.write(Buffer.from('6e616d653de8aeba', 'hex'))
746 test.expect(200, '{"name":"论"}', done)
749 it('should support identity encoding', function (done) {
750 var test = request(this.app).post('/')
751 test.set('Content-Encoding', 'identity')
752 test.set('Content-Type', 'application/x-www-form-urlencoded')
753 test.write(Buffer.from('6e616d653de8aeba', 'hex'))
754 test.expect(200, '{"name":"论"}', done)
757 it('should support gzip encoding', function (done) {
758 var test = request(this.app).post('/')
759 test.set('Content-Encoding', 'gzip')
760 test.set('Content-Type', 'application/x-www-form-urlencoded')
761 test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
762 test.expect(200, '{"name":"论"}', done)
765 it('should support deflate encoding', function (done) {
766 var test = request(this.app).post('/')
767 test.set('Content-Encoding', 'deflate')
768 test.set('Content-Type', 'application/x-www-form-urlencoded')
769 test.write(Buffer.from('789ccb4bcc4db57db16e17001068042f', 'hex'))
770 test.expect(200, '{"name":"论"}', done)
773 it('should be case-insensitive', function (done) {
774 var test = request(this.app).post('/')
775 test.set('Content-Encoding', 'GZIP')
776 test.set('Content-Type', 'application/x-www-form-urlencoded')
777 test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
778 test.expect(200, '{"name":"论"}', done)
781 it('should 415 on unknown encoding', function (done) {
782 var test = request(this.app).post('/')
783 test.set('Content-Encoding', 'nulls')
784 test.set('Content-Type', 'application/x-www-form-urlencoded')
785 test.write(Buffer.from('000000000000', 'hex'))
786 test.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done)
791 function createManyParams (count) {
792 var str = ''
794 if (count === 0) {
795 return str
798 str += '0=0'
800 for (var i = 1; i < count; i++) {
801 var n = i.toString(36)
802 str += '&' + n + '=' + n
805 return str
808 function createApp (options) {
809 var app = express()
811 app.use(express.urlencoded(options))
813 app.use(function (err, req, res, next) {
814 res.status(err.status || 500)
815 res.send(String(req.headers['x-error-property']
816 ? err[req.headers['x-error-property']]
817 : ('[' + err.type + '] ' + err.message)))
820 app.post('/', function (req, res) {
821 res.json(req.body)
824 return app
827 function expectKeyCount (count) {
828 return function (res) {
829 assert.strictEqual(Object.keys(JSON.parse(res.text)).length, count)
833 function tryRequire (name) {
834 try {
835 return require(name)
836 } catch (e) {
837 return {}