// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.net.XhrIoTest');
goog.setTestOnly('goog.net.XhrIoTest');

goog.require('goog.Uri');
goog.require('goog.debug.EntryPointMonitor');
goog.require('goog.debug.ErrorHandler');
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.events');
goog.require('goog.functions');
goog.require('goog.net.EventType');
goog.require('goog.net.WrapperXmlHttpFactory');
goog.require('goog.net.XhrIo');
goog.require('goog.net.XmlHttp');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.net.XhrIo');
goog.require('goog.testing.recordFunction');
goog.require('goog.userAgent.product');

function MockXmlHttp() {
  /**
   * The request headers for this XmlHttpRequest.
   * @type {!Object<string>}
   */
  this.requestHeaders = {};

  /**
   * The response headers for this XmlHttpRequest.
   * @type {!Object<string>}
   */
  this.responseHeaders = {};

  /** @type {string} */
  this.responseHeadersString = null;

  /**
   * The upload object associated with this XmlHttpRequest.
   * @type {!Object}
   */
  this.upload = {};
}

MockXmlHttp.prototype.readyState = goog.net.XmlHttp.ReadyState.UNINITIALIZED;

MockXmlHttp.prototype.status = 200;

MockXmlHttp.syncSend = false;

MockXmlHttp.prototype.send = function(opt_data) {
  this.readyState = goog.net.XmlHttp.ReadyState.UNINITIALIZED;

  if (MockXmlHttp.syncSend) {
    this.complete();
  }
};

MockXmlHttp.prototype.complete = function() {
  this.readyState = goog.net.XmlHttp.ReadyState.LOADING;
  this.onreadystatechange();

  this.readyState = goog.net.XmlHttp.ReadyState.LOADED;
  this.onreadystatechange();

  this.readyState = goog.net.XmlHttp.ReadyState.INTERACTIVE;
  this.onreadystatechange();

  this.readyState = goog.net.XmlHttp.ReadyState.COMPLETE;
  this.onreadystatechange();
};


MockXmlHttp.prototype.open = function(verb, uri, async) {};

MockXmlHttp.prototype.abort = function() {};

MockXmlHttp.prototype.setRequestHeader = function(key, value) {
  this.requestHeaders[key] = value;
};

/**
 * @param {string} key
 * @return {?string}
 */
MockXmlHttp.prototype.getResponseHeader = function(key) {
  return key in this.responseHeaders ? this.responseHeaders[key] : null;
};

/** @return {?string} */
MockXmlHttp.prototype.getAllResponseHeaders = function() {
  return this.responseHeadersString;
};

var lastMockXmlHttp;
goog.net.XmlHttp.setGlobalFactory(
    new goog.net.WrapperXmlHttpFactory(
        function() {
          lastMockXmlHttp = new MockXmlHttp();
          return lastMockXmlHttp;
        },
        function() { return {}; }));


var propertyReplacer = new goog.testing.PropertyReplacer();
var clock;
var originalEntryPoint = goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_;

function setUp() {
  lastMockXmlHttp = null;
  clock = new goog.testing.MockClock(true);
}

function tearDown() {
  MockXmlHttp.syncSend = false;
  propertyReplacer.reset();
  clock.dispose();
  goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = originalEntryPoint;
}


function testSyncSend() {
  if (goog.userAgent.product.SAFARI) {
    // TODO(b/20733468): Disabled so we can get the rest of the Closure test
    // suite running in a continuous build. Will investigate later.
    return;
  }

  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertTrue('Should be successful', e.target.isSuccess());
    count++;

  });

  var inSend = true;
  x.send('url');
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}

function testSyncSendFailure() {
  if (goog.userAgent.product.SAFARI) {
    // TODO(b/20733468): Disabled so we can get the rest of the Closure test
    // suite running in a continuous build. Will investigate later.
    return;
  }

  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertFalse('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send('url');
  lastMockXmlHttp.status = 404;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendRelativeZeroStatus() {
  if (goog.userAgent.product.SAFARI) {
    // TODO(b/20733468): Disabled so we can get the rest of the Closure test
    // suite running in a continuous build. Will investigate later.
    return;
  }

  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertEquals(
        'Should be the same as ', e.target.isSuccess(),
        window.location.href.toLowerCase().indexOf('file:') == 0);
    count++;
  });

  var inSend = true;
  x.send('relative');
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendRelativeUriZeroStatus() {
  if (goog.userAgent.product.SAFARI) {
    // TODO(b/20733468): Disabled so we can get the rest of the Closure test
    // suite running in a continuous build. Will investigate later.
    return;
  }

  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertEquals(
        'Should be the same as ', e.target.isSuccess(),
        window.location.href.toLowerCase().indexOf('file:') == 0);
    count++;
  });

  var inSend = true;
  x.send(goog.Uri.parse('relative'));
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendHttpZeroStatusFailure() {
  if (goog.userAgent.product.SAFARI) {
    // TODO(b/20733468): Disabled so we can get the rest of the Closure test
    // suite running in a continuous build. Will investigate later.
    return;
  }

  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertFalse('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send('http://foo');
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendHttpUpperZeroStatusFailure() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertFalse('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send('HTTP://foo');
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendHttpUpperUriZeroStatusFailure() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertFalse('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send(goog.Uri.parse('HTTP://foo'));
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendHttpUriZeroStatusFailure() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertFalse('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send(goog.Uri.parse('http://foo'));
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendHttpUriZeroStatusFailure() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertFalse('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send(goog.Uri.parse('HTTP://foo'));
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendHttpsZeroStatusFailure() {
  if (goog.userAgent.product.SAFARI) {
    // TODO(b/20733468): Disabled so we can get the rest of the Closure test
    // suite running in a continuous build. Will investigate later.
    return;
  }

  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertFalse('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send('https://foo');
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendFileUpperZeroStatusSuccess() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertTrue('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send('FILE:///foo');
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendFileUriZeroStatusSuccess() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertTrue('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send(goog.Uri.parse('file:///foo'));
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendDummyUriZeroStatusSuccess() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertTrue('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send(goog.Uri.parse('dummy:///foo'));
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendFileUpperUriZeroStatusSuccess() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertFalse('Should not fire complete from inside send', inSend);
    assertTrue('Should not be successful', e.target.isSuccess());
    count++;
  });

  var inSend = true;
  x.send(goog.Uri.parse('FILE:///foo'));
  lastMockXmlHttp.status = 0;
  inSend = false;

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testSendFromListener() {
  MockXmlHttp.syncSend = true;
  var count = 0;

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    count++;

    var e = assertThrows(function() { x.send('url2'); });
    assertEquals(
        '[goog.net.XhrIo] Object is active with another request=url' +
            '; newUri=url2',
        e.message);
  });

  x.send('url');

  clock.tick(1);  // callOnce(f, 0, ...)

  assertEquals('Complete should have been called once', 1, count);
}


function testStatesDuringEvents() {
  if (goog.userAgent.product.SAFARI) {
    // TODO(b/20733468): Disabled so we can get the rest of the Closure test
    // suite running in a continuous build. Will investigate later.
    return;
  }

  MockXmlHttp.syncSend = true;

  var x = new goog.net.XhrIo;
  var readyState = goog.net.XmlHttp.ReadyState.UNINITIALIZED;
  goog.events.listen(x, goog.net.EventType.READY_STATE_CHANGE, function(e) {
    readyState++;
    assertObjectEquals(e.target, x);
    assertEquals(x.getReadyState(), readyState);
    assertTrue(x.isActive());
  });
  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    assertObjectEquals(e.target, x);
    assertTrue(x.isActive());
  });
  goog.events.listen(x, goog.net.EventType.SUCCESS, function(e) {
    assertObjectEquals(e.target, x);
    assertTrue(x.isActive());
  });
  goog.events.listen(x, goog.net.EventType.READY, function(e) {
    assertObjectEquals(e.target, x);
    assertFalse(x.isActive());
  });

  x.send('url');

  clock.tick(1);  // callOnce(f, 0, ...)
}


function testProtectEntryPointCalledOnAsyncSend() {
  MockXmlHttp.syncSend = false;

  var errorHandlerCallbackCalled = false;
  var errorHandler = new goog.debug.ErrorHandler(function() {
    errorHandlerCallbackCalled = true;
  });

  goog.net.XhrIo.protectEntryPoints(errorHandler);

  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.READY_STATE_CHANGE, function(e) {
    throw new Error();
  });

  x.send('url');
  assertThrows(function() { lastMockXmlHttp.complete(); });

  assertTrue(
      'Error handler callback should be called on async send.',
      errorHandlerCallbackCalled);
}

function testXHRIsDiposedEvenIfAListenerThrowsAnExceptionOnComplete() {
  MockXmlHttp.syncSend = false;

  var x = new goog.net.XhrIo;

  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    throw new Error();
  }, false, x);

  x.send('url');
  assertThrows(function() { lastMockXmlHttp.complete(); });

  // The XHR should have been disposed, even though the listener threw an
  // exception.
  assertNull(x.xhr_);
}

function testDisposeInternalDoesNotAbortXhrRequestObjectWhenActiveIsFalse() {
  MockXmlHttp.syncSend = false;

  var xmlHttp = goog.net.XmlHttp;
  var abortCalled = false;
  var x = new goog.net.XhrIo;

  goog.net.XmlHttp.prototype.abort = function() { abortCalled = true; };

  goog.events.listen(x, goog.net.EventType.COMPLETE, function(e) {
    this.active_ = false;
    this.dispose();
  }, false, x);

  x.send('url');
  lastMockXmlHttp.complete();

  goog.net.XmlHttp = xmlHttp;
  assertFalse(abortCalled);
}

function testCallingAbortFromWithinAbortCallbackDoesntLoop() {
  var x = new goog.net.XhrIo;
  goog.events.listen(x, goog.net.EventType.ABORT, function(e) {
    x.abort();  // Shouldn't get a stack overflow
  });
  x.send('url');
  x.abort();
}

function testPostSetsContentTypeHeader() {
  var x = new goog.net.XhrIo;

  x.send('url', 'POST', 'content');
  var headers = lastMockXmlHttp.requestHeaders;
  assertEquals(1, goog.object.getCount(headers));
  assertEquals(
      headers[goog.net.XhrIo.CONTENT_TYPE_HEADER],
      goog.net.XhrIo.FORM_CONTENT_TYPE);
}

function testNonPostSetsContentTypeHeader() {
  var x = new goog.net.XhrIo;

  x.send('url', 'PUT', 'content');
  headers = lastMockXmlHttp.requestHeaders;
  assertEquals(1, goog.object.getCount(headers));
  assertEquals(
      headers[goog.net.XhrIo.CONTENT_TYPE_HEADER],
      goog.net.XhrIo.FORM_CONTENT_TYPE);
}

function testContentTypeIsTreatedCaseInsensitively() {
  var x = new goog.net.XhrIo;

  x.send('url', 'POST', 'content', {'content-type': 'testing'});

  assertObjectEquals(
      'Headers should not be modified since they already contain a ' +
          'content type definition',
      {'content-type': 'testing'}, lastMockXmlHttp.requestHeaders);
}

function testIsContentTypeHeader_() {
  assertTrue(goog.net.XhrIo.isContentTypeHeader_('content-type'));
  assertTrue(goog.net.XhrIo.isContentTypeHeader_('Content-type'));
  assertTrue(goog.net.XhrIo.isContentTypeHeader_('CONTENT-TYPE'));
  assertTrue(goog.net.XhrIo.isContentTypeHeader_('Content-Type'));
  assertFalse(goog.net.XhrIo.isContentTypeHeader_('Content Type'));
}

function testPostFormDataDoesNotSetContentTypeHeader() {
  function FakeFormData() {}

  propertyReplacer.set(goog.global, 'FormData', FakeFormData);

  var x = new goog.net.XhrIo;
  x.send('url', 'POST', new FakeFormData());
  var headers = lastMockXmlHttp.requestHeaders;
  assertTrue(goog.object.isEmpty(headers));
}

function testNonPostFormDataDoesNotSetContentTypeHeader() {
  function FakeFormData() {}

  propertyReplacer.set(goog.global, 'FormData', FakeFormData);

  var x = new goog.net.XhrIo;
  x.send('url', 'PUT', new FakeFormData());
  headers = lastMockXmlHttp.requestHeaders;
  assertTrue(goog.object.isEmpty(headers));
}

function testFactoryInjection() {
  var xhr = new MockXmlHttp();
  var optionsFactoryCalled = 0;
  var xhrFactoryCalled = 0;
  var wrapperFactory = new goog.net.WrapperXmlHttpFactory(
      function() {
        xhrFactoryCalled++;
        return xhr;
      },
      function() {
        optionsFactoryCalled++;
        return {};
      });
  var xhrIo = new goog.net.XhrIo(wrapperFactory);

  xhrIo.send('url');

  assertEquals('XHR factory should have been called', 1, xhrFactoryCalled);
  assertEquals(
      'Options factory should have been called', 1, optionsFactoryCalled);
}

function testGoogTestingNetXhrIoIsInSync() {
  var xhrIo = new goog.net.XhrIo();
  var testingXhrIo = new goog.testing.net.XhrIo();

  var propertyComparator = function(value, key, obj) {
    if (goog.string.endsWith(key, '_')) {
      // Ignore private properties/methods
      return true;
    } else if (typeof value == 'function' && typeof this[key] != 'function') {
      // Only type check is sufficient for functions
      fail(
          'Mismatched property:' + key + ': goog.net.XhrIo has:<' + value +
          '>; while goog.testing.net.XhrIo has:<' + this[key] + '>');
      return true;
    } else {
      // Ignore all other type of properties.
      return true;
    }
  };

  goog.object.every(xhrIo, propertyComparator, testingXhrIo);
}

function testEntryPointRegistry() {
  var monitor = new goog.debug.EntryPointMonitor();
  var replacement = function() {};
  monitor.wrap =
      goog.testing.recordFunction(goog.functions.constant(replacement));

  goog.debug.entryPointRegistry.monitorAll(monitor);
  assertTrue(monitor.wrap.getCallCount() >= 1);
  assertEquals(
      replacement, goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);
}

function testSetWithCredentials() {
  // Test on XHR objects that don't have the withCredentials property (older
  // browsers).
  var x = new goog.net.XhrIo;
  x.setWithCredentials(true);
  x.send('url');
  assertFalse(
      'withCredentials should not be set on an XHR object if the property ' +
          'does not exist.',
      goog.object.containsKey(lastMockXmlHttp, 'withCredentials'));

  // Test on XHR objects that have the withCredentials property.
  MockXmlHttp.prototype.withCredentials = false;
  x = new goog.net.XhrIo;
  x.setWithCredentials(true);
  x.send('url');
  assertTrue(
      'withCredentials should be set on an XHR object if the property exists',
      goog.object.containsKey(lastMockXmlHttp, 'withCredentials'));

  assertTrue(
      'withCredentials value not set on XHR object',
      lastMockXmlHttp.withCredentials);

  // Reset the prototype so it does not effect other tests.
  delete MockXmlHttp.prototype.withCredentials;
}

function testSetProgressEventsEnabled() {
  // The default MockXhr object contained by the XhrIo object has no
  // reference to the necessary onprogress field. This is equivalent
  // to a browser which does not support progress events.
  var progressNotSupported = new goog.net.XhrIo;
  progressNotSupported.setProgressEventsEnabled(true);
  assertTrue(progressNotSupported.getProgressEventsEnabled());
  progressNotSupported.send('url');
  assertUndefined(
      'Progress is not supported for downloads on this request.',
      progressNotSupported.xhr_.onprogress);
  assertUndefined(
      'Progress is not supported for uploads on this request.',
      progressNotSupported.xhr_.upload.onprogress);

  // The following tests will include the necessary onprogress fields
  // indicating progress events are supported.
  MockXmlHttp.prototype.onprogress = null;

  var progressDisabled = new goog.net.XhrIo;
  progressDisabled.setProgressEventsEnabled(false);
  assertFalse(progressDisabled.getProgressEventsEnabled());
  progressDisabled.send('url');
  assertNull(
      'No progress handler should be set for downloads.',
      progressDisabled.xhr_.onprogress);
  assertUndefined(
      'No progress handler should be set for uploads.',
      progressDisabled.xhr_.upload.onprogress);

  var progressEnabled = new goog.net.XhrIo;
  progressEnabled.setProgressEventsEnabled(true);
  assertTrue(progressEnabled.getProgressEventsEnabled());
  progressEnabled.send('url');
  assertTrue(
      'Progress handler should be set for downloads.',
      goog.isFunction(progressEnabled.xhr_.onprogress));
  assertTrue(
      'Progress handler should be set for uploads.',
      goog.isFunction(progressEnabled.xhr_.upload.onprogress));

  // Clean-up.
  delete MockXmlHttp.prototype.onprogress;
}


function testGetResponse() {
  var x = new goog.net.XhrIo;

  // No XHR yet
  assertEquals(null, x.getResponse());

  // XHR with no .response and no response type, gets text.
  x.xhr_ = {};
  x.xhr_.responseText = 'text';
  assertEquals('text', x.getResponse());

  // Response type of text gets text as well.
  x.setResponseType(goog.net.XhrIo.ResponseType.TEXT);
  x.xhr_.responseText = '';
  assertEquals('', x.getResponse());

  // Response type of array buffer gets the array buffer.
  x.xhr_.mozResponseArrayBuffer = 'ab';
  x.setResponseType(goog.net.XhrIo.ResponseType.ARRAY_BUFFER);
  assertEquals('ab', x.getResponse());

  // With a response field, it is returned no matter what value it has.
  x.xhr_.response = undefined;
  assertEquals(undefined, x.getResponse());

  x.xhr_.response = null;
  assertEquals(null, x.getResponse());

  x.xhr_.response = '';
  assertEquals('', x.getResponse());

  x.xhr_.response = 'resp';
  assertEquals('resp', x.getResponse());
}

function testGetResponseHeader() {
  var x = new goog.net.XhrIo();
  x.send('http://foo');

  x.xhr_.responseHeaders['foo'] = null;
  x.xhr_.responseHeaders['bar'] = 'xyz';
  x.xhr_.responseHeaders['baz'] = '';

  // All headers should be undefined prior to the request completing.
  assertUndefined(x.getResponseHeader('foo'));
  assertUndefined(x.getResponseHeader('bar'));
  assertUndefined(x.getResponseHeader('baz'));

  x.xhr_.readyState = goog.net.XmlHttp.ReadyState.COMPLETE;

  assertUndefined(x.getResponseHeader('foo'));
  assertEquals('xyz', x.getResponseHeader('bar'));
  assertEquals('', x.getResponseHeader('baz'));
}

function testGetResponseHeaders() {
  MockXmlHttp.syncSend = true;
  var x = new goog.net.XhrIo();

  // No XHR yet
  assertEquals(0, goog.object.getCount(x.getResponseHeaders()));

  x.send();

  // Simulate an XHR with 2 headers.
  lastMockXmlHttp.responseHeadersString = 'test1: foo\r\ntest2: bar';

  var headers = x.getResponseHeaders();
  assertEquals(2, goog.object.getCount(headers));
  assertEquals('foo', headers['test1']);
  assertEquals('bar', headers['test2']);
}

function testGetResponseHeadersWithColonInValue() {
  MockXmlHttp.syncSend = true;
  var x = new goog.net.XhrIo();

  x.send();

  // Simulate an XHR with a colon in the http header value.
  lastMockXmlHttp.responseHeadersString = 'test1: f:o:o';

  var headers = x.getResponseHeaders();
  assertEquals(1, goog.object.getCount(headers));
  assertEquals('f:o:o', headers['test1']);
}

function testGetResponseHeadersMultipleValuesForOneKey() {
  MockXmlHttp.syncSend = true;
  var x = new goog.net.XhrIo();

  // No XHR yet
  assertEquals(0, goog.object.getCount(x.getResponseHeaders()));

  x.send();

  // Simulate an XHR with 2 headers.
  lastMockXmlHttp.responseHeadersString = 'test1: foo\r\ntest1: bar';

  var headers = x.getResponseHeaders();
  assertEquals(1, goog.object.getCount(headers));
  assertEquals('foo, bar', headers['test1']);
}

function testGetResponseHeadersEmptyHeader() {
  MockXmlHttp.syncSend = true;
  var x = new goog.net.XhrIo();

  // No XHR yet
  assertEquals(0, goog.object.getCount(x.getResponseHeaders()));

  x.send();

  // Simulate an XHR with 2 headers, the last of which is empty.
  lastMockXmlHttp.responseHeadersString = 'test2: bar\r\n';

  var headers = x.getResponseHeaders();
  assertEquals(1, goog.object.getCount(headers));
  assertEquals('bar', headers['test2']);
}


function testGetResponseHeadersNullHeader() {
  MockXmlHttp.syncSend = true;

  var x = new goog.net.XhrIo();

  // No XHR yet
  assertEquals(0, goog.object.getCount(x.getResponseHeaders()));

  x.send();

  var headers = x.getResponseHeaders();
  assertEquals(0, goog.object.getCount(headers));
}
