// Copyright 2015 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.FetchXmlHttpFactoryTest');
goog.setTestOnly('goog.net.FetchXmlHttpFactoryTest');

goog.require('goog.net.FetchXmlHttp');
goog.require('goog.net.FetchXmlHttpFactory');
goog.require('goog.testing.MockControl');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
goog.require('goog.userAgent.product');
goog.require('goog.userAgent.product.isVersion');


/** @type {!goog.testing.MockControl} */
var mockControl;


/** @type {!goog.testing.FunctionMock} */
var fetchMock;


/** @type {!goog.net.FetchXmlHttpFactory} */
var factory;


/** @type {!WorkerGlobalScope} */
var worker;


/**
 * Whether the browser supports running this test.
 * @return {boolean}
 */
function shouldRunTests() {
  return goog.userAgent.product.CHROME && goog.userAgent.product.isVersion(43);
}

function setUp() {
  mockControl = new goog.testing.MockControl();
  worker = {};
  fetchMock = mockControl.createFunctionMock('fetch');
  worker.fetch = fetchMock;
  factory = new goog.net.FetchXmlHttpFactory(worker);
}


function tearDown() {
  mockControl.$tearDown();
}


/**
 * Verifies the open method.
 */
function testOpen() {
  mockControl.$replayAll();

  var xhr = factory.createInstance();
  assertEquals(0, xhr.status);
  assertEquals('', xhr.responseText);
  assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.UNSENT);

  var onReadyStateChangeHandler = new goog.testing.recordFunction();
  xhr.onreadystatechange = onReadyStateChangeHandler;
  xhr.open('GET', 'https://www.google.com', true /* opt_async */);
  assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.OPENED);
  onReadyStateChangeHandler.assertCallCount(1);

  mockControl.$verifyAll();
}


/**
 * Verifies the open method when the ready state is not unsent.
 */
function testOpen_notUnsent() {
  mockControl.$replayAll();

  var xhr = factory.createInstance();
  xhr.open('GET', 'https://www.google.com', true /* opt_async */);
  assertThrows(function() {
    xhr.open('GET', 'https://www.google.com', true /* opt_async */);
  });

  mockControl.$verifyAll();
}


/**
 * Verifies that synchronous fetches are not supported.
 */
function testOpen_notAsync() {
  mockControl.$replayAll();

  var xhr = factory.createInstance();

  assertThrows(function() {
    xhr.open('GET', 'https://www.google.com', false /* opt_async */);
  });

  mockControl.$verifyAll();
}


/**
 * Verifies the send method.
 */
function testSend() {
  fetchMock(new Request('https://www.google.com', {
    headers: new Headers(),
    method: 'GET'
  })).$returns(Promise.resolve(createSuccessResponse()));

  mockControl.$replayAll();
  verifySendSuccess('GET');
}


/**
 * Verifies the send method with POST mode.
 */
function testSendPost() {
  fetchMock(new Request('https://www.google.com', {
    headers: new Headers(),
    method: 'POST'
  })).$returns(Promise.resolve(createSuccessResponse()));

  mockControl.$replayAll();
  verifySendSuccess('POST');
}


/**
 * Verifies the send method including credentials.
 */
function testSend_includeCredentials() {
  factory = new goog.net.FetchXmlHttpFactory(worker);
  factory.setCredentialsMode(/** @type {RequestCredentials} */ ('include'));
  fetchMock(new Request('https://www.google.com', {
    headers: new Headers(),
    method: 'POST',
    credentials: 'include'
  })).$returns(Promise.resolve(createSuccessResponse()));

  mockControl.$replayAll();
  verifySendSuccess('POST');
}


/**
 * Verifies the send method setting cache mode.
 */
function testSend_setCacheMode() {
  factory = new goog.net.FetchXmlHttpFactory(worker);
  factory.setCacheMode(/** @type {RequestCache} */ ('no-cache'));
  fetchMock(new Request('https://www.google.com', {
    headers: new Headers(),
    method: 'POST',
    cache: 'no-cache'
  })).$returns(Promise.resolve(createSuccessResponse()));

  mockControl.$replayAll();
  verifySendSuccess('POST');
}


/**
 * Util function to verify send method is successful.
 */
function verifySendSuccess(sendMethod) {
  var xhr = factory.createInstance();
  xhr.open(sendMethod, 'https://www.google.com', true /* opt_async */);
  xhr.onreadystatechange = function() {
    assertEquals(
        xhr.readyState, goog.net.FetchXmlHttp.RequestState.HEADER_RECEIVED);
    assertEquals(0, xhr.status);
    assertEquals('', xhr.responseText);
    assertEquals(xhr.getResponseHeader('dummyHeader'), 'dummyHeaderValue');
    assertEquals(
        xhr.getAllResponseHeaders(),
        'dummyHeader: dummyHeaderValue\r\ndummyHeader2: dummyHeaderValue2');
    xhr.onreadystatechange = function() {
      assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.LOADING);
      assertEquals(0, xhr.status);
      assertEquals('', xhr.responseText);
      xhr.onreadystatechange = function() {
        assertEquals(200, xhr.status);
        assertEquals('responseBody', xhr.responseText);
        assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.DONE);
      };
    };
  };
  xhr.send();
  assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.OPENED);

  mockControl.$verifyAll();
}


/**
 * Verifies the send method in case of error response.
 */
function testSend_error() {
  fetchMock(new Request('https://www.google.com', {
    headers: new Headers(),
    method: 'GET'
  })).$returns(Promise.resolve(createFailedResponse()));

  mockControl.$replayAll();

  var xhr = factory.createInstance();
  xhr.open('GET', 'https://www.google.com', true /* opt_async */);
  xhr.onreadystatechange = function() {
    assertEquals(
        xhr.readyState, goog.net.FetchXmlHttp.RequestState.HEADER_RECEIVED);
    assertEquals(0, xhr.status);
    assertEquals('', xhr.responseText);
    assertEquals(xhr.getResponseHeader('dummyHeader'), 'dummyHeaderValue');
    xhr.onreadystatechange = function() {
      assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.LOADING);
      assertEquals(0, xhr.status);
      assertEquals('', xhr.responseText);
      xhr.onreadystatechange = function() {
        assertEquals(500, xhr.status);
        assertEquals('responseBody', xhr.responseText);
        assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.DONE);
      };
    };
  };
  xhr.send();
  assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.OPENED);
  mockControl.$verifyAll();
}


/**
 * Verifies the send method in case of failure to fetch the url.
 */
function testSend_failToFetch() {
  var failedPromise = new Promise(function() {
    throw new Error('failed to fetch');
  });
  fetchMock(new Request('https://www.google.com', {
    headers: new Headers(),
    method: 'GET'
  })).$returns(failedPromise);

  mockControl.$replayAll();

  var xhr = factory.createInstance();
  xhr.open('GET', 'https://www.google.com', true /* opt_async */);
  xhr.onreadystatechange = function() {
    assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.DONE);
    assertEquals(0, xhr.status);
    assertEquals('', xhr.responseText);
  };
  xhr.send();
  assertEquals(xhr.readyState, goog.net.FetchXmlHttp.RequestState.OPENED);

  mockControl.$verifyAll();
}


/**
 * Creates a successful response.
 * @return {!Response}
 */
function createSuccessResponse() {
  var headers = new Headers();
  headers.set('dummyHeader', 'dummyHeaderValue');
  headers.set('dummyHeader2', 'dummyHeaderValue2');
  return new Response(
      'responseBody' /* opt_body */, {status: 200, headers: headers});
}


/**
 * Creates a successful response.
 * @return {!Response}
 */
function createFailedResponse() {
  var headers = new Headers();
  headers.set('dummyHeader', 'dummyHeaderValue');
  return new Response(
      'responseBody' /* opt_body */, {status: 500, headers: headers});
}
