"use strict";

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

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

ChromeUtils.defineLazyGetter(this, "URL", function () {
  return "http://localhost:" + httpServer.identity.primaryPort;
});

let httpServer = null;

function pathHandler(metadata, response) {
  var body;
  if (metadata.hasHeader("Idempotency-Key")) {
    response.setStatusLine(metadata.httpVersion, 200, "OK");
    // echo back the header for further validation
    let IDK = metadata.getHeader("Idempotency-Key");
    response.setHeader("Idempotency-Key", IDK, false);
    body = "success";
  } else {
    response.setStatusLine(
      metadata.httpVersion,
      500,
      "missing Idempotency-Key"
    );
    body = "failed";
  }
  response.bodyOutputStream.write(body, body.length);
}

// Helper-method to validate that the "Idempotency-Key" value that we
// generate matches the expected format and is the same in the response.
// Meant to be called inside of a test that's been added via add_task.
function validateAutomaticallyAddedHeaderVal(chan) {
  let valueFromRequest = chan.getRequestHeader("Idempotency-Key");
  let valueFromResponse = chan.getResponseHeader("Idempotency-Key");

  // Validate that the header's got a nonempty value, and that the
  // response matches the request:
  Assert.notEqual(valueFromRequest, "");
  Assert.notEqual(valueFromResponse, "");
  Assert.equal(valueFromRequest, valueFromResponse);

  // Further validation on the value (just focusing on valueFromRequest,
  // since at this point we know that valueFromResponse is equal to it):
  // * We expect the value to be formatted as a nonempy double-quoted string,
  // (i.e. length must be at least 3), as part of the "sf-string" syntax:
  Assert.greaterOrEqual(valueFromRequest.length, 3);
  Assert.ok(valueFromRequest.startsWith('"'));
  Assert.ok(valueFromRequest.endsWith('"'));

  // * Inside the double-quotes, we expect the value to be formatted as an
  // unsigned integer. The spec doesn't require this, but that's the
  // representation we use for now, so let's be sure we don't inadvertently
  // stray from that format (except as a deliberate choice, which would
  // involve an update to this test's expectations).
  let quotedPortion = valueFromRequest.slice(1, -1);
  let parsedInteger = Number.parseInt(quotedPortion);
  Assert.ok(!Number.isNaN(parsedInteger));
  Assert.greaterOrEqual(parsedInteger, 0);
}

add_setup(async () => {
  httpServer = new HttpServer();
  httpServer.registerPathHandler("/test_bug1830022", pathHandler);
  httpServer.start(-1);
  registerCleanupFunction(async () => await httpServer.stop());
});

// tests if we add the header for the POST request
add_task(async function idempotency_key_addition_for_post() {
  let chan = makeChannel(URL + "/test_bug1830022");
  chan.requestMethod = "POST";
  await new Promise(resolve => {
    chan.asyncOpen(new ChannelListener(resolve));
  });
  validateAutomaticallyAddedHeaderVal(chan);
});

// tests if we add the header for the PATCH request
add_task(async function idempotency_key_addition_for_patch() {
  let chan = makeChannel(URL + "/test_bug1830022");
  chan.requestMethod = "PATCH";
  await new Promise(resolve => {
    chan.asyncOpen(new ChannelListener(resolve));
  });
  validateAutomaticallyAddedHeaderVal(chan);
});

// tests Idempotency key's uniqueness
add_task(async function idempotency_key_uniqueness() {
  let chan = makeChannel(URL + "/test_bug1830022");
  chan.requestMethod = "POST";
  await new Promise(resolve => {
    chan.asyncOpen(new ChannelListener(resolve));
  });

  let chan2 = makeChannel(URL + "/test_bug1830022");
  chan2.requestMethod = "POST";
  await new Promise(resolve => {
    chan2.asyncOpen(new ChannelListener(resolve));
  });

  Assert.notEqual(
    chan.getRequestHeader("Idempotency-Key"),
    chan2.getRequestHeader("Idempotency-Key")
  );

  // tests if the Idempotency key is same for reposts
  let chan3 = makeChannel(URL + "/test_bug1830022");
  chan3.requestMethod = "POST";
  await new Promise(resolve => {
    chan3.asyncOpen(new ChannelListener(resolve));
  });

  let cachekey = chan3.QueryInterface(Ci.nsICacheInfoChannel).cacheKey;

  let chan4 = makeChannel(URL + "/test_bug1830022");
  chan4.requestMethod = "POST";
  chan4.QueryInterface(Ci.nsICacheInfoChannel).cacheKey = cachekey;
  await new Promise(resolve => {
    chan4.asyncOpen(new ChannelListener(resolve));
  });

  Assert.equal(
    chan3.getRequestHeader("Idempotency-Key"),
    chan4.getRequestHeader("Idempotency-Key")
  );
});

// tests if we do not overwrite the header that is set before opening the channel
add_task(async function idempotency_key_addition_for_post() {
  // construct the channel
  let chan = makeChannel(URL + "/test_bug1830022");
  chan.setRequestHeader("Idempotency-Key", "U-V-W-X-Y-Z", false);
  chan.requestMethod = "POST";
  await new Promise(resolve => {
    chan.asyncOpen(new ChannelListener(resolve));
  });
  Assert.equal(chan.getRequestHeader("Idempotency-Key"), "U-V-W-X-Y-Z");
  Assert.equal(chan.getResponseHeader("Idempotency-Key"), "U-V-W-X-Y-Z");
});
