From b4e90a229b2b618aa6e4ac1349314ea1dd0d85e2 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 1 Nov 2012 09:08:56 -0700 Subject: [PATCH 1/8] Add test for helper with invalid key --- test/integration/request.js | 16 +- test/unit/server.js | 636 +++++++++++++++++++----------------- 2 files changed, 344 insertions(+), 308 deletions(-) diff --git a/test/integration/request.js b/test/integration/request.js index 11c39af93..bf2cb579d 100755 --- a/test/integration/request.js +++ b/test/integration/request.js @@ -17,7 +17,7 @@ describe('Request', function () { { method: 'GET', path: '/custom', config: { handler: customErrorHandler } }, ]); - var makeRequest = function (path, callback) { + var makeRequest = function (method, path, callback) { var next = function (res) { @@ -25,7 +25,7 @@ describe('Request', function () { }; server.inject({ - method: 'GET', + method: method, url: path }, next); } @@ -45,11 +45,21 @@ describe('Request', function () { it('returns custom error response', function (done) { - makeRequest('/custom', function (rawRes) { + makeRequest('GET', '/custom', function (rawRes) { var headers = parseHeaders(rawRes.raw.res); expect(headers['Content-Type']).to.equal('text/plain'); done(); }); }); + + it('returns valid OPTIONS response', function (done) { + + makeRequest('OPTIONS', '/custom', function (rawRes) { + + var headers = parseHeaders(rawRes.raw.res); + expect(headers['Access-Control-Allow-Origin']).to.equal('*'); + done(); + }); + }); }); \ No newline at end of file diff --git a/test/unit/server.js b/test/unit/server.js index bc9f6055f..659066206 100755 --- a/test/unit/server.js +++ b/test/unit/server.js @@ -4,420 +4,446 @@ var expect = require('chai').expect; var libPath = process.env.TEST_COV ? '../../lib-cov/' : '../../lib/'; var Server = require(libPath + 'server'); -require('../suite')(function(useRedis, useMongo) { - describe('Server', function () { +describe('Server', function () { + + it('throws an error constructed without new', function (done) { + var fn = function () { + Server('0.0.0.0', 8086, {}); + }; + expect(fn).throws(Error, 'Server must be instantiated using new'); + done(); + }); + + it('defaults to port 80 when no port is provided', function (done) { + var server = new Server(); + expect(server.settings.port).to.be.equal(80); + done(); + }); + + it('defaults to localhost when no host is provided', function (done) { + var server = new Server(); + expect(server.settings.host).to.be.equal('localhost'); + done(); + }); + + it('doesn\'t throw an error when host and port are provided', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8083); + }; + expect(fn).to.not.throw(Error); + done(); + }); + + it('throws an error when double port config is provided', function (done) { + var fn = function () { + var server = new Server(8080, 8084); + }; + expect(fn).throws(Error); + done(); + }); + + it('throws an error when double host config is provided', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 'localhost'); + }; + expect(fn).throws(Error); + done(); + }); + + it('throws an error when unknown arg type is provided', function (done) { + var fn = function () { + var server = new Server(true); + }; + expect(fn).throws(Error); + done(); + }); + + it('throws an error when an incomplete authentication config is provided', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8084, { authentication: {} }); + }; + expect(fn).throws(Error); + done(); + }); + + it('doesn\'t throw an error when disabling authentication', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8085, { authentication: false }); + }; + expect(fn).to.not.throw(Error); + done(); + }); + + it('doesn\'t throw an error when enabling docs', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8086, { docs: true }); + }; + expect(fn).to.not.throw(Error); + done(); + }); + + it('doesn\'t throw an error when enabling the debug console', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8087, { debug: { websocketPort: 3002 } }); + }; + expect(fn).to.not.throw(Error); + done(); + }); + + it('doesn\'t throw an error when disabling cache', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8088, { cache: false }); + }; + expect(fn).to.not.throw(Error); + done(); + }); + + it('assigns _monitor when config enables monitor', function (done) { + var server = new Server('0.0.0.0', 8082, { monitor: true }); + expect(server._monitor).to.exist; + done(); + }); + - it('throws an error constructed without new', function (done) { + describe('#_match', function () { + + it('throws an error when the method parameter is null', function (done) { var fn = function () { - Server('0.0.0.0', 8086, {}); + var server = new Server('0.0.0.0', 8092); + server._match(null, '/test'); }; - expect(fn).throws(Error, 'Server must be instantiated using new'); + expect(fn).to.throw(Error, 'The method parameter must be provided'); done(); }); - it('defaults to port 80 when no port is provided', function (done) { - var server = new Server(); - expect(server.settings.port).to.be.equal(80); + it('throws an error when the path parameter is null', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8091); + server._match('POST', null); + }; + expect(fn).to.throw(Error, 'The path parameter must be provided'); done(); }); - it('defaults to localhost when no host is provided', function (done) { - var server = new Server(); - expect(server.settings.host).to.be.equal('localhost'); + it('returns null when no routes are added', function (done) { + var server = new Server('0.0.0.0', 8092); + var result = server._match('GET', '/test'); + + expect(result).to.not.exist; done(); }); - it('doesn\'t throw an error when host and port are provided', function (done) { + it('returns the route when there is a match', function (done) { + var server = new Server('0.0.0.0', 8092); + server.addRoute({ + method: 'GET', + path: '/test', + handler: function () { } + }); + var result = server._match('GET', '/test'); + + expect(result).exist; + expect(result.path).to.equal('/test'); + done(); + }); + }); + + + describe('#start', function () { + + it('doesn\'t throw an error', function (done) { var fn = function () { - var server = new Server('0.0.0.0', 8083); + var server = new Server('0.0.0.0', 8088); + server.start(); }; expect(fn).to.not.throw(Error); done(); }); + }); + - it('throws an error when double port config is provided', function (done) { + describe('#stop', function () { + + it('doesn\'t throw an error when the server is started', function (done) { var fn = function () { - var server = new Server(8080, 8084); + var server = new Server('0.0.0.0', 8089); + server.listener.on('listening', function () { + server.stop(); + done(); + }); + + server.start(); }; - expect(fn).throws(Error); - done(); + expect(fn).to.not.throw(Error); }); - it('throws an error when double host config is provided', function (done) { + it('throws an error when the server isn\'t started', function (done) { var fn = function () { - var server = new Server('0.0.0.0', 'localhost'); + var server = new Server('0.0.0.0', 8090); + server.stop(); }; - expect(fn).throws(Error); + expect(fn).to.throw(Error); done(); }); + }); - it('throws an error when unknown arg type is provided', function (done) { + + describe('#setRoutesDefaults', function () { + + it('throws an error when a default handler is provided', function (done) { var fn = function () { - var server = new Server(true); + var server = new Server('0.0.0.0', 8091); + server.setRoutesDefaults({ handler: function () { } }); }; - expect(fn).throws(Error); + expect(fn).to.throw(Error, 'Defaults cannot include a handler'); + done(); + }); + + it('changes the value of routeDefaults with the passed in object', function (done) { + var server = new Server('0.0.0.0', 8092); + server.setRoutesDefaults({ item: true }); + expect(server.routeDefaults.item).to.be.true; done(); }); + }); + - it('throws an error when an incomplete authentication config is provided', function (done) { + describe('#addRoute', function () { + + it('throws an error when a route is passed in that is missing a path', function (done) { var fn = function () { - var server = new Server('0.0.0.0', 8084, { authentication: {} }); + var route = { + }; + var server = new Server('0.0.0.0', 8093); + server.addRoute(route); }; - expect(fn).throws(Error); + expect(fn).to.throw(Error, 'Route options missing path'); done(); }); - it('doesn\'t throw an error when disabling authentication', function (done) { + it('throws an error when a route is passed in that is missing a method', function (done) { var fn = function () { - var server = new Server('0.0.0.0', 8085, { authentication: false }); + var route = { + path: '/test' + }; + var server = new Server('0.0.0.0', 8094); + server.addRoute(route); }; - expect(fn).to.not.throw(Error); + expect(fn).to.throw(Error, 'Route options missing method'); done(); }); - it('doesn\'t throw an error when enabling docs', function (done) { + it('throws an error when a route is passed in that is missing a handler', function (done) { var fn = function () { - var server = new Server('0.0.0.0', 8086, { docs: true }); + var route = { + path: '/test', + method: 'put' + }; + var server = new Server('0.0.0.0', 8095); + server.addRoute(route); }; - expect(fn).to.not.throw(Error); + expect(fn).to.throw(Error); done(); }); - it('doesn\'t throw an error when enabling the debug console', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8087, { debug: { websocketPort: 3002 } }); + it('adds route to correct _routes method property', function (done) { + var route = { + path: '/test', + method: 'put', + handler: function () { } }; - expect(fn).to.not.throw(Error); + var server = new Server('0.0.0.0', 8096); + server.addRoute(route); + + expect(server._routes.put[0]).to.exist; + expect(server._routes.put[0].path).to.equal('/test'); done(); }); + }); + + describe('#addRoutes', function () { - it('doesn\'t throw an error when disabling cache', function (done) { + it('throws an error when null routes are passed in', function (done) { var fn = function () { - var server = new Server('0.0.0.0', 8088, { cache: false }); + var server = new Server('0.0.0.0', 8097); + server.addRoutes(null); }; - expect(fn).to.not.throw(Error); + expect(fn).to.throw(Error); done(); }); - it('assigns _monitor when config enables monitor', function (done) { - var server = new Server('0.0.0.0', 8082, { monitor: true }); - expect(server._monitor).to.exist; + it('adds to routes object with the passed in routes values', function (done) { + var routes = [{ + path: '/test', + method: 'put', + handler: function () { } + }, { + path: '/test', + method: 'post', + handler: function () { } + }]; + var server = new Server('0.0.0.0', 8098); + server.addRoutes(routes); + + expect(server._routes.put[0].path).to.equal('/test'); done(); }); + }); + describe('#addHelper', function () { - describe('#_match', function () { - - it('throws an error when the method parameter is null', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8092); - server._match(null, '/test'); - }; - expect(fn).to.throw(Error, 'The method parameter must be provided'); - done(); - }); - - it('throws an error when the path parameter is null', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8091); - server._match('POST', null); - }; - expect(fn).to.throw(Error, 'The path parameter must be provided'); - done(); - }); - - it('returns null when no routes are added', function (done) { - var server = new Server('0.0.0.0', 8092); - var result = server._match('GET', '/test'); - - expect(result).to.not.exist; - done(); - }); - - it('returns the route when there is a match', function (done) { - var server = new Server('0.0.0.0', 8092); - server.addRoute({ - method: 'GET', - path: '/test', - handler: function () { } - }); - var result = server._match('GET', '/test'); - - expect(result).exist; - expect(result.path).to.equal('/test'); - done(); - }); + it('throws an error when name is not a string', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8097); + server.addHelper(0, function () { }); + }; + expect(fn).to.throw(Error); + done(); }); - - describe('#start', function () { - - it('doesn\'t throw an error', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8088); - server.start(); - }; - expect(fn).to.not.throw(Error); - done(); - }); + it('throws an error when method is not a function', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8097); + server.addHelper('user', 'function'); + }; + expect(fn).to.throw(Error); + done(); }); - - describe('#stop', function () { - - it('doesn\'t throw an error when the server is started', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8089); - server.listener.on('listening', function () { - server.stop(); - done(); - }); - - server.start(); - }; - expect(fn).to.not.throw(Error); - }); - - it('throws an error when the server isn\'t started', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8090); - server.stop(); - }; - expect(fn).to.throw(Error); - done(); - }); + it('throws an error when options is not an object', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8097); + server.addHelper('user', function () { }, 'options'); + }; + expect(fn).to.throw(Error); + done(); }); - - describe('#setRoutesDefaults', function () { - - it('throws an error when a default handler is provided', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8091); - server.setRoutesDefaults({ handler: function () { } }); - }; - expect(fn).to.throw(Error, 'Defaults cannot include a handler'); - done(); - }); - - it('changes the value of routeDefaults with the passed in object', function (done) { - var server = new Server('0.0.0.0', 8092); - server.setRoutesDefaults({ item: true }); - expect(server.routeDefaults.item).to.be.true; - done(); - }); + it('throws an error when options.generateKey is not a function', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8097); + server.addHelper('user', function () { }, { generateKey: 'function' }); + }; + expect(fn).to.throw(Error); + done(); }); - - describe('#addRoute', function () { - - it('throws an error when a route is passed in that is missing a path', function (done) { - var fn = function () { - var route = { - }; - var server = new Server('0.0.0.0', 8093); - server.addRoute(route); - }; - expect(fn).to.throw(Error, 'Route options missing path'); - done(); - }); - - it('throws an error when a route is passed in that is missing a method', function (done) { - var fn = function () { - var route = { - path: '/test' - }; - var server = new Server('0.0.0.0', 8094); - server.addRoute(route); - }; - expect(fn).to.throw(Error, 'Route options missing method'); - done(); - }); - - it('throws an error when a route is passed in that is missing a handler', function (done) { - var fn = function () { - var route = { - path: '/test', - method: 'put' - }; - var server = new Server('0.0.0.0', 8095); - server.addRoute(route); - }; - expect(fn).to.throw(Error); - done(); - }); - - it('adds route to correct _routes method property', function (done) { - var route = { - path: '/test', - method: 'put', - handler: function () { } - }; - var server = new Server('0.0.0.0', 8096); - server.addRoute(route); - - expect(server._routes.put[0]).to.exist; - expect(server._routes.put[0].path).to.equal('/test'); - done(); - }); + it('throws an error when options.cache is not valid', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8097, { cache: 'redis' }); + server.addHelper('user', function () { }, { cache: { mode: 'none', expiresIn: 3000 } }); + }; + expect(fn).to.throw(Error); + done(); }); - describe('#addRoutes', function () { + it('throws an error when options.cache is not enabled but server cache is not', function (done) { + var fn = function () { + var server = new Server('0.0.0.0', 8097); + server.addHelper('user', function () { }, { cache: { expiresIn: 3000 } }); + }; + expect(fn).to.throw(Error); + done(); + }); - it('throws an error when null routes are passed in', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8097); - server.addRoutes(null); - }; - expect(fn).to.throw(Error); - done(); - }); + it('returns a valid result when calling a helper without using the cache', function (done) { - it('adds to routes object with the passed in routes values', function (done) { - var routes = [{ - path: '/test', - method: 'put', - handler: function () { } - }, { - path: '/test', - method: 'post', - handler: function () { } - }]; - var server = new Server('0.0.0.0', 8098); - server.addRoutes(routes); + var server = new Server('0.0.0.0', 8097); + server.addHelper('user', function (id, next) { return next({ id: id }); }); + server.helpers.user(4, function (result) { - expect(server._routes.put[0].path).to.equal('/test'); + result.id.should.be.equal(4); done(); }); }); - describe('#addHelper', function () { + it('returns a different result when calling a helper without using the cache', function (done) { - it('throws an error when name is not a string', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8097); - server.addHelper(0, function () { }); - }; - expect(fn).to.throw(Error); - done(); - }); + var server = new Server('0.0.0.0', 8097); + var gen = 0; + server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }); + server.helpers.user(4, function (result1) { - it('throws an error when method is not a function', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8097); - server.addHelper('user', 'function'); - }; - expect(fn).to.throw(Error); - done(); - }); - - it('throws an error when options is not an object', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8097); - server.addHelper('user', function () { }, 'options'); - }; - expect(fn).to.throw(Error); - done(); - }); + result1.id.should.be.equal(4); + result1.gen.should.be.equal(1); + server.helpers.user(4, function (result2) { - it('throws an error when options.generateKey is not a function', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8097); - server.addHelper('user', function () { }, { generateKey: 'function' }); - }; - expect(fn).to.throw(Error); - done(); + result2.id.should.be.equal(4); + result2.gen.should.be.equal(2); + done(); + }); }); + }); - it('throws an error when options.cache is not valid', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8097, { cache: 'redis' }); - server.addHelper('user', function () { }, { cache: { mode: 'none', expiresIn: 3000 } }); - }; - expect(fn).to.throw(Error); - done(); - }); + describe('with cache', function () { - it('throws an error when options.cache is not enabled but server cache is not', function (done) { - var fn = function () { - var server = new Server('0.0.0.0', 8097); - server.addHelper('user', function () { }, { cache: { expiresIn: 3000 } }); - }; - expect(fn).to.throw(Error); - done(); - }); + it('returns a valid result when calling a helper using the cache', function (done) { - it('returns a valid result when calling a helper without using the cache', function (done) { + var server = new Server('0.0.0.0', 8097, { cache: 'memory' }); + var gen = 0; + server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }, { cache: { expiresIn: 2000 } }); + var id = Math.random(); + server.helpers.user(id, function (result1) { - var server = new Server('0.0.0.0', 8097); - server.addHelper('user', function (id, next) { return next({ id: id }); }); - server.helpers.user(4, function (result) { + result1.id.should.be.equal(id); + result1.gen.should.be.equal(1); + server.helpers.user(id, function (result2) { - result.id.should.be.equal(4); - done(); + result2.id.should.be.equal(id); + result2.gen.should.be.equal(1); + done(); + }); }); }); - it('returns a different result when calling a helper without using the cache', function (done) { + it('returns valid results when calling a helper (with different keys) using the cache', function (done) { - var server = new Server('0.0.0.0', 8097); + var server = new Server('0.0.0.0', 8097, { cache: 'memory' }); var gen = 0; - server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }); - server.helpers.user(4, function (result1) { + server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }, { cache: { expiresIn: 2000 } }); + var id1 = Math.random(); + server.helpers.user(id1, function (result1) { - result1.id.should.be.equal(4); + result1.id.should.be.equal(id1); result1.gen.should.be.equal(1); - server.helpers.user(4, function (result2) { + var id2 = Math.random(); + server.helpers.user(id2, function (result2) { - result2.id.should.be.equal(4); + result2.id.should.be.equal(id2); result2.gen.should.be.equal(2); done(); }); }); }); - describe('with cache', function() { + it('returns new object (not cached) when second key generation fails when using the cache', function (done) { - if (useRedis) { - it('returns a valid result when calling a helper using the cache', function (done) { + var server = new Server('0.0.0.0', 8097, { cache: 'memory' }); + var id1 = Math.random(); + var gen = 0; + var helper = function (id, next) { - var server = new Server('0.0.0.0', 8097, { cache: 'redis' }); - var gen = 0; - server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }, { cache: { expiresIn: 2000 } }); - var id = Math.random(); - server.helpers.user(id, function (result1) { + if (typeof id === 'function') { + id = id1; + } - result1.id.should.be.equal(id); - result1.gen.should.be.equal(1); - server.helpers.user(id, function (result2) { + return next({ id: id, gen: ++gen }); + }; - result2.id.should.be.equal(id); - result2.gen.should.be.equal(1); - done(); - }); - }); - }); + server.addHelper('user', helper, { cache: { expiresIn: 2000 } }); - it('returns valid results when calling a helper (with different keys) using the cache', function (done) { + server.helpers.user(id1, function (result1) { - var server = new Server('0.0.0.0', 8097, { cache: 'redis' }); - var gen = 0; - server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }, { cache: { expiresIn: 2000 } }); - var id1 = Math.random(); - server.helpers.user(id1, function (result1) { + result1.id.should.be.equal(id1); + result1.gen.should.be.equal(1); - result1.id.should.be.equal(id1); - result1.gen.should.be.equal(1); - var id2 = Math.random(); - server.helpers.user(id2, function (result2) { + server.helpers.user(function () { }, function (result2) { - result2.id.should.be.equal(id2); - result2.gen.should.be.equal(2); - done(); - }); - }); + result2.id.should.be.equal(id1); + result2.gen.should.be.equal(2); + done(); }); - } + }); }); }); }); From eb8bd85332f33a6066e216b1f8f61b06f4df42d1 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 1 Nov 2012 09:18:04 -0700 Subject: [PATCH 2/8] Add server tls config test --- test/unit/server.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/unit/server.js b/test/unit/server.js index 659066206..e1f068d5e 100755 --- a/test/unit/server.js +++ b/test/unit/server.js @@ -1,5 +1,6 @@ // Load modules +var Https = require('https'); var expect = require('chai').expect; var libPath = process.env.TEST_COV ? '../../lib-cov/' : '../../lib/'; var Server = require(libPath + 'server'); @@ -104,6 +105,15 @@ describe('Server', function () { done(); }); + it('creates an https server when passed tls options', function (done) { + var tls = { + }; + + var server = new Server('0.0.0.0', 8082, { tls: tls }); + expect(server.listener instanceof Https.Server).to.equal(true); + done(); + }); + describe('#_match', function () { From fa4d5fedc859bb2ab511ead70a3ab456e6681791 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Mon, 22 Oct 2012 14:57:34 -0700 Subject: [PATCH 3/8] Allow non 4xx 5xx codes in pass-through errors --- lib/error.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/error.js b/lib/error.js index f67bdea84..9fdf302eb 100755 --- a/lib/error.js +++ b/lib/error.js @@ -120,7 +120,14 @@ internals.Error.passThrough = function (code, payload, contentType) { return response; }; - var err = new internals.Error(code, 'Pass-through', { toResponse: format }); + var err = new internals.Error(500, 'Pass-through', { toResponse: format }); // 500 code is only used internally and is not exposed when sent + + err.passThrough = { + code: code, + payload: payload, + contentType: contentType + }; + return err; }; From f7d4573949e4bd6f5552b943ff6b4bc3cb796d0a Mon Sep 17 00:00:00 2001 From: Wyatt Preul Date: Wed, 31 Oct 2012 16:19:26 -0500 Subject: [PATCH 4/8] Fixing issue with error responses being cached + test --- lib/request.js | 2 +- test/integration/cache.js | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/request.js b/lib/request.js index aae6efde7..ae2e2ba1d 100755 --- a/lib/request.js +++ b/lib/request.js @@ -448,7 +448,7 @@ internals.Request.prototype._generateResponse = function () { // Check for Error result - if (response instanceof Error) { + if (response.result instanceof Error) { self.log(['handler', 'result', 'error'], { msec: timer.elapsed() }); return callback(response); } diff --git a/test/integration/cache.js b/test/integration/cache.js index cf0fe293f..53e6800da 100755 --- a/test/integration/cache.js +++ b/test/integration/cache.js @@ -30,6 +30,14 @@ describe('Cache', function() { request.reply.stream(new Stream); }; + var errorHandler = function (request) { + + var error = new Error('myerror'); + error.code = 500; + + request.reply(error); + }; + function setupServer(done) { _server = new Hapi.Server('0.0.0.0', 18085, { cache: 'memory' }); _server.addRoutes([ @@ -37,7 +45,8 @@ describe('Cache', function() { { method: 'GET', path: '/item', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, { method: 'GET', path: '/item2', config: { handler: activeItemHandler, cache: { mode: 'none' } } }, { method: 'GET', path: '/item3', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, - { method: 'GET', path: '/bad', config: { handler: badHandler, cache: { expiresIn: 120000 } } } + { method: 'GET', path: '/bad', config: { handler: badHandler, cache: { expiresIn: 120000 } } }, + { method: 'GET', path: '/error', config: { handler: errorHandler, cache: { expiresIn: 120000 } } } ]); _server.listener.on('listening', function() { done(); @@ -104,4 +113,16 @@ describe('Cache', function() { expect(test).to.throw(Error); done(); }); + + it('doesn\'t cache error responses', function(done) { + + makeRequest('/error', function() { + + _server.cache.get({ segment: '/error', id: '/error' }, function(err, cached) { + + expect(cached).to.not.exist; + done(); + }); + }); + }); }); \ No newline at end of file From 4626a6d6319d77224102f104f735572c80a91f7f Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 31 Oct 2012 17:01:24 -0700 Subject: [PATCH 5/8] initial error refactor --- Readme.md | 10 ++-- lib/error.js | 105 +++++++++++++++++++++++------------- lib/session.js | 15 ++---- test/integration/session.js | 2 +- test/unit/error.js | 16 +++--- 5 files changed, 85 insertions(+), 63 deletions(-) mode change 100644 => 100755 test/integration/session.js diff --git a/Readme.md b/Readme.md index ebc8de096..927344a89 100755 --- a/Readme.md +++ b/Readme.md @@ -701,11 +701,11 @@ In which: ## Response Errors The 'Hapi.Error' module provides helper methods to generate error responses: -- _'badRequest([message])'_ - HTTP 400 (Bad request). +- _'badRequest([message])'_ - HTTP 400 (Bad Request). - _'unauthorized([message])'_ - HTTP 401 (Unauthorized). -- _'forbidden([message])'_ - HTTP 403 (Not allowed). -- _'notFound([message])'_ - HTTP 404 (Not found). -- _'internal([message, data])'_ - HTTP 500 (Internal error). The optional _message_ and _data_ values are not returned to the client but are logged internally. +- _'forbidden([message])'_ - HTTP 403 (Not Allowed). +- _'notFound([message])'_ - HTTP 404 (Not Found). +- _'internal([message, data])'_ - HTTP 500 (Internal Error). The optional _message_ and _data_ values are not returned to the client but are logged internally. - _'create(message, code, text, [options]) - creates a custom error with the provided _message_, _code_ (the HTTP status code), _text_ (the HTTP status message), and any keys present in _options_. The _message_ value is optional and will be returned to the client in the response unless noted otherwise. For example: @@ -719,7 +719,7 @@ function onUnknownRoute(request) { Error responses are send as JSON payload with the following keys (unless an [error response override](#errors) is configured): - _code_ - the HTTP status code (e.g. 400). -- _error_ - the HTTP status message (e.g. 'Bad request'). +- _error_ - the HTTP status message (e.g. 'Bad Request'). - _message_ - the returned message if provided. The complete error repsonse including any additional data is added to the request log. diff --git a/lib/error.js b/lib/error.js index c264044c2..2d8033170 100755 --- a/lib/error.js +++ b/lib/error.js @@ -1,5 +1,7 @@ // Load modules +var Http = require('http'); +var NodeUtil = require('util'); var Utils = require('./utils'); @@ -8,78 +10,107 @@ var Utils = require('./utils'); var internals = {}; -exports.badRequest = function (message) { +exports = module.exports = internals.Error = function (code, message, custom, options) { - return exports.create(message, 400, 'Bad request'); + Utils.assert(this.constructor === internals.Error, 'Error must be instantiated using new'); + Utils.assert(!options || !options.toResponse || typeof options.toResponse === 'function', 'options.toReponse must be a function'); + + Error.call(this); + + this.name = 'HapiError'; + this.code = code; + this.message = message; + this.text = Http.STATUS_CODES[code] || 'Unknown'; + this.settings = Utils.clone(options) || {}; // Options can be reused; + + for (var d in custom) { + if (custom.hasOwnProperty(d)) { + this[d] = custom[d]; + } + } + + return this; }; +NodeUtil.inherits(internals.Error, Error); + -exports.unauthorized = function (message) { +internals.Error.prototype.response = function () { - return exports.create(message, 401, 'Unauthorized'); + if (this.settings.toResponse) { + + return this.settings.toResponse.call(this); + } + + var response = { + code: this.code, + payload: { + error: this.text, + code: this.code, + message: (this.code >= 500 && this.code < 600 ? 'An internal server error occurred' : this.message) + } + } + + return response; }; -exports.forbidden = function (message) { +internals.Error.prototype.format = function () { - return exports.create(message, 403, 'Not allowed'); + return this.response().payload; }; -exports.notFound = function (message) { +// Utilities - return exports.create(message, 404, 'Not Found'); +internals.Error.create = function (message, code, options) { + + return new internals.Error(message, code, options); }; -exports.internal = function (message, data) { +internals.Error.badRequest = function (message) { - var custom = { - trace: Utils.callStack(1) - }; + return new internals.Error(400, message); +}; - if (data) { - custom.data = data; - } - return exports.create(message, 500, 'Internal error', custom); +internals.Error.unauthorized = function (message) { + + return new internals.Error(401, message); }; -exports.format = function (error) { +internals.Error.forbidden = function (message) { - if (error.hasOwnProperty('toResponse') && - typeof error.toResponse === 'function') { + return new internals.Error(403, message); +}; - return error.toResponse(); - } - var err = { - error: error.text, - code: error.code, - message: (error.code >= 500 && error.code < 600 ? 'An internal server error occurred' : error.message) - }; +internals.Error.notFound = function (message) { - return err; + return new internals.Error(404, message); }; -exports.create = function (message, code, text, options) { +internals.Error.internal = function (message, data) { - var err = new Error(); - err.message = message; - err.code = code; - err.text = text; + var custom = { + trace: Utils.callStack(1) + }; - for (var d in options) { - if (options.hasOwnProperty(d)) { - err[d] = options[d]; - } + if (data) { + custom.data = data; } - return err; + return new internals.Error(500, message, custom); }; +internals.Error.format = function (err) { + + return err.format(); +}; + diff --git a/lib/session.js b/lib/session.js index 1cb37d756..1d6d17616 100755 --- a/lib/session.js +++ b/lib/session.js @@ -478,17 +478,12 @@ exports.getRandomString = function (size) { internals.error = function (code, description) { - var err = new Error(); - err.message = 'OAuth'; - err.code = 400; - err.text = description; - err.type = 'oauth'; - err.error = code; + var err = new Err(400, 'OAuth', null, { + toResponse: function () { - err.toResponse = function () { - - return { error: code, error_description: description }; - }; + return { code: 400, payload: { error: code, error_description: description } }; + } + }); return err; }; diff --git a/test/integration/session.js b/test/integration/session.js old mode 100644 new mode 100755 index 9642a1619..3e6cf7d63 --- a/test/integration/session.js +++ b/test/integration/session.js @@ -114,7 +114,7 @@ describe('Session', function() { it('returns bad request error when no grant type is specified', function(done) { makeRequest('/oauth/token', 'POST', null, function(rawRes) { expect(rawRes.result.error).to.exist; - expect(rawRes.result.error).to.equal('Bad request'); + expect(rawRes.result.error).to.equal('Bad Request'); done(); }); }); diff --git a/test/unit/error.js b/test/unit/error.js index e3ccaa62a..e0c83d21c 100755 --- a/test/unit/error.js +++ b/test/unit/error.js @@ -77,18 +77,14 @@ describe('Err', function() { it('formats a custom error', function (done) { - var err = new Error(); - err.toResponse = function () { + var err = new Err(500, 'Unknown', null, { + toResponse: function () { - return { test: true }; - }; + return { payload: { test: true } }; + } + }); - expect(Err.format(err).test).to.equal(true); - done(); - }); - - it('formats internal errors with a standard message', function(done) { - expect(Err.format({ code: 500 }).message).to.equal('An internal server error occurred'); + expect(err.format().test).to.equal(true); done(); }); }); From 96d710b482d8933ed7f404a146e6152a5595c145 Mon Sep 17 00:00:00 2001 From: Wyatt Preul Date: Thu, 1 Nov 2012 01:17:34 -0500 Subject: [PATCH 6/8] Fixing failing ttl tests --- lib/cache/index.js | 4 +--- test/unit/cache.js | 10 +++------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/cache/index.js b/lib/cache/index.js index a4171dd6b..d077dd533 100755 --- a/lib/cache/index.js +++ b/lib/cache/index.js @@ -367,6 +367,4 @@ exports.ttl = function (rule, created) { } return 0; // Bad rule -}; - - +}; \ No newline at end of file diff --git a/test/unit/cache.js b/test/unit/cache.js index 53ad1ddb0..a04e229ad 100755 --- a/test/unit/cache.js +++ b/test/unit/cache.js @@ -522,9 +522,7 @@ require('../suite')(function (useRedis, useMongo) { var config = { expiresAt: '13:00' }; - var created = new Date(Date.now()); - created.setHours(15); - created = new Date(created.setDate(created.getDay() - 4)).getTime(); + var created = Date.now() - 313200000; // 87 hours (3 days + 15 hours) var rule = Cache.compile(config); var ttl = Cache.ttl(rule, created); @@ -532,13 +530,11 @@ require('../suite')(function (useRedis, useMongo) { done(); }); - it('returns the 0 when created several days ago and expiresAt is used with an hour before the created hour', function (done) { + it('returns 0 when created 60 hours ago and expiresAt is used with an hour before the created hour', function (done) { var config = { expiresAt: '12:00' }; - var created = new Date(Date.now()); - created.setHours(10); - created = new Date(created.setDate(created.getDay() - 4)).getTime(); + var created = Date.now() - 342000000; // 95 hours ago (3 days + 23 hours) var rule = Cache.compile(config); var ttl = Cache.ttl(rule, created); From 695247db269f29f0f8c2cc8b53ec492f32c58e2f Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 1 Nov 2012 00:17:17 -0700 Subject: [PATCH 7/8] Added support for pass-through error responses --- .gitignore | 2 + Readme.md | 5 +- lib/error.js | 105 +++++++++++++++++++++--------- lib/request.js | 157 ++++++++++++++++++++++----------------------- lib/session.js | 2 +- test/unit/error.js | 45 +++++++++++-- 6 files changed, 195 insertions(+), 121 deletions(-) diff --git a/.gitignore b/.gitignore index 07f00c30a..77ba16cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ config.json */._* */*/._* coverage.* +lib-cov + diff --git a/Readme.md b/Readme.md index 927344a89..f0c41feac 100755 --- a/Readme.md +++ b/Readme.md @@ -249,7 +249,7 @@ function onUnknownRoute(request) { If a different error format than the default JSON response is required, the server `errors.format` option can be assigned a function to generate a different error response. The function signature is _'function (result, callback)'_ where: - _'result'_ - is the **hapi** error object returned by the route handler, and -- _'callback'_ - is the callback function called with the new result object or string. +- _'callback'_ - is a callback function called with the formatted response. The callback function signature is _'function (code, payload, contentType)'_. For example: ```javascript @@ -257,7 +257,7 @@ var options = { errors: { format: function (result, callback) { - callback('Oops: ' + result.message); + callback(500, 'Oops: ' + result.message, 'text/html'); } } }; @@ -706,7 +706,6 @@ The 'Hapi.Error' module provides helper methods to generate error responses: - _'forbidden([message])'_ - HTTP 403 (Not Allowed). - _'notFound([message])'_ - HTTP 404 (Not Found). - _'internal([message, data])'_ - HTTP 500 (Internal Error). The optional _message_ and _data_ values are not returned to the client but are logged internally. -- _'create(message, code, text, [options]) - creates a custom error with the provided _message_, _code_ (the HTTP status code), _text_ (the HTTP status message), and any keys present in _options_. The _message_ value is optional and will be returned to the client in the response unless noted otherwise. For example: diff --git a/lib/error.js b/lib/error.js index 2d8033170..9a6a9c64f 100755 --- a/lib/error.js +++ b/lib/error.js @@ -10,32 +10,26 @@ var Utils = require('./utils'); var internals = {}; -exports = module.exports = internals.Error = function (code, message, custom, options) { +exports = module.exports = internals.Error = function (code, message, options) { Utils.assert(this.constructor === internals.Error, 'Error must be instantiated using new'); Utils.assert(!options || !options.toResponse || typeof options.toResponse === 'function', 'options.toReponse must be a function'); + Utils.assert(code >= 400 && code < 600, 'Error code must be 4xx or 5xx'); Error.call(this); this.name = 'HapiError'; this.code = code; this.message = message; - this.text = Http.STATUS_CODES[code] || 'Unknown'; this.settings = Utils.clone(options) || {}; // Options can be reused; - for (var d in custom) { - if (custom.hasOwnProperty(d)) { - this[d] = custom[d]; - } - } - return this; }; NodeUtil.inherits(internals.Error, Error); -internals.Error.prototype.response = function () { +internals.Error.prototype.toResponse = function () { if (this.settings.toResponse) { @@ -45,30 +39,27 @@ internals.Error.prototype.response = function () { var response = { code: this.code, payload: { - error: this.text, + error: Http.STATUS_CODES[this.code] || 'Unknown', code: this.code, - message: (this.code >= 500 && this.code < 600 ? 'An internal server error occurred' : this.message) + message: this.message } - } - - return response; -}; + // contentType: 'application/json' + }; + for (var d in this) { + if (this.hasOwnProperty(d) && + !response.payload.hasOwnProperty(d)) { -internals.Error.prototype.format = function () { + response.payload[d] = this[d]; + } + } - return this.response().payload; + return response; }; // Utilities -internals.Error.create = function (message, code, options) { - - return new internals.Error(message, code, options); -}; - - internals.Error.badRequest = function (message) { return new internals.Error(400, message); @@ -95,22 +86,74 @@ internals.Error.notFound = function (message) { internals.Error.internal = function (message, data) { - var custom = { - trace: Utils.callStack(1) + var format = function () { + + var response = { + code: 500, + payload: { + error: Http.STATUS_CODES[500], + code: 500, + message: 'An internal server error occurred' // Hide actual error from user + } + }; + + return response; }; - if (data) { - custom.data = data; - } + var err = new internals.Error(500, message, { toResponse: format }); + err.trace = Utils.callStack(1); + err.data = data; + return err; +}; + + +internals.Error.passThrough = function (code, payload, type) { - return new internals.Error(500, message, custom); + var format = function () { + + var response = { + code: code, + payload: payload + }; + + return response; + }; + + var err = new internals.Error(code, 'Pass-through', { toResponse: format }); + return err; }; -internals.Error.format = function (err) { +internals.Error.toResponse = function (err) { + + Utils.assert(err instanceof Error, 'Input must be instance of Error'); + + if (err instanceof internals.Error) { - return err.format(); + return err.toResponse(); + } + + // Other Error + + var response = { + code: 500, + payload: { + message: err.message, + name: err.name + } + }; + + for (var d in err) { + if (err.hasOwnProperty(d)) { + response.payload[d] = err[d]; + } + } + + return response; }; + + + diff --git a/lib/request.js b/lib/request.js index aae6efde7..12f9dd9be 100755 --- a/lib/request.js +++ b/lib/request.js @@ -568,7 +568,8 @@ internals.Request.prototype._respond = function () { var code = 200; var headers = this.response.options.headers || {}; - var data = null; + var contentType = ''; + var payload = null; var review = function () { @@ -576,118 +577,110 @@ internals.Request.prototype._respond = function () { self._setCache(headers); var result = self.response.result; - if (result) { + if (!result) { + inject(null); + self.log(['http', 'response', 'empty']); + return send(); + } - // Response code and headers + // Error response format - if (result instanceof Error) { - code = result.code; - self.log(['http', 'response', 'error'], result); - } - else { - if (self.response.options.created) { - code = 201; - headers.Location = self.response.options.created; - } + if (result instanceof Error) { + self.log(['http', 'response', 'error'], result); - if (self.response.options.contentType) { - headers['Content-Type'] = self.response.options.contentType; - } + if (self.server.settings.errors && + self.server.settings.errors.format) { - if (self.response.options.contentLength) { - headers['Content-Length'] = self.response.options.contentLength; - } + self.server.settings.errors.format(result, function (errCode, errPayload, errContentType) { - self.log(['http', 'response']); + code = errCode; + result = errPayload; + contentType = errContentType; + }); + } + else { + var errResponse = (result instanceof Err ? result.toResponse() : Err.toResponse(result)); + code = errResponse.code || 500; + result = errResponse.payload || ''; + if (errResponse.contentType) { + contentType = errResponse.contentType; + } } + } + else { + self.log(['http', 'response']); + } - // Payload + // Set options - if (typeof result === 'object') { + if (self.response.options.created) { + code = 201; + headers.Location = self.response.options.created; + } - // Object + if (self.response.options.contentType) { + contentType = self.response.options.contentType; + } - if (result instanceof Error) { + if (self.response.options.contentLength) { + headers['Content-Length'] = self.response.options.contentLength; + } - // Error + // Payload - if (self.server.settings.errors && - self.server.settings.errors.format) { + if (typeof result === 'object') { - self.server.settings.errors.format(result, function (formatted) { + // Object - inject(formatted); - var isString = (typeof formatted === 'string'); - data = (isString ? formatted : JSON.stringify(formatted)); - prepare(isString ? 'text/html' : 'application/json'); - }); - } - else { - var formatted = Err.format(result); - inject(formatted); - data = JSON.stringify(formatted); - prepare('application/json'); - } - } - else if (result instanceof Stream) { + if (result instanceof Stream) { - // Stream + // Stream - data = result; - self.log(['http', 'response', 'stream']); - stream(); - } - else { - - // Object - - inject(result); - data = JSON.stringify(result); - prepare('application/json'); - } + payload = result; + self.log(['http', 'response', 'stream']); + return stream(); } - else { - // Non-object (String, etc.) + // Object - inject(result); - data = (typeof result === 'string' ? result : JSON.stringify(result)); - prepare('text/html'); - } + payload = JSON.stringify(result); + contentType = contentType || 'application/json'; } else { - inject(null); - self.log(['http', 'response', 'empty']); - send(); - } - }; - var inject = function (result) { + // Non-object (String, etc.) - if (Shot.isInjection(self.raw.req)) { - self.raw.res.hapi = { result: result }; + payload = (typeof result === 'string' ? result : JSON.stringify(result)); + contentType = contentType || 'text/html'; } - }; - var prepare = function (contentType) { + // Non-stream + inject(result); if (!headers['Content-Type']) { headers['Content-Type'] = contentType; } - if (data !== null && - !headers['Content-Length']) { // data can be empty string + if (payload !== null && + !headers['Content-Length']) { // payload can be empty string - headers['Content-Length'] = Buffer.byteLength(data); + headers['Content-Length'] = Buffer.byteLength(payload); } - send(); + return send(); + }; + + var inject = function (result) { + + if (Shot.isInjection(self.raw.req)) { + self.raw.res.hapi = { result: result }; + } }; var send = function () { self.raw.res.writeHead(code, headers); - self.raw.res.end(self.method !== 'head' ? data : ''); + self.raw.res.end(self.method !== 'head' ? payload : ''); if (!self._isResponded) { self.server.emit('response', self); @@ -697,38 +690,38 @@ internals.Request.prototype._respond = function () { var stream = function () { - // Check if data is a node HTTP response (data.*) or a (mikeal's) Request object (data.response.*) + // Check if payload is a node HTTP response (payload.*) or a (mikeal's) Request object (payload.response.*) if (!self._route || !self._route.config.proxy || self._route.config.proxy.passThrough) { // Pass headers only if not proxy or proxy with pass-through set - var responseHeaders = data.response ? data.response.headers : data.headers; + var responseHeaders = payload.response ? payload.response.headers : payload.headers; if (responseHeaders) { Utils.merge(headers, responseHeaders); } } - code = data.statusCode || ((data.response && data.response.code) ? data.response.code : code); + code = payload.statusCode || ((payload.response && payload.response.code) ? payload.response.code : code); self.raw.res.writeHead(code, headers); self.raw.req.on('close', function () { - data.destroy.bind(data); + payload.destroy.bind(payload); }); - data.on('error', function () { + payload.on('error', function () { self.raw.req.destroy(); }); - data.on('end', function () { + payload.on('end', function () { self.raw.res.end(); }); - data.pipe(self.raw.res); + payload.pipe(self.raw.res); }; review(); diff --git a/lib/session.js b/lib/session.js index 1d6d17616..e7bfc5fa6 100755 --- a/lib/session.js +++ b/lib/session.js @@ -478,7 +478,7 @@ exports.getRandomString = function (size) { internals.error = function (code, description) { - var err = new Err(400, 'OAuth', null, { + var err = new Err(400, 'OAuth', { toResponse: function () { return { code: 400, payload: { error: code, error_description: description } }; diff --git a/test/unit/error.js b/test/unit/error.js index e0c83d21c..0c0971079 100755 --- a/test/unit/error.js +++ b/test/unit/error.js @@ -73,19 +73,56 @@ describe('Err', function() { }); }); - describe('#format', function() { + describe('#toResponse', function () { it('formats a custom error', function (done) { - var err = new Err(500, 'Unknown', null, { + var err = new Err(500, 'Unknown', { toResponse: function () { return { payload: { test: true } }; } }); - expect(err.format().test).to.equal(true); + expect(err.toResponse().payload.test).to.equal(true); + done(); + }); + + it('formats a generic error', function (done) { + + var err = new Error('boom'); + err.x = 10; + + var response = Err.toResponse(err); + expect(response.payload.message).to.equal('boom'); + expect(response.payload.x).to.equal(10); + expect(response.code).to.equal(500); + done(); + }); + + it('formats an hapi error', function (done) { + + var err = new Err(500, 'boom'); + err.x = 10; + + var response = Err.toResponse(err); + expect(response.payload.message).to.equal('boom'); + expect(response.payload.x).to.equal(10); + expect(response.code).to.equal(500); + done(); + }); + + it('formats an internal error', function (done) { + + var err = Err.internal('boom', { x: 10 }); + + var response = Err.toResponse(err); + expect(response.payload.message).to.equal('An internal server error occurred'); + expect(response.payload.x).to.not.exist; + expect(response.code).to.equal(500); done(); }); }); -}); \ No newline at end of file +}); + + From 80e1319906ec8f0c1cf34e921961d53fb2c5e2d4 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 1 Nov 2012 07:49:08 -0700 Subject: [PATCH 8/8] Custom error test --- lib/error.js | 5 ++-- test/integration/request.js | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100755 test/integration/request.js diff --git a/lib/error.js b/lib/error.js index 9a6a9c64f..f67bdea84 100755 --- a/lib/error.js +++ b/lib/error.js @@ -107,13 +107,14 @@ internals.Error.internal = function (message, data) { }; -internals.Error.passThrough = function (code, payload, type) { +internals.Error.passThrough = function (code, payload, contentType) { var format = function () { var response = { code: code, - payload: payload + payload: payload, + contentType: contentType }; return response; diff --git a/test/integration/request.js b/test/integration/request.js new file mode 100755 index 000000000..11c39af93 --- /dev/null +++ b/test/integration/request.js @@ -0,0 +1,55 @@ +// Load modules + +var expect = require('chai').expect; +var Sinon = require('sinon'); +var Hapi = process.env.TEST_COV ? require('../../lib-cov/hapi') : require('../../lib/hapi'); + + +describe('Request', function () { + + var customErrorHandler = function (request) { + + request.reply(Hapi.error.passThrough(599, 'heya', 'text/plain')); + }; + + var server = new Hapi.Server('0.0.0.0', 18085); + server.addRoutes([ + { method: 'GET', path: '/custom', config: { handler: customErrorHandler } }, + ]); + + var makeRequest = function (path, callback) { + + var next = function (res) { + + return callback(res); + }; + + server.inject({ + method: 'GET', + url: path + }, next); + } + + function parseHeaders(res) { + + var headersObj = {}; + var headers = res._header.split('\r\n'); + for (var i = 0, il = headers.length; i < il; i++) { + var header = headers[i].split(':'); + var headerValue = header[1] ? header[1].trim() : ''; + headersObj[header[0]] = headerValue; + } + + return headersObj; + } + + it('returns custom error response', function (done) { + + makeRequest('/custom', function (rawRes) { + + var headers = parseHeaders(rawRes.raw.res); + expect(headers['Content-Type']).to.equal('text/plain'); + done(); + }); + }); +}); \ No newline at end of file