Merge pull request #9 from strongloop/initial-implementation
Initial implementation
This commit is contained in:
commit
0158d3a735
|
@ -0,0 +1,7 @@
|
|||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4"
|
||||
- "6"
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) IBM Corp. 2016. All Rights Reserved.
|
||||
Node module: strong-error-handler
|
||||
This project is licensed under the MIT License, full text below.
|
||||
|
||||
--------
|
||||
|
||||
MIT license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
73
README.md
73
README.md
|
@ -1 +1,72 @@
|
|||
# strong-error-handler
|
||||
# strong-error-handler
|
||||
|
||||
Error handler for use in development (debug) and production environments.
|
||||
|
||||
- When run in production mode, error responses are purposely undetailed
|
||||
in order to prevent leaking sensitive information.
|
||||
|
||||
- When in debug mode, detailed information such as stack traces
|
||||
are returned in the HTTP responses.
|
||||
|
||||
JSON is the only supported response format at this time.
|
||||
|
||||
*There are plans to support other formats such as Text, HTML, and XML.*
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
$ npm install strong-error-handler
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
In an express-based application:
|
||||
|
||||
```js
|
||||
var express = require('express');
|
||||
var errorHandler = require('strong-error-handler');
|
||||
|
||||
var app = express();
|
||||
// setup your routes
|
||||
app.use(errorHandler({ /* options, see below */ }));
|
||||
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
In LoopBack applications, add the following entry to your
|
||||
`server/middleware.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"final:after": {
|
||||
"strong-error-handler": {
|
||||
"params": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
#### debug
|
||||
|
||||
`boolean`, defaults to `false`.
|
||||
|
||||
When enabled, HTTP responses include all error properties, including
|
||||
sensitive data such as file paths, URLs and stack traces.
|
||||
|
||||
#### log
|
||||
|
||||
`boolean`, defaults to `true`.
|
||||
|
||||
When enabled, all errors are printed via `console.error`.
|
||||
|
||||
Customization of the log format is intentionally not allowed. If you would like
|
||||
to use a different format/logger, disable this option and add your own custom
|
||||
error-handling middleware.
|
||||
|
||||
```js
|
||||
app.use(myErrorLogger());
|
||||
app.use(errorHandler({ log: false }));
|
||||
```
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||
// Node module: strong-error-handler
|
||||
// This file is licensed under the MIT License.
|
||||
// License text available at https://opensource.org/licenses/MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
var httpStatus = require('http-status');
|
||||
|
||||
module.exports = function buildResponseData(err, isDebugMode) {
|
||||
if (Array.isArray(err) && isDebugMode) {
|
||||
err = serializeArrayOfErrors(err);
|
||||
}
|
||||
|
||||
var data = Object.create(null);
|
||||
fillStatusCode(data, err);
|
||||
|
||||
if (typeof err !== 'object') {
|
||||
data.statusCode = 500;
|
||||
data.message = '' + err;
|
||||
err = {};
|
||||
}
|
||||
|
||||
if (isDebugMode) {
|
||||
fillDebugData(data, err);
|
||||
} else if (data.statusCode >= 400 && data.statusCode <= 499) {
|
||||
fillBadRequestError(data, err);
|
||||
} else {
|
||||
fillInternalError(data, err);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
function serializeArrayOfErrors(errors) {
|
||||
var details = [];
|
||||
for (var ix in errors) {
|
||||
var err = errors[ix];
|
||||
if (typeof err !== 'object') {
|
||||
details.push('' + err);
|
||||
continue;
|
||||
}
|
||||
|
||||
var data = {stack: err.stack};
|
||||
for (var p in err) { // eslint-disable-line one-var
|
||||
data[p] = err[p];
|
||||
}
|
||||
details.push(data);
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'ArrayOfErrors',
|
||||
message: 'Failed with multiple errors, ' +
|
||||
'see `details` for more information.',
|
||||
details: details,
|
||||
};
|
||||
}
|
||||
|
||||
function fillStatusCode(data, err) {
|
||||
data.statusCode = err.statusCode || err.status;
|
||||
if (!data.statusCode || data.statusCode < 400)
|
||||
data.statusCode = 500;
|
||||
}
|
||||
|
||||
function fillDebugData(data, err) {
|
||||
for (var p in err) {
|
||||
if ((p in data)) continue;
|
||||
data[p] = err[p];
|
||||
}
|
||||
// NOTE err.stack is not an enumerable property
|
||||
data.stack = err.stack;
|
||||
}
|
||||
|
||||
function fillBadRequestError(data, err) {
|
||||
data.name = err.name;
|
||||
data.message = err.message;
|
||||
data.details = err.details;
|
||||
}
|
||||
|
||||
function fillInternalError(data, err) {
|
||||
data.message = httpStatus[data.statusCode] || 'Unknown Error';
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||
// Node module: strong-error-handler
|
||||
// This file is licensed under the MIT License.
|
||||
// License text available at https://opensource.org/licenses/MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
var buildResponseData = require('./data-builder');
|
||||
var debug = require('debug')('strong-error-handler');
|
||||
var format = require('util').format;
|
||||
var logToConsole = require('./logger');
|
||||
var sendJson = require('./send-json');
|
||||
|
||||
function noop() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a middleware error handler function.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {Function}
|
||||
*/
|
||||
exports = module.exports = function createStrongErrorHandler(options) {
|
||||
options = options || {};
|
||||
|
||||
debug('Initializing with options %j', options);
|
||||
|
||||
// Debugging mode is disabled by default. When turned on (in dev),
|
||||
// all error properties (including) stack traces are sent in the response
|
||||
var isDebugMode = options.debug;
|
||||
|
||||
// Log all errors via console.error (enabled by default)
|
||||
var logError = options.log !== false ? logToConsole : noop;
|
||||
|
||||
return function strongErrorHandler(err, req, res, next) {
|
||||
debug('Handling %s', err.stack || err);
|
||||
|
||||
logError(req, err);
|
||||
|
||||
if (res._header) {
|
||||
debug('Response was already sent, closing the underlying connection');
|
||||
return req.socket.destroy();
|
||||
}
|
||||
|
||||
var data = buildResponseData(err, isDebugMode);
|
||||
debug('Response status %s data %j', data.statusCode, data);
|
||||
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.statusCode = data.statusCode;
|
||||
|
||||
// TODO: negotiate the content-type, take into account options.defaultType
|
||||
// For now, we always return JSON. See
|
||||
// - https://github.com/strongloop/strong-error-handler/issues/4
|
||||
// - https://github.com/strongloop/strong-error-handler/issues/5
|
||||
// - https://github.com/strongloop/strong-error-handler/issues/6
|
||||
sendJson(res, data);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||
// Node module: strong-error-handler
|
||||
// This file is licensed under the MIT License.
|
||||
// License text available at https://opensource.org/licenses/MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
var format = require('util').format;
|
||||
|
||||
module.exports = function logToConsole(req, err) {
|
||||
if (!Array.isArray(err)) {
|
||||
console.error('Unhandled error for request %s %s: %s',
|
||||
req.method, req.url, err.stack || err);
|
||||
return;
|
||||
}
|
||||
|
||||
var errors = err.map(formatError).join('\n');
|
||||
console.error('Unhandled array of errors for request %s %s\n',
|
||||
req.method, req.url, errors);
|
||||
};
|
||||
|
||||
function formatError(err) {
|
||||
return format('%s', err.stack || err);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||
// Node module: strong-error-handler
|
||||
// This file is licensed under the MIT License.
|
||||
// License text available at https://opensource.org/licenses/MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function sendJson(res, data) {
|
||||
var content = JSON.stringify({error: data});
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.end(content, 'utf-8');
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "strong-error-handler",
|
||||
"description": "Error handler for use in development and production environments.",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/strongloop/strong-error-handler.git"
|
||||
},
|
||||
"main": "lib/handler.js",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha",
|
||||
"posttest": "npm run lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^2.2.0",
|
||||
"http-status": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^2.1.1",
|
||||
"eslint": "^2.5.3",
|
||||
"eslint-config-loopback": "^3.0.0",
|
||||
"mocha": "^2.1.0",
|
||||
"supertest": "^1.1.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||
// Node module: strong-error-handler
|
||||
// This file is licensed under the MIT License.
|
||||
// License text available at https://opensource.org/licenses/MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
var debug = require('debug')('test');
|
||||
var expect = require('chai').expect;
|
||||
var http = require('http');
|
||||
var strongErrorHandler = require('..');
|
||||
var supertest = require('supertest');
|
||||
var util = require('util');
|
||||
|
||||
describe('strong-error-handler', function() {
|
||||
before(setupHttpServerAndClient);
|
||||
beforeEach(resetRequestHandler);
|
||||
|
||||
it('sets nosniff header', function(done) {
|
||||
givenErrorHandlerForError();
|
||||
request.get('/')
|
||||
.expect('X-Content-Type-Options', 'nosniff')
|
||||
.expect(500, done);
|
||||
});
|
||||
|
||||
it('handles response headers already sent', function(done) {
|
||||
givenErrorHandlerForError();
|
||||
var handler = _requestHandler;
|
||||
_requestHandler = function(req, res, next) {
|
||||
res.end('empty');
|
||||
process.nextTick(function() {
|
||||
handler(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
request.get('/').expect(200, 'empty', done);
|
||||
});
|
||||
|
||||
context('status code', function() {
|
||||
it('converts non-error "err.status" to 500', function(done) {
|
||||
givenErrorHandlerForError(new ErrorWithProps({status: 200}));
|
||||
request.get('/').expect(500, done);
|
||||
});
|
||||
|
||||
it('converts non-error "err.statusCode" to 500', function(done) {
|
||||
givenErrorHandlerForError(new ErrorWithProps({statusCode: 200}));
|
||||
request.get('/').expect(500, done);
|
||||
});
|
||||
|
||||
it('uses the value from "err.status"', function(done) {
|
||||
givenErrorHandlerForError(new ErrorWithProps({status: 404}));
|
||||
request.get('/').expect(404, done);
|
||||
});
|
||||
|
||||
it('uses the value from "err.statusCode"', function(done) {
|
||||
givenErrorHandlerForError(new ErrorWithProps({statusCode: 404}));
|
||||
request.get('/').expect(404, done);
|
||||
});
|
||||
|
||||
it('prefers "err.statusCode" over "err.status"', function(done) {
|
||||
givenErrorHandlerForError(new ErrorWithProps({
|
||||
statusCode: 400,
|
||||
status: 404,
|
||||
}));
|
||||
|
||||
request.get('/').expect(400, done);
|
||||
});
|
||||
});
|
||||
|
||||
context('logging', function() {
|
||||
var logs;
|
||||
|
||||
beforeEach(redirectConsoleError);
|
||||
afterEach(restoreConsoleError);
|
||||
|
||||
it('logs by default', function(done) {
|
||||
givenErrorHandlerForError(new Error(), {
|
||||
// explicitly set to undefined to prevent givenErrorHandlerForError
|
||||
// from disabling this option
|
||||
log: undefined,
|
||||
});
|
||||
|
||||
request.get('/').end(function(err) {
|
||||
if (err) return done(err);
|
||||
expect(logs).to.have.length(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('honours options.log=false', function(done) {
|
||||
givenErrorHandlerForError(new Error(), {log: false});
|
||||
|
||||
request.get('/api').end(function(err) {
|
||||
if (err) return done(err);
|
||||
expect(logs).to.have.length(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('honours options.log=true', function(done) {
|
||||
givenErrorHandlerForError(new Error(), {log: true});
|
||||
|
||||
request.get('/api').end(function(err) {
|
||||
if (err) return done(err);
|
||||
expect(logs).to.have.length(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('includes relevant information in the log message', function(done) {
|
||||
givenErrorHandlerForError(new TypeError('ERROR-NAME'), {log: true});
|
||||
|
||||
request.get('/api').end(function(err) {
|
||||
if (err) return done(err);
|
||||
|
||||
var msg = logs[0];
|
||||
// the request method
|
||||
expect(msg).to.contain('GET');
|
||||
// the request path
|
||||
expect(msg).to.contain('/api');
|
||||
// the error name & message
|
||||
expect(msg).to.contain('TypeError: ERROR-NAME');
|
||||
// the stack
|
||||
expect(msg).to.contain(__filename);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles array argument', function(done) {
|
||||
givenErrorHandlerForError(
|
||||
[new TypeError('ERR1'), new Error('ERR2')],
|
||||
{log: true});
|
||||
|
||||
request.get('/api').end(function(err) {
|
||||
if (err) return done(err);
|
||||
|
||||
var msg = logs[0];
|
||||
// the request method
|
||||
expect(msg).to.contain('GET');
|
||||
// the request path
|
||||
expect(msg).to.contain('/api');
|
||||
// the error name & message for all errors
|
||||
expect(msg).to.contain('TypeError: ERR1');
|
||||
expect(msg).to.contain('Error: ERR2');
|
||||
// verify that stacks are included too
|
||||
expect(msg).to.contain(__filename);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles non-Error argument', function(done) {
|
||||
givenErrorHandlerForError('STRING ERROR', {log: true});
|
||||
request.get('/').end(function(err) {
|
||||
if (err) return done(err);
|
||||
var msg = logs[0];
|
||||
expect(msg).to.contain('STRING ERROR');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var _consoleError = console.error;
|
||||
function redirectConsoleError() {
|
||||
logs = [];
|
||||
console.error = function() {
|
||||
var msg = util.format.apply(util, arguments);
|
||||
logs.push(msg);
|
||||
};
|
||||
}
|
||||
|
||||
function restoreConsoleError() {
|
||||
console.error = _consoleError;
|
||||
logs = [];
|
||||
}
|
||||
});
|
||||
|
||||
context('JSON response', function() {
|
||||
it('contains all error properties when debug=true', function(done) {
|
||||
var error = new ErrorWithProps({
|
||||
details: 'some details',
|
||||
extra: 'sensitive data',
|
||||
});
|
||||
givenErrorHandlerForError(error, {debug: true});
|
||||
|
||||
requestJson().end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
|
||||
var expectedData = {statusCode: 500, stack: error.stack};
|
||||
for (var key in error) expectedData[key] = error[key];
|
||||
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body.error).to.eql(expectedData);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('contains subset of properties when status=4xx', function(done) {
|
||||
var error = new ErrorWithProps({
|
||||
name: 'ValidationError',
|
||||
message: 'The model instance is not valid.',
|
||||
statusCode: 422,
|
||||
details: 'some details',
|
||||
extra: 'sensitive data',
|
||||
});
|
||||
givenErrorHandlerForError(error);
|
||||
|
||||
requestJson().end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body.error).to.eql({
|
||||
name: 'ValidationError',
|
||||
message: 'The model instance is not valid.',
|
||||
statusCode: 422,
|
||||
details: 'some details',
|
||||
// notice the property "extra" is not included
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('contains only safe info when status=5xx', function(done) {
|
||||
// Mock an error reported by fs.readFile
|
||||
var error = new ErrorWithProps({
|
||||
name: 'Error',
|
||||
message: 'ENOENT: no such file or directory, open "/etc/passwd"',
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/etc/password',
|
||||
});
|
||||
givenErrorHandlerForError(error);
|
||||
|
||||
requestJson().end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body.error).to.eql({
|
||||
statusCode: 500,
|
||||
message: 'Internal Server Error',
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles array argument as 500 when debug=false', function(done) {
|
||||
var errors = [new Error('ERR1'), new Error('ERR2'), 'ERR STRING'];
|
||||
givenErrorHandlerForError(errors);
|
||||
|
||||
requestJson().expect(500).end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body).to.eql({
|
||||
error: {
|
||||
statusCode: 500,
|
||||
message: 'Internal Server Error',
|
||||
},
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns all array items when debug=true', function(done) {
|
||||
var testError = new ErrorWithProps({
|
||||
message: 'expected test error',
|
||||
statusCode: 400,
|
||||
});
|
||||
var anotherError = new ErrorWithProps({
|
||||
message: 'another expected error',
|
||||
statusCode: 500,
|
||||
});
|
||||
var errors = [testError, anotherError, 'ERR STRING'];
|
||||
givenErrorHandlerForError(errors, {debug: true});
|
||||
|
||||
requestJson().expect(500).end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
|
||||
var data = res.body.error;
|
||||
expect(data).to.have.property('message').that.match(/multiple errors/);
|
||||
|
||||
var expectedDetails = [
|
||||
getExpectedErrorData(testError),
|
||||
getExpectedErrorData(anotherError),
|
||||
'ERR STRING',
|
||||
];
|
||||
expect(data).to.have.property('details').to.eql(expectedDetails);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles non-Error argument as 500 when debug=false', function(done) {
|
||||
givenErrorHandlerForError('Error Message', {debug: false});
|
||||
requestJson().expect(500).end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
|
||||
expect(res.body.error).to.eql({
|
||||
statusCode: 500,
|
||||
message: 'Internal Server Error',
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns non-Error argument in message when debug=true', function(done) {
|
||||
givenErrorHandlerForError('Error Message', {debug: true});
|
||||
requestJson().expect(500).end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
|
||||
expect(res.body.error).to.eql({
|
||||
statusCode: 500,
|
||||
message: 'Error Message',
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
function requestJson(url) {
|
||||
return request.get(url || '/')
|
||||
.set('Accept', 'text/plain')
|
||||
.expect('Content-Type', /^application\/json/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var _httpServer, _requestHandler, request;
|
||||
function resetRequestHandler() {
|
||||
_requestHandler = null;
|
||||
}
|
||||
|
||||
function givenErrorHandlerForError(error, options) {
|
||||
if (!error) error = new Error('an error');
|
||||
|
||||
if (!options) options = {};
|
||||
if (!('log' in options)) {
|
||||
// Disable logging to console by default, so that we don't spam
|
||||
// console output. One can use "DEBUG=strong-error-handler" when
|
||||
// troubleshooting.
|
||||
options.log = false;
|
||||
}
|
||||
|
||||
var handler = strongErrorHandler(options);
|
||||
_requestHandler = function(req, res, next) {
|
||||
debug('Invoking strong-error-handler');
|
||||
handler(error, req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
function setupHttpServerAndClient(done) {
|
||||
_httpServer = http.createServer(function(req, res) {
|
||||
if (!_requestHandler) {
|
||||
var msg = 'Error handler middleware was not setup in this test';
|
||||
console.error(msg);
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.end(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
_requestHandler(req, res, function(err) {
|
||||
console.log('unexpected: strong-error-handler called next with '
|
||||
(err && (err.stack || err)) || 'no error');
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.end(err ?
|
||||
'Unhandled strong-error-handler error:\n' + (err.stack || err) :
|
||||
'The error was silently discared by strong-error-handler');
|
||||
});
|
||||
});
|
||||
|
||||
_httpServer.once('error', function(err) {
|
||||
debug('Cannot setup HTTP server: %s', err.stack);
|
||||
done(err);
|
||||
});
|
||||
|
||||
_httpServer.once('listening', function() {
|
||||
var url = 'http://127.0.0.1:' + this.address().port;
|
||||
debug('Test server listening on %s', url);
|
||||
request = supertest(url);
|
||||
done();
|
||||
});
|
||||
|
||||
_httpServer.listen(0, '127.0.0.1');
|
||||
}
|
||||
|
||||
function ErrorWithProps(props) {
|
||||
this.name = props.name || 'ErrorWithProps';
|
||||
for (var p in props) {
|
||||
this[p] = props[p];
|
||||
}
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
// V8 (Chrome, Opera, Node)
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
util.inherits(ErrorWithProps, Error);
|
||||
|
||||
function getExpectedErrorData(err) {
|
||||
// "stack" is a non-enumerable property
|
||||
var data = {stack: err.stack};
|
||||
for (var prop in err) {
|
||||
data[prop] = err[prop];
|
||||
}
|
||||
return data;
|
||||
}
|
Loading…
Reference in New Issue