'use strict'; const fs = require('fs'); const path = require('path'); const http = require('http'); const methods = require('methods'); const request = require('supertest'); const should = require('should'); const expect = require('expect.js'); const strapi = require('../../..'); const Koa = strapi.server; describe('router', function () { it('does not register middleware more than once', function (done) { const app = new Koa(); const parentRouter = strapi.middlewares.router(); const nestedRouter = strapi.middlewares.router(); nestedRouter .get('/first-nested-route', function * (next) { this.body = { n: this.n }; }) .get('/second-nested-route', function * (next) { yield next; }) .get('/third-nested-route', function * (next) { yield next; }); parentRouter.use('/parent-route', function * (next) { this.n = this.n ? (this.n + 1) : 1; yield next; }, nestedRouter.routes()); app.use(parentRouter.routes()); request(http.createServer(app.callback())) .get('/parent-route/first-nested-route') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('n', 1); done(); }); }); it('exposes middleware factory', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.should.have.property('routes'); router.routes.should.be.type('function'); const middleware = router.routes(); should.exist(middleware); middleware.should.be.type('function'); done(); }); it('supports promises for async/await', function (done) { const app = new Koa(); app.experimental = true; const router = strapi.middlewares.router(); router.get('/async', function (next) { const ctx = this; return new Promise(function (resolve, reject) { ctx.body = { msg: 'promises!' }; resolve(); }); }); app.use(router.routes()).use(router.allowedMethods()); request(http.createServer(app.callback())) .get('/async') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('msg', 'promises!'); done(); }); }); it('matches middleware only if route was matched', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); const otherRouter = strapi.middlewares.router(); router.use(function * (next) { this.body = { bar: 'baz' }; yield next; }); otherRouter.get('/bar', function * (next) { this.body = this.body || { foo: 'bar' }; }); app.use(router.routes()).use(otherRouter.routes()); request(http.createServer(app.callback())) .get('/bar') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('foo', 'bar'); expect(res.body).to.not.have.property('bar'); done(); }) }); it('matches first to last', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router .get('user_page', '/user/(.*).jsx', function * (next) { this.body = { order: 1 }; }) .all('app', '/app/(.*).jsx', function * (next) { this.body = { order: 2 }; }) .all('view', '(.*).jsx', function * (next) { this.body = { order: 3 }; }); request(http.createServer(app.use(router.routes()).callback())) .get('/user/account.jsx') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('order', 1); done(); }) }); it('does not run subsequent middleware without yield next', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router .get('user_page', '/user/(.*).jsx', function * (next) { }, function * (next) { this.body = { order: 1 }; }); request(http.createServer(app.use(router.routes()).callback())) .get('/user/account.jsx') .expect(404) .end(done) }); it('nests routers with prefixes at root', function (done) { const app = new Koa(); const api = strapi.middlewares.router(); const forums = strapi.middlewares.router({ prefix: '/forums' }); const posts = strapi.middlewares.router({ prefix: '/:fid/posts' }); let server; posts .get('/', function * (next) { this.status = 204; yield next; }) .get('/:pid', function * (next) { this.body = this.params; yield next; }); forums.use(posts.routes()); server = http.createServer(app.use(forums.routes()).callback()); request(server) .get('/forums/1/posts') .expect(204) .end(function (err) { if (err) { return done(err); } request(server) .get('/forums/1') .expect(404) .end(function (err) { if (err) { return done(err); } request(server) .get('/forums/1/posts/2') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('fid', '1'); expect(res.body).to.have.property('pid', '2'); done(); }); }); }); }); it('nests routers with prefixes at path', function (done) { const app = new Koa(); const api = strapi.middlewares.router(); const forums = strapi.middlewares.router({ prefix: '/api' }); const posts = strapi.middlewares.router({ prefix: '/posts' }); let server; posts .get('/', function * (next) { this.status = 204; yield next; }) .get('/:pid', function * (next) { this.body = this.params; yield next; }); forums.use('/forums/:fid', posts.routes()); server = http.createServer(app.use(forums.routes()).callback()); request(server) .get('/api/forums/1/posts') .expect(204) .end(function (err) { if (err) { return done(err); } request(server) .get('/api/forums/1') .expect(404) .end(function (err) { if (err) { return done(err); } request(server) .get('/api/forums/1/posts/2') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('fid', '1'); expect(res.body).to.have.property('pid', '2'); done(); }); }); }); }); it('runs subrouter middleware after parent', function (done) { const app = new Koa(); const subrouter = strapi.middlewares.router() .use(function * (next) { this.msg = 'subrouter'; yield next; }) .get('/', function * () { this.body = { msg: this.msg }; }); const router = strapi.middlewares.router() .use(function * (next) { this.msg = 'router'; yield next; }) .use(subrouter.routes()); request(http.createServer(app.use(router.routes()).callback())) .get('/') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('msg', 'subrouter'); done(); }); }); it('runs parent middleware for subrouter routes', function (done) { const app = new Koa(); const subrouter = strapi.middlewares.router() .get('/sub', function * () { this.body = { msg: this.msg }; }); const router = strapi.middlewares.router() .use(function * (next) { this.msg = 'router'; yield next; }) .use('/parent', subrouter.routes()); request(http.createServer(app.use(router.routes()).callback())) .get('/parent/sub') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('msg', 'router'); done(); }); }); it('matches corresponding requests', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); router.get('/:category/:title', function * (next) { this.should.have.property('params'); this.params.should.have.property('category', 'programming'); this.params.should.have.property('title', 'how-to-node'); this.status = 204; }); router.post('/:category', function * (next) { this.should.have.property('params'); this.params.should.have.property('category', 'programming'); this.status = 204; }); router.put('/:category/not-a-title', function * (next) { this.should.have.property('params'); this.params.should.have.property('category', 'programming'); this.params.should.not.have.property('title'); this.status = 204; }); const server = http.createServer(app.callback()); request(server) .get('/programming/how-to-node') .expect(204) .end(function (err, res) { if (err) { return done(err); } request(server) .post('/programming') .expect(204) .end(function (err, res) { if (err) { return done(err); } request(server) .put('/programming/not-a-title') .expect(204) .end(function (err, res) { done(err); }); }); }); }); it('executes route middleware using app.context', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); router.use(function * (next) { this.bar = 'baz'; yield next; }); router.get('/:category/:title', function * (next) { this.foo = 'bar'; yield next; }, function * (next) { this.should.have.property('bar', 'baz'); this.should.have.property('foo', 'bar'); this.should.have.property('app'); this.should.have.property('req'); this.should.have.property('res'); this.status = 204; done(); }); request(http.createServer(app.callback())) .get('/match/this') .expect(204) .end(function (err) { if (err) { return done(err); } }); }); it('supports generators for route middleware', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); const readVersion = function () { return function (fn) { const packagePath = path.join(__dirname, '..', '..', '..', 'package.json'); fs.readFile(packagePath, 'utf8', function (err, data) { if (err) return fn(err); fn(null, JSON.parse(data).version); }); }; }; router .get('/', function * (next) { yield next; }, function * (next) { const version = yield readVersion(); this.status = 204; return yield next; }); request(http.createServer(app.callback())) .get('/') .expect(204) .end(done); }); it('responds to OPTIONS requests', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); app.use(router.allowedMethods()); router.get('/users', function * () {}); router.put('/users', function * () {}); request(http.createServer(app.callback())) .options('/users') .expect(204) .end(function (err, res) { if (err) { return done(err); } res.header.should.have.property('allow', 'HEAD, GET, PUT'); done(); }); }); it('responds with 405 Method Not Allowed', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); app.use(router.allowedMethods()); router.get('/users', function * () {}); router.put('/users', function * () {}); router.post('/events', function * () {}); request(http.createServer(app.callback())) .post('/users') .expect(405) .end(function (err, res) { if (err) { return done(err); } res.header.should.have.property('allow', 'HEAD, GET, PUT'); done(); }); }); it('responds with 501 Not Implemented', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); app.use(router.allowedMethods()); router.get('/users', function * () {}); router.put('/users', function * () {}); request(http.createServer(app.callback())) .search('/users') .expect(501) .end(function (err, res) { if (err) { return done(err); } done(); }); }); it('does not send 405 if route matched but status is 404', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); app.use(router.allowedMethods()); router.get('/users', function * () { this.status = 404; }); request(http.createServer(app.callback())) .get('/users') .expect(404) .end(function (err, res) { if (err) { return done(err); } done(); }); }); it('supports custom routing detect path: ctx.routerPath', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(function * (next) { const appname = this.request.hostname.split('.', 1)[0]; this.routerPath = '/' + appname + this.path; yield * next; }); app.use(router.routes()); router.get('/helloworld/users', function * () { this.body = this.method + ' ' + this.url; }); request(http.createServer(app.callback())) .get('/users') .set('Host', 'helloworld.example.com') .expect(200) .expect('GET /users', done); }); describe('router#[verb]()', function () { it('registers route specific to HTTP verb', function () { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); methods.forEach(function (method) { router.should.have.property(method); router[method].should.be.type('function'); router[method]('/', function * () {}); }); router.stack.should.have.length(methods.length); }); it('enables route chaining', function () { const router = strapi.middlewares.router(); methods.forEach(function (method) { router[method]('/', function * () {}).should.equal(router); }); }); it('registers routes without params before routes with params', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.get('/:parameter', function * (next) { this.body = { test: 'foo' }; }); router.get('/notparameter', function * (next) { this.body = { test: 'bar' }; }); app.use(router.routes()); request(http.createServer(app.callback())) .get('/testparameter') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('test', 'foo'); request(http.createServer(app.callback())) .get('/notparameter') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('test', 'bar'); done(); }); }); }); }); describe('router#use()', function (done) { it('uses router middleware without path', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.get('/foo/bar', function * (next) { this.body = { foobar: this.foo + 'bar' }; }); router.use(function * (next) { this.foo = 'baz'; yield next; }); router.use(function * (next) { this.foo = 'foo'; yield next; }); app.use(router.routes()); request(http.createServer(app.callback())) .get('/foo/bar') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('foobar', 'foobar'); done(); }); }); it('uses router middleware at given path', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.use('/foo/bar', function * (next) { this.foo = 'foo'; yield next; }); router.get('/foo/bar', function * (next) { this.body = { foobar: this.foo + 'bar' }; }); app.use(router.routes()); request(http.createServer(app.callback())) .get('/foo/bar') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('foobar', 'foobar'); done(); }); }); it('runs router middleware before subrouter middleware', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); const subrouter = strapi.middlewares.router(); router.use(function * (next) { this.foo = 'boo'; yield next; }); subrouter .use(function * (next) { this.foo = 'foo'; yield next; }) .get('/bar', function * (next) { this.body = { foobar: this.foo + 'bar' }; }); router.use('/foo', subrouter.routes()); app.use(router.routes()); request(http.createServer(app.callback())) .get('/foo/bar') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('foobar', 'foobar'); done(); }); }); it('assigns middleware to array of paths', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.use(['/foo', '/bar'], function * (next) { this.foo = 'foo'; this.bar = 'bar'; yield next; }); router.get('/foo', function * (next) { this.body = { foobar: this.foo + 'bar' }; }); router.get('/bar', function * (next) { this.body = { foobar: 'foo' + this.bar }; }); app.use(router.routes()); request(http.createServer(app.callback())) .get('/foo') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('foobar', 'foobar'); request(http.createServer(app.callback())) .get('/bar') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.have.property('foobar', 'foobar'); done(); }); }); }); }); describe('router#register()', function () { it('registers new routes', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.should.have.property('register'); router.register.should.be.type('function'); const route = router.register('/', ['GET', 'POST'], function * () {}); app.use(router.routes()); router.stack.should.be.an.instanceOf(Array); router.stack.should.have.property('length', 1); router.stack[0].should.have.property('path', '/'); done(); }); }); describe('router#redirect()', function () { it('redirects using route names', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); router.get('home', '/', function * () {}); router.get('sign-up-form', '/sign-up-form', function * () {}); router.redirect('home', 'sign-up-form'); request(http.createServer(app.callback())) .post('/') .expect(301) .end(function (err, res) { if (err) { return done(err); } res.header.should.have.property('location', '/sign-up-form'); done(); }); }); }); describe('router#route()', function () { it('inherits routes from nested router', function () { const app = new Koa(); const subrouter = strapi.middlewares.router().get('child', '/hello', function * (next) { this.body = { hello: 'world' }; }); const router = strapi.middlewares.router().use(subrouter.routes()); expect(router.route('child')).to.have.property('name', 'child'); }); }); describe('router#url()', function () { it('generates URL for given route', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); router.get('books', '/:category/:title', function * (next) { this.status = 204; }); let url = router.url('books', { category: 'programming', title: 'how to node' }); url.should.equal('/programming/how%20to%20node'); url = router.url('books', 'programming', 'how to node'); url.should.equal('/programming/how%20to%20node'); done(); }); }); describe('router#param()', function () { it('runs parameter middleware', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); app.use(router.routes()); router .param('user', function * (id, next) { this.user = { name: 'alex' }; if (!id) { return this.status = 404; } yield next; }) .get('/users/:user', function * (next) { this.body = this.user; }); request(http.createServer(app.callback())) .get('/users/3') .expect(200) .end(function (err, res) { if (err) { return done(err); } res.should.have.property('body'); res.body.should.have.property('name', 'alex'); done(); }); }); it('runs parameter middleware in order of URL appearance', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router .param('user', function * (id, next) { this.user = { name: 'alex' }; if (this.ranFirst) { this.user.ordered = 'parameters'; } if (!id) { return this.status = 404; } yield next; }) .param('first', function * (id, next) { this.ranFirst = true; if (this.user) { this.ranFirst = false; } if (!id) { return this.status = 404; } yield next; }) .get('/:first/users/:user', function * (next) { this.body = this.user; }); request(http.createServer( app .use(router.routes()) .callback())) .get('/first/users/3') .expect(200) .end(function (err, res) { if (err) { return done(err); } res.should.have.property('body'); res.body.should.have.property('name', 'alex'); res.body.should.have.property('ordered', 'parameters'); done(); }); }); it('runs parent parameter middleware for subrouter', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); const subrouter = strapi.middlewares.router(); subrouter.get('/:cid', function * (next) { this.body = { id: this.params.id, cid: this.params.cid }; }); router .param('id', function * (id, next) { this.params.id = 'ran'; if (!id) { return this.status = 404; } yield next; }) .use('/:id/children', subrouter.routes()); request(http.createServer(app.use(router.routes()).callback())) .get('/did-not-run/children/2') .expect(200) .end(function (err, res) { if (err) { return done(err); } res.should.have.property('body'); res.body.should.have.property('id', 'ran'); res.body.should.have.property('cid', '2'); done(); }); }); }); describe('router#opts', function () { it('responds with 200', function (done) { const app = new Koa(); const router = strapi.middlewares.router({ strict: true }); router.get('/info', function * () { this.body = 'hello'; }); request(http.createServer( app .use(router.routes()) .callback())) .get('/info') .expect(200) .end(function (err, res) { if (err) { return done(err); } res.text.should.equal('hello'); done(); }); }); it('should allow setting a prefix', function (done) { const app = new Koa(); const routes = strapi.middlewares.router({ prefix: '/things/:thing_id' }); routes.get('/list', function * (next) { this.body = this.params; }); app.use(routes.routes()); request(http.createServer(app.callback())) .get('/things/1/list') .expect(200) .end(function (err, res) { if (err) { return done(err); } res.body.thing_id.should.equal('1'); done(); }); }); it('responds with 404 when has a trailing slash', function (done) { const app = new Koa(); const router = strapi.middlewares.router({ strict: true }); router.get('/info', function * () { this.body = 'hello'; }); request(http.createServer( app .use(router.routes()) .callback())) .get('/info/') .expect(404) .end(function (err, res) { if (err) { return done(err); } done(); }); }); }); describe('use middleware with opts', function () { it('responds with 200', function (done) { const app = new Koa(); const router = strapi.middlewares.router({ strict: true }); router.get('/info', function * () { this.body = 'hello'; }) request(http.createServer( app .use(router.routes()) .callback())) .get('/info') .expect(200) .end(function (err, res) { if (err) { return done(err); } res.text.should.equal('hello'); done(); }); }); it('responds with 404 when has a trailing slash', function (done) { const app = new Koa(); const router = strapi.middlewares.router({ strict: true }); router.get('/info', function * () { this.body = 'hello'; }) request(http.createServer( app .use(router.routes()) .callback())) .get('/info/') .expect(404) .end(function (err, res) { if (err) { return done(err); } done(); }); }); }); describe('router.routes()', function () { it('should return composed middleware', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); let middlewareCount = 0; const middlewareA = function * (next) { middlewareCount++; yield next; }; const middlewareB = function * (next) { middlewareCount++; yield next; }; router.use(middlewareA, middlewareB); router.get('/users/:id', function * () { should.exist(this.params.id); this.body = { hello: 'world' }; }); const routerMiddleware = router.routes(); expect(routerMiddleware).to.be.a('function'); request(http.createServer( app .use(routerMiddleware) .callback())) .get('/users/1') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.be.an('object'); expect(res.body).to.have.property('hello', 'world'); expect(middlewareCount).to.equal(2); done(); }); }); }); describe('if no HEAD method, default to GET', function () { it('should default to GET', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.get('/users/:id', function * () { should.exist(this.params.id); this.body = 'hello'; }); request(http.createServer( app .use(router.routes()) .callback())) .head('/users/1') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.be.empty(); done(); }); }); it('should work with middleware', function (done) { const app = new Koa(); const router = strapi.middlewares.router(); router.get('/users/:id', function * () { should.exist(this.params.id); this.body = 'hello'; }); request(http.createServer( app .use(router.routes()) .callback())) .head('/users/1') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(res.body).to.be.empty(); done(); }); }); }); describe('router#prefix', function () { it('should set opts.prefix', function () { const router = strapi.middlewares.router(); expect(router.opts).to.not.have.key('prefix'); router.prefix('/things/:thing_id'); expect(router.opts.prefix).to.equal('/things/:thing_id'); }); it('should prefix existing routes', function () { const router = strapi.middlewares.router(); router.get('/users/:id', function * () { this.body = 'test'; }) router.prefix('/things/:thing_id'); const route = router.stack[0]; expect(route.path).to.equal('/things/:thing_id/users/:id'); expect(route.paramNames).to.have.length(2); expect(route.paramNames[0]).to.have.property('name', 'thing_id'); expect(route.paramNames[1]).to.have.property('name', 'id'); }); describe('with trailing slash', testPrefix('/admin/')); describe('without trailing slash', testPrefix('/admin')); function testPrefix (prefix) { return function () { let server; let middlewareCount = 0; before(function () { const app = new Koa(); const router = strapi.middlewares.router(); router.get('/', function * () { middlewareCount++; this.body = { name: this.thing }; }); router.use(function * (next) { middlewareCount++; this.thing = 'worked'; yield next; }); router.prefix(prefix); server = http.createServer(app.use(router.routes()).callback()); }); after(function () { server.close(); }); beforeEach(function () { middlewareCount = 0; }); it('should support root level router middleware', function (done) { request(server) .get(prefix) .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(middlewareCount).to.equal(2); expect(res.body).to.be.an('object'); expect(res.body).to.have.property('name', 'worked'); done(); }); }); it('should support requests with a trailing path slash', function (done) { request(server) .get('/admin/') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(middlewareCount).to.equal(2); expect(res.body).to.be.an('object'); expect(res.body).to.have.property('name', 'worked'); done(); }); }); it('should support requests without a trailing path slash', function (done) { request(server) .get('/admin') .expect(200) .end(function (err, res) { if (err) { return done(err); } expect(middlewareCount).to.equal(2); expect(res.body).to.be.an('object'); expect(res.body).to.have.property('name', 'worked'); done(); }); }); } } }); describe('router#url()', function () { it('generates route URL', function () { const router = strapi.middlewares.router; const url = router.url('/:category/:title', { category: 'programming', title: 'how-to-node' }); url.should.equal('/programming/how-to-node'); }); it('escapes using encodeURIComponent()', function () { const router = strapi.middlewares.router; const url = router.url('/:category/:title', { category: 'programming', title: 'how to node' }); url.should.equal('/programming/how%20to%20node'); }); }); });