UNPKG

11.7 kBJavaScriptView Raw
1import sinon from "sinon";
2
3import expectjs from "expect.js";
4
5import HttpClient from "../lib/HttpClient";
6import NullLogger from "../lib/NullLogger";
7
8import http from "http";
9
10var expect = require("sinon-expect").enhance(expectjs, sinon, "was");
11
12var logger = new NullLogger();
13var fakeHttp = {
14 request: function() {}
15};
16var fakeRequest = {
17 end: function() {},
18 on: function() {}
19};
20
21var defaultHeaders = {
22 "Content-Type": "application/x-www-form-urlencoded",
23 Accept: "application/json"
24};
25
26var client = null;
27
28describe("HttpClient Object", function() {
29 afterEach(function() {
30 fakeHttp.request.restore();
31 });
32
33 it("should support requests over https", function() {
34 var mock = sinon.mock(fakeHttp);
35 mock
36 .expects("request")
37 .once()
38 .withArgs({
39 headers: defaultHeaders,
40 host: "api.nexmo.com",
41 method: "GET",
42 path: "/api",
43 port: 443
44 })
45 .returns(fakeRequest);
46
47 var client = new HttpClient({
48 https: fakeHttp,
49 port: 443,
50 logger: logger
51 });
52
53 client.request(
54 {
55 host: "api.nexmo.com",
56 path: "/api"
57 },
58 "GET",
59 {
60 some: "data"
61 }
62 );
63 });
64
65 it("should support requests over http", function() {
66 var mock = sinon.mock(fakeHttp);
67 mock
68 .expects("request")
69 .once()
70 .withArgs({
71 headers: defaultHeaders,
72 host: "api.nexmo.com",
73 method: "GET",
74 path: "/api",
75 port: 80
76 })
77 .returns(fakeRequest);
78
79 var client = new HttpClient({
80 http: fakeHttp,
81 port: 80,
82 logger: logger
83 });
84
85 client.request(
86 {
87 host: "api.nexmo.com",
88 path: "/api"
89 },
90 "GET",
91 {
92 some: "data"
93 }
94 );
95 });
96
97 it("should be possible to set the host", function() {
98 var mock = sinon.mock(fakeHttp);
99 mock
100 .expects("request")
101 .once()
102 .withArgs({
103 headers: defaultHeaders,
104 host: "rest.nexmo.com",
105 method: "GET",
106 path: "/api",
107 port: 80
108 })
109 .returns(fakeRequest);
110
111 var client = new HttpClient({
112 http: fakeHttp,
113 port: 80,
114 logger: logger
115 });
116
117 client.request(
118 {
119 host: "rest.nexmo.com",
120 path: "/api"
121 },
122 "GET",
123 {
124 some: "data"
125 }
126 );
127 });
128
129 it("should be possible to set the path", function() {
130 var mock = sinon.mock(fakeHttp);
131 mock
132 .expects("request")
133 .once()
134 .withArgs({
135 headers: defaultHeaders,
136 host: "api.nexmo.com",
137 method: "GET",
138 path: "/some_path",
139 port: 80
140 })
141 .returns(fakeRequest);
142
143 var client = new HttpClient({
144 http: fakeHttp,
145 port: 80,
146 logger: logger
147 });
148
149 client.request(
150 {
151 host: "api.nexmo.com",
152 path: "/some_path"
153 },
154 "GET",
155 {
156 some: "data"
157 }
158 );
159 });
160
161 it("should be possible to set the method", function() {
162 var mock = sinon.mock(fakeHttp);
163 mock
164 .expects("request")
165 .once()
166 .withArgs({
167 headers: defaultHeaders,
168 host: "api.nexmo.com",
169 method: "POST",
170 path: "/api",
171 port: 443
172 })
173 .returns(fakeRequest);
174
175 var client = new HttpClient({
176 https: fakeHttp,
177 logger: logger
178 });
179
180 client.request(
181 {
182 host: "api.nexmo.com",
183 path: "/api"
184 },
185 "POST",
186 {
187 some: "data"
188 }
189 );
190 });
191
192 it("should be possible to set the timeout", function() {
193 var mock = sinon.mock(fakeHttp);
194 mock
195 .expects("request")
196 .once()
197 .withArgs({
198 headers: defaultHeaders,
199 host: "api.nexmo.com",
200 method: "POST",
201 path: "/api",
202 port: 443,
203 timeout: 5000
204 })
205 .returns(fakeRequest);
206
207 var client = new HttpClient({
208 https: fakeHttp,
209 logger: logger,
210 timeout: 5000
211 });
212
213 client.request(
214 {
215 host: "api.nexmo.com",
216 path: "/api"
217 },
218 "POST",
219 {
220 some: "data"
221 }
222 );
223 });
224
225 it("should not override the method when method and callback are undefined", function() {
226 var mock = sinon.mock(fakeHttp);
227 mock
228 .expects("request")
229 .once()
230 .withArgs({
231 headers: defaultHeaders,
232 host: "api.nexmo.com",
233 method: "POST",
234 path: "/api",
235 port: 443
236 })
237 .returns(fakeRequest);
238
239 var client = new HttpClient({
240 https: fakeHttp,
241 logger: logger
242 });
243
244 client.request({
245 host: "api.nexmo.com",
246 path: "/api",
247 method: "POST"
248 });
249 });
250
251 it("should log requests", function() {
252 var mock = sinon.mock(fakeHttp);
253 mock.expects("request").returns(fakeRequest);
254
255 var logged = false;
256 var testLogger = {
257 info: function() {
258 logged = true;
259 }
260 };
261 var client = new HttpClient({
262 https: fakeHttp,
263 logger: testLogger
264 });
265
266 client.request(
267 {
268 host: "api.nexmo.com",
269 path: "/api"
270 },
271 "GET",
272 {
273 some: "data"
274 }
275 );
276
277 expect(logged).to.be(true);
278 });
279
280 it("should allow User-Agent header to be set via options", function() {
281 var expectedUserAgent = "nexmo-node/1.0.0/v4.4.7";
282
283 var mock = sinon.mock(fakeHttp);
284 mock
285 .expects("request")
286 .once()
287 .withArgs({
288 headers: {
289 "Content-Type": "application/x-www-form-urlencoded",
290 Accept: "application/json",
291 "User-Agent": expectedUserAgent
292 },
293 host: "api.nexmo.com",
294 method: "POST",
295 path: "/api",
296 port: 443
297 })
298 .returns(fakeRequest);
299
300 var client = new HttpClient({
301 https: fakeHttp,
302 logger: logger,
303 userAgent: expectedUserAgent
304 });
305
306 client.request(
307 {
308 host: "api.nexmo.com",
309 path: "/api"
310 },
311 "POST",
312 {
313 some: "data"
314 }
315 );
316 });
317});
318
319describe("parseResponse", function() {
320 beforeEach(function() {
321 client = new HttpClient({
322 https: fakeHttp,
323 logger: logger
324 });
325 });
326
327 it("should parse a 500+ status code as an error", function() {
328 var callback = sinon.spy();
329 const headers = { "content-type": "application/json" };
330 const response = { statusCode: 504, headers: headers };
331 client.__parseResponse(response, [""], "GET", callback);
332 expect(callback).was.calledWith(
333 { message: "Server Error", statusCode: 504, headers: headers },
334 null
335 );
336 });
337
338 it("should parse a 400-499 status code as a JSON error", function() {
339 var callback = sinon.spy();
340 const headers = { "content-type": "application/json" };
341 const response = { statusCode: 404, headers: headers };
342 client.__parseResponse(
343 response,
344 ['{ "error" : "error" }'],
345 "GET",
346 callback
347 );
348 expect(callback).was.calledWith(
349 { statusCode: 404, body: { error: "error" }, headers: headers },
350 null
351 );
352 });
353
354 it("should parse a 204 status code as null", function() {
355 var callback = sinon.spy();
356 const headers = { "content-type": "application/json" };
357 const response = { statusCode: 204, headers: headers };
358 client.__parseResponse(response, [""], "GET", callback);
359 expect(callback).was.calledWith(null, null);
360 });
361
362 it("should parse a 200-299 status code as a JSON object", function() {
363 var callback = sinon.spy();
364 const response = {
365 statusCode: 201,
366 headers: { "content-type": "application/json" }
367 };
368 client.__parseResponse(response, ['{ "data" : "data" }'], "GET", callback);
369 expect(callback).was.calledWith(null, { data: "data" });
370 });
371
372 it("should not try and parse successful DELETE request to JSON", function() {
373 var callback = sinon.spy();
374 const response = {
375 statusCode: 201,
376 headers: { "content-type": "application/json" }
377 };
378 client.__parseResponse(response, [""], "DELETE", callback);
379 expect(callback).was.calledWith(null, [""]);
380 });
381
382 it("should catch invalid json and expose the data in the error", function() {
383 var callback = sinon.spy();
384 const response = {
385 statusCode: 201,
386 headers: { "content-type": "application/json" }
387 };
388 const data = "not_json";
389 client.__parseResponse(response, [data], "GET", callback);
390 expect(callback).was.calledWith(
391 sinon.match({
392 message: "The API response could not be parsed.",
393 body: data,
394 statusCode: 201
395 }),
396 null
397 );
398 });
399
400 it("should not error with invalid JSON if parsing is disabled", function() {
401 var callback = sinon.spy();
402 const response = {
403 statusCode: 200,
404 headers: { "content-type": "application/json" }
405 };
406 const data = "not_json";
407 client.__parseResponse(response, [data], "GET", callback, true);
408 expect(callback).was.calledWith(null, data);
409 });
410
411 it("should parse binary data", function() {
412 var callback = sinon.spy();
413 var data = new Buffer("data");
414 const response = {
415 statusCode: 200,
416 headers: { "content-type": "application/octet-stream" }
417 };
418 client.__parseResponse(response, data, "GET", callback);
419 expect(callback).was.calledWith(null, data);
420 });
421
422 it("should set a default retry-after of 200 for a GET with a 429 response", function() {
423 var callback = sinon.spy();
424 const headers = {};
425 const response = { statusCode: 429, headers: headers };
426 client.__parseResponse(response, [""], "GET", callback);
427 expect(callback).was.calledWith(
428 { statusCode: 429, body: "", headers: { "retry-after": 200 } },
429 null
430 );
431 });
432
433 it("should set a default retry-after of 500 for a POST with a 429 response", function() {
434 var callback = sinon.spy();
435 const headers = {};
436 const response = { statusCode: 429, headers: headers };
437 client.__parseResponse(response, [""], "POST", callback);
438 expect(callback).was.calledWith(
439 { statusCode: 429, body: "", headers: { "retry-after": 500 } },
440 null
441 );
442 });
443
444 it("should use the server returned retry-after header with a 429 response", function() {
445 var callback = sinon.spy();
446 const headers = { "retry-after": 400 };
447 const response = { statusCode: 429, headers: headers };
448 client.__parseResponse(response, [""], "GET", callback);
449 expect(callback).was.calledWith(
450 { statusCode: 429, body: "", headers: { "retry-after": 400 } },
451 null
452 );
453 });
454
455 it("should not modify the default headers", function() {
456 var mock = sinon.mock(http);
457 mock
458 .expects("request")
459 .once()
460 .withArgs({
461 headers: {
462 // We expect application/json in our request as we explicitly
463 // specify it
464 "Content-Type": "application/json",
465 Accept: "application/json"
466 },
467 host: "api.nexmo.com",
468 method: "GET",
469 path: "/api",
470 port: 80
471 })
472 .returns(fakeRequest);
473
474 var client = new HttpClient({
475 port: 80,
476 logger: logger
477 });
478
479 // We expect our initial headers to be set
480 expect(client.headers["Content-Type"]).to.eql(
481 "application/x-www-form-urlencoded"
482 );
483
484 // Make a request, passing in a different Content-Type
485 client.request(
486 {
487 host: "api.nexmo.com",
488 path: "/api",
489 headers: {
490 "Content-Type": "application/json"
491 }
492 },
493 "GET",
494 function() {}
495 );
496
497 // Our default headers should still be the same
498 expect(client.headers["Content-Type"]).to.eql(
499 "application/x-www-form-urlencoded"
500 );
501
502 mock.verify();
503 });
504});