'use strict'; const request = require('supertest'); const statuses = require('statuses'); const assert = require('assert'); const Koa = require('../..'); const fs = require('fs'); describe('app.respond', function(){ describe('when ctx.respond === false', function(){ it('should function *(ctx)', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = 'Hello'; ctx.respond = false; const res = ctx.res; res.statusCode = 200; setImmediate(function(){ res.setHeader('Content-Type', 'text/plain'); res.end('lol'); }); }); const server = app.listen(); request(server) .get('/') .expect(200) .expect('lol') .end(done); }); }); describe('when this.type === null', function(){ it('should not send Content-Type header', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = ''; ctx.type = null; }); const server = app.listen(); request(server) .get('/') .expect(200) .end(function(err, res){ if (err) return done(err); res.should.not.have.header('content-type'); done(); }); }); }); describe('when HEAD is used', function(){ it('should not respond with the body', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = 'Hello'; }); const server = app.listen(); request(server) .head('/') .expect(200) .end(function(err, res){ if (err) return done(err); res.should.have.header('Content-Type', 'text/plain; charset=utf-8'); res.should.have.header('Content-Length', '5'); assert(0 == res.text.length); done(); }); }); it('should keep json headers', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = { hello: 'world' }; }); const server = app.listen(); request(server) .head('/') .expect(200) .end(function(err, res){ if (err) return done(err); res.should.have.header('Content-Type', 'application/json; charset=utf-8'); res.should.have.header('Content-Length', '17'); assert(0 == res.text.length); done(); }); }); it('should keep string headers', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = 'hello world'; }); const server = app.listen(); request(server) .head('/') .expect(200) .end(function(err, res){ if (err) return done(err); res.should.have.header('Content-Type', 'text/plain; charset=utf-8'); res.should.have.header('Content-Length', '11'); assert(0 == res.text.length); done(); }); }); it('should keep buffer headers', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = new Buffer('hello world'); }); const server = app.listen(); request(server) .head('/') .expect(200) .end(function(err, res){ if (err) return done(err); res.should.have.header('Content-Type', 'application/octet-stream'); res.should.have.header('Content-Length', '11'); assert(0 == res.text.length); done(); }); }); it('should respond with a 404 if no body was set', function(done){ const app = new Koa(); app.use(function *(ctx){ }); const server = app.listen(); request(server) .head('/') .expect(404, done); }); it('should respond with a 200 if body = ""', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = ''; }); const server = app.listen(); request(server) .head('/') .expect(200, done); }); it('should not overwrite the content-type', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 200; ctx.type = 'application/javascript'; }); const server = app.listen(); request(server) .head('/') .expect('content-type', /application\/javascript/) .expect(200, done); }); }); describe('when no middleware are present', function(){ it('should 404', function(done){ const app = new Koa(); const server = app.listen(); request(server) .get('/') .expect(404, done); }); }); describe('when res has already been written to', function(){ it('should not cause an app error', function(done){ const app = new Koa(); app.use(function *(ctx, next){ const res = ctx.res; ctx.status = 200; res.setHeader('Content-Type', 'text/html'); res.write('Hello'); setTimeout(function(){ res.end('Goodbye'); }, 0); }); let errorCaught = false; app.on('error', function(err){ errorCaught = err; }); const server = app.listen(); request(server) .get('/') .expect(200) .end(function(err, res){ if (err) return done(err); if (errorCaught) return done(errorCaught); done(); }); }); it('should send the right body', function(done){ const app = new Koa(); app.use(function *(ctx, next){ const res = ctx.res; ctx.status = 200; res.setHeader('Content-Type', 'text/html'); res.write('Hello'); setTimeout(function(){ res.end('Goodbye'); }, 0); }); const server = app.listen(); request(server) .get('/') .expect(200) .expect('HelloGoodbye', done); }); }); describe('when .body is missing', function(){ describe('with status=400', function(){ it('should respond with the associated status message', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 400; }); const server = app.listen(); request(server) .get('/') .expect(400) .expect('Content-Length', 11) .expect('Bad Request', done); }); }); describe('with status=204', function(){ it('should respond without a body', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 204; }); const server = app.listen(); request(server) .get('/') .expect(204) .expect('') .end(function(err, res){ if (err) return done(err); res.header.should.not.have.property('content-type'); done(); }); }); }); describe('with status=205', function(){ it('should respond without a body', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 205; }); const server = app.listen(); request(server) .get('/') .expect(205) .expect('') .end(function(err, res){ if (err) return done(err); res.header.should.not.have.property('content-type'); done(); }); }); }); describe('with status=304', function(){ it('should respond without a body', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 304; }); const server = app.listen(); request(server) .get('/') .expect(304) .expect('') .end(function(err, res){ if (err) return done(err); res.header.should.not.have.property('content-type'); done(); }); }); }); describe('with custom status=700', function(){ it('should respond with the associated status message', function(done){ const app = new Koa(); statuses['700'] = 'custom status'; app.use(function *(ctx){ ctx.status = 700; }); const server = app.listen(); request(server) .get('/') .expect(700) .expect('custom status') .end(function(err, res){ if (err) return done(err); res.res.statusMessage.should.equal('custom status'); done(); }); }); }); describe('with custom statusMessage=ok', function(){ it('should respond with the custom status message', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 200; ctx.message = 'ok'; }); const server = app.listen(); request(server) .get('/') .expect(200) .expect('ok') .end(function(err, res){ if (err) return done(err); res.res.statusMessage.should.equal('ok'); done(); }); }); }); describe('with custom status without message', function(){ it('should respond with the status code number', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.res.statusCode = 701; }); const server = app.listen(); request(server) .get('/') .expect(701) .expect('701', done); }); }); }); describe('when .body is a null', function(){ it('should respond 204 by default', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = null; }); const server = app.listen(); request(server) .get('/') .expect(204) .expect('') .end(function(err, res){ if (err) return done(err); res.header.should.not.have.property('content-type'); done(); }); }); it('should respond 204 with status=200', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 200; ctx.body = null; }); const server = app.listen(); request(server) .get('/') .expect(204) .expect('') .end(function(err, res){ if (err) return done(err); res.header.should.not.have.property('content-type'); done(); }); }); it('should respond 205 with status=205', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 205; ctx.body = null; }); const server = app.listen(); request(server) .get('/') .expect(205) .expect('') .end(function(err, res){ if (err) return done(err); res.header.should.not.have.property('content-type'); done(); }); }); it('should respond 304 with status=304', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 304; ctx.body = null; }); const server = app.listen(); request(server) .get('/') .expect(304) .expect('') .end(function(err, res){ if (err) return done(err); res.header.should.not.have.property('content-type'); done(); }); }); }); describe('when .body is a string', function(){ it('should respond', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = 'Hello'; }); const server = app.listen(); request(server) .get('/') .expect('Hello', done); }); }); describe('when .body is a Buffer', function(){ it('should respond', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = new Buffer('Hello'); }); const server = app.listen(); request(server) .get('/') .expect('Hello', done); }); }); describe('when .body is a Stream', function(){ it('should respond', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = fs.createReadStream('package.json'); ctx.set('Content-Type', 'application/json; charset=utf-8'); }); const server = app.listen(); request(server) .get('/') .expect('Content-Type', 'application/json; charset=utf-8') .end(function(err, res){ if (err) return done(err); const pkg = require('../../package'); res.should.not.have.header('Content-Length'); res.body.should.eql(pkg); done(); }); }); it('should strip content-length when overwriting', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = 'hello'; ctx.body = fs.createReadStream('package.json'); ctx.set('Content-Type', 'application/json; charset=utf-8'); }); const server = app.listen(); request(server) .get('/') .expect('Content-Type', 'application/json; charset=utf-8') .end(function(err, res){ if (err) return done(err); const pkg = require('../../package'); res.should.not.have.header('Content-Length'); res.body.should.eql(pkg); done(); }); }); it('should keep content-length if not overwritten', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.length = fs.readFileSync('package.json').length; ctx.body = fs.createReadStream('package.json'); ctx.set('Content-Type', 'application/json; charset=utf-8'); }); const server = app.listen(); request(server) .get('/') .expect('Content-Type', 'application/json; charset=utf-8') .end(function(err, res){ if (err) return done(err); const pkg = require('../../package'); res.should.have.header('Content-Length'); res.body.should.eql(pkg); done(); }); }); it('should keep content-length if overwritten with the same stream', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.length = fs.readFileSync('package.json').length; const stream = fs.createReadStream('package.json'); ctx.body = stream; ctx.body = stream; ctx.set('Content-Type', 'application/json; charset=utf-8'); }); const server = app.listen(); request(server) .get('/') .expect('Content-Type', 'application/json; charset=utf-8') .end(function(err, res){ if (err) return done(err); const pkg = require('../../package'); res.should.have.header('Content-Length'); res.body.should.eql(pkg); done(); }); }); it('should handle errors', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.set('Content-Type', 'application/json; charset=utf-8'); ctx.body = fs.createReadStream('does not exist'); }); const server = app.listen(); request(server) .get('/') .expect('Content-Type', 'text/plain; charset=utf-8') .expect(404) .end(done); }); it('should handle errors when no content status', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 204; ctx.body = fs.createReadStream('does not exist'); }); const server = app.listen(); request(server) .get('/') .expect(204, done); }); it('should handle all intermediate stream body errors', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = fs.createReadStream('does not exist'); ctx.body = fs.createReadStream('does not exist'); ctx.body = fs.createReadStream('does not exist'); }); const server = app.listen(); request(server) .get('/') .expect(404, done); }); }); describe('when .body is an Object', function(){ it('should respond with json', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.body = { hello: 'world' }; }); const server = app.listen(); request(server) .get('/') .expect('Content-Type', 'application/json; charset=utf-8') .expect('{"hello":"world"}', done); }); }); describe('when an error occurs', function(){ it('should emit "error" on the app', function(done){ const app = new Koa(); app.use(function *(ctx){ throw new Error('boom'); }); app.on('error', function(err){ err.message.should.equal('boom'); done(); }); request(app.listen()) .get('/') .end(function(){}); }); describe('with an .expose property', function(){ it('should expose the message', function(done){ const app = new Koa(); app.use(function *(ctx){ const err = new Error('sorry!'); err.status = 403; err.expose = true; throw err; }); request(app.listen()) .get('/') .expect(403, 'sorry!') .end(done); }); }); describe('with a .status property', function(){ it('should respond with .status', function(done){ const app = new Koa(); app.use(function *(ctx){ const err = new Error('s3 explodes'); err.status = 403; throw err; }); request(app.listen()) .get('/') .expect(403, 'Forbidden') .end(done); }); }); it('should respond with 500', function(done){ const app = new Koa(); app.use(function *(ctx){ throw new Error('boom!'); }); const server = app.listen(); request(server) .get('/') .expect(500, 'Internal Server Error') .end(done); }); it('should be catchable', function(done){ const app = new Koa(); app.use(function *(ctx, next){ try { yield next(); ctx.body = 'Hello'; } catch (err) { ctx.body = 'Got error'; } }); app.use(function *(ctx, next){ throw new Error('boom!'); }); const server = app.listen(); request(server) .get('/') .expect(200, 'Got error') .end(done); }); }); describe('when status and body property', function(){ it('should 200', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 304; ctx.body = 'hello'; ctx.status = 200; }); const server = app.listen(); request(server) .get('/') .expect(200) .expect('hello', done); }); it('should 204', function(done){ const app = new Koa(); app.use(function *(ctx){ ctx.status = 200; ctx.body = 'hello'; ctx.set('content-type', 'text/plain; charset=utf8'); ctx.status = 204; }); const server = app.listen(); request(server) .get('/') .expect(204) .end(function(err, res){ res.should.not.have.header('content-type'); done(err); }); }); }); });