1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489 |
- // test tools
- var chai = require('chai');
- var cap = require('chai-as-promised');
- chai.use(cap);
- var expect = chai.expect;
- var bluebird = require('bluebird');
- var then = require('promise');
- var spawn = require('child_process').spawn;
- var stream = require('stream');
- var resumer = require('resumer');
- var FormData = require('form-data');
- var http = require('http');
- var fs = require('fs');
- var TestServer = require('./server');
- // test subjects
- var fetch = require('../index.js');
- var Headers = require('../lib/headers.js');
- var Response = require('../lib/response.js');
- var Request = require('../lib/request.js');
- var Body = require('../lib/body.js');
- var FetchError = require('../lib/fetch-error.js');
- // test with native promise on node 0.11, and bluebird for node 0.10
- fetch.Promise = fetch.Promise || bluebird;
- var url, opts, local, base;
- describe('node-fetch', function() {
- before(function(done) {
- local = new TestServer();
- base = 'http://' + local.hostname + ':' + local.port;
- local.start(done);
- });
- after(function(done) {
- local.stop(done);
- });
- it('should return a promise', function() {
- url = 'http://example.com/';
- var p = fetch(url);
- expect(p).to.be.an.instanceof(fetch.Promise);
- expect(p).to.have.property('then');
- });
- it('should allow custom promise', function() {
- url = 'http://example.com/';
- var old = fetch.Promise;
- fetch.Promise = then;
- expect(fetch(url)).to.be.an.instanceof(then);
- expect(fetch(url)).to.not.be.an.instanceof(bluebird);
- fetch.Promise = old;
- });
- it('should throw error when no promise implementation are found', function() {
- url = 'http://example.com/';
- var old = fetch.Promise;
- fetch.Promise = undefined;
- expect(function() {
- fetch(url)
- }).to.throw(Error);
- fetch.Promise = old;
- });
- it('should expose Headers, Response and Request constructors', function() {
- expect(fetch.Headers).to.equal(Headers);
- expect(fetch.Response).to.equal(Response);
- expect(fetch.Request).to.equal(Request);
- });
- it('should reject with error if url is protocol relative', function() {
- url = '//example.com/';
- return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
- });
- it('should reject with error if url is relative path', function() {
- url = '/some/path';
- return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
- });
- it('should reject with error if protocol is unsupported', function() {
- url = 'ftp://example.com/';
- return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
- });
- it('should reject with error on network failure', function() {
- url = 'http://localhost:50000/';
- return expect(fetch(url)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.include({ type: 'system', code: 'ECONNREFUSED', errno: 'ECONNREFUSED' });
- });
- it('should resolve into response', function() {
- url = base + '/hello';
- return fetch(url).then(function(res) {
- expect(res).to.be.an.instanceof(Response);
- expect(res.headers).to.be.an.instanceof(Headers);
- expect(res.body).to.be.an.instanceof(stream.Transform);
- expect(res.bodyUsed).to.be.false;
- expect(res.url).to.equal(url);
- expect(res.ok).to.be.true;
- expect(res.status).to.equal(200);
- expect(res.statusText).to.equal('OK');
- });
- });
- it('should accept plain text response', function() {
- url = base + '/plain';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return res.text().then(function(result) {
- expect(res.bodyUsed).to.be.true;
- expect(result).to.be.a('string');
- expect(result).to.equal('text');
- });
- });
- });
- it('should accept html response (like plain text)', function() {
- url = base + '/html';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/html');
- return res.text().then(function(result) {
- expect(res.bodyUsed).to.be.true;
- expect(result).to.be.a('string');
- expect(result).to.equal('<html></html>');
- });
- });
- });
- it('should accept json response', function() {
- url = base + '/json';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('application/json');
- return res.json().then(function(result) {
- expect(res.bodyUsed).to.be.true;
- expect(result).to.be.an('object');
- expect(result).to.deep.equal({ name: 'value' });
- });
- });
- });
- it('should send request with custom headers', function() {
- url = base + '/inspect';
- opts = {
- headers: { 'x-custom-header': 'abc' }
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.headers['x-custom-header']).to.equal('abc');
- });
- });
- it('should accept headers instance', function() {
- url = base + '/inspect';
- opts = {
- headers: new Headers({ 'x-custom-header': 'abc' })
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.headers['x-custom-header']).to.equal('abc');
- });
- });
- it('should accept custom host header', function() {
- url = base + '/inspect';
- opts = {
- headers: {
- host: 'example.com'
- }
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.headers['host']).to.equal('example.com');
- });
- });
- it('should follow redirect code 301', function() {
- url = base + '/redirect/301';
- return fetch(url).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- expect(res.ok).to.be.true;
- });
- });
- it('should follow redirect code 302', function() {
- url = base + '/redirect/302';
- return fetch(url).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- });
- });
- it('should follow redirect code 303', function() {
- url = base + '/redirect/303';
- return fetch(url).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- });
- });
- it('should follow redirect code 307', function() {
- url = base + '/redirect/307';
- return fetch(url).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- });
- });
- it('should follow redirect code 308', function() {
- url = base + '/redirect/308';
- return fetch(url).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- });
- });
- it('should follow redirect chain', function() {
- url = base + '/redirect/chain';
- return fetch(url).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- });
- });
- it('should follow POST request redirect code 301 with GET', function() {
- url = base + '/redirect/301';
- opts = {
- method: 'POST'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- return res.json().then(function(result) {
- expect(result.method).to.equal('GET');
- expect(result.body).to.equal('');
- });
- });
- });
- it('should follow POST request redirect code 302 with GET', function() {
- url = base + '/redirect/302';
- opts = {
- method: 'POST'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- return res.json().then(function(result) {
- expect(result.method).to.equal('GET');
- expect(result.body).to.equal('');
- });
- });
- });
- it('should follow redirect code 303 with GET', function() {
- url = base + '/redirect/303';
- opts = {
- method: 'PUT'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- return res.json().then(function(result) {
- expect(result.method).to.equal('GET');
- expect(result.body).to.equal('');
- });
- });
- });
- it('should obey maximum redirect, reject case', function() {
- url = base + '/redirect/chain';
- opts = {
- follow: 1
- }
- return expect(fetch(url, opts)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'max-redirect');
- });
- it('should obey redirect chain, resolve case', function() {
- url = base + '/redirect/chain';
- opts = {
- follow: 2
- }
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- expect(res.status).to.equal(200);
- });
- });
- it('should allow not following redirect', function() {
- url = base + '/redirect/301';
- opts = {
- follow: 0
- }
- return expect(fetch(url, opts)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'max-redirect');
- });
- it('should support redirect mode, manual flag', function() {
- url = base + '/redirect/301';
- opts = {
- redirect: 'manual'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(url);
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.equal(base + '/inspect');
- });
- });
- it('should support redirect mode, error flag', function() {
- url = base + '/redirect/301';
- opts = {
- redirect: 'error'
- };
- return expect(fetch(url, opts)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'no-redirect');
- });
- it('should support redirect mode, manual flag when there is no redirect', function() {
- url = base + '/hello';
- opts = {
- redirect: 'manual'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(url);
- expect(res.status).to.equal(200);
- expect(res.headers.get('location')).to.be.null;
- });
- });
- it('should follow redirect code 301 and keep existing headers', function() {
- url = base + '/redirect/301';
- opts = {
- headers: new Headers({ 'x-custom-header': 'abc' })
- };
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(base + '/inspect');
- return res.json();
- }).then(function(res) {
- expect(res.headers['x-custom-header']).to.equal('abc');
- });
- });
- it('should reject broken redirect', function() {
- url = base + '/error/redirect';
- return expect(fetch(url)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'invalid-redirect');
- });
- it('should not reject broken redirect under manual redirect', function() {
- url = base + '/error/redirect';
- opts = {
- redirect: 'manual'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.url).to.equal(url);
- expect(res.status).to.equal(301);
- expect(res.headers.get('location')).to.be.null;
- });
- });
- it('should handle client-error response', function() {
- url = base + '/error/400';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- expect(res.status).to.equal(400);
- expect(res.statusText).to.equal('Bad Request');
- expect(res.ok).to.be.false;
- return res.text().then(function(result) {
- expect(res.bodyUsed).to.be.true;
- expect(result).to.be.a('string');
- expect(result).to.equal('client error');
- });
- });
- });
- it('should handle server-error response', function() {
- url = base + '/error/500';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- expect(res.status).to.equal(500);
- expect(res.statusText).to.equal('Internal Server Error');
- expect(res.ok).to.be.false;
- return res.text().then(function(result) {
- expect(res.bodyUsed).to.be.true;
- expect(result).to.be.a('string');
- expect(result).to.equal('server error');
- });
- });
- });
- it('should handle network-error response', function() {
- url = base + '/error/reset';
- return expect(fetch(url)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('code', 'ECONNRESET');
- });
- it('should handle DNS-error response', function() {
- url = 'http://domain.invalid';
- return expect(fetch(url)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('code', 'ENOTFOUND');
- });
- it('should reject invalid json response', function() {
- url = base + '/error/json';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('application/json');
- return expect(res.json()).to.eventually.be.rejectedWith(Error);
- });
- });
- it('should handle no content response', function() {
- url = base + '/no-content';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(204);
- expect(res.statusText).to.equal('No Content');
- expect(res.ok).to.be.true;
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.be.empty;
- });
- });
- });
- it('should throw on no-content json response', function() {
- url = base + '/no-content';
- return fetch(url).then(function(res) {
- return expect(res.json()).to.eventually.be.rejectedWith(FetchError);
- });
- });
- it('should handle no content response with gzip encoding', function() {
- url = base + '/no-content/gzip';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(204);
- expect(res.statusText).to.equal('No Content');
- expect(res.headers.get('content-encoding')).to.equal('gzip');
- expect(res.ok).to.be.true;
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.be.empty;
- });
- });
- });
- it('should handle not modified response', function() {
- url = base + '/not-modified';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(304);
- expect(res.statusText).to.equal('Not Modified');
- expect(res.ok).to.be.false;
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.be.empty;
- });
- });
- });
- it('should handle not modified response with gzip encoding', function() {
- url = base + '/not-modified/gzip';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(304);
- expect(res.statusText).to.equal('Not Modified');
- expect(res.headers.get('content-encoding')).to.equal('gzip');
- expect(res.ok).to.be.false;
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.be.empty;
- });
- });
- });
- it('should decompress gzip response', function() {
- url = base + '/gzip';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.equal('hello world');
- });
- });
- });
- it('should decompress deflate response', function() {
- url = base + '/deflate';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.equal('hello world');
- });
- });
- });
- it('should decompress deflate raw response from old apache server', function() {
- url = base + '/deflate-raw';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.equal('hello world');
- });
- });
- });
- it('should skip decompression if unsupported', function() {
- url = base + '/sdch';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.equal('fake sdch string');
- });
- });
- });
- it('should reject if response compression is invalid', function() {
- url = base + '/invalid-content-encoding';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return expect(res.text()).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('code', 'Z_DATA_ERROR');
- });
- });
- it('should allow disabling auto decompression', function() {
- url = base + '/gzip';
- opts = {
- compress: false
- };
- return fetch(url, opts).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return res.text().then(function(result) {
- expect(result).to.be.a('string');
- expect(result).to.not.equal('hello world');
- });
- });
- });
- it('should allow custom timeout', function() {
- this.timeout(500);
- url = base + '/timeout';
- opts = {
- timeout: 100
- };
- return expect(fetch(url, opts)).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'request-timeout');
- });
- it('should allow custom timeout on response body', function() {
- this.timeout(500);
- url = base + '/slow';
- opts = {
- timeout: 100
- };
- return fetch(url, opts).then(function(res) {
- expect(res.ok).to.be.true;
- return expect(res.text()).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'body-timeout');
- });
- });
- it('should clear internal timeout on fetch response', function (done) {
- this.timeout(1000);
- spawn('node', ['-e', 'require("./")("' + base + '/hello", { timeout: 5000 })'])
- .on('exit', function () {
- done();
- });
- });
- it('should clear internal timeout on fetch redirect', function (done) {
- this.timeout(1000);
- spawn('node', ['-e', 'require("./")("' + base + '/redirect/301", { timeout: 5000 })'])
- .on('exit', function () {
- done();
- });
- });
- it('should clear internal timeout on fetch error', function (done) {
- this.timeout(1000);
- spawn('node', ['-e', 'require("./")("' + base + '/error/reset", { timeout: 5000 })'])
- .on('exit', function () {
- done();
- });
- });
- it('should allow POST request', function() {
- url = base + '/inspect';
- opts = {
- method: 'POST'
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.headers['transfer-encoding']).to.be.undefined;
- expect(res.headers['content-length']).to.equal('0');
- });
- });
- it('should allow POST request with string body', function() {
- url = base + '/inspect';
- opts = {
- method: 'POST'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.body).to.equal('a=1');
- expect(res.headers['transfer-encoding']).to.be.undefined;
- expect(res.headers['content-length']).to.equal('3');
- });
- });
- it('should allow POST request with buffer body', function() {
- url = base + '/inspect';
- opts = {
- method: 'POST'
- , body: new Buffer('a=1', 'utf-8')
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.body).to.equal('a=1');
- expect(res.headers['transfer-encoding']).to.equal('chunked');
- expect(res.headers['content-length']).to.be.undefined;
- });
- });
- it('should allow POST request with readable stream as body', function() {
- var body = resumer().queue('a=1').end();
- body = body.pipe(new stream.PassThrough());
- url = base + '/inspect';
- opts = {
- method: 'POST'
- , body: body
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.body).to.equal('a=1');
- expect(res.headers['transfer-encoding']).to.equal('chunked');
- expect(res.headers['content-length']).to.be.undefined;
- });
- });
- it('should allow POST request with form-data as body', function() {
- var form = new FormData();
- form.append('a','1');
- url = base + '/multipart';
- opts = {
- method: 'POST'
- , body: form
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.headers['content-type']).to.contain('multipart/form-data');
- expect(res.headers['content-length']).to.be.a('string');
- expect(res.body).to.equal('a=1');
- });
- });
- it('should allow POST request with form-data using stream as body', function() {
- var form = new FormData();
- form.append('my_field', fs.createReadStream('test/dummy.txt'));
- url = base + '/multipart';
- opts = {
- method: 'POST'
- , body: form
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.headers['content-type']).to.contain('multipart/form-data');
- expect(res.headers['content-length']).to.be.undefined;
- expect(res.body).to.contain('my_field=');
- });
- });
- it('should allow POST request with form-data as body and custom headers', function() {
- var form = new FormData();
- form.append('a','1');
- var headers = form.getHeaders();
- headers['b'] = '2';
- url = base + '/multipart';
- opts = {
- method: 'POST'
- , body: form
- , headers: headers
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.headers['content-type']).to.contain('multipart/form-data');
- expect(res.headers['content-length']).to.be.a('string');
- expect(res.headers.b).to.equal('2');
- expect(res.body).to.equal('a=1');
- });
- });
- it('should allow POST request with object body', function() {
- url = base + '/inspect';
- // note that fetch simply calls tostring on an object
- opts = {
- method: 'POST'
- , body: { a:1 }
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.body).to.equal('[object Object]');
- });
- });
- it('should allow PUT request', function() {
- url = base + '/inspect';
- opts = {
- method: 'PUT'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('PUT');
- expect(res.body).to.equal('a=1');
- });
- });
- it('should allow DELETE request', function() {
- url = base + '/inspect';
- opts = {
- method: 'DELETE'
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('DELETE');
- });
- });
- it('should allow POST request with string body', function() {
- url = base + '/inspect';
- opts = {
- method: 'POST'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('POST');
- expect(res.body).to.equal('a=1');
- expect(res.headers['transfer-encoding']).to.be.undefined;
- expect(res.headers['content-length']).to.equal('3');
- });
- });
- it('should allow DELETE request with string body', function() {
- url = base + '/inspect';
- opts = {
- method: 'DELETE'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('DELETE');
- expect(res.body).to.equal('a=1');
- expect(res.headers['transfer-encoding']).to.be.undefined;
- expect(res.headers['content-length']).to.equal('3');
- });
- });
- it('should allow PATCH request', function() {
- url = base + '/inspect';
- opts = {
- method: 'PATCH'
- , body: 'a=1'
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.method).to.equal('PATCH');
- expect(res.body).to.equal('a=1');
- });
- });
- it('should allow HEAD request', function() {
- url = base + '/hello';
- opts = {
- method: 'HEAD'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.status).to.equal(200);
- expect(res.statusText).to.equal('OK');
- expect(res.headers.get('content-type')).to.equal('text/plain');
- expect(res.body).to.be.an.instanceof(stream.Transform);
- return res.text();
- }).then(function(text) {
- expect(text).to.equal('');
- });
- });
- it('should allow HEAD request with content-encoding header', function() {
- url = base + '/error/404';
- opts = {
- method: 'HEAD'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.status).to.equal(404);
- expect(res.headers.get('content-encoding')).to.equal('gzip');
- return res.text();
- }).then(function(text) {
- expect(text).to.equal('');
- });
- });
- it('should allow OPTIONS request', function() {
- url = base + '/options';
- opts = {
- method: 'OPTIONS'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.status).to.equal(200);
- expect(res.statusText).to.equal('OK');
- expect(res.headers.get('allow')).to.equal('GET, HEAD, OPTIONS');
- expect(res.body).to.be.an.instanceof(stream.Transform);
- });
- });
- it('should reject decoding body twice', function() {
- url = base + '/plain';
- return fetch(url).then(function(res) {
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return res.text().then(function(result) {
- expect(res.bodyUsed).to.be.true;
- return expect(res.text()).to.eventually.be.rejectedWith(Error);
- });
- });
- });
- it('should support maximum response size, multiple chunk', function() {
- url = base + '/size/chunk';
- opts = {
- size: 5
- };
- return fetch(url, opts).then(function(res) {
- expect(res.status).to.equal(200);
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return expect(res.text()).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'max-size');
- });
- });
- it('should support maximum response size, single chunk', function() {
- url = base + '/size/long';
- opts = {
- size: 5
- };
- return fetch(url, opts).then(function(res) {
- expect(res.status).to.equal(200);
- expect(res.headers.get('content-type')).to.equal('text/plain');
- return expect(res.text()).to.eventually.be.rejected
- .and.be.an.instanceOf(FetchError)
- .and.have.property('type', 'max-size');
- });
- });
- it('should support encoding decode, xml dtd detect', function() {
- url = base + '/encoding/euc-jp';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- return res.text().then(function(result) {
- expect(result).to.equal('<?xml version="1.0" encoding="EUC-JP"?><title>日本語</title>');
- });
- });
- });
- it('should support encoding decode, content-type detect', function() {
- url = base + '/encoding/shift-jis';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- return res.text().then(function(result) {
- expect(result).to.equal('<div>日本語</div>');
- });
- });
- });
- it('should support encoding decode, html5 detect', function() {
- url = base + '/encoding/gbk';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- return res.text().then(function(result) {
- expect(result).to.equal('<meta charset="gbk"><div>中文</div>');
- });
- });
- });
- it('should support encoding decode, html4 detect', function() {
- url = base + '/encoding/gb2312';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- return res.text().then(function(result) {
- expect(result).to.equal('<meta http-equiv="Content-Type" content="text/html; charset=gb2312"><div>中文</div>');
- });
- });
- });
- it('should default to utf8 encoding', function() {
- url = base + '/encoding/utf8';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- expect(res.headers.get('content-type')).to.be.null;
- return res.text().then(function(result) {
- expect(result).to.equal('中文');
- });
- });
- });
- it('should support uncommon content-type order, charset in front', function() {
- url = base + '/encoding/order1';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- return res.text().then(function(result) {
- expect(result).to.equal('中文');
- });
- });
- });
- it('should support uncommon content-type order, end with qs', function() {
- url = base + '/encoding/order2';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- return res.text().then(function(result) {
- expect(result).to.equal('中文');
- });
- });
- });
- it('should support chunked encoding, html4 detect', function() {
- url = base + '/encoding/chunked';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- // because node v0.12 doesn't have str.repeat
- var padding = new Array(10 + 1).join('a');
- return res.text().then(function(result) {
- expect(result).to.equal(padding + '<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" /><div>日本語</div>');
- });
- });
- });
- it('should only do encoding detection up to 1024 bytes', function() {
- url = base + '/encoding/invalid';
- return fetch(url).then(function(res) {
- expect(res.status).to.equal(200);
- // because node v0.12 doesn't have str.repeat
- var padding = new Array(1200 + 1).join('a');
- return res.text().then(function(result) {
- expect(result).to.not.equal(padding + '中文');
- });
- });
- });
- it('should allow piping response body as stream', function(done) {
- url = base + '/hello';
- fetch(url).then(function(res) {
- expect(res.body).to.be.an.instanceof(stream.Transform);
- res.body.on('data', function(chunk) {
- if (chunk === null) {
- return;
- }
- expect(chunk.toString()).to.equal('world');
- });
- res.body.on('end', function() {
- done();
- });
- });
- });
- it('should allow cloning a response, and use both as stream', function(done) {
- url = base + '/hello';
- return fetch(url).then(function(res) {
- var counter = 0;
- var r1 = res.clone();
- expect(res.body).to.be.an.instanceof(stream.Transform);
- expect(r1.body).to.be.an.instanceof(stream.Transform);
- res.body.on('data', function(chunk) {
- if (chunk === null) {
- return;
- }
- expect(chunk.toString()).to.equal('world');
- });
- res.body.on('end', function() {
- counter++;
- if (counter == 2) {
- done();
- }
- });
- r1.body.on('data', function(chunk) {
- if (chunk === null) {
- return;
- }
- expect(chunk.toString()).to.equal('world');
- });
- r1.body.on('end', function() {
- counter++;
- if (counter == 2) {
- done();
- }
- });
- });
- });
- it('should allow cloning a json response and log it as text response', function() {
- url = base + '/json';
- return fetch(url).then(function(res) {
- var r1 = res.clone();
- return fetch.Promise.all([res.json(), r1.text()]).then(function(results) {
- expect(results[0]).to.deep.equal({name: 'value'});
- expect(results[1]).to.equal('{"name":"value"}');
- });
- });
- });
- it('should allow cloning a json response, and then log it as text response', function() {
- url = base + '/json';
- return fetch(url).then(function(res) {
- var r1 = res.clone();
- return res.json().then(function(result) {
- expect(result).to.deep.equal({name: 'value'});
- return r1.text().then(function(result) {
- expect(result).to.equal('{"name":"value"}');
- });
- });
- });
- });
- it('should allow cloning a json response, first log as text response, then return json object', function() {
- url = base + '/json';
- return fetch(url).then(function(res) {
- var r1 = res.clone();
- return r1.text().then(function(result) {
- expect(result).to.equal('{"name":"value"}');
- return res.json().then(function(result) {
- expect(result).to.deep.equal({name: 'value'});
- });
- });
- });
- });
- it('should not allow cloning a response after its been used', function() {
- url = base + '/hello';
- return fetch(url).then(function(res) {
- return res.text().then(function(result) {
- expect(function() {
- var r1 = res.clone();
- }).to.throw(Error);
- });
- })
- });
- it('should allow get all responses of a header', function() {
- url = base + '/cookie';
- return fetch(url).then(function(res) {
- expect(res.headers.get('set-cookie')).to.equal('a=1');
- expect(res.headers.get('Set-Cookie')).to.equal('a=1');
- expect(res.headers.getAll('set-cookie')).to.deep.equal(['a=1', 'b=1']);
- expect(res.headers.getAll('Set-Cookie')).to.deep.equal(['a=1', 'b=1']);
- });
- });
- it('should allow iterating through all headers', function() {
- var headers = new Headers({
- a: 1
- , b: [2, 3]
- , c: [4]
- });
- expect(headers).to.have.property('forEach');
- var result = [];
- headers.forEach(function(val, key) {
- result.push([key, val]);
- });
- expected = [
- ["a", "1"]
- , ["b", "2"]
- , ["b", "3"]
- , ["c", "4"]
- ];
- expect(result).to.deep.equal(expected);
- });
- it('should allow deleting header', function() {
- url = base + '/cookie';
- return fetch(url).then(function(res) {
- res.headers.delete('set-cookie');
- expect(res.headers.get('set-cookie')).to.be.null;
- expect(res.headers.getAll('set-cookie')).to.be.empty;
- });
- });
- it('should send request with connection keep-alive if agent is provided', function() {
- url = base + '/inspect';
- opts = {
- agent: new http.Agent({
- keepAlive: true
- })
- };
- return fetch(url, opts).then(function(res) {
- return res.json();
- }).then(function(res) {
- expect(res.headers['connection']).to.equal('keep-alive');
- });
- });
- it('should ignore unsupported attributes while reading headers', function() {
- var FakeHeader = function() {};
- // prototypes are ignored
- FakeHeader.prototype.z = 'fake';
- var res = new FakeHeader;
- // valid
- res.a = 'string';
- res.b = ['1','2'];
- res.c = '';
- res.d = [];
- // common mistakes, normalized
- res.e = 1;
- res.f = [1, 2];
- // invalid, ignored
- res.g = { a:1 };
- res.h = undefined;
- res.i = null;
- res.j = NaN;
- res.k = true;
- res.l = false;
- res.m = new Buffer('test');
- var h1 = new Headers(res);
- expect(h1._headers['a']).to.include('string');
- expect(h1._headers['b']).to.include('1');
- expect(h1._headers['b']).to.include('2');
- expect(h1._headers['c']).to.include('');
- expect(h1._headers['d']).to.be.undefined;
- expect(h1._headers['e']).to.include('1');
- expect(h1._headers['f']).to.include('1');
- expect(h1._headers['f']).to.include('2');
- expect(h1._headers['g']).to.be.undefined;
- expect(h1._headers['h']).to.be.undefined;
- expect(h1._headers['i']).to.be.undefined;
- expect(h1._headers['j']).to.be.undefined;
- expect(h1._headers['k']).to.be.undefined;
- expect(h1._headers['l']).to.be.undefined;
- expect(h1._headers['m']).to.be.undefined;
- expect(h1._headers['z']).to.be.undefined;
- });
- it('should wrap headers', function() {
- var h1 = new Headers({
- a: '1'
- });
- var h2 = new Headers(h1);
- h2.set('b', '1');
- var h3 = new Headers(h2);
- h3.append('a', '2');
- expect(h1._headers['a']).to.include('1');
- expect(h1._headers['a']).to.not.include('2');
- expect(h2._headers['a']).to.include('1');
- expect(h2._headers['a']).to.not.include('2');
- expect(h2._headers['b']).to.include('1');
- expect(h3._headers['a']).to.include('1');
- expect(h3._headers['a']).to.include('2');
- expect(h3._headers['b']).to.include('1');
- });
- it('should support fetch with Request instance', function() {
- url = base + '/hello';
- var req = new Request(url);
- return fetch(req).then(function(res) {
- expect(res.url).to.equal(url);
- expect(res.ok).to.be.true;
- expect(res.status).to.equal(200);
- });
- });
- it('should support wrapping Request instance', function() {
- url = base + '/hello';
- var form = new FormData();
- form.append('a', '1');
- var r1 = new Request(url, {
- method: 'POST'
- , follow: 1
- , body: form
- });
- var r2 = new Request(r1, {
- follow: 2
- });
- expect(r2.url).to.equal(url);
- expect(r2.method).to.equal('POST');
- // note that we didn't clone the body
- expect(r2.body).to.equal(form);
- expect(r1.follow).to.equal(1);
- expect(r2.follow).to.equal(2);
- expect(r1.counter).to.equal(0);
- expect(r2.counter).to.equal(0);
- });
- it('should support overwrite Request instance', function() {
- url = base + '/inspect';
- var req = new Request(url, {
- method: 'POST'
- , headers: {
- a: '1'
- }
- });
- return fetch(req, {
- method: 'GET'
- , headers: {
- a: '2'
- }
- }).then(function(res) {
- return res.json();
- }).then(function(body) {
- expect(body.method).to.equal('GET');
- expect(body.headers.a).to.equal('2');
- });
- });
- it('should support empty options in Response constructor', function() {
- var body = resumer().queue('a=1').end();
- body = body.pipe(new stream.PassThrough());
- var res = new Response(body);
- return res.text().then(function(result) {
- expect(result).to.equal('a=1');
- });
- });
- it('should support parsing headers in Response constructor', function() {
- var res = new Response(null, {
- headers: {
- a: '1'
- }
- });
- expect(res.headers.get('a')).to.equal('1');
- });
- it('should support text() method in Response constructor', function() {
- var res = new Response('a=1');
- return res.text().then(function(result) {
- expect(result).to.equal('a=1');
- });
- });
- it('should support json() method in Response constructor', function() {
- var res = new Response('{"a":1}');
- return res.json().then(function(result) {
- expect(result.a).to.equal(1);
- });
- });
- it('should support buffer() method in Response constructor', function() {
- var res = new Response('a=1');
- return res.buffer().then(function(result) {
- expect(result.toString()).to.equal('a=1');
- });
- });
- it('should support clone() method in Response constructor', function() {
- var body = resumer().queue('a=1').end();
- body = body.pipe(new stream.PassThrough());
- var res = new Response(body, {
- headers: {
- a: '1'
- }
- , url: base
- , status: 346
- , statusText: 'production'
- });
- var cl = res.clone();
- expect(cl.headers.get('a')).to.equal('1');
- expect(cl.url).to.equal(base);
- expect(cl.status).to.equal(346);
- expect(cl.statusText).to.equal('production');
- expect(cl.ok).to.be.false;
- // clone body shouldn't be the same body
- expect(cl.body).to.not.equal(body);
- return cl.text().then(function(result) {
- expect(result).to.equal('a=1');
- });
- });
- it('should support stream as body in Response constructor', function() {
- var body = resumer().queue('a=1').end();
- body = body.pipe(new stream.PassThrough());
- var res = new Response(body);
- return res.text().then(function(result) {
- expect(result).to.equal('a=1');
- });
- });
- it('should support string as body in Response constructor', function() {
- var res = new Response('a=1');
- return res.text().then(function(result) {
- expect(result).to.equal('a=1');
- });
- });
- it('should support buffer as body in Response constructor', function() {
- var res = new Response(new Buffer('a=1'));
- return res.text().then(function(result) {
- expect(result).to.equal('a=1');
- });
- });
- it('should default to 200 as status code', function() {
- var res = new Response(null);
- expect(res.status).to.equal(200);
- });
- it('should support parsing headers in Request constructor', function() {
- url = base;
- var req = new Request(url, {
- headers: {
- a: '1'
- }
- });
- expect(req.url).to.equal(url);
- expect(req.headers.get('a')).to.equal('1');
- });
- it('should support text() method in Request constructor', function() {
- url = base;
- var req = new Request(url, {
- body: 'a=1'
- });
- expect(req.url).to.equal(url);
- return req.text().then(function(result) {
- expect(result).to.equal('a=1');
- });
- });
- it('should support json() method in Request constructor', function() {
- url = base;
- var req = new Request(url, {
- body: '{"a":1}'
- });
- expect(req.url).to.equal(url);
- return req.json().then(function(result) {
- expect(result.a).to.equal(1);
- });
- });
- it('should support buffer() method in Request constructor', function() {
- url = base;
- var req = new Request(url, {
- body: 'a=1'
- });
- expect(req.url).to.equal(url);
- return req.buffer().then(function(result) {
- expect(result.toString()).to.equal('a=1');
- });
- });
- it('should support arbitrary url in Request constructor', function() {
- url = 'anything';
- var req = new Request(url);
- expect(req.url).to.equal('anything');
- });
- it('should support clone() method in Request constructor', function() {
- url = base;
- var body = resumer().queue('a=1').end();
- body = body.pipe(new stream.PassThrough());
- var agent = new http.Agent();
- var req = new Request(url, {
- body: body
- , method: 'POST'
- , redirect: 'manual'
- , headers: {
- b: '2'
- }
- , follow: 3
- , compress: false
- , agent: agent
- });
- var cl = req.clone();
- expect(cl.url).to.equal(url);
- expect(cl.method).to.equal('POST');
- expect(cl.redirect).to.equal('manual');
- expect(cl.headers.get('b')).to.equal('2');
- expect(cl.follow).to.equal(3);
- expect(cl.compress).to.equal(false);
- expect(cl.method).to.equal('POST');
- expect(cl.counter).to.equal(0);
- expect(cl.agent).to.equal(agent);
- // clone body shouldn't be the same body
- expect(cl.body).to.not.equal(body);
- return fetch.Promise.all([cl.text(), req.text()]).then(function(results) {
- expect(results[0]).to.equal('a=1');
- expect(results[1]).to.equal('a=1');
- });
- });
- it('should support text(), json() and buffer() method in Body constructor', function() {
- var body = new Body('a=1');
- expect(body).to.have.property('text');
- expect(body).to.have.property('json');
- expect(body).to.have.property('buffer');
- });
- it('should create custom FetchError', function funcName() {
- var systemError = new Error('system');
- systemError.code = 'ESOMEERROR';
- var err = new FetchError('test message', 'test-error', systemError);
- expect(err).to.be.an.instanceof(Error);
- expect(err).to.be.an.instanceof(FetchError);
- expect(err.name).to.equal('FetchError');
- expect(err.message).to.equal('test message');
- expect(err.type).to.equal('test-error');
- expect(err.code).to.equal('ESOMEERROR');
- expect(err.errno).to.equal('ESOMEERROR');
- expect(err.stack).to.include('funcName');
- expect(err.stack.split('\n')[0]).to.equal(err.name + ': ' + err.message);
- });
- it('should support https request', function() {
- this.timeout(5000);
- url = 'https://github.com/';
- opts = {
- method: 'HEAD'
- };
- return fetch(url, opts).then(function(res) {
- expect(res.status).to.equal(200);
- expect(res.ok).to.be.true;
- });
- });
- });
|