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

/**
 * This file only contains the |test_abortedSessionQueued| test. This needs
 * to be in a separate, stand-alone file since we're initializing Telemetry
 * twice, in a non-standard way to simulate incorrect shutdowns. Doing this
 * in other files might interfere with the other tests.
 */

const { TelemetryStorage } = ChromeUtils.importESModule(
  "resource://gre/modules/TelemetryStorage.sys.mjs"
);

const DATAREPORTING_DIR = "datareporting";
const ABORTED_PING_FILE_NAME = "aborted-session-ping";
const ABORTED_SESSION_UPDATE_INTERVAL_MS = 5 * 60 * 1000;

const PING_TYPE_MAIN = "main";
const REASON_ABORTED_SESSION = "aborted-session";
const TEST_PING_TYPE = "test-ping-type";

ChromeUtils.defineLazyGetter(this, "DATAREPORTING_PATH", function () {
  return PathUtils.join(PathUtils.profileDir, DATAREPORTING_DIR);
});

function sendPing() {
  if (PingServer.started) {
    TelemetrySend.setServer("http://localhost:" + PingServer.port);
  } else {
    TelemetrySend.setServer("http://doesnotexist");
  }

  let options = {
    addClientId: true,
    addEnvironment: true,
  };
  return TelemetryController.submitExternalPing(TEST_PING_TYPE, {}, options);
}

add_task(async function test_setup() {
  do_get_profile();
  PingServer.start();
  Services.prefs.setCharPref(
    TelemetryUtils.Preferences.Server,
    "http://localhost:" + PingServer.port
  );
});

add_task(async function test_abortedSessionQueued() {
  const ABORTED_FILE = PathUtils.join(
    DATAREPORTING_PATH,
    ABORTED_PING_FILE_NAME
  );

  // Make sure the aborted sessions directory does not exist to test its creation.
  await IOUtils.remove(DATAREPORTING_PATH, {
    ignoreAbsent: true,
    recursive: true,
  });

  let schedulerTickCallback = null;
  let now = new Date(2040, 1, 1, 0, 0, 0);
  fakeNow(now);
  // Fake scheduler functions to control aborted-session flow in tests.
  fakeSchedulerTimer(
    callback => (schedulerTickCallback = callback),
    () => {}
  );
  await TelemetryController.testReset();

  Assert.ok(
    await IOUtils.exists(DATAREPORTING_PATH),
    "Telemetry must create the aborted session directory when starting."
  );

  // Fake now again so that the scheduled aborted-session save takes place.
  now = futureDate(now, ABORTED_SESSION_UPDATE_INTERVAL_MS);
  fakeNow(now);
  // The first aborted session checkpoint must take place right after the initialisation.
  Assert.ok(!!schedulerTickCallback);
  // Execute one scheduler tick.
  await schedulerTickCallback();
  // Check that the aborted session is due at the correct time.
  Assert.ok(
    await IOUtils.exists(ABORTED_FILE),
    "There must be an aborted session ping."
  );

  await TelemetryStorage.testClearPendingPings();
  PingServer.clearRequests();
  await TelemetryController.testReset();

  Assert.ok(
    !(await IOUtils.exists(ABORTED_FILE)),
    "The aborted session ping must be removed from the aborted session ping directory."
  );

  // Restarting Telemetry again to trigger sending pings in TelemetrySend.
  await TelemetryController.testReset();

  // We should have received an aborted-session ping.
  const receivedPing = await PingServer.promiseNextPing();
  Assert.equal(
    receivedPing.type,
    PING_TYPE_MAIN,
    "Should have the correct type"
  );
  Assert.equal(
    receivedPing.payload.info.reason,
    REASON_ABORTED_SESSION,
    "Ping should have the correct reason"
  );

  await TelemetryController.testShutdown();
});

/*
 * An aborted-session ping might have been written when Telemetry upload was disabled and
 * the profile had a canary client ID.
 * These pings should not be sent out at a later point when Telemetry is enabled again.
 */
add_task(async function test_abortedSession_canary_clientid() {
  const ABORTED_FILE = PathUtils.join(
    DATAREPORTING_PATH,
    ABORTED_PING_FILE_NAME
  );

  // Make sure the aborted sessions directory does not exist to test its creation.
  await IOUtils.remove(DATAREPORTING_PATH, {
    ignoreAbsent: true,
    recursive: true,
  });

  let schedulerTickCallback = null;
  let now = new Date(2040, 1, 1, 0, 0, 0);
  fakeNow(now);
  // Fake scheduler functions to control aborted-session flow in tests.
  fakeSchedulerTimer(
    callback => (schedulerTickCallback = callback),
    () => {}
  );
  await TelemetryController.testReset();

  Assert.ok(
    await IOUtils.exists(DATAREPORTING_PATH),
    "Telemetry must create the aborted session directory when starting."
  );

  // Fake now again so that the scheduled aborted-session save takes place.
  now = futureDate(now, ABORTED_SESSION_UPDATE_INTERVAL_MS);
  fakeNow(now);
  // The first aborted session checkpoint must take place right after the initialisation.
  Assert.ok(!!schedulerTickCallback);
  // Execute one scheduler tick.
  await schedulerTickCallback();
  // Check that the aborted session is due at the correct time.
  Assert.ok(
    await IOUtils.exists(ABORTED_FILE),
    "There must be an aborted session ping."
  );

  // Set clientID in aborted-session ping to canary value
  let abortedPing = await IOUtils.readJSON(ABORTED_FILE);
  abortedPing.clientId = TelemetryUtils.knownClientID;
  await IOUtils.writeJSON(ABORTED_FILE, abortedPing);

  await TelemetryStorage.testClearPendingPings();
  PingServer.clearRequests();
  await TelemetryController.testReset();

  Assert.ok(
    !(await IOUtils.exists(ABORTED_FILE)),
    "The aborted session ping must be removed from the aborted session ping directory."
  );

  // Restarting Telemetry again to trigger sending pings in TelemetrySend.
  await TelemetryController.testReset();

  // Trigger a test ping, so we can verify the server received something.
  sendPing();

  // We should have received an aborted-session ping.
  const receivedPing = await PingServer.promiseNextPing();
  Assert.equal(
    receivedPing.type,
    TEST_PING_TYPE,
    "Should have received test ping"
  );

  await TelemetryController.testShutdown();
});

add_task(async function stopServer() {
  await PingServer.stop();
});
