nodeunit-api-client 1.4.0 → 1.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/package.json +1 -1
- package/src/httpclient.js +178 -146
package/package.json
CHANGED
package/src/httpclient.js
CHANGED
|
@@ -1,161 +1,181 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
var http = require('http'),
|
|
4
|
+
https = require('https'),
|
|
5
|
+
querystring = require('querystring'),
|
|
6
|
+
underscore = require('underscore');
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* @param {object} Options:
|
|
10
|
+
* https (false) - Set to true for HTTPS calls
|
|
8
11
|
* auth ('username:password')
|
|
9
12
|
* host ('localhost')
|
|
10
|
-
* port (80)
|
|
13
|
+
* port (80 for http, 443 for https)
|
|
11
14
|
* path ('') - Base path URL e.g. '/api'
|
|
12
|
-
* headers ({}) -
|
|
13
|
-
* status (null) -
|
|
14
|
-
*
|
|
15
|
+
* headers ({}) - Default headers for all requests
|
|
16
|
+
* status (null) - Expected status code for all responses
|
|
17
|
+
* timeout (0) - Request timeout in milliseconds (0 = no timeout)
|
|
15
18
|
* @param options
|
|
16
19
|
*/
|
|
17
20
|
var HttpClient = module.exports = function(options) {
|
|
18
21
|
options = options || {};
|
|
19
22
|
|
|
23
|
+
this.https = options.https || false;
|
|
20
24
|
this.auth = options.auth || undefined;
|
|
21
25
|
this.host = options.host || 'localhost';
|
|
22
|
-
this.port = options.port || (
|
|
26
|
+
this.port = options.port || (this.https ? 443 : 80);
|
|
23
27
|
this.path = options.path || '';
|
|
24
28
|
this.headers = options.headers || {};
|
|
25
29
|
this.status = options.status;
|
|
26
|
-
this.
|
|
27
|
-
this.http = require(this.https ? 'https' : 'http');
|
|
28
|
-
debug = options.debug ? true : false;
|
|
30
|
+
this.timeout = options.timeout || 0;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
HttpClient.create = function(options) {
|
|
32
34
|
return new HttpClient(options);
|
|
33
35
|
};
|
|
34
36
|
|
|
35
|
-
var methods = ['get', 'post', 'head', 'put', 'del', 'trace', 'options', 'connect'];
|
|
37
|
+
var methods = ['get', 'post', 'head', 'put', 'del', 'trace', 'options', 'connect', 'patch'];
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
* Performs
|
|
40
|
+
* Performs HTTP/HTTPS request
|
|
39
41
|
*
|
|
40
|
-
* @param {Assert
|
|
41
|
-
*
|
|
42
|
-
* @param {
|
|
43
|
-
*
|
|
44
|
-
* @param {
|
|
45
|
-
* Object containing request related attributes like headers or body.
|
|
46
|
-
* @param {object} [res=undefined]
|
|
47
|
-
* Object to compare with the response of the http call
|
|
48
|
-
* @param {Function} [cb=undefined]
|
|
49
|
-
* Callback that will be called after the http call. Receives the http response object.
|
|
42
|
+
* @param {object|null} assert - Assert object for testing (can be null)
|
|
43
|
+
* @param {string} path - Request path
|
|
44
|
+
* @param {object} req - Request options (headers, data, auth, etc.)
|
|
45
|
+
* @param {object} res - Expected response options (status, headers, body, etc.)
|
|
46
|
+
* @param {Function} cb - Callback function
|
|
50
47
|
*/
|
|
51
48
|
methods.forEach(function(method) {
|
|
52
49
|
HttpClient.prototype[method] = function(assert, path, req, res, cb) {
|
|
53
50
|
var self = this;
|
|
54
51
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
// Initialize default values
|
|
53
|
+
req = req || {};
|
|
54
|
+
res = res || {};
|
|
55
|
+
|
|
56
|
+
// Handle different signatures
|
|
57
|
+
if (arguments.length === 2) {
|
|
58
|
+
// (assert, path) or (null, path)
|
|
59
|
+
cb = null;
|
|
60
|
+
} else if (arguments.length === 3) {
|
|
58
61
|
if (typeof req === 'function') {
|
|
62
|
+
// (assert, path, cb)
|
|
59
63
|
cb = req;
|
|
60
64
|
req = {};
|
|
61
65
|
res = {};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
else {
|
|
66
|
+
} else {
|
|
67
|
+
// (assert, path, res)
|
|
65
68
|
cb = null;
|
|
66
69
|
res = req;
|
|
67
70
|
req = {};
|
|
68
71
|
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (arguments.length == 4) {
|
|
73
|
-
if (typeof res == 'function') {
|
|
72
|
+
} else if (arguments.length === 4) {
|
|
73
|
+
if (typeof res === 'function') {
|
|
74
|
+
// (assert, path, req, cb)
|
|
74
75
|
cb = res;
|
|
75
76
|
res = {};
|
|
76
77
|
}
|
|
78
|
+
// else: (assert, path, req, res)
|
|
77
79
|
}
|
|
80
|
+
// arguments.length === 5: (assert, path, req, res, cb)
|
|
78
81
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
//(assert, path, req, res, cb)
|
|
82
|
+
// Generate full path
|
|
83
|
+
var fullPath = this.path + (path || '');
|
|
82
84
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
// Add query parameters for GET-like methods
|
|
86
|
+
if (['post', 'put', 'patch'].indexOf(method) === -1) {
|
|
87
|
+
var queryData = req.query || req.data;
|
|
88
|
+
if (queryData && typeof queryData === 'object') {
|
|
89
|
+
var queryStr = querystring.stringify(queryData);
|
|
90
|
+
if (queryStr) {
|
|
91
|
+
fullPath += (fullPath.indexOf('?') === -1 ? '?' : '&') + queryStr;
|
|
92
|
+
}
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
// Prepare request headers
|
|
97
|
+
var requestHeaders = underscore.extend({}, this.headers, req.headers || {});
|
|
98
|
+
|
|
99
|
+
// Clean undefined headers
|
|
100
|
+
Object.keys(requestHeaders).forEach(function(key) {
|
|
101
|
+
if (requestHeaders[key] === undefined || requestHeaders[key] === null) {
|
|
102
|
+
delete requestHeaders[key];
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
94
106
|
var options = {
|
|
95
107
|
host: this.host,
|
|
96
108
|
port: this.port,
|
|
97
109
|
path: fullPath,
|
|
98
|
-
method: method
|
|
99
|
-
headers:
|
|
100
|
-
rejectUnauthorized: false //
|
|
110
|
+
method: method === 'del' ? 'DELETE' : method.toUpperCase(),
|
|
111
|
+
headers: requestHeaders,
|
|
112
|
+
rejectUnauthorized: false // Disable SSL verification
|
|
101
113
|
};
|
|
102
114
|
|
|
115
|
+
// Set authentication
|
|
103
116
|
if (req.auth) {
|
|
104
117
|
options.auth = req.auth;
|
|
105
118
|
} else if (this.auth) {
|
|
106
119
|
options.auth = this.auth;
|
|
107
120
|
}
|
|
108
121
|
|
|
109
|
-
|
|
122
|
+
// Set timeout
|
|
123
|
+
if (req.timeout || this.timeout) {
|
|
124
|
+
options.timeout = req.timeout || this.timeout;
|
|
125
|
+
}
|
|
110
126
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
// Choose HTTP or HTTPS module
|
|
128
|
+
var requestModule = this.https ? https : http;
|
|
129
|
+
var request = requestModule.request(options);
|
|
114
130
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
// Set request timeout
|
|
132
|
+
if (options.timeout) {
|
|
133
|
+
request.setTimeout(options.timeout, function() {
|
|
134
|
+
request.abort();
|
|
135
|
+
var error = new Error('Request timeout after ' + options.timeout + 'ms');
|
|
136
|
+
error.code = 'TIMEOUT';
|
|
137
|
+
if (cb) {
|
|
138
|
+
return cb(null, error);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Handle request body for POST/PUT/PATCH
|
|
144
|
+
if (['post', 'put', 'patch'].indexOf(method) !== -1) {
|
|
145
|
+
var bodyData = req.body || req.data;
|
|
146
|
+
|
|
147
|
+
if (bodyData) {
|
|
148
|
+
if (typeof bodyData === 'object') {
|
|
149
|
+
// JSON data
|
|
150
|
+
var jsonData = JSON.stringify(bodyData);
|
|
151
|
+
request.setHeader('Content-Type', 'application/json');
|
|
152
|
+
request.setHeader('Content-Length', Buffer.byteLength(jsonData));
|
|
120
153
|
request.write(jsonData);
|
|
121
|
-
} else {
|
|
122
|
-
|
|
123
|
-
request.
|
|
154
|
+
} else if (typeof bodyData === 'string') {
|
|
155
|
+
// String data
|
|
156
|
+
request.setHeader('Content-Length', Buffer.byteLength(bodyData));
|
|
157
|
+
request.write(bodyData);
|
|
158
|
+
} else if (Buffer.isBuffer(bodyData)) {
|
|
159
|
+
// Buffer data
|
|
160
|
+
request.setHeader('Content-Length', bodyData.length);
|
|
161
|
+
request.write(bodyData);
|
|
124
162
|
}
|
|
125
163
|
}
|
|
126
164
|
}
|
|
127
165
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
headers: options.headers
|
|
135
|
-
});
|
|
166
|
+
// Handle form data
|
|
167
|
+
if (req.form && typeof req.form === 'object') {
|
|
168
|
+
var formData = querystring.stringify(req.form);
|
|
169
|
+
request.setHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
170
|
+
request.setHeader('Content-Length', Buffer.byteLength(formData));
|
|
171
|
+
request.write(formData);
|
|
136
172
|
}
|
|
137
173
|
|
|
138
|
-
//Send
|
|
174
|
+
// Send request
|
|
139
175
|
request.end();
|
|
140
176
|
|
|
141
|
-
|
|
142
|
-
console.error('Request error:', err);
|
|
143
|
-
if (cb) {
|
|
144
|
-
return cb(null, err);
|
|
145
|
-
} else if (assert) {
|
|
146
|
-
assert.ok(false, 'Request failed: ' + err.message);
|
|
147
|
-
return assert.done();
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
177
|
+
// Handle response
|
|
151
178
|
request.on('response', function(response) {
|
|
152
|
-
if (debug) {
|
|
153
|
-
httpClientLogger.log('RESPONSE', {
|
|
154
|
-
statusCode: response.statusCode,
|
|
155
|
-
headers: response.headers
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
179
|
response.setEncoding('utf8');
|
|
160
180
|
response.body = '';
|
|
161
181
|
|
|
@@ -163,81 +183,93 @@ methods.forEach(function(method) {
|
|
|
163
183
|
response.body += chunk;
|
|
164
184
|
});
|
|
165
185
|
|
|
166
|
-
//Handle the response; run response tests and hand back control to test
|
|
167
186
|
response.on('end', function() {
|
|
168
|
-
//
|
|
169
|
-
var contentType = response.headers['content-type'];
|
|
170
|
-
if (contentType
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
console.log('JSON.parse response.body error:');
|
|
177
|
-
console.log(err);
|
|
178
|
-
if (debug) {
|
|
179
|
-
httpClientLogger.log('RESPONSE.BODY', response.body);
|
|
180
|
-
}
|
|
181
|
-
var responseTest = response.body.split('{');
|
|
182
|
-
if (responseTest.length > 1) {
|
|
183
|
-
var actualResponse = '{' + responseTest[1];
|
|
184
|
-
try {
|
|
185
|
-
response.data = JSON.parse(actualResponse);
|
|
186
|
-
console.log('JSON.parse second attempt success.');
|
|
187
|
-
} catch (err2) {
|
|
188
|
-
console.log('JSON.parse error on second parse attempt.');
|
|
189
|
-
console.log(err2);
|
|
190
|
-
if (debug) {
|
|
191
|
-
httpClientLogger.log('FILTERED RESPONSE.BODY', actualResponse);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
187
|
+
// Parse JSON if content-type indicates JSON
|
|
188
|
+
var contentType = response.headers['content-type'] || '';
|
|
189
|
+
if (contentType.indexOf('application/json') !== -1) {
|
|
190
|
+
try {
|
|
191
|
+
response.data = JSON.parse(response.body);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
response.data = null;
|
|
194
|
+
response.parseError = e;
|
|
196
195
|
}
|
|
197
196
|
}
|
|
198
197
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
// Add convenience properties
|
|
199
|
+
response.ok = response.statusCode >= 200 && response.statusCode < 300;
|
|
200
|
+
response.clientError = response.statusCode >= 400 && response.statusCode < 500;
|
|
201
|
+
response.serverError = response.statusCode >= 500;
|
|
203
202
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
// Run response tests if assert is provided
|
|
204
|
+
if (assert) {
|
|
205
|
+
try {
|
|
206
|
+
// Test status code
|
|
207
|
+
var expectedStatus = res.status || self.status;
|
|
208
|
+
if (expectedStatus) {
|
|
209
|
+
assert.equal(response.statusCode, expectedStatus,
|
|
210
|
+
'Expected status ' + expectedStatus + ' but got ' + response.statusCode);
|
|
211
|
+
}
|
|
209
212
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
// Test headers
|
|
214
|
+
var expectedHeaders = underscore.extend({}, self.headers, res.headers || {});
|
|
215
|
+
Object.keys(expectedHeaders).forEach(function(key) {
|
|
216
|
+
var expected = expectedHeaders[key];
|
|
217
|
+
var actual = response.headers[key.toLowerCase()];
|
|
218
|
+
if (expected !== undefined) {
|
|
219
|
+
assert.equal(actual, expected,
|
|
220
|
+
'Expected header "' + key + '" to be "' + expected + '" but got "' + actual + '"');
|
|
221
|
+
}
|
|
222
|
+
});
|
|
215
223
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
// Test response body
|
|
225
|
+
if (res.body !== undefined) {
|
|
226
|
+
assert.equal(response.body, res.body, 'Response body mismatch');
|
|
227
|
+
}
|
|
220
228
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
// Test JSON data
|
|
230
|
+
if (res.data !== undefined) {
|
|
231
|
+
assert.deepEqual(response.data, res.data, 'Response data mismatch');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Test response properties
|
|
235
|
+
if (res.ok !== undefined) {
|
|
236
|
+
assert.equal(response.ok, res.ok, 'Response ok status mismatch');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
} catch (assertError) {
|
|
240
|
+
if (cb) {
|
|
241
|
+
return cb(response, assertError);
|
|
242
|
+
} else if (assert.done) {
|
|
243
|
+
return assert.done(assertError);
|
|
244
|
+
}
|
|
245
|
+
throw assertError;
|
|
224
246
|
}
|
|
225
|
-
}
|
|
247
|
+
}
|
|
226
248
|
|
|
227
|
-
//
|
|
249
|
+
// Call callback or assert.done()
|
|
228
250
|
if (cb) {
|
|
229
251
|
return cb(response);
|
|
230
|
-
} else if (assert) {
|
|
252
|
+
} else if (assert && assert.done) {
|
|
231
253
|
return assert.done();
|
|
232
254
|
}
|
|
233
255
|
});
|
|
234
256
|
});
|
|
257
|
+
|
|
258
|
+
// Handle request errors
|
|
259
|
+
request.on('error', function(error) {
|
|
260
|
+
error.request = {
|
|
261
|
+
method: options.method,
|
|
262
|
+
url: (self.https ? 'https' : 'http') + '://' + self.host + ':' + self.port + fullPath,
|
|
263
|
+
headers: options.headers
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
if (cb) {
|
|
267
|
+
return cb(null, error);
|
|
268
|
+
} else if (assert && assert.done) {
|
|
269
|
+
return assert.done(error);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
throw error;
|
|
273
|
+
});
|
|
235
274
|
};
|
|
236
275
|
});
|
|
237
|
-
|
|
238
|
-
var httpClientLogger = {
|
|
239
|
-
log: function(header, data) {
|
|
240
|
-
console.log('=== ' + header + ' ===');
|
|
241
|
-
console.log(JSON.stringify(data, null, 2));
|
|
242
|
-
}
|
|
243
|
-
};
|