UNPKG

16.1 kBJavaScriptView Raw
1
2// test tools
3var chai = require('chai');
4var cap = require('chai-as-promised');
5chai.use(cap);
6var expect = chai.expect;
7var bluebird = require('bluebird');
8var then = require('promise');
9var stream = require('stream');
10var resumer = require('resumer');
11
12var TestServer = require('./server');
13
14// test subjects
15var fetch = require('../index.js');
16var Headers = require('../lib/headers.js');
17var Response = require('../lib/response.js');
18// test with native promise on node 0.11, and bluebird for node 0.10
19fetch.Promise = fetch.Promise || bluebird;
20
21var url, opts, local, base;
22
23describe('node-fetch', function() {
24
25 before(function(done) {
26 local = new TestServer();
27 base = 'http://' + local.hostname + ':' + local.port;
28 local.start(done);
29 });
30
31 after(function(done) {
32 local.stop(done);
33 });
34
35 it('should return a promise', function() {
36 url = 'http://example.com/';
37 var p = fetch(url);
38 expect(p).to.be.an.instanceof(fetch.Promise);
39 expect(p).to.have.property('then');
40 });
41
42 it('should allow custom promise', function() {
43 url = 'http://example.com/';
44 var old = fetch.Promise;
45 fetch.Promise = then;
46 expect(fetch(url)).to.be.an.instanceof(then);
47 expect(fetch(url)).to.not.be.an.instanceof(bluebird);
48 fetch.Promise = old;
49 });
50
51 it('should throw error when no promise implementation are found', function() {
52 url = 'http://example.com/';
53 var old = fetch.Promise;
54 fetch.Promise = undefined;
55 expect(function() {
56 fetch(url)
57 }).to.throw(Error);
58 fetch.Promise = old;
59 });
60
61 it('should reject with error if url is protocol relative', function() {
62 url = '//example.com/';
63 return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
64 });
65
66 it('should reject with error if url is relative path', function() {
67 url = '/some/path';
68 return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
69 });
70
71 it('should reject with error if protocol is unsupported', function() {
72 url = 'ftp://example.com/';
73 return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
74 });
75
76 it('should reject with error on network failure', function() {
77 url = 'http://localhost:50000/';
78 return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
79 });
80
81 it('should resolve into response', function() {
82 url = base + '/hello';
83 return fetch(url).then(function(res) {
84 expect(res).to.be.an.instanceof(Response);
85 expect(res.headers).to.be.an.instanceof(Headers);
86 expect(res.body).to.be.an.instanceof(stream.Transform);
87 expect(res.bodyUsed).to.be.false;
88
89 expect(res.url).to.equal(url);
90 expect(res.ok).to.be.true;
91 expect(res.status).to.equal(200);
92 expect(res.statusText).to.equal('OK');
93 });
94 });
95
96 it('should accept plain text response', function() {
97 url = base + '/plain';
98 return fetch(url).then(function(res) {
99 expect(res.headers.get('content-type')).to.equal('text/plain');
100 return res.text().then(function(result) {
101 expect(res.bodyUsed).to.be.true;
102 expect(result).to.be.a('string');
103 expect(result).to.equal('text');
104 });
105 });
106 });
107
108 it('should accept html response (like plain text)', function() {
109 url = base + '/html';
110 return fetch(url).then(function(res) {
111 expect(res.headers.get('content-type')).to.equal('text/html');
112 return res.text().then(function(result) {
113 expect(res.bodyUsed).to.be.true;
114 expect(result).to.be.a('string');
115 expect(result).to.equal('<html></html>');
116 });
117 });
118 });
119
120 it('should accept json response', function() {
121 url = base + '/json';
122 return fetch(url).then(function(res) {
123 expect(res.headers.get('content-type')).to.equal('application/json');
124 return res.json().then(function(result) {
125 expect(res.bodyUsed).to.be.true;
126 expect(result).to.be.an('object');
127 expect(result).to.deep.equal({ name: 'value' });
128 });
129 });
130 });
131
132 it('should send request with custom headers', function() {
133 url = base + '/inspect';
134 opts = {
135 headers: { 'x-custom-header': 'abc' }
136 };
137 return fetch(url, opts).then(function(res) {
138 return res.json();
139 }).then(function(res) {
140 expect(res.headers['x-custom-header']).to.equal('abc');
141 });
142 });
143
144 it('should follow redirect code 301', function() {
145 url = base + '/redirect/301';
146 return fetch(url).then(function(res) {
147 expect(res.url).to.equal(base + '/inspect');
148 expect(res.status).to.equal(200);
149 expect(res.ok).to.be.true;
150 });
151 });
152
153 it('should follow redirect code 302', function() {
154 url = base + '/redirect/302';
155 return fetch(url).then(function(res) {
156 expect(res.url).to.equal(base + '/inspect');
157 expect(res.status).to.equal(200);
158 });
159 });
160
161 it('should follow redirect code 303', function() {
162 url = base + '/redirect/303';
163 return fetch(url).then(function(res) {
164 expect(res.url).to.equal(base + '/inspect');
165 expect(res.status).to.equal(200);
166 });
167 });
168
169 it('should follow redirect code 307', function() {
170 url = base + '/redirect/307';
171 return fetch(url).then(function(res) {
172 expect(res.url).to.equal(base + '/inspect');
173 expect(res.status).to.equal(200);
174 });
175 });
176
177 it('should follow redirect code 308', function() {
178 url = base + '/redirect/308';
179 return fetch(url).then(function(res) {
180 expect(res.url).to.equal(base + '/inspect');
181 expect(res.status).to.equal(200);
182 });
183 });
184
185 it('should follow redirect chain', function() {
186 url = base + '/redirect/chain';
187 return fetch(url).then(function(res) {
188 expect(res.url).to.equal(base + '/inspect');
189 expect(res.status).to.equal(200);
190 });
191 });
192
193 it('should obey maximum redirect', function() {
194 url = base + '/redirect/chain';
195 opts = {
196 follow: 1
197 }
198 return expect(fetch(url, opts)).to.eventually.be.rejectedWith(Error);
199 });
200
201 it('should allow not following redirect', function() {
202 url = base + '/redirect/301';
203 opts = {
204 follow: 0
205 }
206 return expect(fetch(url, opts)).to.eventually.be.rejectedWith(Error);
207 });
208
209 it('should reject broken redirect', function() {
210 url = base + '/error/redirect';
211 return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
212 });
213
214 it('should handle client-error response', function() {
215 url = base + '/error/400';
216 return fetch(url).then(function(res) {
217 expect(res.headers.get('content-type')).to.equal('text/plain');
218 expect(res.status).to.equal(400);
219 expect(res.statusText).to.equal('Bad Request');
220 expect(res.ok).to.be.false;
221 return res.text().then(function(result) {
222 expect(res.bodyUsed).to.be.true;
223 expect(result).to.be.a('string');
224 expect(result).to.equal('client error');
225 });
226 });
227 });
228
229 it('should handle server-error response', function() {
230 url = base + '/error/500';
231 return fetch(url).then(function(res) {
232 expect(res.headers.get('content-type')).to.equal('text/plain');
233 expect(res.status).to.equal(500);
234 expect(res.statusText).to.equal('Internal Server Error');
235 expect(res.ok).to.be.false;
236 return res.text().then(function(result) {
237 expect(res.bodyUsed).to.be.true;
238 expect(result).to.be.a('string');
239 expect(result).to.equal('server error');
240 });
241 });
242 });
243
244 it('should handle network-error response', function() {
245 url = base + '/error/reset';
246 return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
247 });
248
249 it('should reject invalid json response', function() {
250 url = base + '/error/json';
251 return fetch(url).then(function(res) {
252 expect(res.headers.get('content-type')).to.equal('application/json');
253 return expect(res.json()).to.eventually.be.rejectedWith(Error);
254 });
255 });
256
257 it('should handle empty response', function() {
258 url = base + '/empty';
259 return fetch(url).then(function(res) {
260 expect(res.status).to.equal(204);
261 expect(res.statusText).to.equal('No Content');
262 expect(res.ok).to.be.true;
263 return res.text().then(function(result) {
264 expect(result).to.be.a('string');
265 expect(result).to.be.empty;
266 });
267 });
268 });
269
270 it('should decompress gzip response', function() {
271 url = base + '/gzip';
272 return fetch(url).then(function(res) {
273 expect(res.headers.get('content-type')).to.equal('text/plain');
274 return res.text().then(function(result) {
275 expect(result).to.be.a('string');
276 expect(result).to.equal('hello world');
277 });
278 });
279 });
280
281 it('should decompress deflate response', function() {
282 url = base + '/deflate';
283 return fetch(url).then(function(res) {
284 expect(res.headers.get('content-type')).to.equal('text/plain');
285 return res.text().then(function(result) {
286 expect(result).to.be.a('string');
287 expect(result).to.equal('hello world');
288 });
289 });
290 });
291
292 it('should skip decompression if unsupported', function() {
293 url = base + '/sdch';
294 return fetch(url).then(function(res) {
295 expect(res.headers.get('content-type')).to.equal('text/plain');
296 return res.text().then(function(result) {
297 expect(result).to.be.a('string');
298 expect(result).to.equal('fake sdch string');
299 });
300 });
301 });
302
303 it('should allow disabling auto decompression', function() {
304 url = base + '/gzip';
305 opts = {
306 compress: false
307 };
308 return fetch(url, opts).then(function(res) {
309 expect(res.headers.get('content-type')).to.equal('text/plain');
310 return res.text().then(function(result) {
311 expect(result).to.be.a('string');
312 expect(result).to.not.equal('hello world');
313 });
314 });
315 });
316
317 it('should allow custom timeout', function() {
318 url = base + '/timeout';
319 opts = {
320 timeout: 100
321 };
322 return expect(fetch(url, opts)).to.eventually.be.rejectedWith(Error);
323 });
324
325 it('should allow POST request', function() {
326 url = base + '/inspect';
327 opts = {
328 method: 'POST'
329 };
330 return fetch(url, opts).then(function(res) {
331 return res.json();
332 }).then(function(res) {
333 expect(res.method).to.equal('POST');
334 });
335 });
336
337 it('should allow POST request with string body', function() {
338 url = base + '/inspect';
339 opts = {
340 method: 'POST'
341 , body: 'a=1'
342 };
343 return fetch(url, opts).then(function(res) {
344 return res.json();
345 }).then(function(res) {
346 expect(res.method).to.equal('POST');
347 expect(res.body).to.equal('a=1');
348 });
349 });
350
351 it('should allow POST request with readable stream as body', function() {
352 url = base + '/inspect';
353 opts = {
354 method: 'POST'
355 , body: resumer().queue('a=1').end()
356 };
357 return fetch(url, opts).then(function(res) {
358 return res.json();
359 }).then(function(res) {
360 expect(res.method).to.equal('POST');
361 expect(res.body).to.equal('a=1');
362 });
363 });
364
365 it('should allow PUT request', function() {
366 url = base + '/inspect';
367 opts = {
368 method: 'PUT'
369 , body: 'a=1'
370 };
371 return fetch(url, opts).then(function(res) {
372 return res.json();
373 }).then(function(res) {
374 expect(res.method).to.equal('PUT');
375 expect(res.body).to.equal('a=1');
376 });
377 });
378
379 it('should allow DELETE request', function() {
380 url = base + '/inspect';
381 opts = {
382 method: 'DELETE'
383 };
384 return fetch(url, opts).then(function(res) {
385 return res.json();
386 }).then(function(res) {
387 expect(res.method).to.equal('DELETE');
388 });
389 });
390
391 it('should allow PATCH request', function() {
392 url = base + '/inspect';
393 opts = {
394 method: 'PATCH'
395 , body: 'a=1'
396 };
397 return fetch(url, opts).then(function(res) {
398 return res.json();
399 }).then(function(res) {
400 expect(res.method).to.equal('PATCH');
401 expect(res.body).to.equal('a=1');
402 });
403 });
404
405 it('should allow HEAD request', function() {
406 url = base + '/hello';
407 opts = {
408 method: 'HEAD'
409
410 };
411 return fetch(url, opts).then(function(res) {
412 expect(res.status).to.equal(200);
413 expect(res.statusText).to.equal('OK');
414 expect(res.headers.get('content-type')).to.equal('text/plain');
415 expect(res.body).to.be.an.instanceof(stream.Transform);
416 });
417 });
418
419 it('should reject decoding body twice', function() {
420 url = base + '/plain';
421 return fetch(url).then(function(res) {
422 expect(res.headers.get('content-type')).to.equal('text/plain');
423 return res.text().then(function(result) {
424 expect(res.bodyUsed).to.be.true;
425 return expect(res.text()).to.eventually.be.rejectedWith(Error);
426 });
427 });
428 });
429
430 it('should support maximum response size, multiple chunk', function() {
431 url = base + '/size/chunk';
432 opts = {
433 size: 5
434 };
435 return fetch(url, opts).then(function(res) {
436 expect(res.status).to.equal(200);
437 expect(res.headers.get('content-type')).to.equal('text/plain');
438 return expect(res.text()).to.eventually.be.rejectedWith(Error);
439 });
440 });
441
442 it('should support maximum response size, single chunk', function() {
443 url = base + '/size/long';
444 opts = {
445 size: 5
446 };
447 return fetch(url, opts).then(function(res) {
448 expect(res.status).to.equal(200);
449 expect(res.headers.get('content-type')).to.equal('text/plain');
450 return expect(res.text()).to.eventually.be.rejectedWith(Error);
451 });
452 });
453
454 it('should support encoding decode, xml dtd detect', function() {
455 url = base + '/encoding/euc-jp';
456 return fetch(url).then(function(res) {
457 expect(res.status).to.equal(200);
458 return res.text().then(function(result) {
459 expect(result).to.equal('<?xml version="1.0" encoding="EUC-JP"?><title>日本語</title>');
460 });
461 });
462 });
463
464 it('should support encoding decode, content-type detect', function() {
465 url = base + '/encoding/shift-jis';
466 return fetch(url).then(function(res) {
467 expect(res.status).to.equal(200);
468 return res.text().then(function(result) {
469 expect(result).to.equal('<div>日本語</div>');
470 });
471 });
472 });
473
474 it('should support encoding decode, html5 detect', function() {
475 url = base + '/encoding/gbk';
476 return fetch(url).then(function(res) {
477 expect(res.status).to.equal(200);
478 return res.text().then(function(result) {
479 expect(result).to.equal('<meta charset="gbk"><div>中文</div>');
480 });
481 });
482 });
483
484 it('should support encoding decode, html4 detect', function() {
485 url = base + '/encoding/gb2312';
486 return fetch(url).then(function(res) {
487 expect(res.status).to.equal(200);
488 return res.text().then(function(result) {
489 expect(result).to.equal('<meta http-equiv="Content-Type" content="text/html; charset=gb2312"><div>中文</div>');
490 });
491 });
492 });
493
494 it('should default to utf8 encoding', function() {
495 url = base + '/encoding/utf8';
496 return fetch(url).then(function(res) {
497 expect(res.status).to.equal(200);
498 expect(res.headers.get('content-type')).to.be.null;
499 return res.text().then(function(result) {
500 expect(result).to.equal('中文');
501 });
502 });
503 });
504
505 it('should allow piping response body as stream', function(done) {
506 url = base + '/hello';
507 fetch(url).then(function(res) {
508 expect(res.body).to.be.an.instanceof(stream.Transform);
509 res.body.on('data', function(chunk) {
510 if (chunk === null) {
511 return;
512 }
513 expect(chunk.toString()).to.equal('world');
514 });
515 res.body.on('end', function() {
516 done();
517 });
518 });
519 });
520
521 it('should allow get all responses of a header', function() {
522 url = base + '/cookie';
523 return fetch(url).then(function(res) {
524 expect(res.headers.get('set-cookie')).to.equal('a=1');
525 expect(res.headers.get('Set-Cookie')).to.equal('a=1');
526 expect(res.headers.getAll('set-cookie')).to.deep.equal(['a=1', 'b=1']);
527 expect(res.headers.getAll('Set-Cookie')).to.deep.equal(['a=1', 'b=1']);
528 });
529 });
530
531 it('should allow deleting header', function() {
532 url = base + '/cookie';
533 return fetch(url).then(function(res) {
534 res.headers.delete('set-cookie');
535 expect(res.headers.get('set-cookie')).to.be.null;
536 expect(res.headers.getAll('set-cookie')).to.be.empty;
537 });
538 });
539
540 it('should skip prototype when reading response headers', function() {
541 var FakeHeader = function() {};
542 FakeHeader.prototype.c = 'string';
543 FakeHeader.prototype.d = [1,2,3,4];
544 var res = new FakeHeader;
545 res.a = 'string';
546 res.b = [];
547 var headers = new Headers(res);
548 expect(headers._headers['a']).to.include('string');
549 expect(headers._headers['b']).to.be.undefined;
550 expect(headers._headers['c']).to.be.undefined;
551 expect(headers._headers['d']).to.be.undefined;
552 });
553
554 it('should support https request', function() {
555 this.timeout(5000);
556 url = 'https://github.com/';
557 opts = {
558 method: 'HEAD'
559 };
560 return fetch(url, opts).then(function(res) {
561 expect(res.status).to.equal(200);
562 expect(res.ok).to.be.true;
563 });
564 });
565
566});