// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview */ (function() { 'use strict'; /** @type {sinon.FakeXhrCtrl} */ var fakeXhrCtrl; /** @type {sinon.FakeXhr} */ var fakeXhr; QUnit.module('xhr', { beforeEach: function() { fakeXhr = null; fakeXhrCtrl = sinon.useFakeXMLHttpRequest(); fakeXhrCtrl.onCreate = function(/** sinon.FakeXhr */ xhr) { fakeXhr = xhr; }; remoting.identity = new remoting.Identity(); chromeMocks.identity.mock$setToken('my_token'); }, afterEach: function() { remoting.identity = null; fakeXhrCtrl.restore(); } }); QUnit.test('urlencodeParamHash', function(assert) { assert.equal( remoting.Xhr.urlencodeParamHash({}), ''); assert.equal( remoting.Xhr.urlencodeParamHash({'key': 'value'}), 'key=value'); assert.equal( remoting.Xhr.urlencodeParamHash({'key /?=&': 'value /?=&'}), 'key%20%2F%3F%3D%26=value%20%2F%3F%3D%26'); assert.equal( remoting.Xhr.urlencodeParamHash({'k1': 'v1', 'k2': 'v2'}), 'k1=v1&k2=v2'); }); // // Test for what happens when the parameters are specified // incorrectly. // QUnit.test('invalid *content parameters', function(assert) { assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', jsonContent: {}, formContent: {} }); }); assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', textContent: '', formContent: {} }); }); assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', textContent: '', jsonContent: {} }); }); }); QUnit.test('invalid URL parameters', function(assert) { assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com?', urlParams: {} }); }); assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com#', urlParams: {} }); }); }); QUnit.test('invalid auth parameters', function(assert) { assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', useIdentity: false, oauthToken: '', headers: { 'Authorization': '' } }); }); assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', useIdentity: true, headers: { 'Authorization': '' } }); }); assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', useIdentity: true, oauthToken: '', headers: {} }); }); }); QUnit.test('invalid auth parameters', function(assert) { assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', useIdentity: false, oauthToken: '', headers: { 'Authorization': '' } }); }); assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', useIdentity: true, headers: { 'Authorization': '' } }); }); assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', useIdentity: true, oauthToken: '', headers: {} }); }); }); QUnit.test('unexpected parameters', function(assert) { assert.throws(function() { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', xyzzy: 'not a real parameter' }); }); }); // // The typical case. // QUnit.test('successful GET', function(assert) { var promise = new remoting.Xhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(!response.isError()); assert.equal(response.status, 200); assert.equal(response.getText(), 'body'); }); assert.equal(fakeXhr.method, 'GET'); assert.equal(fakeXhr.url, 'http://foo.com'); assert.equal(fakeXhr.withCredentials, false); assert.equal(fakeXhr.requestBody, null); assert.ok(!('Content-type' in fakeXhr.requestHeaders)); fakeXhr.respond(200, {}, 'body'); return promise; }); // // Tests for the effect of acceptJson. // QUnit.test('acceptJson required', function(assert) { var promise = new remoting.Xhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.throws(response.getJson); }); fakeXhr.respond(200, {}, '{}'); return promise; }); QUnit.test('JSON response', function(assert) { var responseJson = { 'myJsonData': [true] }; var responseText = JSON.stringify(responseJson); var promise = new remoting.Xhr({ method: 'GET', url: 'http://foo.com', acceptJson: true }).start().then(function(response) { // Calling getText is still OK even when a JSON response is // requested. assert.equal( response.getText(), responseText); // Check that getJson works as advertised. assert.deepEqual( response.getJson(), responseJson); // Calling getJson multiple times doesn't re-parse the response. assert.strictEqual( response.getJson(), response.getJson()); }); fakeXhr.respond(200, {}, responseText); return promise; }); // // Tests for various parameters that modify the HTTP request. // QUnit.test('GET with param string', function(assert) { new remoting.Xhr({ method: 'GET', url: 'http://foo.com', urlParams: 'the_param_string' }).start(); assert.equal(fakeXhr.url, 'http://foo.com?the_param_string'); }); QUnit.test('GET with param object', function(assert) { new remoting.Xhr({ method: 'GET', url: 'http://foo.com', urlParams: {'a': 'b', 'c': 'd'} }).start(); assert.equal(fakeXhr.url, 'http://foo.com?a=b&c=d'); }); QUnit.test('GET with headers', function(assert) { new remoting.Xhr({ method: 'GET', url: 'http://foo.com', headers: {'Header1': 'headerValue1', 'Header2': 'headerValue2'} }).start(); assert.equal( fakeXhr.requestHeaders['Header1'], 'headerValue1'); assert.equal( fakeXhr.requestHeaders['Header2'], 'headerValue2'); }); QUnit.test('GET with credentials', function(assert) { new remoting.Xhr({ method: 'GET', url: 'http://foo.com', withCredentials: true }).start(); assert.equal(fakeXhr.withCredentials, true); }); // // Checking that typical POST requests work. // QUnit.test('POST with text content', function(assert) { var done = assert.async(); var promise = new remoting.Xhr({ method: 'POST', url: 'http://foo.com', textContent: 'the_content_string' }).start().then(function(response) { assert.equal(response.status, 200); assert.equal(response.getText(), 'body'); done(); }); assert.equal(fakeXhr.method, 'POST'); assert.equal(fakeXhr.url, 'http://foo.com'); assert.equal(fakeXhr.withCredentials, false); assert.equal(fakeXhr.requestBody, 'the_content_string'); assert.equal(fakeXhr.requestHeaders['Content-type'], 'text/plain; charset=UTF-8'); fakeXhr.respond(200, {}, 'body'); return promise; }); QUnit.test('POST with form content', function(assert) { new remoting.Xhr({ method: 'POST', url: 'http://foo.com', formContent: {'a': 'b', 'c': 'd'} }).start(); assert.equal(fakeXhr.requestBody, 'a=b&c=d'); assert.equal( fakeXhr.requestHeaders['Content-type'], 'application/x-www-form-urlencoded; charset=UTF-8'); }); // // Tests for authentication-related options. // QUnit.test('GET with auth token', function(assert) { new remoting.Xhr({ method: 'GET', url: 'http://foo.com', oauthToken: 'my_token' }).start(); assert.equal(fakeXhr.requestHeaders['Authorization'], 'Bearer my_token'); }); QUnit.test('GET with useIdentity', function(assert) { var xhr = new remoting.Xhr({ method: 'GET', url: 'http://foo.com', useIdentity: true }); xhr.start(); var done = assert.async(); fakeXhr.addEventListener('loadstart', function() { assert.equal(fakeXhr.requestHeaders['Authorization'], 'Bearer my_token'); done(); }); }); // // Error responses. // QUnit.test('GET with error response', function(assert) { var promise = new remoting.Xhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(response.isError()); assert.equal(response.status, 500); assert.equal(response.getText(), 'body'); }); fakeXhr.respond(500, {}, 'body'); return promise; }); QUnit.test('204 is not an error', function(assert) { var promise = new remoting.Xhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(!response.isError()); assert.equal(response.status, 204); assert.equal(response.getText(), ''); }); fakeXhr.respond(204, {}, null); return promise; }); QUnit.test('GET with non-HTTP response', function(assert) { var promise = new remoting.Xhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(response.isError()); }); fakeXhr.respond(0, {}, null); return promise; }); QUnit.module('AutoRetryXhr', { beforeEach: function() { fakeXhr = null; fakeXhrCtrl = sinon.useFakeXMLHttpRequest(); fakeXhrCtrl.onCreate = function(/** sinon.FakeXhr */ xhr) { fakeXhr = xhr; }; }, afterEach: function() { fakeXhrCtrl.restore(); } }); /** * A class allows you to specify a sequence of canned responses, to be returned * in order in response to XHRs. * * @param {Array} cannedResponses * @constructor */ var CannedXhrResponder = function(cannedResponses) { /** @private */ this.mockXhr_ = sinon.useFakeXMLHttpRequest(); this.cannedResponses_ = cannedResponses; this.mockXhr_.onCreate = this.onCreate_.bind(this); }; /** @private */ CannedXhrResponder.prototype.onCreate_ = function(/** sinon.FakeXhr */ xhr) { var that = this; var response = that.cannedResponses_.shift(); Promise.resolve().then(function() { xhr.respond.apply(xhr, response); }); }; CannedXhrResponder.prototype.dispose = function() { this.mockXhr_.restore(); }; QUnit.test('retries on status code equals 0', /** * 'this' is not defined for jscompile, so it can't figure out the type of * this.clock. * @suppress {reportUnknownTypes|checkVars|checkTypes} */ function(assert) { this.clock.restore(); fakeXhrCtrl.restore(); var responder = new CannedXhrResponder([ [0, {}, null], [0, {}, null], [200, {}, 'body'] ]); var promise = new remoting.AutoRetryXhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(!response.isError()); }).catch(function(){ throw new Error('Expect retry to succeed.'); }); return promise; }); QUnit.test('respects opt_maxRetryAttempts', /** * 'this' is not defined for jscompile, so it can't figure out the type of * this.clock. * @suppress {reportUnknownTypes|checkVars|checkTypes} */ function(assert) { this.clock.restore(); fakeXhrCtrl.restore(); var responder = new CannedXhrResponder([ [0, {}, null], [0, {}, null], [0, {}, null], [200, {}, 'body'] ]); var promise = new remoting.AutoRetryXhr({ method: 'GET', url: 'http://foo.com' }, 2).start().then(function(response) { throw new Error('Expect retry to fail.'); }).catch(function(){ assert.ok(true); }); return promise; }); QUnit.test('does not retry when offline', function(assert) { var isOnlineStub = sinon.stub(base, 'isOnline'); isOnlineStub.returns(false); var promise = new remoting.AutoRetryXhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(false, 'Expect failure'); }).catch(function(/** remoting.Error */ error) { assert.equal(error.getTag(), remoting.Error.Tag.NETWORK_FAILURE); }); isOnlineStub.restore(); return promise; }); QUnit.test('resolves with successful responses', function(assert) { var promise = new remoting.AutoRetryXhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(!response.isError()); assert.equal(response.getText(), 'body'); }); fakeXhr.respond(200, {}, 'body'); return promise; }); QUnit.test('rejects with failed responses', function(assert) { var promise = new remoting.AutoRetryXhr({ method: 'GET', url: 'http://foo.com' }).start().then(function(response) { assert.ok(response.isError()); assert.equal(response.getText(), 'failure'); }); fakeXhr.respond(500, {}, 'failure'); return promise; }); })();