/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This file tests async handling of a channel suspended in
// notifying http-on-examine-merged-response observers.
// Note that this test is developed based on test_bug482601.js.
"use strict";

const { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);

var httpserv = null;
var test_nr = 0;
var buffer = "";
var observerCalled = false;
var channelResumed = false;

var observer = {
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  observe(subject, topic) {
    if (
      topic === "http-on-examine-merged-response" &&
      subject instanceof Ci.nsIHttpChannel
    ) {
      var chan = subject.QueryInterface(Ci.nsIHttpChannel);
      executeSoon(() => {
        Assert.equal(channelResumed, false);
        channelResumed = true;
        chan.resume();
      });
      Assert.equal(observerCalled, false);
      observerCalled = true;
      chan.suspend();
    }
  },
};

var listener = {
  onStartRequest() {
    buffer = "";
  },

  onDataAvailable(request, stream, offset, count) {
    buffer = buffer.concat(read_stream(stream, count));
  },

  onStopRequest(request, status) {
    Assert.equal(status, Cr.NS_OK);
    Assert.equal(buffer, "0123456789");
    Assert.equal(channelResumed, true);
    Assert.equal(observerCalled, true);
    test_nr++;
    do_timeout(0, do_test);
  },
};

function run_test() {
  httpserv = new HttpServer();
  httpserv.registerPathHandler("/path/partial", path_partial);
  httpserv.registerPathHandler("/path/cached", path_cached);
  httpserv.start(-1);

  Services.obs.addObserver(observer, "http-on-examine-merged-response");

  do_timeout(0, do_test);
  do_test_pending();
}

function do_test() {
  if (test_nr < tests.length) {
    tests[test_nr]();
  } else {
    Services.obs.removeObserver(observer, "http-on-examine-merged-response");
    httpserv.stop(do_test_finished);
  }
}

var tests = [test_partial, test_cached];

function makeChan(url) {
  return NetUtil.newChannel({
    uri: url,
    loadUsingSystemPrincipal: true,
  }).QueryInterface(Ci.nsIHttpChannel);
}

function storeCache(aCacheEntry, aResponseHeads, aContent) {
  aCacheEntry.setMetaDataElement("request-method", "GET");
  aCacheEntry.setMetaDataElement("response-head", aResponseHeads);
  aCacheEntry.setMetaDataElement("charset", "ISO-8859-1");

  var oStream = aCacheEntry.openOutputStream(0, aContent.length);
  var written = oStream.write(aContent, aContent.length);
  if (written != aContent.length) {
    do_throw(
      "oStream.write has not written all data!\n" +
        "  Expected: " +
        written +
        "\n" +
        "  Actual: " +
        aContent.length +
        "\n"
    );
  }
  oStream.close();
}

function test_partial() {
  observerCalled = false;
  channelResumed = false;
  asyncOpenCacheEntry(
    "http://localhost:" + httpserv.identity.primaryPort + "/path/partial",
    "disk",
    Ci.nsICacheStorage.OPEN_NORMALLY,
    null,
    test_partial2
  );
}

function test_partial2(status, entry) {
  Assert.equal(status, Cr.NS_OK);
  storeCache(
    entry,
    "HTTP/1.1 200 OK\r\n" +
      "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
      "Server: httpd.js\r\n" +
      "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
      "Accept-Ranges: bytes\r\n" +
      "Content-Length: 10\r\n" +
      "Content-Type: text/plain\r\n",
    "0123"
  );

  observerCalled = false;

  var chan = makeChan(
    "http://localhost:" + httpserv.identity.primaryPort + "/path/partial"
  );
  chan.asyncOpen(listener);
}

function test_cached() {
  observerCalled = false;
  channelResumed = false;
  asyncOpenCacheEntry(
    "http://localhost:" + httpserv.identity.primaryPort + "/path/cached",
    "disk",
    Ci.nsICacheStorage.OPEN_NORMALLY,
    null,
    test_cached2
  );
}

function test_cached2(status, entry) {
  Assert.equal(status, Cr.NS_OK);
  storeCache(
    entry,
    "HTTP/1.1 200 OK\r\n" +
      "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
      "Server: httpd.js\r\n" +
      "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
      "Accept-Ranges: bytes\r\n" +
      "Content-Length: 10\r\n" +
      "Content-Type: text/plain\r\n",
    "0123456789"
  );

  observerCalled = false;

  var chan = makeChan(
    "http://localhost:" + httpserv.identity.primaryPort + "/path/cached"
  );
  chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
  chan.asyncOpen(listener);
}

// PATHS

// /path/partial
function path_partial(metadata, response) {
  Assert.ok(metadata.hasHeader("If-Range"));
  Assert.equal(metadata.getHeader("If-Range"), "Thu, 1 Jan 2009 00:00:00 GMT");
  Assert.ok(metadata.hasHeader("Range"));
  Assert.equal(metadata.getHeader("Range"), "bytes=4-");

  response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
  response.setHeader("Content-Range", "bytes 4-9/10", false);
  response.setHeader("Content-Type", "text/plain", false);
  response.setHeader("Last-Modified", "Thu, 1 Jan 2009 00:00:00 GMT");

  var body = "456789";
  response.bodyOutputStream.write(body, body.length);
}

// /path/cached
function path_cached(metadata, response) {
  Assert.ok(metadata.hasHeader("If-Modified-Since"));
  Assert.equal(
    metadata.getHeader("If-Modified-Since"),
    "Thu, 1 Jan 2009 00:00:00 GMT"
  );

  response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
}
