'use strict' var after = require('after'); var express = require('../') , request = require('supertest') , assert = require('node:assert') , methods = require('../lib/utils').methods; var shouldSkipQuery = require('./support/utils').shouldSkipQuery describe('app.router', function () { it('should restore req.params after leaving router', function (done) { var app = express(); var router = new express.Router(); function handler1(req, res, next) { res.setHeader('x-user-id', String(req.params.id)); next() } function handler2(req, res) { res.send(req.params.id); } router.use(function (req, res, next) { res.setHeader('x-router', String(req.params.id)); next(); }); app.get('/user/:id', handler1, router, handler2); request(app) .get('/user/1') .expect('x-router', 'undefined') .expect('x-user-id', '1') .expect(200, '1', done); }) describe('methods', function () { methods.forEach(function (method) { if (method === 'connect') return; it('should include ' + method.toUpperCase(), function (done) { if (method === 'query' && shouldSkipQuery(process.versions.node)) { this.skip() } var app = express(); app[method]('/foo', function (req, res) { res.send(method) }); request(app) [method]('/foo') .expect(200, done) }) it('should reject numbers for app.' + method, function () { var app = express(); assert.throws(app[method].bind(app, '/', 3), /argument handler must be a function/); }) }); it('should re-route when method is altered', function (done) { var app = express(); var cb = after(3, done); app.use(function (req, res, next) { if (req.method !== 'POST') return next(); req.method = 'DELETE'; res.setHeader('X-Method-Altered', '1'); next(); }); app.delete('/', function (req, res) { res.end('deleted everything'); }); request(app) .get('/') .expect(404, cb) request(app) .delete('/') .expect(200, 'deleted everything', cb); request(app) .post('/') .expect('X-Method-Altered', '1') .expect(200, 'deleted everything', cb); }); }) describe('decode params', function () { it('should decode correct params', function (done) { var app = express(); app.get('/:name', function (req, res) { res.send(req.params.name); }); request(app) .get('/foo%2Fbar') .expect('foo/bar', done); }) it('should not accept params in malformed paths', function (done) { var app = express(); app.get('/:name', function (req, res) { res.send(req.params.name); }); request(app) .get('/%foobar') .expect(400, done); }) it('should not decode spaces', function (done) { var app = express(); app.get('/:name', function (req, res) { res.send(req.params.name); }); request(app) .get('/foo+bar') .expect('foo+bar', done); }) it('should work with unicode', function (done) { var app = express(); app.get('/:name', function (req, res) { res.send(req.params.name); }); request(app) .get('/%ce%b1') .expect('\u03b1', done); }) }) it('should be .use()able', function (done) { var app = express(); var calls = []; app.use(function (req, res, next) { calls.push('before'); next(); }); app.get('/', function (req, res, next) { calls.push('GET /') next(); }); app.use(function (req, res, next) { calls.push('after'); res.json(calls) }); request(app) .get('/') .expect(200, ['before', 'GET /', 'after'], done) }) describe('when given a regexp', function () { it('should match the pathname only', function (done) { var app = express(); app.get(/^\/user\/[0-9]+$/, function (req, res) { res.end('user'); }); request(app) .get('/user/12?foo=bar') .expect('user', done); }) it('should populate req.params with the captures', function (done) { var app = express(); app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function (req, res) { var id = req.params[0] , op = req.params[1]; res.end(op + 'ing user ' + id); }); request(app) .get('/user/10/edit') .expect('editing user 10', done); }) if (supportsRegexp('(?.*)')) { it('should populate req.params with named captures', function (done) { var app = express(); var re = new RegExp('^/user/(?[0-9]+)/(view|edit)?$'); app.get(re, function (req, res) { var id = req.params.userId , op = req.params[0]; res.end(op + 'ing user ' + id); }); request(app) .get('/user/10/edit') .expect('editing user 10', done); }) } it('should ensure regexp matches path prefix', function (done) { var app = express() var p = [] app.use(/\/api.*/, function (req, res, next) { p.push('a') next() }) app.use(/api/, function (req, res, next) { p.push('b') next() }) app.use(/\/test/, function (req, res, next) { p.push('c') next() }) app.use(function (req, res) { res.end() }) request(app) .get('/test/api/1234') .expect(200, function (err) { if (err) return done(err) assert.deepEqual(p, ['c']) done() }) }) }) describe('case sensitivity', function () { it('should be disabled by default', function (done) { var app = express(); app.get('/user', function (req, res) { res.end('tj'); }); request(app) .get('/USER') .expect('tj', done); }) describe('when "case sensitive routing" is enabled', function () { it('should match identical casing', function (done) { var app = express(); app.enable('case sensitive routing'); app.get('/uSer', function (req, res) { res.end('tj'); }); request(app) .get('/uSer') .expect('tj', done); }) it('should not match otherwise', function (done) { var app = express(); app.enable('case sensitive routing'); app.get('/uSer', function (req, res) { res.end('tj'); }); request(app) .get('/user') .expect(404, done); }) }) }) describe('params', function () { it('should overwrite existing req.params by default', function (done) { var app = express(); var router = new express.Router(); router.get('/:action', function (req, res) { res.send(req.params); }); app.use('/user/:user', router); request(app) .get('/user/1/get') .expect(200, '{"action":"get"}', done); }) it('should allow merging existing req.params', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); router.get('/:action', function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use('/user/:user', router); request(app) .get('/user/tj/get') .expect(200, '[["action","get"],["user","tj"]]', done); }) it('should use params from router', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); router.get('/:thing', function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use('/user/:thing', router); request(app) .get('/user/tj/get') .expect(200, '[["thing","get"]]', done); }) it('should merge numeric indices req.params', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); router.get(/^\/(.*)\.(.*)/, function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use(/^\/user\/id:(\d+)/, router); request(app) .get('/user/id:10/profile.json') .expect(200, '[["0","10"],["1","profile"],["2","json"]]', done); }) it('should merge numeric indices req.params when more in parent', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); router.get(/\/(.*)/, function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use(/^\/user\/id:(\d+)\/name:(\w+)/, router); request(app) .get('/user/id:10/name:tj/profile') .expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done); }) it('should merge numeric indices req.params when parent has same number', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); router.get(/\/name:(\w+)/, function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use(/\/user\/id:(\d+)/, router); request(app) .get('/user/id:10/name:tj') .expect(200, '[["0","10"],["1","tj"]]', done); }) it('should ignore invalid incoming req.params', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); router.get('/:name', function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use('/user/', function (req, res, next) { req.params = 3; // wat? router(req, res, next); }); request(app) .get('/user/tj') .expect(200, '[["name","tj"]]', done); }) it('should restore req.params', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); router.get(/\/user:(\w+)\//, function (req, res, next) { next(); }); app.use(/\/user\/id:(\d+)/, function (req, res, next) { router(req, res, function (err) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function (k) { return [k, req.params[k]] })); }); }); request(app) .get('/user/id:42/user:tj/profile') .expect(200, '[["0","42"]]', done); }) }) describe('trailing slashes', function () { it('should be optional by default', function (done) { var app = express(); app.get('/user', function (req, res) { res.end('tj'); }); request(app) .get('/user/') .expect('tj', done); }) describe('when "strict routing" is enabled', function () { it('should match trailing slashes', function (done) { var app = express(); app.enable('strict routing'); app.get('/user/', function (req, res) { res.end('tj'); }); request(app) .get('/user/') .expect('tj', done); }) it('should pass-though middleware', function (done) { var app = express(); app.enable('strict routing'); app.use(function (req, res, next) { res.setHeader('x-middleware', 'true'); next(); }); app.get('/user/', function (req, res) { res.end('tj'); }); request(app) .get('/user/') .expect('x-middleware', 'true') .expect(200, 'tj', done); }) it('should pass-though mounted middleware', function (done) { var app = express(); app.enable('strict routing'); app.use('/user/', function (req, res, next) { res.setHeader('x-middleware', 'true'); next(); }); app.get('/user/test/', function (req, res) { res.end('tj'); }); request(app) .get('/user/test/') .expect('x-middleware', 'true') .expect(200, 'tj', done); }) it('should match no slashes', function (done) { var app = express(); app.enable('strict routing'); app.get('/user', function (req, res) { res.end('tj'); }); request(app) .get('/user') .expect('tj', done); }) it('should match middleware when omitting the trailing slash', function (done) { var app = express(); app.enable('strict routing'); app.use('/user/', function (req, res) { res.end('tj'); }); request(app) .get('/user') .expect(200, 'tj', done); }) it('should match middleware', function (done) { var app = express(); app.enable('strict routing'); app.use('/user', function (req, res) { res.end('tj'); }); request(app) .get('/user') .expect(200, 'tj', done); }) it('should match middleware when adding the trailing slash', function (done) { var app = express(); app.enable('strict routing'); app.use('/user', function (req, res) { res.end('tj'); }); request(app) .get('/user/') .expect(200, 'tj', done); }) it('should fail when omitting the trailing slash', function (done) { var app = express(); app.enable('strict routing'); app.get('/user/', function (req, res) { res.end('tj'); }); request(app) .get('/user') .expect(404, done); }) it('should fail when adding the trailing slash', function (done) { var app = express(); app.enable('strict routing'); app.get('/user', function (req, res) { res.end('tj'); }); request(app) .get('/user/') .expect(404, done); }) }) }) it('should allow literal "."', function (done) { var app = express(); app.get('/api/users/:from..:to', function (req, res) { var from = req.params.from , to = req.params.to; res.end('users from ' + from + ' to ' + to); }); request(app) .get('/api/users/1..50') .expect('users from 1 to 50', done); }) describe(':name', function () { it('should denote a capture group', function (done) { var app = express(); app.get('/user/:user', function (req, res) { res.end(req.params.user); }); request(app) .get('/user/tj') .expect('tj', done); }) it('should match a single segment only', function (done) { var app = express(); app.get('/user/:user', function (req, res) { res.end(req.params.user); }); request(app) .get('/user/tj/edit') .expect(404, done); }) it('should allow several capture groups', function (done) { var app = express(); app.get('/user/:user/:op', function (req, res) { res.end(req.params.op + 'ing ' + req.params.user); }); request(app) .get('/user/tj/edit') .expect('editing tj', done); }) it('should work following a partial capture group', function (done) { var app = express(); var cb = after(2, done); app.get('/user{s}/:user/:op', function (req, res) { res.end(req.params.op + 'ing ' + req.params.user + (req.url.startsWith('/users') ? ' (old)' : '')); }); request(app) .get('/user/tj/edit') .expect('editing tj', cb); request(app) .get('/users/tj/edit') .expect('editing tj (old)', cb); }) it('should work inside literal parenthesis', function (done) { var app = express(); app.get('/:user\\(:op\\)', function (req, res) { res.end(req.params.op + 'ing ' + req.params.user); }); request(app) .get('/tj(edit)') .expect('editing tj', done); }) it('should work in array of paths', function (done) { var app = express(); var cb = after(2, done); app.get(['/user/:user/poke', '/user/:user/pokes'], function (req, res) { res.end('poking ' + req.params.user); }); request(app) .get('/user/tj/poke') .expect('poking tj', cb); request(app) .get('/user/tj/pokes') .expect('poking tj', cb); }) }) describe(':name?', function () { it('should denote an optional capture group', function (done) { var app = express(); app.get('/user/:user{/:op}', function (req, res) { var op = req.params.op || 'view'; res.end(op + 'ing ' + req.params.user); }); request(app) .get('/user/tj') .expect('viewing tj', done); }) it('should populate the capture group', function (done) { var app = express(); app.get('/user/:user{/:op}', function (req, res) { var op = req.params.op || 'view'; res.end(op + 'ing ' + req.params.user); }); request(app) .get('/user/tj/edit') .expect('editing tj', done); }) }) describe(':name*', function () { it('should match one segment', function (done) { var app = express() app.get('/user/*user', function (req, res) { res.end(req.params.user[0]) }) request(app) .get('/user/122') .expect('122', done) }) it('should match many segments', function (done) { var app = express() app.get('/user/*user', function (req, res) { res.end(req.params.user.join('/')) }) request(app) .get('/user/1/2/3/4') .expect('1/2/3/4', done) }) it('should match zero segments', function (done) { var app = express() app.get('/user{/*user}', function (req, res) { res.end(req.params.user) }) request(app) .get('/user') .expect('', done) }) }) describe(':name+', function () { it('should match one segment', function (done) { var app = express() app.get('/user/*user', function (req, res) { res.end(req.params.user[0]) }) request(app) .get('/user/122') .expect(200, '122', done) }) it('should match many segments', function (done) { var app = express() app.get('/user/*user', function (req, res) { res.end(req.params.user.join('/')) }) request(app) .get('/user/1/2/3/4') .expect(200, '1/2/3/4', done) }) it('should not match zero segments', function (done) { var app = express() app.get('/user/*user', function (req, res) { res.end(req.params.user) }) request(app) .get('/user') .expect(404, done) }) }) describe('.:name', function () { it('should denote a format', function (done) { var app = express(); var cb = after(2, done) app.get('/:name.:format', function (req, res) { res.end(req.params.name + ' as ' + req.params.format); }); request(app) .get('/foo.json') .expect(200, 'foo as json', cb) request(app) .get('/foo') .expect(404, cb) }) }) describe('.:name?', function () { it('should denote an optional format', function (done) { var app = express(); var cb = after(2, done) app.get('/:name{.:format}', function (req, res) { res.end(req.params.name + ' as ' + (req.params.format || 'html')); }); request(app) .get('/foo') .expect(200, 'foo as html', cb) request(app) .get('/foo.json') .expect(200, 'foo as json', cb) }) }) describe('when next() is called', function () { it('should continue lookup', function (done) { var app = express() , calls = []; app.get('/foo{/:bar}', function (req, res, next) { calls.push('/foo/:bar?'); next(); }); app.get('/bar', function () { assert(0); }); app.get('/foo', function (req, res, next) { calls.push('/foo'); next(); }); app.get('/foo', function (req, res) { calls.push('/foo 2'); res.json(calls) }); request(app) .get('/foo') .expect(200, ['/foo/:bar?', '/foo', '/foo 2'], done) }) }) describe('when next("route") is called', function () { it('should jump to next route', function (done) { var app = express() function fn(req, res, next) { res.set('X-Hit', '1') next('route') } app.get('/foo', fn, function (req, res) { res.end('failure') }); app.get('/foo', function (req, res) { res.end('success') }) request(app) .get('/foo') .expect('X-Hit', '1') .expect(200, 'success', done) }) }) describe('when next("router") is called', function () { it('should jump out of router', function (done) { var app = express() var router = express.Router() function fn(req, res, next) { res.set('X-Hit', '1') next('router') } router.get('/foo', fn, function (req, res) { res.end('failure') }) router.get('/foo', function (req, res) { res.end('failure') }) app.use(router) app.get('/foo', function (req, res) { res.end('success') }) request(app) .get('/foo') .expect('X-Hit', '1') .expect(200, 'success', done) }) }) describe('when next(err) is called', function () { it('should break out of app.router', function (done) { var app = express() , calls = []; app.get('/foo{/:bar}', function (req, res, next) { calls.push('/foo/:bar?'); next(); }); app.get('/bar', function () { assert(0); }); app.get('/foo', function (req, res, next) { calls.push('/foo'); next(new Error('fail')); }); app.get('/foo', function () { assert(0); }); app.use(function (err, req, res, next) { res.json({ calls: calls, error: err.message }) }) request(app) .get('/foo') .expect(200, { calls: ['/foo/:bar?', '/foo'], error: 'fail' }, done) }) it('should call handler in same route, if exists', function (done) { var app = express(); function fn1(req, res, next) { next(new Error('boom!')); } function fn2(req, res, next) { res.send('foo here'); } function fn3(err, req, res, next) { res.send('route go ' + err.message); } app.get('/foo', fn1, fn2, fn3); app.use(function (err, req, res, next) { res.end('error!'); }) request(app) .get('/foo') .expect('route go boom!', done) }) }) describe('promise support', function () { it('should pass rejected promise value', function (done) { var app = express() var router = new express.Router() router.use(function createError(req, res, next) { return Promise.reject(new Error('boom!')) }) router.use(function sawError(err, req, res, next) { res.send('saw ' + err.name + ': ' + err.message) }) app.use(router) request(app) .get('/') .expect(200, 'saw Error: boom!', done) }) it('should pass rejected promise without value', function (done) { var app = express() var router = new express.Router() router.use(function createError(req, res, next) { return Promise.reject() }) router.use(function sawError(err, req, res, next) { res.send('saw ' + err.name + ': ' + err.message) }) app.use(router) request(app) .get('/') .expect(200, 'saw Error: Rejected promise', done) }) it('should ignore resolved promise', function (done) { var app = express() var router = new express.Router() router.use(function createError(req, res, next) { res.send('saw GET /foo') return Promise.resolve('foo') }) router.use(function () { done(new Error('Unexpected middleware invoke')) }) app.use(router) request(app) .get('/foo') .expect(200, 'saw GET /foo', done) }) describe('error handling', function () { it('should pass rejected promise value', function (done) { var app = express() var router = new express.Router() router.use(function createError(req, res, next) { return Promise.reject(new Error('boom!')) }) router.use(function handleError(err, req, res, next) { return Promise.reject(new Error('caught: ' + err.message)) }) router.use(function sawError(err, req, res, next) { res.send('saw ' + err.name + ': ' + err.message) }) app.use(router) request(app) .get('/') .expect(200, 'saw Error: caught: boom!', done) }) it('should pass rejected promise without value', function (done) { var app = express() var router = new express.Router() router.use(function createError(req, res, next) { return Promise.reject() }) router.use(function handleError(err, req, res, next) { return Promise.reject(new Error('caught: ' + err.message)) }) router.use(function sawError(err, req, res, next) { res.send('saw ' + err.name + ': ' + err.message) }) app.use(router) request(app) .get('/') .expect(200, 'saw Error: caught: Rejected promise', done) }) it('should ignore resolved promise', function (done) { var app = express() var router = new express.Router() router.use(function createError(req, res, next) { return Promise.reject(new Error('boom!')) }) router.use(function handleError(err, req, res, next) { res.send('saw ' + err.name + ': ' + err.message) return Promise.resolve('foo') }) router.use(function () { done(new Error('Unexpected middleware invoke')) }) app.use(router) request(app) .get('/foo') .expect(200, 'saw Error: boom!', done) }) }) }) it('should allow rewriting of the url', function (done) { var app = express(); app.get('/account/edit', function (req, res, next) { req.user = { id: 12 }; // faux authenticated user req.url = '/user/' + req.user.id + '/edit'; next(); }); app.get('/user/:id/edit', function (req, res) { res.send('editing user ' + req.params.id); }); request(app) .get('/account/edit') .expect('editing user 12', done); }) it('should run in order added', function (done) { var app = express(); var path = []; app.get('/*path', function (req, res, next) { path.push(0); next(); }); app.get('/user/:id', function (req, res, next) { path.push(1); next(); }); app.use(function (req, res, next) { path.push(2); next(); }); app.all('/user/:id', function (req, res, next) { path.push(3); next(); }); app.get('/*splat', function (req, res, next) { path.push(4); next(); }); app.use(function (req, res, next) { path.push(5); res.end(path.join(',')) }); request(app) .get('/user/1') .expect(200, '0,1,2,3,4,5', done); }) it('should be chainable', function () { var app = express(); assert.strictEqual(app.get('/', function () { }), app) }) it('should not use disposed router/middleware', function (done) { // more context: https://github.com/expressjs/express/issues/5743#issuecomment-2277148412 var app = express(); var router = new express.Router(); router.use(function (req, res, next) { res.setHeader('old', 'foo'); next(); }); app.use(function (req, res, next) { return router.handle(req, res, next); }); app.get('/', function (req, res, next) { res.send('yee'); next(); }); request(app) .get('/') .expect('old', 'foo') .expect(function (res) { if (typeof res.headers['new'] !== 'undefined') { throw new Error('`new` header should not be present'); } }) .expect(200, 'yee', function (err, res) { if (err) return done(err); router = new express.Router(); router.use(function (req, res, next) { res.setHeader('new', 'bar'); next(); }); request(app) .get('/') .expect('new', 'bar') .expect(function (res) { if (typeof res.headers['old'] !== 'undefined') { throw new Error('`old` header should not be present'); } }) .expect(200, 'yee', done); }); }) }) function supportsRegexp(source) { try { new RegExp(source) return true } catch (e) { return false } }