strong-error-handler 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +8 -0
- package/README.md +26 -25
- package/index.d.ts +1 -0
- package/lib/handler.js +2 -3
- package/lib/send-html.js +3 -3
- package/lib/send-json.js +8 -2
- package/lib/send-xml.js +6 -2
- package/package.json +6 -6
- package/views/default-error.ejs +1 -1
- package/.travis.yml +0 -6
- package/test/handler.test.js +0 -942
package/CHANGES.md
CHANGED
package/README.md
CHANGED
|
@@ -14,9 +14,9 @@ In debug mode, `strong-error-handler` returns full error stack traces and intern
|
|
|
14
14
|
|
|
15
15
|
## Supported versions
|
|
16
16
|
|
|
17
|
-
Current|Long Term Support|Maintenance
|
|
18
|
-
|
|
19
|
-
3.x|2.x|
|
|
17
|
+
| Current | Long Term Support | Maintenance |
|
|
18
|
+
| :-----: | :---------------: | :---------: |
|
|
19
|
+
| 4.x | 3.x | 2.x |
|
|
20
20
|
|
|
21
21
|
Learn more about our LTS plan in [docs](http://loopback.io/doc/en/contrib/Long-term-support.html).
|
|
22
22
|
|
|
@@ -108,24 +108,25 @@ The content type of the response depends on the request's `Accepts` header.
|
|
|
108
108
|
|
|
109
109
|
## Options
|
|
110
110
|
|
|
111
|
-
| Option
|
|
112
|
-
|
|
|
113
|
-
| debug
|
|
114
|
-
| log
|
|
115
|
-
| safeFields
|
|
116
|
-
| defaultType
|
|
117
|
-
|
|
|
111
|
+
| Option | Type | Default | Description |
|
|
112
|
+
| -------------------- | ------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
113
|
+
| debug | Boolean | `false` | If `true`, HTTP responses include all error properties, including sensitive data such as file paths, URLs and stack traces. See [Example output](#example) below. |
|
|
114
|
+
| log | Boolean | `true` | If `true`, all errors are printed via `console.error`, including an array of fields (custom error properties) that are safe to include in response messages (both 4xx and 5xx). <br/> If `false`, sends only the error back in the response. |
|
|
115
|
+
| safeFields | [String] | `[]` | Specifies property names on errors that are allowed to be passed through in 4xx and 5xx responses. See [Safe error fields](#safe-error-fields) below. |
|
|
116
|
+
| defaultType | String | `"json"` | Specifies the default response content type to use when the client does not provide any Accepts header. |
|
|
117
|
+
| rootProperty | String or false | `"error"` | Specifies the root property name for json or xml. If the value is set to `false`, no wrapper will be added to the json object. The false value is ignored by XML as a root element is always required. |
|
|
118
|
+
| negotiateContentType | Boolean | true | Negotiate the response content type via Accepts request header. When disabled, strong-error-handler will always use the default content type when producing responses. Disabling content type negotiation is useful if you want to see JSON-formatted error responses in browsers, because browsers usually prefer HTML and XML over other content types. |
|
|
118
119
|
|
|
119
120
|
### Customizing log format
|
|
120
121
|
|
|
121
|
-
**Express**
|
|
122
|
+
**Express**
|
|
122
123
|
|
|
123
|
-
To use a different log format, add your own custom error-handling middleware then disable `errorHandler.log`.
|
|
124
|
+
To use a different log format, add your own custom error-handling middleware then disable `errorHandler.log`.
|
|
124
125
|
For example, in an Express application:
|
|
125
126
|
|
|
126
127
|
```js
|
|
127
128
|
app.use(myErrorLogger());
|
|
128
|
-
app.use(errorHandler({
|
|
129
|
+
app.use(errorHandler({log: false}));
|
|
129
130
|
```
|
|
130
131
|
|
|
131
132
|
In general, add `strong-error-handler` as the last middleware function, just before calling `app.listen()`.
|
|
@@ -234,7 +235,7 @@ To migrate a LoopBack 2.x application to use `strong-error-handler`:
|
|
|
234
235
|
}
|
|
235
236
|
</pre>
|
|
236
237
|
|
|
237
|
-
For more information, see
|
|
238
|
+
For more information, see
|
|
238
239
|
[Migrating apps to LoopBack 3.0](http://loopback.io/doc/en/lb3/Migrating-to-3.0.html#update-use-of-rest-error-handler).
|
|
239
240
|
|
|
240
241
|
## Example
|
|
@@ -252,17 +253,17 @@ The same error generated when `debug: true` :
|
|
|
252
253
|
{ statusCode: 500,
|
|
253
254
|
name: 'Error',
|
|
254
255
|
message: 'a test error message',
|
|
255
|
-
stack: 'Error: a test error message
|
|
256
|
-
at Context.<anonymous> (User/strong-error-handler/test/handler.test.js:220:21)
|
|
257
|
-
at callFnAsync (User/strong-error-handler/node_modules/mocha/lib/runnable.js:349:8)
|
|
258
|
-
at Test.Runnable.run (User/strong-error-handler/node_modules/mocha/lib/runnable.js:301:7)
|
|
259
|
-
at Runner.runTest (User/strong-error-handler/node_modules/mocha/lib/runner.js:422:10)
|
|
260
|
-
at User/strong-error-handler/node_modules/mocha/lib/runner.js:528:12
|
|
261
|
-
at next (User/strong-error-handler/node_modules/mocha/lib/runner.js:342:14)
|
|
262
|
-
at User/strong-error-handler/node_modules/mocha/lib/runner.js:352:7
|
|
263
|
-
at next (User/strong-error-handler/node_modules/mocha/lib/runner.js:284:14)
|
|
264
|
-
at Immediate._onImmediate (User/strong-error-handler/node_modules/mocha/lib/runner.js:320:5)
|
|
265
|
-
at tryOnImmediate (timers.js:543:15)
|
|
256
|
+
stack: 'Error: a test error message
|
|
257
|
+
at Context.<anonymous> (User/strong-error-handler/test/handler.test.js:220:21)
|
|
258
|
+
at callFnAsync (User/strong-error-handler/node_modules/mocha/lib/runnable.js:349:8)
|
|
259
|
+
at Test.Runnable.run (User/strong-error-handler/node_modules/mocha/lib/runnable.js:301:7)
|
|
260
|
+
at Runner.runTest (User/strong-error-handler/node_modules/mocha/lib/runner.js:422:10)
|
|
261
|
+
at User/strong-error-handler/node_modules/mocha/lib/runner.js:528:12
|
|
262
|
+
at next (User/strong-error-handler/node_modules/mocha/lib/runner.js:342:14)
|
|
263
|
+
at User/strong-error-handler/node_modules/mocha/lib/runner.js:352:7
|
|
264
|
+
at next (User/strong-error-handler/node_modules/mocha/lib/runner.js:284:14)
|
|
265
|
+
at Immediate._onImmediate (User/strong-error-handler/node_modules/mocha/lib/runner.js:320:5)
|
|
266
|
+
at tryOnImmediate (timers.js:543:15)
|
|
266
267
|
at processImmediate [as _immediateCallback] (timers.js:523:5)' }}
|
|
267
268
|
```
|
|
268
269
|
|
package/index.d.ts
CHANGED
package/lib/handler.js
CHANGED
|
@@ -10,7 +10,6 @@ const SG = require('strong-globalize');
|
|
|
10
10
|
SG.SetRootDir(path.resolve(__dirname, '..'));
|
|
11
11
|
const buildResponseData = require('./data-builder');
|
|
12
12
|
const debug = require('debug')('strong-error-handler');
|
|
13
|
-
const format = require('util').format;
|
|
14
13
|
const logToConsole = require('./logger');
|
|
15
14
|
const negotiateContentProducer = require('./content-negotiation');
|
|
16
15
|
|
|
@@ -50,7 +49,7 @@ function writeErrorToResponse(err, req, res, options) {
|
|
|
50
49
|
|
|
51
50
|
options = options || {};
|
|
52
51
|
|
|
53
|
-
if (res.
|
|
52
|
+
if (res.headersSent) {
|
|
54
53
|
debug('Response was already sent, closing the underlying connection');
|
|
55
54
|
return req.socket.destroy();
|
|
56
55
|
}
|
|
@@ -66,7 +65,7 @@ function writeErrorToResponse(err, req, res, options) {
|
|
|
66
65
|
res.statusCode = data.statusCode;
|
|
67
66
|
|
|
68
67
|
const sendResponse = negotiateContentProducer(req, warn, options);
|
|
69
|
-
sendResponse(res, data);
|
|
68
|
+
sendResponse(res, data, options);
|
|
70
69
|
|
|
71
70
|
function warn(msg) {
|
|
72
71
|
res.header('X-Warning', msg);
|
package/lib/send-html.js
CHANGED
|
@@ -17,10 +17,10 @@ const compiledTemplates = {
|
|
|
17
17
|
module.exports = sendHtml;
|
|
18
18
|
|
|
19
19
|
function sendHtml(res, data, options) {
|
|
20
|
-
const toRender = {options
|
|
20
|
+
const toRender = {options, data};
|
|
21
21
|
// TODO: ability to call non-default template functions from options
|
|
22
22
|
const body = compiledTemplates.default(toRender);
|
|
23
|
-
|
|
23
|
+
sendResponse(res, body);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -41,7 +41,7 @@ function loadDefaultTemplates() {
|
|
|
41
41
|
return compileTemplate(defaultTemplate);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
44
|
+
function sendResponse(res, body) {
|
|
45
45
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
46
46
|
res.end(body);
|
|
47
47
|
}
|
package/lib/send-json.js
CHANGED
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
const safeStringify = require('fast-safe-stringify');
|
|
9
9
|
|
|
10
|
-
module.exports = function sendJson(res, data) {
|
|
11
|
-
|
|
10
|
+
module.exports = function sendJson(res, data, options) {
|
|
11
|
+
options = options || {};
|
|
12
|
+
// Set `options.rootProperty` to not wrap the data into an `error` object
|
|
13
|
+
const err = options.rootProperty === false ? data : {
|
|
14
|
+
// Use `options.rootProperty`, if not set, default to `error`
|
|
15
|
+
[options.rootProperty || 'error']: data,
|
|
16
|
+
};
|
|
17
|
+
const content = safeStringify(err);
|
|
12
18
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
13
19
|
res.end(content, 'utf-8');
|
|
14
20
|
};
|
package/lib/send-xml.js
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
const js2xmlparser = require('js2xmlparser');
|
|
9
9
|
|
|
10
|
-
module.exports = function sendXml(res, data) {
|
|
11
|
-
|
|
10
|
+
module.exports = function sendXml(res, data, options) {
|
|
11
|
+
options = options || {};
|
|
12
|
+
// Xml always requires a root element.
|
|
13
|
+
// `options.rootProperty === false` is not honored
|
|
14
|
+
const root = options.rootProperty || 'error';
|
|
15
|
+
const content = js2xmlparser.parse(root, data);
|
|
12
16
|
res.setHeader('Content-Type', 'text/xml; charset=utf-8');
|
|
13
17
|
res.end(content, 'utf-8');
|
|
14
18
|
};
|
package/package.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"name": "strong-error-handler",
|
|
3
3
|
"description": "Error handler for use in development and production environments.",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.5.0",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=10"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -20,18 +20,18 @@
|
|
|
20
20
|
"@types/express": "^4.16.0",
|
|
21
21
|
"accepts": "^1.3.3",
|
|
22
22
|
"debug": "^4.1.1",
|
|
23
|
-
"ejs": "^
|
|
23
|
+
"ejs": "^3.1.3",
|
|
24
24
|
"fast-safe-stringify": "^2.0.6",
|
|
25
25
|
"http-status": "^1.1.2",
|
|
26
26
|
"js2xmlparser": "^4.0.0",
|
|
27
|
-
"strong-globalize": "^
|
|
27
|
+
"strong-globalize": "^6.0.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"chai": "^4.1.2",
|
|
31
|
-
"eslint": "^
|
|
31
|
+
"eslint": "^7.0.0",
|
|
32
32
|
"eslint-config-loopback": "^13.1.0",
|
|
33
33
|
"express": "^4.16.3",
|
|
34
|
-
"mocha": "^
|
|
34
|
+
"mocha": "^7.1.2",
|
|
35
35
|
"supertest": "^4.0.2"
|
|
36
36
|
},
|
|
37
37
|
"browser": {
|
package/views/default-error.ejs
CHANGED
package/.travis.yml
DELETED
package/test/handler.test.js
DELETED
|
@@ -1,942 +0,0 @@
|
|
|
1
|
-
// Copyright IBM Corp. 2016,2018. All Rights Reserved.
|
|
2
|
-
// Node module: strong-error-handler
|
|
3
|
-
// This file is licensed under the MIT License.
|
|
4
|
-
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
-
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
const cloneAllProperties = require('../lib/clone.js');
|
|
9
|
-
const debug = require('debug')('test');
|
|
10
|
-
const expect = require('chai').expect;
|
|
11
|
-
const express = require('express');
|
|
12
|
-
const strongErrorHandler = require('..');
|
|
13
|
-
const supertest = require('supertest');
|
|
14
|
-
const util = require('util');
|
|
15
|
-
|
|
16
|
-
describe('strong-error-handler', function() {
|
|
17
|
-
before(setupHttpServerAndClient);
|
|
18
|
-
beforeEach(resetRequestHandler);
|
|
19
|
-
after(stopHttpServerAndClient);
|
|
20
|
-
|
|
21
|
-
it('sets nosniff header', function(done) {
|
|
22
|
-
givenErrorHandlerForError();
|
|
23
|
-
request.get('/')
|
|
24
|
-
.expect('X-Content-Type-Options', 'nosniff')
|
|
25
|
-
.expect(500, done);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('handles response headers already sent', function(done) {
|
|
29
|
-
givenErrorHandlerForError();
|
|
30
|
-
const handler = _requestHandler;
|
|
31
|
-
_requestHandler = function(req, res, next) {
|
|
32
|
-
res.end('empty');
|
|
33
|
-
process.nextTick(function() {
|
|
34
|
-
handler(req, res, next);
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
request.get('/').expect(200, 'empty', done);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
context('status code', function() {
|
|
42
|
-
it('converts non-error "err.status" to 500', function(done) {
|
|
43
|
-
givenErrorHandlerForError(new ErrorWithProps({status: 200}));
|
|
44
|
-
request.get('/').expect(500, done);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('converts non-error "err.statusCode" to 500', function(done) {
|
|
48
|
-
givenErrorHandlerForError(new ErrorWithProps({statusCode: 200}));
|
|
49
|
-
request.get('/').expect(500, done);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('uses the value from "err.status"', function(done) {
|
|
53
|
-
givenErrorHandlerForError(new ErrorWithProps({status: 404}));
|
|
54
|
-
request.get('/').expect(404, done);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('uses the value from "err.statusCode"', function(done) {
|
|
58
|
-
givenErrorHandlerForError(new ErrorWithProps({statusCode: 404}));
|
|
59
|
-
request.get('/').expect(404, done);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('prefers "err.statusCode" over "err.status"', function(done) {
|
|
63
|
-
givenErrorHandlerForError(new ErrorWithProps({
|
|
64
|
-
statusCode: 400,
|
|
65
|
-
status: 404,
|
|
66
|
-
}));
|
|
67
|
-
|
|
68
|
-
request.get('/').expect(400, done);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('handles error from `res.statusCode`', function(done) {
|
|
72
|
-
givenErrorHandlerForError();
|
|
73
|
-
const handler = _requestHandler;
|
|
74
|
-
_requestHandler = function(req, res, next) {
|
|
75
|
-
res.statusCode = 507;
|
|
76
|
-
handler(req, res, next);
|
|
77
|
-
};
|
|
78
|
-
request.get('/').expect(
|
|
79
|
-
507,
|
|
80
|
-
{error: {statusCode: 507, message: 'Insufficient Storage'}},
|
|
81
|
-
done
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
context('logging', function() {
|
|
87
|
-
let logs;
|
|
88
|
-
|
|
89
|
-
beforeEach(redirectConsoleError);
|
|
90
|
-
afterEach(restoreConsoleError);
|
|
91
|
-
|
|
92
|
-
it('logs by default', function(done) {
|
|
93
|
-
givenErrorHandlerForError(new Error(), {
|
|
94
|
-
// explicitly set to undefined to prevent givenErrorHandlerForError
|
|
95
|
-
// from disabling this option
|
|
96
|
-
log: undefined,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
request.get('/').end(function(err) {
|
|
100
|
-
if (err) return done(err);
|
|
101
|
-
expect(logs).to.have.length(1);
|
|
102
|
-
done();
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('honours options.log=false', function(done) {
|
|
107
|
-
givenErrorHandlerForError(new Error(), {log: false});
|
|
108
|
-
|
|
109
|
-
request.get('/api').end(function(err) {
|
|
110
|
-
if (err) return done(err);
|
|
111
|
-
expect(logs).to.have.length(0);
|
|
112
|
-
done();
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('honours options.log=true', function(done) {
|
|
117
|
-
givenErrorHandlerForError(new Error(), {log: true});
|
|
118
|
-
|
|
119
|
-
request.get('/api').end(function(err) {
|
|
120
|
-
if (err) return done(err);
|
|
121
|
-
expect(logs).to.have.length(1);
|
|
122
|
-
done();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('includes relevant information in the log message', function(done) {
|
|
127
|
-
givenErrorHandlerForError(new TypeError('ERROR-NAME'), {log: true});
|
|
128
|
-
|
|
129
|
-
request.get('/api').end(function(err) {
|
|
130
|
-
if (err) return done(err);
|
|
131
|
-
|
|
132
|
-
const msg = logs[0];
|
|
133
|
-
// the request method
|
|
134
|
-
expect(msg).to.contain('GET');
|
|
135
|
-
// the request path
|
|
136
|
-
expect(msg).to.contain('/api');
|
|
137
|
-
// the error name & message
|
|
138
|
-
expect(msg).to.contain('TypeError: ERROR-NAME');
|
|
139
|
-
// the stack
|
|
140
|
-
expect(msg).to.contain(__filename);
|
|
141
|
-
|
|
142
|
-
done();
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('handles array argument', function(done) {
|
|
147
|
-
givenErrorHandlerForError(
|
|
148
|
-
[new TypeError('ERR1'), new Error('ERR2')],
|
|
149
|
-
{log: true}
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
request.get('/api').end(function(err) {
|
|
153
|
-
if (err) return done(err);
|
|
154
|
-
|
|
155
|
-
const msg = logs[0];
|
|
156
|
-
// the request method
|
|
157
|
-
expect(msg).to.contain('GET');
|
|
158
|
-
// the request path
|
|
159
|
-
expect(msg).to.contain('/api');
|
|
160
|
-
// the error name & message for all errors
|
|
161
|
-
expect(msg).to.contain('TypeError: ERR1');
|
|
162
|
-
expect(msg).to.contain('Error: ERR2');
|
|
163
|
-
// verify that stacks are included too
|
|
164
|
-
expect(msg).to.contain(__filename);
|
|
165
|
-
|
|
166
|
-
done();
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('handles non-Error argument', function(done) {
|
|
171
|
-
givenErrorHandlerForError('STRING ERROR', {log: true});
|
|
172
|
-
request.get('/').end(function(err) {
|
|
173
|
-
if (err) return done(err);
|
|
174
|
-
const msg = logs[0];
|
|
175
|
-
expect(msg).to.contain('STRING ERROR');
|
|
176
|
-
done();
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const _consoleError = console.error;
|
|
181
|
-
function redirectConsoleError() {
|
|
182
|
-
logs = [];
|
|
183
|
-
console.error = function() {
|
|
184
|
-
const msg = util.format.apply(util, arguments);
|
|
185
|
-
logs.push(msg);
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function restoreConsoleError() {
|
|
190
|
-
console.error = _consoleError;
|
|
191
|
-
logs = [];
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
context('JSON response', function() {
|
|
196
|
-
it('contains all error properties when debug=true', function(done) {
|
|
197
|
-
const error = new ErrorWithProps({
|
|
198
|
-
message: 'a test error message',
|
|
199
|
-
code: 'MACHINE_READABLE_CODE',
|
|
200
|
-
details: 'some details',
|
|
201
|
-
extra: 'sensitive data',
|
|
202
|
-
});
|
|
203
|
-
givenErrorHandlerForError(error, {debug: true});
|
|
204
|
-
|
|
205
|
-
requestJson().end(function(err, res) {
|
|
206
|
-
if (err) return done(err);
|
|
207
|
-
|
|
208
|
-
const expectedData = {
|
|
209
|
-
statusCode: 500,
|
|
210
|
-
message: 'a test error message',
|
|
211
|
-
name: 'ErrorWithProps',
|
|
212
|
-
code: 'MACHINE_READABLE_CODE',
|
|
213
|
-
details: 'some details',
|
|
214
|
-
extra: 'sensitive data',
|
|
215
|
-
stack: error.stack,
|
|
216
|
-
};
|
|
217
|
-
expect(res.body).to.have.property('error');
|
|
218
|
-
expect(res.body.error).to.eql(expectedData);
|
|
219
|
-
done();
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('includes code property for 4xx status codes when debug=false',
|
|
224
|
-
function(done) {
|
|
225
|
-
const error = new ErrorWithProps({
|
|
226
|
-
statusCode: 400,
|
|
227
|
-
message: 'error with code',
|
|
228
|
-
name: 'ErrorWithCode',
|
|
229
|
-
code: 'MACHINE_READABLE_CODE',
|
|
230
|
-
});
|
|
231
|
-
givenErrorHandlerForError(error, {debug: false});
|
|
232
|
-
|
|
233
|
-
requestJson().end(function(err, res) {
|
|
234
|
-
if (err) return done(err);
|
|
235
|
-
|
|
236
|
-
const expectedData = {
|
|
237
|
-
statusCode: 400,
|
|
238
|
-
message: 'error with code',
|
|
239
|
-
name: 'ErrorWithCode',
|
|
240
|
-
code: 'MACHINE_READABLE_CODE',
|
|
241
|
-
};
|
|
242
|
-
expect(res.body).to.have.property('error');
|
|
243
|
-
expect(res.body.error).to.eql(expectedData);
|
|
244
|
-
done();
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('excludes code property for 5xx status codes when debug=false',
|
|
249
|
-
function(done) {
|
|
250
|
-
const error = new ErrorWithProps({
|
|
251
|
-
statusCode: 500,
|
|
252
|
-
code: 'MACHINE_READABLE_CODE',
|
|
253
|
-
});
|
|
254
|
-
givenErrorHandlerForError(error, {debug: false});
|
|
255
|
-
|
|
256
|
-
requestJson().end(function(err, res) {
|
|
257
|
-
if (err) return done(err);
|
|
258
|
-
|
|
259
|
-
const expectedData = {
|
|
260
|
-
statusCode: 500,
|
|
261
|
-
message: 'Internal Server Error',
|
|
262
|
-
};
|
|
263
|
-
expect(res.body).to.have.property('error');
|
|
264
|
-
expect(res.body.error).to.eql(expectedData);
|
|
265
|
-
done();
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('contains non-enumerable Error properties when debug=true',
|
|
270
|
-
function(done) {
|
|
271
|
-
const error = new Error('a test error message');
|
|
272
|
-
givenErrorHandlerForError(error, {debug: true});
|
|
273
|
-
requestJson().end(function(err, res) {
|
|
274
|
-
if (err) return done(err);
|
|
275
|
-
expect(res.body).to.have.property('error');
|
|
276
|
-
const resError = res.body.error;
|
|
277
|
-
expect(resError).to.have.property('name', 'Error');
|
|
278
|
-
expect(resError).to.have.property('message',
|
|
279
|
-
'a test error message');
|
|
280
|
-
expect(resError).to.have.property('stack', error.stack);
|
|
281
|
-
done();
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('should allow setting safe fields when status=5xx', function(done) {
|
|
286
|
-
const error = new ErrorWithProps({
|
|
287
|
-
name: 'Error',
|
|
288
|
-
safeField: 'SAFE',
|
|
289
|
-
unsafeField: 'UNSAFE',
|
|
290
|
-
});
|
|
291
|
-
givenErrorHandlerForError(error, {
|
|
292
|
-
safeFields: ['safeField'],
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
requestJson().end(function(err, res) {
|
|
296
|
-
if (err) return done(err);
|
|
297
|
-
|
|
298
|
-
expect(res.body).to.have.property('error');
|
|
299
|
-
expect(res.body.error).to.have.property('safeField', 'SAFE');
|
|
300
|
-
expect(res.body.error).not.to.have.property('unsafeField');
|
|
301
|
-
|
|
302
|
-
done();
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('safe fields falls back to existing data', function(done) {
|
|
307
|
-
const error = new ErrorWithProps({
|
|
308
|
-
name: 'Error',
|
|
309
|
-
isSafe: false,
|
|
310
|
-
});
|
|
311
|
-
givenErrorHandlerForError(error, {
|
|
312
|
-
safeFields: ['statusCode', 'isSafe'],
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
requestJson().end(function(err, res) {
|
|
316
|
-
if (err) return done(err);
|
|
317
|
-
expect(res.body.error.statusCode).to.equal(500);
|
|
318
|
-
expect(res.body.error.isSafe).to.equal(false);
|
|
319
|
-
|
|
320
|
-
done();
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should allow setting safe fields when status=4xx', function(done) {
|
|
325
|
-
const error = new ErrorWithProps({
|
|
326
|
-
name: 'Error',
|
|
327
|
-
statusCode: 422,
|
|
328
|
-
safeField: 'SAFE',
|
|
329
|
-
unsafeField: 'UNSAFE',
|
|
330
|
-
});
|
|
331
|
-
givenErrorHandlerForError(error, {
|
|
332
|
-
safeFields: ['safeField'],
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
requestJson().end(function(err, res) {
|
|
336
|
-
if (err) return done(err);
|
|
337
|
-
|
|
338
|
-
expect(res.body).to.have.property('error');
|
|
339
|
-
expect(res.body.error).to.have.property('safeField', 'SAFE');
|
|
340
|
-
expect(res.body.error).not.to.have.property('unsafeField');
|
|
341
|
-
|
|
342
|
-
done();
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('contains subset of properties when status=4xx', function(done) {
|
|
347
|
-
const error = new ErrorWithProps({
|
|
348
|
-
name: 'ValidationError',
|
|
349
|
-
message: 'The model instance is not valid.',
|
|
350
|
-
statusCode: 422,
|
|
351
|
-
details: 'some details',
|
|
352
|
-
extra: 'sensitive data',
|
|
353
|
-
});
|
|
354
|
-
givenErrorHandlerForError(error);
|
|
355
|
-
|
|
356
|
-
requestJson().end(function(err, res) {
|
|
357
|
-
if (err) return done(err);
|
|
358
|
-
|
|
359
|
-
expect(res.body).to.have.property('error');
|
|
360
|
-
expect(res.body.error).to.eql({
|
|
361
|
-
name: 'ValidationError',
|
|
362
|
-
message: 'The model instance is not valid.',
|
|
363
|
-
statusCode: 422,
|
|
364
|
-
details: 'some details',
|
|
365
|
-
// notice the property "extra" is not included
|
|
366
|
-
});
|
|
367
|
-
done();
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('contains only safe info when status=5xx', function(done) {
|
|
372
|
-
// Mock an error reported by fs.readFile
|
|
373
|
-
const error = new ErrorWithProps({
|
|
374
|
-
name: 'Error',
|
|
375
|
-
message: 'ENOENT: no such file or directory, open "/etc/passwd"',
|
|
376
|
-
errno: -2,
|
|
377
|
-
code: 'ENOENT',
|
|
378
|
-
syscall: 'open',
|
|
379
|
-
path: '/etc/password',
|
|
380
|
-
});
|
|
381
|
-
givenErrorHandlerForError(error);
|
|
382
|
-
|
|
383
|
-
requestJson().end(function(err, res) {
|
|
384
|
-
if (err) return done(err);
|
|
385
|
-
|
|
386
|
-
expect(res.body).to.have.property('error');
|
|
387
|
-
expect(res.body.error).to.eql({
|
|
388
|
-
statusCode: 500,
|
|
389
|
-
message: 'Internal Server Error',
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
done();
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('handles array argument as 500 when debug=false', function(done) {
|
|
397
|
-
const errors = [new Error('ERR1'), new Error('ERR2'), 'ERR STRING'];
|
|
398
|
-
givenErrorHandlerForError(errors);
|
|
399
|
-
|
|
400
|
-
requestJson().expect(500).end(function(err, res) {
|
|
401
|
-
if (err) return done(err);
|
|
402
|
-
const data = res.body.error;
|
|
403
|
-
expect(data).to.have.property('message').that.match(/multiple errors/);
|
|
404
|
-
expect(data).to.have.property('details').eql([
|
|
405
|
-
{statusCode: 500, message: 'Internal Server Error'},
|
|
406
|
-
{statusCode: 500, message: 'Internal Server Error'},
|
|
407
|
-
{statusCode: 500, message: 'Internal Server Error'},
|
|
408
|
-
]);
|
|
409
|
-
done();
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it('returns all array items when debug=true', function(done) {
|
|
414
|
-
const testError = new ErrorWithProps({
|
|
415
|
-
message: 'expected test error',
|
|
416
|
-
statusCode: 400,
|
|
417
|
-
});
|
|
418
|
-
const anotherError = new ErrorWithProps({
|
|
419
|
-
message: 'another expected error',
|
|
420
|
-
statusCode: 500,
|
|
421
|
-
});
|
|
422
|
-
const errors = [testError, anotherError, 'ERR STRING'];
|
|
423
|
-
givenErrorHandlerForError(errors, {debug: true});
|
|
424
|
-
|
|
425
|
-
requestJson().expect(500).end(function(err, res) {
|
|
426
|
-
if (err) return done(err);
|
|
427
|
-
|
|
428
|
-
const data = res.body.error;
|
|
429
|
-
expect(data).to.have.property('message').that.match(/multiple errors/);
|
|
430
|
-
|
|
431
|
-
const expectedDetails = [
|
|
432
|
-
getExpectedErrorData(testError),
|
|
433
|
-
getExpectedErrorData(anotherError),
|
|
434
|
-
{message: 'ERR STRING', statusCode: 500},
|
|
435
|
-
];
|
|
436
|
-
expect(data).to.have.property('details').to.eql(expectedDetails);
|
|
437
|
-
done();
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
it('includes safeFields of array items when debug=false', (done) => {
|
|
442
|
-
const internalError = new ErrorWithProps({
|
|
443
|
-
message: 'a test error message',
|
|
444
|
-
code: 'MACHINE_READABLE_CODE',
|
|
445
|
-
details: 'some details',
|
|
446
|
-
extra: 'sensitive data',
|
|
447
|
-
});
|
|
448
|
-
const validationError = new ErrorWithProps({
|
|
449
|
-
name: 'ValidationError',
|
|
450
|
-
message: 'The model instance is not valid.',
|
|
451
|
-
statusCode: 422,
|
|
452
|
-
code: 'VALIDATION_ERROR',
|
|
453
|
-
details: 'some details',
|
|
454
|
-
extra: 'sensitive data',
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
const errors = [internalError, validationError, 'ERR STRING'];
|
|
458
|
-
givenErrorHandlerForError(errors, {
|
|
459
|
-
debug: false,
|
|
460
|
-
safeFields: ['code'],
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
requestJson().end(function(err, res) {
|
|
464
|
-
if (err) return done(err);
|
|
465
|
-
const data = res.body.error;
|
|
466
|
-
|
|
467
|
-
const expectedInternalError = {
|
|
468
|
-
statusCode: 500,
|
|
469
|
-
message: 'Internal Server Error',
|
|
470
|
-
code: 'MACHINE_READABLE_CODE',
|
|
471
|
-
// notice the property "extra" is not included
|
|
472
|
-
};
|
|
473
|
-
const expectedValidationError = {
|
|
474
|
-
statusCode: 422,
|
|
475
|
-
message: 'The model instance is not valid.',
|
|
476
|
-
name: 'ValidationError',
|
|
477
|
-
code: 'VALIDATION_ERROR',
|
|
478
|
-
details: 'some details',
|
|
479
|
-
// notice the property "extra" is not included
|
|
480
|
-
};
|
|
481
|
-
const expectedErrorFromString = {
|
|
482
|
-
message: 'Internal Server Error',
|
|
483
|
-
statusCode: 500,
|
|
484
|
-
};
|
|
485
|
-
const expectedDetails = [
|
|
486
|
-
expectedInternalError,
|
|
487
|
-
expectedValidationError,
|
|
488
|
-
expectedErrorFromString,
|
|
489
|
-
];
|
|
490
|
-
|
|
491
|
-
expect(data).to.have.property('message').that.match(/multiple errors/);
|
|
492
|
-
expect(data).to.have.property('details').to.eql(expectedDetails);
|
|
493
|
-
done();
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
it('handles non-Error argument as 500 when debug=false', function(done) {
|
|
498
|
-
givenErrorHandlerForError('Error Message', {debug: false});
|
|
499
|
-
requestJson().expect(500).end(function(err, res) {
|
|
500
|
-
if (err) return done(err);
|
|
501
|
-
|
|
502
|
-
expect(res.body.error).to.eql({
|
|
503
|
-
statusCode: 500,
|
|
504
|
-
message: 'Internal Server Error',
|
|
505
|
-
});
|
|
506
|
-
done();
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
it('returns non-Error argument in message when debug=true', function(done) {
|
|
511
|
-
givenErrorHandlerForError('Error Message', {debug: true});
|
|
512
|
-
requestJson().expect(500).end(function(err, res) {
|
|
513
|
-
if (err) return done(err);
|
|
514
|
-
|
|
515
|
-
expect(res.body.error).to.eql({
|
|
516
|
-
statusCode: 500,
|
|
517
|
-
message: 'Error Message',
|
|
518
|
-
});
|
|
519
|
-
done();
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
it('handles Error objects containing circular properties', function(done) {
|
|
524
|
-
const circularObject = {};
|
|
525
|
-
circularObject.recursiveProp = circularObject;
|
|
526
|
-
const error = new ErrorWithProps({
|
|
527
|
-
statusCode: 422,
|
|
528
|
-
message: 'The model instance is not valid.',
|
|
529
|
-
name: 'ValidationError',
|
|
530
|
-
code: 'VALIDATION_ERROR',
|
|
531
|
-
details: circularObject,
|
|
532
|
-
});
|
|
533
|
-
givenErrorHandlerForError(error, {debug: true});
|
|
534
|
-
requestJson().end(function(err, res) {
|
|
535
|
-
if (err) return done(err);
|
|
536
|
-
expect(res.body).to.have.property('error');
|
|
537
|
-
expect(res.body.error).to.have.property('details');
|
|
538
|
-
expect(res.body.error.details).to.have.property('recursiveProp',
|
|
539
|
-
'[Circular]');
|
|
540
|
-
done();
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
function requestJson(url) {
|
|
545
|
-
return request.get(url || '/')
|
|
546
|
-
.set('Accept', 'text/plain')
|
|
547
|
-
.expect('Content-Type', /^application\/json/);
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
context('HTML response', function() {
|
|
552
|
-
it('contains all error properties when debug=true', function(done) {
|
|
553
|
-
const error = new ErrorWithProps({
|
|
554
|
-
message: 'a test error message',
|
|
555
|
-
details: 'some details',
|
|
556
|
-
extra: 'sensitive data',
|
|
557
|
-
});
|
|
558
|
-
error.statusCode = 500;
|
|
559
|
-
givenErrorHandlerForError(error, {debug: true});
|
|
560
|
-
requestHTML()
|
|
561
|
-
.expect(500)
|
|
562
|
-
.expect(/<title>ErrorWithProps<\/title>/)
|
|
563
|
-
.expect(/500(.*?)a test error message/)
|
|
564
|
-
.expect(/extra(.*?)sensitive data/)
|
|
565
|
-
.expect(/details(.*?)some details/)
|
|
566
|
-
.expect(/id="stacktrace"(.*?)ErrorWithProps: a test error message/,
|
|
567
|
-
done);
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
it('HTML-escapes all 4xx response properties in production mode',
|
|
571
|
-
function(done) {
|
|
572
|
-
const error = new ErrorWithProps({
|
|
573
|
-
name: 'Error<img onerror=alert(1) src=a>',
|
|
574
|
-
message:
|
|
575
|
-
'No instance with id <img onerror=alert(1) src=a> found for Model',
|
|
576
|
-
statusCode: 404,
|
|
577
|
-
});
|
|
578
|
-
givenErrorHandlerForError(error, {debug: false});
|
|
579
|
-
requestHTML()
|
|
580
|
-
.end(function(err, res) {
|
|
581
|
-
expect(res.statusCode).to.eql(404);
|
|
582
|
-
const body = res.error.text;
|
|
583
|
-
expect(body).to.match(
|
|
584
|
-
/<title>Error<img onerror=alert\(1\) src=a><\/title>/
|
|
585
|
-
);
|
|
586
|
-
expect(body).to.match(
|
|
587
|
-
/with id <img onerror=alert\(1\) src=a> found for Model/
|
|
588
|
-
);
|
|
589
|
-
done();
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
it('HTML-escapes all 5xx response properties in development mode',
|
|
594
|
-
function(done) {
|
|
595
|
-
const error = new ErrorWithProps({
|
|
596
|
-
message: 'a test error message<img onerror=alert(1) src=a>',
|
|
597
|
-
});
|
|
598
|
-
error.statusCode = 500;
|
|
599
|
-
givenErrorHandlerForError(error, {debug: true});
|
|
600
|
-
requestHTML()
|
|
601
|
-
.expect(500)
|
|
602
|
-
.expect(/<title>ErrorWithProps<\/title>/)
|
|
603
|
-
.expect(
|
|
604
|
-
/500(.*?)a test error message<img onerror=alert\(1\) src=a>/,
|
|
605
|
-
done
|
|
606
|
-
);
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
it('contains subset of properties when status=4xx', function(done) {
|
|
610
|
-
const error = new ErrorWithProps({
|
|
611
|
-
name: 'ValidationError',
|
|
612
|
-
message: 'The model instance is not valid.',
|
|
613
|
-
statusCode: 422,
|
|
614
|
-
details: 'some details',
|
|
615
|
-
extra: 'sensitive data',
|
|
616
|
-
});
|
|
617
|
-
givenErrorHandlerForError(error, {debug: false});
|
|
618
|
-
requestHTML()
|
|
619
|
-
.end(function(err, res) {
|
|
620
|
-
expect(res.statusCode).to.eql(422);
|
|
621
|
-
const body = res.error.text;
|
|
622
|
-
expect(body).to.match(/some details/);
|
|
623
|
-
expect(body).to.not.match(/sensitive data/);
|
|
624
|
-
expect(body).to.match(/<title>ValidationError<\/title>/);
|
|
625
|
-
expect(body).to.match(/422(.*?)The model instance is not valid./);
|
|
626
|
-
done();
|
|
627
|
-
});
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
it('contains only safe info when status=5xx', function(done) {
|
|
631
|
-
// Mock an error reported by fs.readFile
|
|
632
|
-
const error = new ErrorWithProps({
|
|
633
|
-
name: 'Error',
|
|
634
|
-
message: 'ENOENT: no such file or directory, open "/etc/passwd"',
|
|
635
|
-
errno: -2,
|
|
636
|
-
code: 'ENOENT',
|
|
637
|
-
syscall: 'open',
|
|
638
|
-
path: '/etc/password',
|
|
639
|
-
});
|
|
640
|
-
givenErrorHandlerForError(error);
|
|
641
|
-
|
|
642
|
-
requestHTML()
|
|
643
|
-
.end(function(err, res) {
|
|
644
|
-
expect(res.statusCode).to.eql(500);
|
|
645
|
-
const body = res.error.text;
|
|
646
|
-
expect(body).to.not.match(/\/etc\/password/);
|
|
647
|
-
expect(body).to.not.match(/-2/);
|
|
648
|
-
expect(body).to.not.match(/ENOENT/);
|
|
649
|
-
// only have the following
|
|
650
|
-
expect(body).to.match(/<title>Internal Server Error<\/title>/);
|
|
651
|
-
expect(body).to.match(/500(.*?)Internal Server Error/);
|
|
652
|
-
done();
|
|
653
|
-
});
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
function requestHTML(url) {
|
|
657
|
-
return request.get(url || '/')
|
|
658
|
-
.set('Accept', 'text/html')
|
|
659
|
-
.expect('Content-Type', /^text\/html/);
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
context('XML response', function() {
|
|
664
|
-
it('contains all error properties when debug=true', function(done) {
|
|
665
|
-
const error = new ErrorWithProps({
|
|
666
|
-
message: 'a test error message',
|
|
667
|
-
details: 'some details',
|
|
668
|
-
extra: 'sensitive data',
|
|
669
|
-
});
|
|
670
|
-
error.statusCode = 500;
|
|
671
|
-
givenErrorHandlerForError(error, {debug: true});
|
|
672
|
-
requestXML()
|
|
673
|
-
.expect(500)
|
|
674
|
-
.expect(/<statusCode>500<\/statusCode>/)
|
|
675
|
-
.expect(/<name>ErrorWithProps<\/name>/)
|
|
676
|
-
.expect(/<message>a test error message<\/message>/)
|
|
677
|
-
.expect(/<details>some details<\/details>/)
|
|
678
|
-
.expect(/<extra>sensitive data<\/extra>/)
|
|
679
|
-
.expect(/<stack>ErrorWithProps: a test error message(.*?)/, done);
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
it('contains subset of properties when status=4xx', function(done) {
|
|
683
|
-
const error = new ErrorWithProps({
|
|
684
|
-
name: 'ValidationError',
|
|
685
|
-
message: 'The model instance is not valid.',
|
|
686
|
-
statusCode: 422,
|
|
687
|
-
details: 'some details',
|
|
688
|
-
extra: 'sensitive data',
|
|
689
|
-
});
|
|
690
|
-
givenErrorHandlerForError(error, {debug: false});
|
|
691
|
-
requestXML()
|
|
692
|
-
.end(function(err, res) {
|
|
693
|
-
expect(res.statusCode).to.eql(422);
|
|
694
|
-
const body = res.error.text;
|
|
695
|
-
expect(body).to.match(/<details>some details<\/details>/);
|
|
696
|
-
expect(body).to.not.match(/<extra>sensitive data<\/extra>/);
|
|
697
|
-
expect(body).to.match(/<name>ValidationError<\/name>/);
|
|
698
|
-
expect(body).to.match(
|
|
699
|
-
/<message>The model instance is not valid.<\/message>/
|
|
700
|
-
);
|
|
701
|
-
done();
|
|
702
|
-
});
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
it('contains only safe info when status=5xx', function(done) {
|
|
706
|
-
// Mock an error reported by fs.readFile
|
|
707
|
-
const error = new ErrorWithProps({
|
|
708
|
-
name: 'Error',
|
|
709
|
-
message: 'ENOENT: no such file or directory, open "/etc/passwd"',
|
|
710
|
-
errno: -2,
|
|
711
|
-
code: 'ENOENT',
|
|
712
|
-
syscall: 'open',
|
|
713
|
-
path: '/etc/password',
|
|
714
|
-
});
|
|
715
|
-
givenErrorHandlerForError(error);
|
|
716
|
-
|
|
717
|
-
requestXML()
|
|
718
|
-
.end(function(err, res) {
|
|
719
|
-
expect(res.statusCode).to.eql(500);
|
|
720
|
-
const body = res.error.text;
|
|
721
|
-
expect(body).to.not.match(/\/etc\/password/);
|
|
722
|
-
expect(body).to.not.match(/-2/);
|
|
723
|
-
expect(body).to.not.match(/ENOENT/);
|
|
724
|
-
// only have the following
|
|
725
|
-
expect(body).to.match(/<statusCode>500<\/statusCode>/);
|
|
726
|
-
expect(body).to.match(/<message>Internal Server Error<\/message>/);
|
|
727
|
-
done();
|
|
728
|
-
});
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
function requestXML(url) {
|
|
732
|
-
return request.get(url || '/')
|
|
733
|
-
.set('Accept', 'text/xml')
|
|
734
|
-
.expect('Content-Type', /^text\/xml/);
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
context('Content Negotiation', function() {
|
|
739
|
-
it('defaults to json without options', function(done) {
|
|
740
|
-
givenErrorHandlerForError(new Error('Some error'), {});
|
|
741
|
-
request.get('/')
|
|
742
|
-
.set('Accept', '*/*')
|
|
743
|
-
.expect('Content-Type', /^application\/json/, done);
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
it('honors accepted content-type', function(done) {
|
|
747
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
748
|
-
defaultType: 'application/json',
|
|
749
|
-
});
|
|
750
|
-
request.get('/')
|
|
751
|
-
.set('Accept', 'text/html')
|
|
752
|
-
.expect('Content-Type', /^text\/html/, done);
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
it('honors order of accepted content-type', function(done) {
|
|
756
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
757
|
-
defaultType: 'text/html',
|
|
758
|
-
});
|
|
759
|
-
request.get('/')
|
|
760
|
-
// `application/json` will be used because its provided first
|
|
761
|
-
.set('Accept', 'application/json, text/html')
|
|
762
|
-
.expect('Content-Type', /^application\/json/, done);
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
it('disables content-type negotiation when negotiateContentType=false',
|
|
766
|
-
function(done) {
|
|
767
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
768
|
-
negotiateContentType: false,
|
|
769
|
-
defaultType: 'application/json',
|
|
770
|
-
});
|
|
771
|
-
request.get('/')
|
|
772
|
-
.set('Accept', 'text/html')
|
|
773
|
-
.expect('Content-Type', /^application\/json/, done);
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
it('chooses resolved type when negotiateContentType=false + not-supported',
|
|
777
|
-
function(done) {
|
|
778
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
779
|
-
negotiateContentType: false,
|
|
780
|
-
defaultType: 'unsupported/type',
|
|
781
|
-
});
|
|
782
|
-
request.get('/')
|
|
783
|
-
.set('Accept', 'text/html')
|
|
784
|
-
.expect('Content-Type', /^text\/html/, done);
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
it('chooses default type when negotiateContentType=false + not-supported ',
|
|
788
|
-
function(done) {
|
|
789
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
790
|
-
negotiateContentType: false,
|
|
791
|
-
defaultType: 'unsupported/type',
|
|
792
|
-
});
|
|
793
|
-
request.get('/')
|
|
794
|
-
.expect('Content-Type', /^application\/json/, done);
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
it('honors order of accepted content-types of text/html', function(done) {
|
|
798
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
799
|
-
defaultType: 'application/json',
|
|
800
|
-
});
|
|
801
|
-
request.get('/')
|
|
802
|
-
// text/html will be used because its provided first
|
|
803
|
-
.set('Accept', 'text/html, application/json')
|
|
804
|
-
.expect('Content-Type', /^text\/html/, done);
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
it('picks first supported type upon multiple accepted', function(done) {
|
|
808
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
809
|
-
defaultType: 'application/json',
|
|
810
|
-
});
|
|
811
|
-
request.get('/')
|
|
812
|
-
.set('Accept', '*/*, not-supported, text/html, application/json')
|
|
813
|
-
.expect('Content-Type', /^text\/html/, done);
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
it('falls back for unsupported option.defaultType', function(done) {
|
|
817
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
818
|
-
defaultType: 'unsupported',
|
|
819
|
-
});
|
|
820
|
-
request.get('/')
|
|
821
|
-
.set('Accept', '*/*')
|
|
822
|
-
.expect('Content-Type', /^application\/json/, done);
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
it('returns defaultType for unsupported type', function(done) {
|
|
826
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
827
|
-
defaultType: 'text/html',
|
|
828
|
-
});
|
|
829
|
-
request.get('/')
|
|
830
|
-
.set('Accept', 'unsupported/type')
|
|
831
|
-
.expect('Content-Type', /^text\/html/, done);
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
it('supports query _format', function(done) {
|
|
835
|
-
givenErrorHandlerForError(new Error('Some error'), {
|
|
836
|
-
defaultType: 'text/html',
|
|
837
|
-
});
|
|
838
|
-
request.get('/?_format=html')
|
|
839
|
-
.set('Accept', 'application/json')
|
|
840
|
-
.expect('Content-Type', /^text\/html/, done);
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
it('handles unknown _format query', function() {
|
|
844
|
-
givenErrorHandlerForError();
|
|
845
|
-
return request.get('/?_format=unknown')
|
|
846
|
-
.expect('X-Warning', /_format.*not supported/);
|
|
847
|
-
});
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
it('does not modify "options" argument', function(done) {
|
|
851
|
-
const options = {log: false, debug: false};
|
|
852
|
-
givenErrorHandlerForError(new Error(), options);
|
|
853
|
-
request.get('/').end(function(err) {
|
|
854
|
-
if (err) return done(err);
|
|
855
|
-
expect(options).to.eql({log: false, debug: false});
|
|
856
|
-
done();
|
|
857
|
-
});
|
|
858
|
-
});
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
let app, _requestHandler, request, server;
|
|
862
|
-
function resetRequestHandler() {
|
|
863
|
-
_requestHandler = null;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
function givenErrorHandlerForError(error, options) {
|
|
867
|
-
if (!error) error = new Error('an error');
|
|
868
|
-
|
|
869
|
-
if (!options) options = {};
|
|
870
|
-
if (!('log' in options)) {
|
|
871
|
-
// Disable logging to console by default, so that we don't spam
|
|
872
|
-
// console output. One can use "DEBUG=strong-error-handler" when
|
|
873
|
-
// troubleshooting.
|
|
874
|
-
options.log = false;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const handler = strongErrorHandler(options);
|
|
878
|
-
_requestHandler = function(req, res, next) {
|
|
879
|
-
debug('Invoking strong-error-handler');
|
|
880
|
-
handler(error, req, res, next);
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
function setupHttpServerAndClient(done) {
|
|
885
|
-
app = express();
|
|
886
|
-
app.use(function(req, res, next) {
|
|
887
|
-
if (!_requestHandler) {
|
|
888
|
-
const msg = 'Error handler middleware was not setup in this test';
|
|
889
|
-
console.error(msg);
|
|
890
|
-
res.statusCode = 500;
|
|
891
|
-
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
892
|
-
res.end(msg);
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
_requestHandler(req, res, warnUnhandledError);
|
|
897
|
-
|
|
898
|
-
function warnUnhandledError(err) {
|
|
899
|
-
console.log('unexpected: strong-error-handler called next with',
|
|
900
|
-
(err && (err.stack || err)) || 'no error');
|
|
901
|
-
res.statusCode = 500;
|
|
902
|
-
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
903
|
-
res.end(err ?
|
|
904
|
-
'Unhandled strong-error-handler error:\n' + (err.stack || err) :
|
|
905
|
-
'The error was silently discared by strong-error-handler');
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
server = app.listen(0, function() {
|
|
910
|
-
const url = 'http://127.0.0.1:' + this.address().port;
|
|
911
|
-
debug('Test server listening on %s', url);
|
|
912
|
-
request = supertest(app);
|
|
913
|
-
done();
|
|
914
|
-
})
|
|
915
|
-
.once('error', function(err) {
|
|
916
|
-
debug('Cannot setup HTTP server: %s', err.stack);
|
|
917
|
-
done(err);
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
function stopHttpServerAndClient() {
|
|
922
|
-
server.close();
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
function ErrorWithProps(props) {
|
|
926
|
-
this.name = props.name || 'ErrorWithProps';
|
|
927
|
-
for (const p in props) {
|
|
928
|
-
this[p] = props[p];
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (Error.captureStackTrace) {
|
|
932
|
-
// V8 (Chrome, Opera, Node)
|
|
933
|
-
Error.captureStackTrace(this, this.constructor);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
util.inherits(ErrorWithProps, Error);
|
|
937
|
-
|
|
938
|
-
function getExpectedErrorData(err) {
|
|
939
|
-
const data = {};
|
|
940
|
-
cloneAllProperties(data, err);
|
|
941
|
-
return data;
|
|
942
|
-
}
|