/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * Test that correct security state indicator appears depending on the security
 * state.
 */

add_task(function* () {
  const EXPECTED_SECURITY_STATES = {
    "test1.example.com": "security-state-insecure",
    "example.com": "security-state-secure",
    "nocert.example.com": "security-state-broken",
    "localhost": "security-state-local",
  };

  let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
  let { document, gStore, windowRequire } = monitor.panelWin;
  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");

  gStore.dispatch(Actions.batchEnable(false));

  yield performRequests();

  for (let subitemNode of Array.from(document.querySelectorAll(
    "requests-list-column.requests-list-security-and-domain"))) {
    let domain = subitemNode.querySelector(".requests-list-domain").textContent;

    info("Found a request to " + domain);
    ok(domain in EXPECTED_SECURITY_STATES, "Domain " + domain + " was expected.");

    let classes = subitemNode.querySelector(".requests-security-state-icon").classList;
    let expectedClass = EXPECTED_SECURITY_STATES[domain];

    info("Classes of security state icon are: " + classes);
    info("Security state icon is expected to contain class: " + expectedClass);
    ok(classes.contains(expectedClass), "Icon contained the correct class name.");
  }

  return teardown(monitor);

  /**
   * A helper that performs requests to
   *  - https://nocert.example.com (broken)
   *  - https://example.com (secure)
   *  - http://test1.example.com (insecure)
   *  - http://localhost (local)
   * and waits until NetworkMonitor has handled all packets sent by the server.
   */
  function* performRequests() {
    function executeRequests(count, url) {
      return ContentTask.spawn(tab.linkedBrowser, {count, url}, function* (args) {
        content.wrappedJSObject.performRequests(args.count, args.url);
      });
    }

    // waitForNetworkEvents does not work for requests with security errors as
    // those only emit 9/13 events of a successful request.
    let done = waitForSecurityBrokenNetworkEvent();

    info("Requesting a resource that has a certificate problem.");
    yield executeRequests(1, "https://nocert.example.com");

    // Wait for the request to complete before firing another request. Otherwise
    // the request with security issues interfere with waitForNetworkEvents.
    info("Waiting for request to complete.");
    yield done;

    // Next perform a request over HTTP. If done the other way around the latter
    // occasionally hangs waiting for event timings that don't seem to appear...
    done = waitForNetworkEvents(monitor, 1);
    info("Requesting a resource over HTTP.");
    yield executeRequests(1, "http://test1.example.com" + CORS_SJS_PATH);
    yield done;

    done = waitForNetworkEvents(monitor, 1);
    info("Requesting a resource over HTTPS.");
    yield executeRequests(1, "https://example.com" + CORS_SJS_PATH);
    yield done;

    done = waitForSecurityBrokenNetworkEvent(true);
    info("Requesting a resource over HTTP to localhost.");
    yield executeRequests(1, "http://localhost" + CORS_SJS_PATH);
    yield done;

    const expectedCount = Object.keys(EXPECTED_SECURITY_STATES).length;
    is(gStore.getState().requests.requests.size,
      expectedCount,
      expectedCount + " events logged.");
  }

  /**
   * Returns a promise that's resolved once a request with security issues is
   * completed.
   */
  function waitForSecurityBrokenNetworkEvent(networkError) {
    let awaitedEvents = [
      "UPDATING_REQUEST_HEADERS",
      "RECEIVED_REQUEST_HEADERS",
      "UPDATING_REQUEST_COOKIES",
      "RECEIVED_REQUEST_COOKIES",
      "STARTED_RECEIVING_RESPONSE",
      "UPDATING_RESPONSE_CONTENT",
      "RECEIVED_RESPONSE_CONTENT",
      "UPDATING_EVENT_TIMINGS",
      "RECEIVED_EVENT_TIMINGS",
    ];

    // If the reason for breakage is a network error, then the
    // STARTED_RECEIVING_RESPONSE event does not fire.
    if (networkError) {
      awaitedEvents = awaitedEvents.filter(e => e !== "STARTED_RECEIVING_RESPONSE");
    }

    let promises = awaitedEvents.map((event) => {
      return monitor.panelWin.once(EVENTS[event]);
    });

    return Promise.all(promises);
  }
});
