nodeunit-api-client 1.3.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 +186 -118
package/package.json
CHANGED
package/src/httpclient.js
CHANGED
|
@@ -1,207 +1,275 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
var querystring = require('querystring'),
|
|
3
|
-
underscore = require('underscore'),
|
|
4
|
-
debug;
|
|
5
2
|
|
|
3
|
+
var http = require('http'),
|
|
4
|
+
https = require('https'),
|
|
5
|
+
querystring = require('querystring'),
|
|
6
|
+
underscore = require('underscore');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {object} Options:
|
|
10
|
+
* https (false) - Set to true for HTTPS calls
|
|
9
11
|
* auth ('username:password')
|
|
10
12
|
* host ('localhost')
|
|
11
|
-
* port (80)
|
|
13
|
+
* port (80 for http, 443 for https)
|
|
12
14
|
* path ('') - Base path URL e.g. '/api'
|
|
13
|
-
* headers ({}) -
|
|
14
|
-
* status (null) -
|
|
15
|
-
*
|
|
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)
|
|
16
18
|
* @param options
|
|
17
19
|
*/
|
|
18
20
|
var HttpClient = module.exports = function(options) {
|
|
19
21
|
options = options || {};
|
|
20
22
|
|
|
23
|
+
this.https = options.https || false;
|
|
21
24
|
this.auth = options.auth || undefined;
|
|
22
25
|
this.host = options.host || 'localhost';
|
|
23
|
-
this.port = options.port || 80;
|
|
26
|
+
this.port = options.port || (this.https ? 443 : 80);
|
|
24
27
|
this.path = options.path || '';
|
|
25
28
|
this.headers = options.headers || {};
|
|
26
29
|
this.status = options.status;
|
|
27
|
-
this.
|
|
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
|
-
//(assert, path, res)
|
|
65
|
-
else {
|
|
66
|
+
} else {
|
|
67
|
+
// (assert, path, res)
|
|
66
68
|
cb = null;
|
|
67
69
|
res = req;
|
|
68
70
|
req = {};
|
|
69
71
|
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (arguments.length == 4) {
|
|
74
|
-
if (typeof res == 'function') {
|
|
72
|
+
} else if (arguments.length === 4) {
|
|
73
|
+
if (typeof res === 'function') {
|
|
74
|
+
// (assert, path, req, cb)
|
|
75
75
|
cb = res;
|
|
76
76
|
res = {};
|
|
77
77
|
}
|
|
78
|
+
// else: (assert, path, req, res)
|
|
78
79
|
}
|
|
80
|
+
// arguments.length === 5: (assert, path, req, res, cb)
|
|
79
81
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
//(assert, path, req, res, cb)
|
|
82
|
+
// Generate full path
|
|
83
|
+
var fullPath = this.path + (path || '');
|
|
83
84
|
|
|
84
|
-
//
|
|
85
|
-
|
|
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
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
86
95
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
var data = req.data;
|
|
96
|
+
// Prepare request headers
|
|
97
|
+
var requestHeaders = underscore.extend({}, this.headers, req.headers || {});
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
});
|
|
93
105
|
|
|
94
106
|
var options = {
|
|
95
107
|
host: this.host,
|
|
96
108
|
port: this.port,
|
|
97
109
|
path: fullPath,
|
|
98
|
-
method: method
|
|
99
|
-
headers:
|
|
110
|
+
method: method === 'del' ? 'DELETE' : method.toUpperCase(),
|
|
111
|
+
headers: requestHeaders,
|
|
112
|
+
rejectUnauthorized: false // Disable SSL verification
|
|
100
113
|
};
|
|
101
114
|
|
|
115
|
+
// Set authentication
|
|
102
116
|
if (req.auth) {
|
|
103
117
|
options.auth = req.auth;
|
|
104
118
|
} else if (this.auth) {
|
|
105
119
|
options.auth = this.auth;
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
|
|
122
|
+
// Set timeout
|
|
123
|
+
if (req.timeout || this.timeout) {
|
|
124
|
+
options.timeout = req.timeout || this.timeout;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Choose HTTP or HTTPS module
|
|
128
|
+
var requestModule = this.https ? https : http;
|
|
129
|
+
var request = requestModule.request(options);
|
|
130
|
+
|
|
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
|
+
}
|
|
109
142
|
|
|
110
|
-
//
|
|
111
|
-
if (['post', 'put'].indexOf(method)
|
|
112
|
-
var
|
|
143
|
+
// Handle request body for POST/PUT/PATCH
|
|
144
|
+
if (['post', 'put', 'patch'].indexOf(method) !== -1) {
|
|
145
|
+
var bodyData = req.body || req.data;
|
|
113
146
|
|
|
114
|
-
if (
|
|
115
|
-
if (typeof
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
request.
|
|
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));
|
|
153
|
+
request.write(jsonData);
|
|
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);
|
|
120
162
|
}
|
|
121
163
|
}
|
|
122
164
|
}
|
|
123
165
|
|
|
124
|
-
|
|
125
|
-
|
|
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);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Send request
|
|
126
175
|
request.end();
|
|
127
176
|
|
|
177
|
+
// Handle response
|
|
128
178
|
request.on('response', function(response) {
|
|
129
|
-
if (debug) {httpClientLogger.log('RESPONSE', response);}
|
|
130
|
-
|
|
131
179
|
response.setEncoding('utf8');
|
|
180
|
+
response.body = '';
|
|
132
181
|
|
|
133
182
|
response.on('data', function(chunk) {
|
|
134
|
-
|
|
183
|
+
response.body += chunk;
|
|
135
184
|
});
|
|
136
185
|
|
|
137
|
-
//Handle the response; run response tests and hand back control to test
|
|
138
186
|
response.on('end', function() {
|
|
139
|
-
//
|
|
140
|
-
var contentType = response.headers['content-type'];
|
|
141
|
-
if (contentType
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
console.log('JSON.parse response.body error:');
|
|
148
|
-
console.log(err);
|
|
149
|
-
if (debug) {httpClientLogger.log('RESPONSE.BODY', response.body);}
|
|
150
|
-
var responseTest = response.body.split('{');
|
|
151
|
-
if (responseTest.length > 1) {
|
|
152
|
-
var actualResponse = '{' + responseTest[1];
|
|
153
|
-
try {
|
|
154
|
-
response.data = JSON.parse(actualResponse);
|
|
155
|
-
console.log('JSON.parse second attempt success.');
|
|
156
|
-
} catch (err) {
|
|
157
|
-
console.log('JSON.parse error on second parse attempt.');
|
|
158
|
-
console.log(err);
|
|
159
|
-
if (debug) {httpClientLogger.log('FILTERED RESPONSE.BODY', actualResponse);}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
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;
|
|
163
195
|
}
|
|
164
196
|
}
|
|
165
197
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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;
|
|
170
202
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
}
|
|
176
212
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
});
|
|
182
223
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
224
|
+
// Test response body
|
|
225
|
+
if (res.body !== undefined) {
|
|
226
|
+
assert.equal(response.body, res.body, 'Response body mismatch');
|
|
227
|
+
}
|
|
187
228
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
})();
|
|
229
|
+
// Test JSON data
|
|
230
|
+
if (res.data !== undefined) {
|
|
231
|
+
assert.deepEqual(response.data, res.data, 'Response data mismatch');
|
|
232
|
+
}
|
|
193
233
|
|
|
234
|
+
// Test response properties
|
|
235
|
+
if (res.ok !== undefined) {
|
|
236
|
+
assert.equal(response.ok, res.ok, 'Response ok status mismatch');
|
|
237
|
+
}
|
|
194
238
|
|
|
195
|
-
|
|
196
|
-
|
|
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;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Call callback or assert.done()
|
|
250
|
+
if (cb) {
|
|
251
|
+
return cb(response);
|
|
252
|
+
} else if (assert && assert.done) {
|
|
253
|
+
return assert.done();
|
|
254
|
+
}
|
|
197
255
|
});
|
|
198
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
|
+
});
|
|
199
274
|
};
|
|
200
275
|
});
|
|
201
|
-
|
|
202
|
-
var httpClientLogger = {
|
|
203
|
-
log: function(header, data) {
|
|
204
|
-
console.log(header);
|
|
205
|
-
console.log(data);
|
|
206
|
-
}
|
|
207
|
-
};
|