docs: add note about security report location
[express.git] / test / app.router.js
bloba4fe57cc2bbfa7ee8889df3161c54e42ae2b07b7
2 var after = require('after');
3 var express = require('../')
4   , request = require('supertest')
5   , assert = require('assert')
6   , methods = require('methods');
8 describe('app.router', function(){
9   it('should restore req.params after leaving router', function(done){
10     var app = express();
11     var router = new express.Router();
13     function handler1(req, res, next){
14       res.setHeader('x-user-id', String(req.params.id));
15       next()
16     }
18     function handler2(req, res){
19       res.send(req.params.id);
20     }
22     router.use(function(req, res, next){
23       res.setHeader('x-router', String(req.params.id));
24       next();
25     });
27     app.get('/user/:id', handler1, router, handler2);
29     request(app)
30     .get('/user/1')
31     .expect('x-router', 'undefined')
32     .expect('x-user-id', '1')
33     .expect(200, '1', done);
34   })
36   describe('methods', function(){
37     methods.concat('del').forEach(function(method){
38       if (method === 'connect') return;
40       it('should include ' + method.toUpperCase(), function(done){
41         var app = express();
43         app[method]('/foo', function(req, res){
44           res.send(method)
45         });
47         request(app)
48         [method]('/foo')
49         .expect(200, done)
50       })
52       it('should reject numbers for app.' + method, function(){
53         var app = express();
54         app[method].bind(app, '/', 3).should.throw(/Number/);
55       })
56     });
58     it('should re-route when method is altered', function (done) {
59       var app = express();
60       var cb = after(3, done);
62       app.use(function (req, res, next) {
63         if (req.method !== 'POST') return next();
64         req.method = 'DELETE';
65         res.setHeader('X-Method-Altered', '1');
66         next();
67       });
69       app.delete('/', function (req, res) {
70         res.end('deleted everything');
71       });
73       request(app)
74       .get('/')
75       .expect(404, cb)
77       request(app)
78       .delete('/')
79       .expect(200, 'deleted everything', cb);
81       request(app)
82       .post('/')
83       .expect('X-Method-Altered', '1')
84       .expect(200, 'deleted everything', cb);
85     });
86   })
88   describe('decode params', function () {
89     it('should decode correct params', function(done){
90       var app = express();
92       app.get('/:name', function(req, res, next){
93         res.send(req.params.name);
94       });
96       request(app)
97       .get('/foo%2Fbar')
98       .expect('foo/bar', done);
99     })
101     it('should not accept params in malformed paths', function(done) {
102       var app = express();
104       app.get('/:name', function(req, res, next){
105         res.send(req.params.name);
106       });
108       request(app)
109       .get('/%foobar')
110       .expect(400, done);
111     })
113     it('should not decode spaces', function(done) {
114       var app = express();
116       app.get('/:name', function(req, res, next){
117         res.send(req.params.name);
118       });
120       request(app)
121       .get('/foo+bar')
122       .expect('foo+bar', done);
123     })
125     it('should work with unicode', function(done) {
126       var app = express();
128       app.get('/:name', function(req, res, next){
129         res.send(req.params.name);
130       });
132       request(app)
133       .get('/%ce%b1')
134       .expect('\u03b1', done);
135     })
136   })
138   it('should be .use()able', function(done){
139     var app = express();
141     var calls = [];
143     app.use(function(req, res, next){
144       calls.push('before');
145       next();
146     });
148     app.get('/', function(req, res, next){
149       calls.push('GET /')
150       next();
151     });
153     app.use(function(req, res, next){
154       calls.push('after');
155       res.json(calls)
156     });
158     request(app)
159     .get('/')
160     .expect(200, ['before', 'GET /', 'after'], done)
161   })
163   describe('when given a regexp', function(){
164     it('should match the pathname only', function(done){
165       var app = express();
167       app.get(/^\/user\/[0-9]+$/, function(req, res){
168         res.end('user');
169       });
171       request(app)
172       .get('/user/12?foo=bar')
173       .expect('user', done);
174     })
176     it('should populate req.params with the captures', function(done){
177       var app = express();
179       app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function(req, res){
180         var id = req.params[0]
181           , op = req.params[1];
182         res.end(op + 'ing user ' + id);
183       });
185       request(app)
186       .get('/user/10/edit')
187       .expect('editing user 10', done);
188     })
190     it('should ensure regexp matches path prefix', function (done) {
191       var app = express()
192       var p = []
194       app.use(/\/api.*/, function (req, res, next) {
195         p.push('a')
196         next()
197       })
198       app.use(/api/, function (req, res, next) {
199         p.push('b')
200         next()
201       })
202       app.use(/\/test/, function (req, res, next) {
203         p.push('c')
204         next()
205       })
206       app.use(function (req, res) {
207         res.end()
208       })
210       request(app)
211         .get('/test/api/1234')
212         .expect(200, function (err) {
213           if (err) return done(err)
214           assert.deepEqual(p, ['c'])
215           done()
216         })
217     })
218   })
220   describe('case sensitivity', function(){
221     it('should be disabled by default', function(done){
222       var app = express();
224       app.get('/user', function(req, res){
225         res.end('tj');
226       });
228       request(app)
229       .get('/USER')
230       .expect('tj', done);
231     })
233     describe('when "case sensitive routing" is enabled', function(){
234       it('should match identical casing', function(done){
235         var app = express();
237         app.enable('case sensitive routing');
239         app.get('/uSer', function(req, res){
240           res.end('tj');
241         });
243         request(app)
244         .get('/uSer')
245         .expect('tj', done);
246       })
248       it('should not match otherwise', function(done){
249         var app = express();
251         app.enable('case sensitive routing');
253         app.get('/uSer', function(req, res){
254           res.end('tj');
255         });
257         request(app)
258         .get('/user')
259         .expect(404, done);
260       })
261     })
262   })
264   describe('params', function(){
265     it('should overwrite existing req.params by default', function(done){
266       var app = express();
267       var router = new express.Router();
269       router.get('/:action', function(req, res){
270         res.send(req.params);
271       });
273       app.use('/user/:user', router);
275       request(app)
276       .get('/user/1/get')
277       .expect(200, '{"action":"get"}', done);
278     })
280     it('should allow merging existing req.params', function(done){
281       var app = express();
282       var router = new express.Router({ mergeParams: true });
284       router.get('/:action', function(req, res){
285         var keys = Object.keys(req.params).sort();
286         res.send(keys.map(function(k){ return [k, req.params[k]] }));
287       });
289       app.use('/user/:user', router);
291       request(app)
292       .get('/user/tj/get')
293       .expect(200, '[["action","get"],["user","tj"]]', done);
294     })
296     it('should use params from router', function(done){
297       var app = express();
298       var router = new express.Router({ mergeParams: true });
300       router.get('/:thing', function(req, res){
301         var keys = Object.keys(req.params).sort();
302         res.send(keys.map(function(k){ return [k, req.params[k]] }));
303       });
305       app.use('/user/:thing', router);
307       request(app)
308       .get('/user/tj/get')
309       .expect(200, '[["thing","get"]]', done);
310     })
312     it('should merge numeric indices req.params', function(done){
313       var app = express();
314       var router = new express.Router({ mergeParams: true });
316       router.get('/*.*', function(req, res){
317         var keys = Object.keys(req.params).sort();
318         res.send(keys.map(function(k){ return [k, req.params[k]] }));
319       });
321       app.use('/user/id:(\\d+)', router);
323       request(app)
324       .get('/user/id:10/profile.json')
325       .expect(200, '[["0","10"],["1","profile"],["2","json"]]', done);
326     })
328     it('should merge numeric indices req.params when more in parent', function(done){
329       var app = express();
330       var router = new express.Router({ mergeParams: true });
332       router.get('/*', function(req, res){
333         var keys = Object.keys(req.params).sort();
334         res.send(keys.map(function(k){ return [k, req.params[k]] }));
335       });
337       app.use('/user/id:(\\d+)/name:(\\w+)', router);
339       request(app)
340       .get('/user/id:10/name:tj/profile')
341       .expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done);
342     })
344     it('should merge numeric indices req.params when parent has same number', function(done){
345       var app = express();
346       var router = new express.Router({ mergeParams: true });
348       router.get('/name:(\\w+)', function(req, res){
349         var keys = Object.keys(req.params).sort();
350         res.send(keys.map(function(k){ return [k, req.params[k]] }));
351       });
353       app.use('/user/id:(\\d+)', router);
355       request(app)
356       .get('/user/id:10/name:tj')
357       .expect(200, '[["0","10"],["1","tj"]]', done);
358     })
360     it('should ignore invalid incoming req.params', function(done){
361       var app = express();
362       var router = new express.Router({ mergeParams: true });
364       router.get('/:name', function(req, res){
365         var keys = Object.keys(req.params).sort();
366         res.send(keys.map(function(k){ return [k, req.params[k]] }));
367       });
369       app.use('/user/', function (req, res, next) {
370         req.params = 3; // wat?
371         router(req, res, next);
372       });
374       request(app)
375       .get('/user/tj')
376       .expect(200, '[["name","tj"]]', done);
377     })
379     it('should restore req.params', function(done){
380       var app = express();
381       var router = new express.Router({ mergeParams: true });
383       router.get('/user:(\\w+)/*', function (req, res, next) {
384         next();
385       });
387       app.use('/user/id:(\\d+)', function (req, res, next) {
388         router(req, res, function (err) {
389           var keys = Object.keys(req.params).sort();
390           res.send(keys.map(function(k){ return [k, req.params[k]] }));
391         });
392       });
394       request(app)
395       .get('/user/id:42/user:tj/profile')
396       .expect(200, '[["0","42"]]', done);
397     })
398   })
400   describe('trailing slashes', function(){
401     it('should be optional by default', function(done){
402       var app = express();
404       app.get('/user', function(req, res){
405         res.end('tj');
406       });
408       request(app)
409       .get('/user/')
410       .expect('tj', done);
411     })
413     describe('when "strict routing" is enabled', function(){
414       it('should match trailing slashes', function(done){
415         var app = express();
417         app.enable('strict routing');
419         app.get('/user/', function(req, res){
420           res.end('tj');
421         });
423         request(app)
424         .get('/user/')
425         .expect('tj', done);
426       })
428       it('should pass-though middleware', function(done){
429         var app = express();
431         app.enable('strict routing');
433         app.use(function (req, res, next) {
434           res.setHeader('x-middleware', 'true');
435           next();
436         });
438         app.get('/user/', function(req, res){
439           res.end('tj');
440         });
442         request(app)
443         .get('/user/')
444         .expect('x-middleware', 'true')
445         .expect(200, 'tj', done);
446       })
448       it('should pass-though mounted middleware', function(done){
449         var app = express();
451         app.enable('strict routing');
453         app.use('/user/', function (req, res, next) {
454           res.setHeader('x-middleware', 'true');
455           next();
456         });
458         app.get('/user/test/', function(req, res){
459           res.end('tj');
460         });
462         request(app)
463         .get('/user/test/')
464         .expect('x-middleware', 'true')
465         .expect(200, 'tj', done);
466       })
468       it('should match no slashes', function(done){
469         var app = express();
471         app.enable('strict routing');
473         app.get('/user', function(req, res){
474           res.end('tj');
475         });
477         request(app)
478         .get('/user')
479         .expect('tj', done);
480       })
482       it('should match middleware when omitting the trailing slash', function(done){
483         var app = express();
485         app.enable('strict routing');
487         app.use('/user/', function(req, res){
488           res.end('tj');
489         });
491         request(app)
492         .get('/user')
493         .expect(200, 'tj', done);
494       })
496       it('should match middleware', function(done){
497         var app = express();
499         app.enable('strict routing');
501         app.use('/user', function(req, res){
502           res.end('tj');
503         });
505         request(app)
506         .get('/user')
507         .expect(200, 'tj', done);
508       })
510       it('should match middleware when adding the trailing slash', function(done){
511         var app = express();
513         app.enable('strict routing');
515         app.use('/user', function(req, res){
516           res.end('tj');
517         });
519         request(app)
520         .get('/user/')
521         .expect(200, 'tj', done);
522       })
524       it('should fail when omitting the trailing slash', function(done){
525         var app = express();
527         app.enable('strict routing');
529         app.get('/user/', function(req, res){
530           res.end('tj');
531         });
533         request(app)
534         .get('/user')
535         .expect(404, done);
536       })
538       it('should fail when adding the trailing slash', function(done){
539         var app = express();
541         app.enable('strict routing');
543         app.get('/user', function(req, res){
544           res.end('tj');
545         });
547         request(app)
548         .get('/user/')
549         .expect(404, done);
550       })
551     })
552   })
554   it('should allow escaped regexp', function(done){
555     var app = express();
557     app.get('/user/\\d+', function(req, res){
558       res.end('woot');
559     });
561     request(app)
562     .get('/user/10')
563     .expect(200, function (err) {
564       if (err) return done(err)
565       request(app)
566       .get('/user/tj')
567       .expect(404, done);
568     });
569   })
571   it('should allow literal "."', function(done){
572     var app = express();
574     app.get('/api/users/:from..:to', function(req, res){
575       var from = req.params.from
576         , to = req.params.to;
578       res.end('users from ' + from + ' to ' + to);
579     });
581     request(app)
582     .get('/api/users/1..50')
583     .expect('users from 1 to 50', done);
584   })
586   describe('*', function(){
587     it('should capture everything', function (done) {
588       var app = express()
590       app.get('*', function (req, res) {
591         res.end(req.params[0])
592       })
594       request(app)
595       .get('/user/tobi.json')
596       .expect('/user/tobi.json', done)
597     })
599     it('should decode the capture', function (done) {
600       var app = express()
602       app.get('*', function (req, res) {
603         res.end(req.params[0])
604       })
606       request(app)
607       .get('/user/tobi%20and%20loki.json')
608       .expect('/user/tobi and loki.json', done)
609     })
611     it('should denote a greedy capture group', function(done){
612       var app = express();
614       app.get('/user/*.json', function(req, res){
615         res.end(req.params[0]);
616       });
618       request(app)
619       .get('/user/tj.json')
620       .expect('tj', done);
621     })
623     it('should work with several', function(done){
624       var app = express();
626       app.get('/api/*.*', function(req, res){
627         var resource = req.params[0]
628           , format = req.params[1];
629         res.end(resource + ' as ' + format);
630       });
632       request(app)
633       .get('/api/users/foo.bar.json')
634       .expect('users/foo.bar as json', done);
635     })
637     it('should work cross-segment', function(done){
638       var app = express();
640       app.get('/api*', function(req, res){
641         res.send(req.params[0]);
642       });
644       request(app)
645       .get('/api')
646       .expect('', function(){
647         request(app)
648         .get('/api/hey')
649         .expect('/hey', done);
650       });
651     })
653     it('should allow naming', function(done){
654       var app = express();
656       app.get('/api/:resource(*)', function(req, res){
657         var resource = req.params.resource;
658         res.end(resource);
659       });
661       request(app)
662       .get('/api/users/0.json')
663       .expect('users/0.json', done);
664     })
666     it('should not be greedy immediately after param', function(done){
667       var app = express();
669       app.get('/user/:user*', function(req, res){
670         res.end(req.params.user);
671       });
673       request(app)
674       .get('/user/122')
675       .expect('122', done);
676     })
678     it('should eat everything after /', function(done){
679       var app = express();
681       app.get('/user/:user*', function(req, res){
682         res.end(req.params.user);
683       });
685       request(app)
686       .get('/user/122/aaa')
687       .expect('122', done);
688     })
690     it('should span multiple segments', function(done){
691       var app = express();
693       app.get('/file/*', function(req, res){
694         res.end(req.params[0]);
695       });
697       request(app)
698       .get('/file/javascripts/jquery.js')
699       .expect('javascripts/jquery.js', done);
700     })
702     it('should be optional', function(done){
703       var app = express();
705       app.get('/file/*', function(req, res){
706         res.end(req.params[0]);
707       });
709       request(app)
710       .get('/file/')
711       .expect('', done);
712     })
714     it('should require a preceding /', function(done){
715       var app = express();
717       app.get('/file/*', function(req, res){
718         res.end(req.params[0]);
719       });
721       request(app)
722       .get('/file')
723       .expect(404, done);
724     })
726     it('should keep correct parameter indexes', function(done){
727       var app = express();
729       app.get('/*/user/:id', function (req, res) {
730         res.send(req.params);
731       });
733       request(app)
734       .get('/1/user/2')
735       .expect(200, '{"0":"1","id":"2"}', done);
736     })
738     it('should work within arrays', function(done){
739       var app = express();
741       app.get(['/user/:id', '/foo/*', '/:bar'], function (req, res) {
742         res.send(req.params.bar);
743       });
745       request(app)
746       .get('/test')
747       .expect(200, 'test', done);
748     })
749   })
751   describe(':name', function(){
752     it('should denote a capture group', function(done){
753       var app = express();
755       app.get('/user/:user', function(req, res){
756         res.end(req.params.user);
757       });
759       request(app)
760       .get('/user/tj')
761       .expect('tj', done);
762     })
764     it('should match a single segment only', function(done){
765       var app = express();
767       app.get('/user/:user', function(req, res){
768         res.end(req.params.user);
769       });
771       request(app)
772       .get('/user/tj/edit')
773       .expect(404, done);
774     })
776     it('should allow several capture groups', function(done){
777       var app = express();
779       app.get('/user/:user/:op', function(req, res){
780         res.end(req.params.op + 'ing ' + req.params.user);
781       });
783       request(app)
784       .get('/user/tj/edit')
785       .expect('editing tj', done);
786     })
788     it('should work following a partial capture group', function(done){
789       var app = express();
790       var cb = after(2, done);
792       app.get('/user(s)?/:user/:op', function(req, res){
793         res.end(req.params.op + 'ing ' + req.params.user + (req.params[0] ? ' (old)' : ''));
794       });
796       request(app)
797       .get('/user/tj/edit')
798       .expect('editing tj', cb);
800       request(app)
801       .get('/users/tj/edit')
802       .expect('editing tj (old)', cb);
803     })
805     it('should work inside literal parenthesis', function(done){
806       var app = express();
808       app.get('/:user\\(:op\\)', function(req, res){
809         res.end(req.params.op + 'ing ' + req.params.user);
810       });
812       request(app)
813       .get('/tj(edit)')
814       .expect('editing tj', done);
815     })
817     it('should work in array of paths', function(done){
818       var app = express();
819       var cb = after(2, done);
821       app.get(['/user/:user/poke', '/user/:user/pokes'], function(req, res){
822         res.end('poking ' + req.params.user);
823       });
825       request(app)
826       .get('/user/tj/poke')
827       .expect('poking tj', cb);
829       request(app)
830       .get('/user/tj/pokes')
831       .expect('poking tj', cb);
832     })
833   })
835   describe(':name?', function(){
836     it('should denote an optional capture group', function(done){
837       var app = express();
839       app.get('/user/:user/:op?', function(req, res){
840         var op = req.params.op || 'view';
841         res.end(op + 'ing ' + req.params.user);
842       });
844       request(app)
845       .get('/user/tj')
846       .expect('viewing tj', done);
847     })
849     it('should populate the capture group', function(done){
850       var app = express();
852       app.get('/user/:user/:op?', function(req, res){
853         var op = req.params.op || 'view';
854         res.end(op + 'ing ' + req.params.user);
855       });
857       request(app)
858       .get('/user/tj/edit')
859       .expect('editing tj', done);
860     })
861   })
863   describe('.:name', function(){
864     it('should denote a format', function(done){
865       var app = express();
867       app.get('/:name.:format', function(req, res){
868         res.end(req.params.name + ' as ' + req.params.format);
869       });
871       request(app)
872       .get('/foo.json')
873       .expect('foo as json', function(){
874         request(app)
875         .get('/foo')
876         .expect(404, done);
877       });
878     })
879   })
881   describe('.:name?', function(){
882     it('should denote an optional format', function(done){
883       var app = express();
885       app.get('/:name.:format?', function(req, res){
886         res.end(req.params.name + ' as ' + (req.params.format || 'html'));
887       });
889       request(app)
890       .get('/foo')
891       .expect('foo as html', function(){
892         request(app)
893         .get('/foo.json')
894         .expect('foo as json', done);
895       });
896     })
897   })
899   describe('when next() is called', function(){
900     it('should continue lookup', function(done){
901       var app = express()
902         , calls = [];
904       app.get('/foo/:bar?', function(req, res, next){
905         calls.push('/foo/:bar?');
906         next();
907       });
909       app.get('/bar', function(req, res){
910         assert(0);
911       });
913       app.get('/foo', function(req, res, next){
914         calls.push('/foo');
915         next();
916       });
918       app.get('/foo', function(req, res, next){
919         calls.push('/foo 2');
920         res.json(calls)
921       });
923       request(app)
924       .get('/foo')
925       .expect(200, ['/foo/:bar?', '/foo', '/foo 2'], done)
926     })
927   })
929   describe('when next("route") is called', function(){
930     it('should jump to next route', function(done){
931       var app = express()
933       function fn(req, res, next){
934         res.set('X-Hit', '1')
935         next('route')
936       }
938       app.get('/foo', fn, function(req, res, next){
939         res.end('failure')
940       });
942       app.get('/foo', function(req, res){
943         res.end('success')
944       })
946       request(app)
947       .get('/foo')
948       .expect('X-Hit', '1')
949       .expect(200, 'success', done)
950     })
951   })
953   describe('when next("router") is called', function () {
954     it('should jump out of router', function (done) {
955       var app = express()
956       var router = express.Router()
958       function fn (req, res, next) {
959         res.set('X-Hit', '1')
960         next('router')
961       }
963       router.get('/foo', fn, function (req, res, next) {
964         res.end('failure')
965       })
967       router.get('/foo', function (req, res, next) {
968         res.end('failure')
969       })
971       app.use(router)
973       app.get('/foo', function (req, res) {
974         res.end('success')
975       })
977       request(app)
978       .get('/foo')
979       .expect('X-Hit', '1')
980       .expect(200, 'success', done)
981     })
982   })
984   describe('when next(err) is called', function(){
985     it('should break out of app.router', function(done){
986       var app = express()
987         , calls = [];
989       app.get('/foo/:bar?', function(req, res, next){
990         calls.push('/foo/:bar?');
991         next();
992       });
994       app.get('/bar', function(req, res){
995         assert(0);
996       });
998       app.get('/foo', function(req, res, next){
999         calls.push('/foo');
1000         next(new Error('fail'));
1001       });
1003       app.get('/foo', function(req, res, next){
1004         assert(0);
1005       });
1007       app.use(function(err, req, res, next){
1008         res.json({
1009           calls: calls,
1010           error: err.message
1011         })
1012       })
1014       request(app)
1015       .get('/foo')
1016       .expect(200, { calls: ['/foo/:bar?', '/foo'], error: 'fail' }, done)
1017     })
1019     it('should call handler in same route, if exists', function(done){
1020       var app = express();
1022       function fn1(req, res, next) {
1023         next(new Error('boom!'));
1024       }
1026       function fn2(req, res, next) {
1027         res.send('foo here');
1028       }
1030       function fn3(err, req, res, next) {
1031         res.send('route go ' + err.message);
1032       }
1034       app.get('/foo', fn1, fn2, fn3);
1036       app.use(function (err, req, res, next) {
1037         res.end('error!');
1038       })
1040       request(app)
1041       .get('/foo')
1042       .expect('route go boom!', done)
1043     })
1044   })
1046   it('should allow rewriting of the url', function(done){
1047     var app = express();
1049     app.get('/account/edit', function(req, res, next){
1050       req.user = { id: 12 }; // faux authenticated user
1051       req.url = '/user/' + req.user.id + '/edit';
1052       next();
1053     });
1055     app.get('/user/:id/edit', function(req, res){
1056       res.send('editing user ' + req.params.id);
1057     });
1059     request(app)
1060     .get('/account/edit')
1061     .expect('editing user 12', done);
1062   })
1064   it('should run in order added', function(done){
1065     var app = express();
1066     var path = [];
1068     app.get('*', function(req, res, next){
1069       path.push(0);
1070       next();
1071     });
1073     app.get('/user/:id', function(req, res, next){
1074       path.push(1);
1075       next();
1076     });
1078     app.use(function(req, res, next){
1079       path.push(2);
1080       next();
1081     });
1083     app.all('/user/:id', function(req, res, next){
1084       path.push(3);
1085       next();
1086     });
1088     app.get('*', function(req, res, next){
1089       path.push(4);
1090       next();
1091     });
1093     app.use(function(req, res, next){
1094       path.push(5);
1095       res.end(path.join(','))
1096     });
1098     request(app)
1099     .get('/user/1')
1100     .expect(200, '0,1,2,3,4,5', done);
1101   })
1103   it('should be chainable', function(){
1104     var app = express();
1105     app.get('/', function(){}).should.equal(app);
1106   })