diff --git a/index.js b/index.js index f628e42..7fdba82 100644 --- a/index.js +++ b/index.js @@ -28,9 +28,12 @@ var DOUBLE_SPACE_REGEXP = /\x20{2}/g var NEWLINE_REGEXP = /\n/g /* istanbul ignore next */ -var defer = typeof setImmediate === 'function' - ? setImmediate - : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) } +var defer = + typeof setImmediate === 'function' + ? setImmediate + : function (fn) { + process.nextTick(fn.bind.apply(fn, arguments)) + } var isFinished = onFinished.isFinished /** @@ -41,20 +44,22 @@ var isFinished = onFinished.isFinished */ function createHtmlDocument (message) { - var body = escapeHtml(message) - .replace(NEWLINE_REGEXP, '
') - .replace(DOUBLE_SPACE_REGEXP, '  ') + var body = escapeHtml(message).replace(NEWLINE_REGEXP, '
').replace(DOUBLE_SPACE_REGEXP, '  ') - return '\n' + + return ( + '\n' + '\n' + '\n' + '\n' + 'Error\n' + '\n' + '\n' + - '
' + body + '
\n' + + '
' +
+    body +
+    '
\n' + '\n' + '\n' + ) } /** @@ -175,6 +180,13 @@ function getErrorMessage (err, status, env) { // use err.stack, which typically includes err.message msg = err.stack + // if error cause included + if (err && err.cause && err.cause.stack) { + msg += '\n[cause]: ' + err.cause.stack + } else if (err && err.stack) { + msg += '\n[stack]: ' + err.stack + } + // fallback to err.toString() when possible if (!msg && typeof err.toString === 'function') { msg = err.toString() @@ -253,9 +265,7 @@ function getResponseStatusCode (res) { */ function headersSent (res) { - return typeof res.headersSent !== 'boolean' - ? Boolean(res._header) - : res.headersSent + return typeof res.headersSent !== 'boolean' ? Boolean(res._header) : res.headersSent } /** diff --git a/test/test.js b/test/test.js index a815654..8975427 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,3 @@ - var Buffer = require('safe-buffer').Buffer var finalhandler = require('..') var http = require('http') @@ -14,46 +13,60 @@ var shouldHaveStatusMessage = utils.shouldHaveStatusMessage var shouldNotHaveBody = utils.shouldNotHaveBody var shouldNotHaveHeader = utils.shouldNotHaveHeader -var describeStatusMessage = !/statusMessage/.test(http.IncomingMessage.toString()) - ? describe.skip - : describe +var describeStatusMessage = !/statusMessage/.test(http.IncomingMessage.toString()) ? describe.skip : describe describe('finalhandler(req, res)', function () { describe('headers', function () { it('should ignore err.headers without status code', function (done) { - request(createServer(createError('oops!', { - headers: { 'X-Custom-Header': 'foo' } - }))) + request( + createServer( + createError('oops!', { + headers: { 'X-Custom-Header': 'foo' } + }) + ) + ) .get('/') .expect(shouldNotHaveHeader('X-Custom-Header')) .expect(500, done) }) it('should ignore err.headers with invalid res.status', function (done) { - request(createServer(createError('oops!', { - headers: { 'X-Custom-Header': 'foo' }, - status: 601 - }))) + request( + createServer( + createError('oops!', { + headers: { 'X-Custom-Header': 'foo' }, + status: 601 + }) + ) + ) .get('/') .expect(shouldNotHaveHeader('X-Custom-Header')) .expect(500, done) }) it('should ignore err.headers with invalid res.statusCode', function (done) { - request(createServer(createError('oops!', { - headers: { 'X-Custom-Header': 'foo' }, - statusCode: 601 - }))) + request( + createServer( + createError('oops!', { + headers: { 'X-Custom-Header': 'foo' }, + statusCode: 601 + }) + ) + ) .get('/') .expect(shouldNotHaveHeader('X-Custom-Header')) .expect(500, done) }) it('should include err.headers with err.status', function (done) { - request(createServer(createError('oops!', { - headers: { 'X-Custom-Header': 'foo=500', 'X-Custom-Header2': 'bar' }, - status: 500 - }))) + request( + createServer( + createError('oops!', { + headers: { 'X-Custom-Header': 'foo=500', 'X-Custom-Header2': 'bar' }, + status: 500 + }) + ) + ) .get('/') .expect('X-Custom-Header', 'foo=500') .expect('X-Custom-Header2', 'bar') @@ -61,20 +74,28 @@ describe('finalhandler(req, res)', function () { }) it('should include err.headers with err.statusCode', function (done) { - request(createServer(createError('too many requests', { - headers: { 'Retry-After': '5' }, - statusCode: 429 - }))) + request( + createServer( + createError('too many requests', { + headers: { 'Retry-After': '5' }, + statusCode: 429 + }) + ) + ) .get('/') .expect('Retry-After', '5') .expect(429, done) }) it('should ignore err.headers when not an object', function (done) { - request(createServer(createError('oops!', { - headers: 'foobar', - statusCode: 500 - }))) + request( + createServer( + createError('oops!', { + headers: 'foobar', + statusCode: 500 + }) + ) + ) .get('/') .expect(500, done) }) @@ -82,95 +103,131 @@ describe('finalhandler(req, res)', function () { describe('status code', function () { it('should 404 on no error', function (done) { - request(createServer()) - .get('/') - .expect(404, done) + request(createServer()).get('/').expect(404, done) }) it('should 500 on error', function (done) { - request(createServer(createError())) - .get('/') - .expect(500, done) + request(createServer(createError())).get('/').expect(500, done) }) it('should use err.statusCode', function (done) { - request(createServer(createError('nope', { - statusCode: 400 - }))) + request( + createServer( + createError('nope', { + statusCode: 400 + }) + ) + ) .get('/') .expect(400, done) }) it('should ignore non-error err.statusCode code', function (done) { - request(createServer(createError('created', { - statusCode: 201 - }))) + request( + createServer( + createError('created', { + statusCode: 201 + }) + ) + ) .get('/') .expect(500, done) }) it('should ignore non-numeric err.statusCode', function (done) { - request(createServer(createError('oops', { - statusCode: 'oh no' - }))) + request( + createServer( + createError('oops', { + statusCode: 'oh no' + }) + ) + ) .get('/') .expect(500, done) }) it('should use err.status', function (done) { - request(createServer(createError('nope', { - status: 400 - }))) + request( + createServer( + createError('nope', { + status: 400 + }) + ) + ) .get('/') .expect(400, done) }) it('should use err.status over err.statusCode', function (done) { - request(createServer(createError('nope', { - status: 400, - statusCode: 401 - }))) + request( + createServer( + createError('nope', { + status: 400, + statusCode: 401 + }) + ) + ) .get('/') .expect(400, done) }) it('should set status to 500 when err.status < 400', function (done) { - request(createServer(createError('oops', { - status: 202 - }))) + request( + createServer( + createError('oops', { + status: 202 + }) + ) + ) .get('/') .expect(500, done) }) it('should set status to 500 when err.status > 599', function (done) { - request(createServer(createError('oops', { - status: 601 - }))) + request( + createServer( + createError('oops', { + status: 601 + }) + ) + ) .get('/') .expect(500, done) }) it('should use err.statusCode over invalid err.status', function (done) { - request(createServer(createError('nope', { - status: 50, - statusCode: 410 - }))) + request( + createServer( + createError('nope', { + status: 50, + statusCode: 410 + }) + ) + ) .get('/') .expect(410, done) }) it('should ignore non-error err.status code', function (done) { - request(createServer(createError('created', { - status: 201 - }))) + request( + createServer( + createError('created', { + status: 201 + }) + ) + ) .get('/') .expect(500, done) }) it('should ignore non-numeric err.status', function (done) { - request(createServer(createError('oops', { - status: 'oh no' - }))) + request( + createServer( + createError('oops', { + status: 'oh no' + }) + ) + ) .get('/') .expect(500, done) }) @@ -178,10 +235,7 @@ describe('finalhandler(req, res)', function () { describeStatusMessage('status message', function () { it('should be "Not Found" on no error', function (done) { - request(createServer()) - .get('/') - .expect(shouldHaveStatusMessage('Not Found')) - .expect(404, done) + request(createServer()).get('/').expect(shouldHaveStatusMessage('Not Found')).expect(404, done) }) it('should be "Internal Server Error" on error', function (done) { @@ -192,9 +246,13 @@ describe('finalhandler(req, res)', function () { }) it('should be "Bad Request" when err.statusCode = 400', function (done) { - request(createServer(createError('oops', { - status: 400 - }))) + request( + createServer( + createError('oops', { + status: 400 + }) + ) + ) .get('/') .expect(shouldHaveStatusMessage('Bad Request')) .expect(400, done) @@ -222,7 +280,7 @@ describe('finalhandler(req, res)', function () { it('should escape method and pathname characters', function (done) { rawrequest(createServer()) - .get('/') + .get("/") .expect(404, /
Cannot GET \/%3Cla'me%3E<\/pre>/, done)
     })
 
@@ -263,25 +321,15 @@ describe('finalhandler(req, res)', function () {
     })
 
     it('should handle HEAD', function (done) {
-      request(createServer())
-        .head('/foo')
-        .expect(404)
-        .expect(shouldNotHaveBody())
-        .end(done)
+      request(createServer()).head('/foo').expect(404).expect(shouldNotHaveBody()).end(done)
     })
 
     it('should include X-Content-Type-Options header', function (done) {
-      request(createServer())
-        .get('/foo')
-        .expect('X-Content-Type-Options', 'nosniff')
-        .expect(404, done)
+      request(createServer()).get('/foo').expect('X-Content-Type-Options', 'nosniff').expect(404, done)
     })
 
     it('should include Content-Security-Policy header', function (done) {
-      request(createServer())
-        .get('/foo')
-        .expect('Content-Security-Policy', "default-src 'none'")
-        .expect(404, done)
+      request(createServer()).get('/foo').expect('Content-Security-Policy', "default-src 'none'").expect(404, done)
     })
 
     it('should not hang/error if there is a request body', function (done) {
@@ -340,9 +388,11 @@ describe('finalhandler(req, res)', function () {
       var err = createError('boom!', {
         status: 501
       })
-      request(createServer(err, {
-        env: 'production'
-      }))
+      request(
+        createServer(err, {
+          env: 'production'
+        })
+      )
         .get('/foo')
         .expect(501, /
Not Implemented<\/pre>/, done)
     })
@@ -399,9 +449,7 @@ describe('finalhandler(req, res)', function () {
           done(new Error('oops'))
         })
 
-        request(server)
-          .get('/foo')
-          .expect(503, done)
+        request(server).get('/foo').expect(503, done)
       })
 
       it('should convert to 500 is not a number', function (done) {
@@ -411,9 +459,7 @@ describe('finalhandler(req, res)', function () {
           done(new Error('oops'))
         })
 
-        request(server)
-          .get('/foo')
-          .expect(500, done)
+        request(server).get('/foo').expect(500, done)
       })
 
       it('should override with err.status', function (done) {
@@ -426,18 +472,18 @@ describe('finalhandler(req, res)', function () {
           done(err)
         })
 
-        request(server)
-          .get('/foo')
-          .expect(414, done)
+        request(server).get('/foo').expect(414, done)
       })
 
       it('should default body to status message in production', function (done) {
         var err = createError('boom!', {
           status: 509
         })
-        request(createServer(err, {
-          env: 'production'
-        }))
+        request(
+          createServer(err, {
+            env: 'production'
+          })
+        )
           .get('/foo')
           .expect(509, /
Bandwidth Limit Exceeded<\/pre>/, done)
       })
@@ -451,9 +497,7 @@ describe('finalhandler(req, res)', function () {
           done(new Error('oops'))
         })
 
-        request(server)
-          .get('/foo')
-          .expect(500, done)
+        request(server).get('/foo').expect(500, done)
       })
     })
   })
@@ -466,11 +510,7 @@ describe('finalhandler(req, res)', function () {
         done()
       })
 
-      request(server)
-        .get('/foo')
-        .expect(404)
-        .expect('Server', 'foobar')
-        .end(done)
+      request(server).get('/foo').expect(404).expect('Server', 'foobar').end(done)
     })
 
     it('should override content-type and length', function (done) {
@@ -520,9 +560,7 @@ describe('finalhandler(req, res)', function () {
         })
       })
 
-      request(server)
-        .get('/foo')
-        .expect(301, '01', done)
+      request(server).get('/foo').expect(301, '01', done)
     })
 
     it('should terminate on error', function (done) {
@@ -531,10 +569,12 @@ describe('finalhandler(req, res)', function () {
         res.statusCode = 301
         res.write('0')
         process.nextTick(function () {
-          done(createError('too many requests', {
-            status: 429,
-            headers: { 'Retry-After': '5' }
-          }))
+          done(
+            createError('too many requests', {
+              status: 429,
+              headers: { 'Retry-After': '5' }
+            })
+          )
           res.end('1')
         })
       })
@@ -577,4 +617,17 @@ describe('finalhandler(req, res)', function () {
         })
     })
   })
+
+  describe('should display cause in the Error output', function () {
+    it('should return error message with stack trace in development', function (done) {
+      process.env.NODE_ENV = 'development'
+      const err = new Error('foo', { cause: new Error('bar') })
+
+      const server = createServer(err)
+
+      request(server)
+        .get('/')
+        .expect(500, /[Error: bar]/, done)
+    })
+  })
 })