xhr_test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. // Copyright 2011 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.labs.net.xhrTest');
  15. goog.setTestOnly('goog.labs.net.xhrTest');
  16. goog.require('goog.Promise');
  17. goog.require('goog.events');
  18. goog.require('goog.events.EventType');
  19. goog.require('goog.labs.net.xhr');
  20. goog.require('goog.net.WrapperXmlHttpFactory');
  21. goog.require('goog.net.XmlHttp');
  22. goog.require('goog.testing.MockClock');
  23. goog.require('goog.testing.TestCase');
  24. goog.require('goog.testing.jsunit');
  25. goog.require('goog.userAgent');
  26. /** Path to a small download target used for testing binary requests. */
  27. var TEST_IMAGE = 'testdata/cleardot.gif';
  28. /** The expected bytes of the test image. */
  29. var TEST_IMAGE_BYTES = [
  30. 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80,
  31. 0xFF, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04,
  32. 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01,
  33. 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3B
  34. ];
  35. function setUpPage() {
  36. goog.testing.TestCase.getActiveTestCase().promiseTimeout = 10000; // 10s
  37. }
  38. function stubXhrToReturn(status, opt_responseText, opt_latency) {
  39. if (goog.isDefAndNotNull(opt_latency)) {
  40. mockClock = new goog.testing.MockClock(true);
  41. }
  42. var stubXhr = {
  43. sent: false,
  44. aborted: false,
  45. status: 0,
  46. headers: {},
  47. open: function(method, url, async) {
  48. this.method = method;
  49. this.url = url;
  50. this.async = async;
  51. },
  52. setRequestHeader: function(key, value) { this.headers[key] = value; },
  53. overrideMimeType: function(mimeType) { this.mimeType = mimeType; },
  54. abort: function() {
  55. this.aborted = true;
  56. this.load(0);
  57. },
  58. send: function(data) {
  59. this.data = data;
  60. this.sent = true;
  61. // Fulfill the send asynchronously, or possibly with the MockClock.
  62. window.setTimeout(goog.bind(this.load, this, status), opt_latency || 0);
  63. if (mockClock) {
  64. mockClock.tick(opt_latency);
  65. }
  66. },
  67. load: function(status) {
  68. this.status = status;
  69. if (goog.isDefAndNotNull(opt_responseText)) {
  70. this.responseText = opt_responseText;
  71. }
  72. this.readyState = 4;
  73. if (this.onreadystatechange) this.onreadystatechange();
  74. }
  75. };
  76. stubXmlHttpWith(stubXhr);
  77. }
  78. function stubXhrToThrow(err) {
  79. stubXmlHttpWith(buildThrowingStubXhr(err));
  80. }
  81. function buildThrowingStubXhr(err) {
  82. return {
  83. sent: false,
  84. aborted: false,
  85. status: 0,
  86. headers: {},
  87. open: function(method, url, async) {
  88. this.method = method;
  89. this.url = url;
  90. this.async = async;
  91. },
  92. setRequestHeader: function(key, value) { this.headers[key] = value; },
  93. overrideMimeType: function(mimeType) { this.mimeType = mimeType; },
  94. send: function(data) { throw err; }
  95. };
  96. }
  97. function stubXmlHttpWith(stubXhr) {
  98. goog.net.XmlHttp = function() { return stubXhr; };
  99. for (var x in originalXmlHttp) {
  100. goog.net.XmlHttp[x] = originalXmlHttp[x];
  101. }
  102. }
  103. var xhr = goog.labs.net.xhr;
  104. var originalXmlHttp = goog.net.XmlHttp;
  105. var mockClock;
  106. function tearDown() {
  107. if (mockClock) {
  108. mockClock.dispose();
  109. mockClock = null;
  110. }
  111. goog.net.XmlHttp = originalXmlHttp;
  112. }
  113. /**
  114. * Tests whether the test was loaded from a file: protocol. Tests that use a
  115. * real network request cannot be run from the local file system due to
  116. * cross-origin restrictions, but will run if the tests are hosted on a server.
  117. * A log message is added to the test case to warn users that the a test was
  118. * skipped.
  119. *
  120. * @return {boolean} Whether the test is running on a local file system.
  121. */
  122. function isRunningLocally() {
  123. if (window.location.protocol == 'file:') {
  124. var testCase = goog.global['G_testRunner'].testCase;
  125. testCase.saveMessage('Test skipped while running on local file system.');
  126. return true;
  127. }
  128. return false;
  129. }
  130. function testSimpleRequest() {
  131. if (isRunningLocally()) return;
  132. return xhr.send('GET', 'testdata/xhr_test_text.data').then(function(xhr) {
  133. assertEquals('Just some data.', xhr.responseText);
  134. assertEquals(200, xhr.status);
  135. });
  136. }
  137. function testGetText() {
  138. if (isRunningLocally()) return;
  139. return xhr.get('testdata/xhr_test_text.data').then(function(responseText) {
  140. assertEquals('Just some data.', responseText);
  141. });
  142. }
  143. function testGetTextWithJson() {
  144. if (isRunningLocally()) return;
  145. return xhr.get('testdata/xhr_test_json.data').then(function(responseText) {
  146. assertEquals('while(1);\n{"stat":"ok","count":12345}\n', responseText);
  147. });
  148. }
  149. function testPostText() {
  150. if (isRunningLocally()) return;
  151. return xhr.post('testdata/xhr_test_text.data', 'post-data')
  152. .then(function(responseText) {
  153. // No good way to test post-data gets transported.
  154. assertEquals('Just some data.', responseText);
  155. });
  156. }
  157. function testGetJson() {
  158. if (isRunningLocally()) return;
  159. return xhr.getJson('testdata/xhr_test_json.data', {xssiPrefix: 'while(1);\n'})
  160. .then(function(responseObj) {
  161. assertEquals('ok', responseObj['stat']);
  162. assertEquals(12345, responseObj['count']);
  163. });
  164. }
  165. function testGetBlob() {
  166. if (isRunningLocally()) return;
  167. // IE9 and earlier do not support blobs.
  168. if (!('Blob' in goog.global)) {
  169. var err = assertThrows(function() { xhr.getBlob(TEST_IMAGE); });
  170. assertEquals(
  171. 'Assertion failed: getBlob is not supported in this browser.',
  172. err.message);
  173. return;
  174. }
  175. var options = {withCredentials: true};
  176. return xhr.getBlob(TEST_IMAGE, options)
  177. .then(function(blob) {
  178. var reader = new FileReader();
  179. return new goog.Promise(function(resolve, reject) {
  180. goog.events.listenOnce(reader, goog.events.EventType.LOAD, resolve);
  181. reader.readAsArrayBuffer(blob);
  182. });
  183. })
  184. .then(function(e) {
  185. assertElementsEquals(TEST_IMAGE_BYTES, new Uint8Array(e.target.result));
  186. assertObjectEquals(
  187. 'input options should not have mutated.', {withCredentials: true},
  188. options);
  189. });
  190. }
  191. function testGetBytes() {
  192. if (isRunningLocally()) return;
  193. // IE8 requires a VBScript fallback to read the bytes from the response.
  194. if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
  195. var err = assertThrows(function() { xhr.getBytes(TEST_IMAGE); });
  196. assertEquals(
  197. 'Assertion failed: getBytes is not supported in this browser.',
  198. err.message);
  199. return;
  200. }
  201. var options = {withCredentials: true};
  202. return xhr.getBytes(TEST_IMAGE).then(function(bytes) {
  203. assertElementsEquals(TEST_IMAGE_BYTES, bytes);
  204. assertObjectEquals(
  205. 'input options should not have mutated.', {withCredentials: true},
  206. options);
  207. });
  208. }
  209. function testSerialRequests() {
  210. if (isRunningLocally()) return;
  211. return xhr.get('testdata/xhr_test_text.data')
  212. .then(function(response) {
  213. return xhr.getJson(
  214. 'testdata/xhr_test_json.data', {xssiPrefix: 'while(1);\n'});
  215. })
  216. .then(function(responseObj) {
  217. // Data that comes through to callbacks should be from the 2nd request.
  218. assertEquals('ok', responseObj['stat']);
  219. assertEquals(12345, responseObj['count']);
  220. });
  221. }
  222. function testBadUrlDetectedAsError() {
  223. if (isRunningLocally()) return;
  224. return xhr.getJson('unknown-file.dat')
  225. .then(fail /* opt_onFulfilled */, function(err) {
  226. assertTrue(
  227. 'Error should be an HTTP error', err instanceof xhr.HttpError);
  228. assertEquals(404, err.status);
  229. assertNotNull(err.xhr);
  230. });
  231. }
  232. function testBadOriginTriggersOnErrorHandler() {
  233. if (goog.userAgent.EDGE) return; // failing b/62677027
  234. return xhr.get('http://www.google.com')
  235. .then(
  236. function() {
  237. fail(
  238. 'XHR to http://www.google.com should\'ve failed due to ' +
  239. 'same-origin policy.');
  240. } /* opt_onFulfilled */,
  241. function(err) {
  242. // In IE this will be a goog.labs.net.xhr.Error since it is thrown
  243. // when calling xhr.open(), other browsers will raise an HttpError.
  244. assertTrue(
  245. 'Error should be an xhr error', err instanceof xhr.Error);
  246. assertNotNull(err.xhr);
  247. });
  248. }
  249. //============================================================================
  250. // The following tests use a stubbed out XMLHttpRequest.
  251. //============================================================================
  252. function testSendNoOptions() {
  253. var called = false;
  254. stubXhrToReturn(200);
  255. assertFalse('Callback should not yet have been called', called);
  256. return xhr.send('GET', 'test-url', null).then(function(stubXhr) {
  257. called = true;
  258. assertEquals('GET', stubXhr.method);
  259. assertEquals('test-url', stubXhr.url);
  260. });
  261. }
  262. function testSendPostSetsDefaultHeader() {
  263. stubXhrToReturn(200);
  264. return xhr.send('POST', 'test-url', null).then(function(stubXhr) {
  265. assertEquals('POST', stubXhr.method);
  266. assertEquals('test-url', stubXhr.url);
  267. assertEquals(
  268. 'application/x-www-form-urlencoded;charset=utf-8',
  269. stubXhr.headers['Content-Type']);
  270. });
  271. }
  272. function testSendPostDoesntSetHeaderWithFormData() {
  273. if (!goog.global['FormData']) {
  274. return;
  275. }
  276. var formData = new goog.global['FormData']();
  277. formData.append('name', 'value');
  278. stubXhrToReturn(200);
  279. return xhr.send('POST', 'test-url', formData).then(function(stubXhr) {
  280. assertEquals('POST', stubXhr.method);
  281. assertEquals('test-url', stubXhr.url);
  282. assertEquals(undefined, stubXhr.headers['Content-Type']);
  283. });
  284. }
  285. function testSendPostHeaders() {
  286. stubXhrToReturn(200);
  287. return xhr
  288. .send(
  289. 'POST', 'test-url', null,
  290. {headers: {'Content-Type': 'text/plain', 'X-Made-Up': 'FooBar'}})
  291. .then(function(stubXhr) {
  292. assertEquals('POST', stubXhr.method);
  293. assertEquals('test-url', stubXhr.url);
  294. assertEquals('text/plain', stubXhr.headers['Content-Type']);
  295. assertEquals('FooBar', stubXhr.headers['X-Made-Up']);
  296. });
  297. }
  298. function testSendPostHeadersWithFormData() {
  299. if (!goog.global['FormData']) {
  300. return;
  301. }
  302. var formData = new goog.global['FormData']();
  303. formData.append('name', 'value');
  304. stubXhrToReturn(200);
  305. return xhr
  306. .send(
  307. 'POST', 'test-url', formData,
  308. {headers: {'Content-Type': 'text/plain', 'X-Made-Up': 'FooBar'}})
  309. .then(function(stubXhr) {
  310. assertEquals('POST', stubXhr.method);
  311. assertEquals('test-url', stubXhr.url);
  312. assertEquals('text/plain', stubXhr.headers['Content-Type']);
  313. assertEquals('FooBar', stubXhr.headers['X-Made-Up']);
  314. });
  315. }
  316. function testSendNullPostHeaders() {
  317. stubXhrToReturn(200);
  318. return xhr
  319. .send('POST', 'test-url', null, {
  320. headers:
  321. {'Content-Type': null, 'X-Made-Up': 'FooBar', 'Y-Made-Up': null}
  322. })
  323. .then(function(stubXhr) {
  324. assertEquals('POST', stubXhr.method);
  325. assertEquals('test-url', stubXhr.url);
  326. assertEquals(undefined, stubXhr.headers['Content-Type']);
  327. assertEquals('FooBar', stubXhr.headers['X-Made-Up']);
  328. assertEquals(undefined, stubXhr.headers['Y-Made-Up']);
  329. });
  330. }
  331. function testSendNullPostHeadersWithFormData() {
  332. if (!goog.global['FormData']) {
  333. return;
  334. }
  335. var formData = new goog.global['FormData']();
  336. formData.append('name', 'value');
  337. stubXhrToReturn(200);
  338. return xhr
  339. .send('POST', 'test-url', formData, {
  340. headers:
  341. {'Content-Type': null, 'X-Made-Up': 'FooBar', 'Y-Made-Up': null}
  342. })
  343. .then(function(stubXhr) {
  344. assertEquals('POST', stubXhr.method);
  345. assertEquals('test-url', stubXhr.url);
  346. assertEquals(undefined, stubXhr.headers['Content-Type']);
  347. assertEquals('FooBar', stubXhr.headers['X-Made-Up']);
  348. assertEquals(undefined, stubXhr.headers['Y-Made-Up']);
  349. });
  350. }
  351. function testSendWithCredentials() {
  352. stubXhrToReturn(200);
  353. return xhr.send('POST', 'test-url', null, {withCredentials: true})
  354. .then(function(stubXhr) {
  355. assertTrue('XHR should have been sent', stubXhr.sent);
  356. assertTrue(stubXhr.withCredentials);
  357. });
  358. }
  359. function testSendWithMimeType() {
  360. stubXhrToReturn(200);
  361. return xhr.send('POST', 'test-url', null, {mimeType: 'text/plain'})
  362. .then(function(stubXhr) {
  363. assertTrue('XHR should have been sent', stubXhr.sent);
  364. assertEquals('text/plain', stubXhr.mimeType);
  365. });
  366. }
  367. function testSendWithHttpError() {
  368. stubXhrToReturn(500);
  369. return xhr.send('POST', 'test-url', null)
  370. .then(fail /* opt_onResolved */, function(err) {
  371. assertTrue(err instanceof xhr.HttpError);
  372. assertTrue(err.xhr.sent);
  373. assertEquals(500, err.status);
  374. });
  375. }
  376. function testSendWithTimeoutNotHit() {
  377. stubXhrToReturn(200, null /* opt_responseText */, 1400 /* opt_latency */);
  378. return xhr.send('POST', 'test-url', null, {timeoutMs: 1500})
  379. .then(function(stubXhr) {
  380. assertTrue(mockClock.getTimeoutsMade() > 0);
  381. assertTrue('XHR should have been sent', stubXhr.sent);
  382. assertFalse('XHR should not have been aborted', stubXhr.aborted);
  383. });
  384. }
  385. function testSendWithTimeoutHit() {
  386. stubXhrToReturn(200, null /* opt_responseText */, 50 /* opt_latency */);
  387. return xhr.send('POST', 'test-url', null, {timeoutMs: 50})
  388. .then(fail /* opt_onResolved */, function(err) {
  389. assertTrue('XHR should have been sent', err.xhr.sent);
  390. assertTrue('XHR should have been aborted', err.xhr.aborted);
  391. assertTrue(err instanceof xhr.TimeoutError);
  392. });
  393. }
  394. function testCancelRequest() {
  395. stubXhrToReturn(200);
  396. var promise =
  397. xhr.send('GET', 'test-url')
  398. .then(fail /* opt_onResolved */, function(error) {
  399. assertTrue(error instanceof goog.Promise.CancellationError);
  400. return null; // Return a non-error value for the test runner.
  401. });
  402. promise.cancel();
  403. return promise;
  404. }
  405. function testGetJson() {
  406. var stubXhr = stubXhrToReturn(200, '{"a": 1, "b": 2}');
  407. xhr.getJson('test-url').then(function(responseObj) {
  408. assertObjectEquals({a: 1, b: 2}, responseObj);
  409. });
  410. }
  411. function testGetJsonWithXssiPrefix() {
  412. stubXhrToReturn(200, 'while(1);\n{"a": 1, "b": 2}');
  413. return xhr.getJson('test-url', {xssiPrefix: 'while(1);\n'})
  414. .then(function(responseObj) {
  415. assertObjectEquals({a: 1, b: 2}, responseObj);
  416. });
  417. }
  418. function testSendWithClientException() {
  419. stubXhrToThrow(new Error('CORS XHR with file:// schemas not allowed.'));
  420. return xhr.send('POST', 'file://test-url', null)
  421. .then(fail /* opt_onResolved */, function(err) {
  422. assertFalse('XHR should not have been sent', err.xhr.sent);
  423. assertTrue(err instanceof Error);
  424. assertTrue(
  425. /CORS XHR with file:\/\/ schemas not allowed./.test(err.message));
  426. });
  427. }
  428. function testSendWithFactory() {
  429. stubXhrToReturn(200);
  430. var options = {
  431. xmlHttpFactory: new goog.net.WrapperXmlHttpFactory(
  432. goog.partial(buildThrowingStubXhr, new Error('Bad factory')),
  433. goog.net.XmlHttp.getOptions)
  434. };
  435. return xhr.send('POST', 'file://test-url', null, options)
  436. .then(fail /* opt_onResolved */, function(err) {
  437. assertTrue(err instanceof Error);
  438. });
  439. }