/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=4 ts=4 sts=4 et
 * 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/. */

"use strict";

const { BackgroundUpdate } = ChromeUtils.importESModule(
  "resource://gre/modules/BackgroundUpdate.sys.mjs"
);
let reasons = () => BackgroundUpdate._reasonsToNotUpdateInstallation();
let REASON = BackgroundUpdate.REASON;
const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
  "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
);
const { UpdateService } = ChromeUtils.importESModule(
  "resource://gre/modules/UpdateService.sys.mjs"
);

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

// We can't reasonably check NO_MOZ_BACKGROUNDTASKS, nor NO_OMNIJAR.

// These tests use per-installation prefs, and those are a shared resource, so
// they require some non-trivial setup.
setupTestCommon(null);
standardInit();

function setup_enterprise_policy_testing() {
  // This initializes the policy engine for xpcshell tests
  let policies = Cc["@mozilla.org/enterprisepolicies;1"].getService(
    Ci.nsIObserver
  );
  policies.observe(null, "policies-startup", null);
}
setup_enterprise_policy_testing();

async function setupPolicyEngineWithJson(json, customSchema) {
  if (typeof json != "object") {
    let filePath = do_get_file(json ? json : "non-existing-file.json").path;
    return EnterprisePolicyTesting.setupPolicyEngineWithJson(
      filePath,
      customSchema
    );
  }
  return EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema);
}

add_setup(function test_setup() {
  // FOG needs a profile directory to put its data in.
  do_get_profile();

  // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
  Services.fog.initializeFOG();

  setupProfileService();
});

add_task(async function test_reasons_update_no_app_update_auto() {
  let prev = await UpdateUtils.getAppUpdateAutoEnabled();
  try {
    await UpdateUtils.setAppUpdateAutoEnabled(false);
    let result = await reasons();
    Assert.ok(result.includes(REASON.NO_APP_UPDATE_AUTO));
    result = await checkGleanPing();
    Assert.ok(result.includes(REASON.NO_APP_UPDATE_AUTO));

    await UpdateUtils.setAppUpdateAutoEnabled(true);
    result = await reasons();
    Assert.ok(!result.includes(REASON.NO_APP_UPDATE_AUTO));

    result = await checkGleanPing();
    Assert.ok(!result.includes(REASON.NO_APP_UPDATE_AUTO));
  } finally {
    await UpdateUtils.setAppUpdateAutoEnabled(prev);
  }
});

add_task(async function test_reasons_update_no_app_update_background_enabled() {
  let prev = await UpdateUtils.readUpdateConfigSetting(
    "app.update.background.enabled"
  );
  try {
    await UpdateUtils.writeUpdateConfigSetting(
      "app.update.background.enabled",
      false
    );
    let result = await reasons();
    Assert.ok(result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
    result = await checkGleanPing();
    Assert.ok(result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));

    await UpdateUtils.writeUpdateConfigSetting(
      "app.update.background.enabled",
      true
    );
    result = await reasons();
    Assert.ok(!result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
    result = await checkGleanPing();
    Assert.ok(!result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
  } finally {
    await UpdateUtils.writeUpdateConfigSetting(
      "app.update.background.enabled",
      prev
    );
  }
});

add_task(async function test_reasons_update_cannot_usually_check() {
  // It's difficult to arrange the conditions in a testing environment, so
  // we'll use mocks to get a little assurance.
  let result = await reasons();
  Assert.ok(!result.includes(REASON.CANNOT_USUALLY_CHECK));

  let sandbox = sinon.createSandbox();
  try {
    sandbox
      .stub(UpdateService.prototype, "canUsuallyCheckForUpdates")
      .get(() => false);
    result = await reasons();
    Assert.ok(result.includes(REASON.CANNOT_USUALLY_CHECK));
    result = await checkGleanPing();
    Assert.ok(result.includes(REASON.CANNOT_USUALLY_CHECK));
  } finally {
    sandbox.restore();
  }
});

add_task(async function test_reasons_update_can_usually_stage_or_appl() {
  // It's difficult to arrange the conditions in a testing environment, so
  // we'll use mocks to get a little assurance.
  let sandbox = sinon.createSandbox();
  try {
    sandbox
      .stub(UpdateService.prototype, "canUsuallyStageUpdates")
      .get(() => true);
    sandbox
      .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
      .get(() => true);
    let result = await reasons();
    Assert.ok(
      !result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
    );
    result = await checkGleanPing();
    Assert.ok(
      !result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
    );

    sandbox
      .stub(UpdateService.prototype, "canUsuallyStageUpdates")
      .get(() => false);
    sandbox
      .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
      .get(() => false);
    result = await reasons();
    Assert.ok(
      result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
    );
    result = await checkGleanPing();
    Assert.ok(
      result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
    );
  } finally {
    sandbox.restore();
  }
});

add_task(
  {
    skip_if: () =>
      !AppConstants.MOZ_BITS_DOWNLOAD || AppConstants.platform != "win",
  },
  async function test_reasons_update_can_usually_use_bits() {
    let prev = Services.prefs.getBoolPref("app.update.BITS.enabled");

    // Here we use mocks to "get by" preconditions that are not
    // satisfied in the testing environment.
    let sandbox = sinon.createSandbox();
    try {
      sandbox
        .stub(UpdateService.prototype, "canUsuallyStageUpdates")
        .get(() => true);
      sandbox
        .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
        .get(() => true);

      Services.prefs.setBoolPref("app.update.BITS.enabled", false);
      let result = await reasons();
      Assert.ok(result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
      result = await checkGleanPing();
      Assert.ok(
        result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS),
        "result : " + result.join("', '") + "']"
      );

      Services.prefs.setBoolPref("app.update.BITS.enabled", true);
      result = await reasons();
      Assert.ok(!result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
      result = await checkGleanPing();
      Assert.ok(!result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
    } finally {
      sandbox.restore();
      Services.prefs.setBoolPref("app.update.BITS.enabled", prev);
    }
  }
);

add_task(async function test_reasons_update_manual_update_only() {
  await setupPolicyEngineWithJson({
    policies: {
      ManualAppUpdateOnly: true,
    },
  });
  Assert.equal(
    Services.policies.status,
    Ci.nsIEnterprisePolicies.ACTIVE,
    "Engine is active"
  );

  let result = await reasons();
  Assert.ok(result.includes(REASON.MANUAL_UPDATE_ONLY));
  result = await checkGleanPing();
  Assert.ok(result.includes(REASON.MANUAL_UPDATE_ONLY));

  await setupPolicyEngineWithJson({});

  result = await reasons();
  Assert.ok(!result.includes(REASON.MANUAL_UPDATE_ONLY));
  result = await checkGleanPing();
  Assert.ok(!result.includes(REASON.MANUAL_UPDATE_ONLY));
});

add_task(() => {
  // `setupTestCommon()` calls `do_test_pending()`; this calls
  // `do_test_finish()`.  The `add_task` schedules this to run after all the
  // other tests have completed.
  doTestFinish();
});
