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

"use strict";

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

AddonTestUtils.initMochitest(this);

const XPI_INCOMPATIBLE_ID = "incompatible-xpi@tests.mozilla.org";
// NOTE: we are using an HTTP url on purpose here, the test case fails
// otherwise... We disable `AddonManager.checkUpdateSecurity` to allow
// retrieving updates from HTTP (which is restored in a
// `registerCleanupFunction()` or at the end of the task).
//
// eslint-disable-next-line sdl/no-insecure-url
const BASE_URL = "http://fake-updates.example.com";

const server = AddonTestUtils.createHttpServer({
  hosts: ["fake-updates.example.com"],
});

const UPDATE_ENTRY_COMPATIBLE = {
  // NOTE: this version must be the exact same one associated than the
  // initially incompatible XPI, otherwise it won't override the initial
  // compatibility range.
  // See the check in `AddonUpdateChecker.getCompatibilityUpdate` here:
  // https://searchfox.org/mozilla-central/rev/4044c340/toolkit/mozapps/extensions/internal/AddonUpdateChecker.sys.mjs#489
  version: "4.0",
  // An empty compatibility range will make this update to be overriding the
  // incompatible range in the xpi and makes the xpi version to be considered
  // compatible.
  applications: { gecko: {} },
};

const UPDATE_ENTRY_INCOMPATIBLE = {
  ...UPDATE_ENTRY_COMPATIBLE,
  // This update entry instead is including a compatibility range that would
  // makes the xpi version being installed to be considered still incompatible.
  applications: {
    gecko: {
      strict_min_version: "41",
      strict_max_version: "41.*",
    },
  },
};

AddonTestUtils.registerJSON(server, "/updates-still-incompatible.json", {
  addons: {
    [XPI_INCOMPATIBLE_ID]: {
      updates: [UPDATE_ENTRY_INCOMPATIBLE],
    },
  },
});

AddonTestUtils.registerJSON(server, "/updates-now-compatible.json", {
  addons: {
    [XPI_INCOMPATIBLE_ID]: {
      updates: [UPDATE_ENTRY_COMPATIBLE],
    },
  },
});

async function test_local_install_blocked({ id, stash, expectedError }) {
  await AddonTestUtils.loadBlocklistRawData({
    extensionsMLBF: [
      {
        stash,
        stash_time: 0,
      },
    ],
  });
  let needsCleanupBlocklist = true;
  const cleanupBlocklist = async () => {
    if (!needsCleanupBlocklist) {
      return;
    }
    await AddonTestUtils.loadBlocklistRawData({
      extensionsMLBF: [
        {
          stash: { blocked: [], unblocked: [] },
          stash_time: 0,
        },
      ],
    });
    needsCleanupBlocklist = false;
  };
  registerCleanupFunction(cleanupBlocklist);

  const xpiFilePath = getTestFilePath("../xpinstall/amosigned.xpi");
  const xpiFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  xpiFile.initWithPath(xpiFilePath);
  ok(xpiFile.exists(), "Expect the xpi file to exist");
  const xpiFileURI = Services.io.newFileURI(xpiFile);

  const promiseInstallCancelled = AddonTestUtils.promiseInstallEvent(
    "onDownloadCancelled",
    install => install.addon.id === id
  );

  let install = await AddonManager.getInstallForURL(xpiFileURI.spec, {
    telemetryInfo: { source: "file-url" },
  });
  const promiseInstallFailed = BrowserUtils.promiseObserved(
    "addon-install-failed",
    subject => {
      return subject.wrappedJSObject.installs[0] == install;
    }
  );

  AddonManager.installAddonFromWebpage(
    "application/x-xpinstall",
    gBrowser.selectedBrowser,
    Services.scriptSecurityManager.getSystemPrincipal(),
    install
  );

  info("Wait for addon-install-failed to be notified");
  await promiseInstallFailed;
  Assert.equal(
    install.error,
    expectedError,
    "LocalInstall cancelled with the expected error"
  );

  info("Wait for onDownloadCancelled to be notified");
  await promiseInstallCancelled;
  Assert.equal(
    install.state,
    AddonManager.STATE_CANCELLED,
    "Expect the install.state to be STATE_CANCELLED"
  );

  await cleanupBlocklist();
}

add_task(async function test_local_install_hard_blocked() {
  let id = "amosigned-xpi@tests.mozilla.org";
  let version = "2.2";

  await test_local_install_blocked({
    id,
    stash: { blocked: [`${id}:${version}`], unblocked: [] },
    expectedError: AddonManager.ERROR_BLOCKLISTED,
  });
});

add_task(async function test_local_install_soft_blocked() {
  let id = "amosigned-xpi@tests.mozilla.org";
  let version = "2.2";

  await SpecialPowers.pushPrefEnv({
    set: [["extensions.blocklist.softblock.enabled", true]],
  });

  await test_local_install_blocked({
    id,
    stash: { softblocked: [`${id}:${version}`], blocked: [], unblocked: [] },
    expectedError: AddonManager.ERROR_SOFT_BLOCKED,
  });

  await SpecialPowers.popPrefEnv();
});

add_task(async function test_local_install_incompatible() {
  const xpiFilePath = getTestFilePath("../xpinstall/incompatible.xpi");
  const xpiFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  xpiFile.initWithPath(xpiFilePath);
  ok(xpiFile.exists(), "Expect the xpi file to exist");
  const xpiFileURI = Services.io.newFileURI(xpiFile);

  const installTestExtension = async ({ expectIncompatible }) => {
    let install = await AddonManager.getInstallForURL(xpiFileURI.spec, {
      telemetryInfo: { source: "file-url" },
    });
    const promiseInstallDone = expectIncompatible
      ? BrowserUtils.promiseObserved(
          "addon-install-failed",
          subject => subject.wrappedJSObject.installs[0] == install
        )
      : BrowserUtils.promiseObserved(
          "webextension-permission-prompt",
          subject => subject.wrappedJSObject.info.addon == install.addon
        );

    AddonManager.installAddonFromWebpage(
      "application/x-xpinstall",
      gBrowser.selectedBrowser,
      Services.scriptSecurityManager.getSystemPrincipal(),
      install
    );

    if (expectIncompatible) {
      info("Wait for addon-install-failed to be notified");
      await promiseInstallDone;
      Assert.equal(
        install.error,
        AddonManager.ERROR_INCOMPATIBLE,
        "LocalInstall cancelled with the expected error"
      );
    } else {
      info("Wait for webextension-permission-prompt to be notified");
      await promiseInstallDone;
      Assert.equal(
        install.error,
        0,
        "no error expected on the LocalInstall instance"
      );
      Assert.equal(
        install.state,
        AddonManager.STATE_DOWNLOADED,
        "Got the expected LocalInstall state"
      );
      Assert.ok(
        install.addon.isCompatible,
        "updated Addon XPI is expected to be compatible"
      );
      Assert.equal(
        install.addon.version,
        "4.0",
        "Addon version expected to match the updated xpi file"
      );
      // Cancel the installation, before exiting the test.
      await install.cancel();
    }
  };

  info("Test incompatible xpi without a compatibility override");
  // Use a new tab to make sure the doorhanger will be gone when
  // the test tab is being removed (same when repeating the
  // test with expectIncompatible set to false).
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await installTestExtension({ expectIncompatible: true });
  });

  // Add the prefs to ignore signature checks for this test (allowed on all
  // channels while running in automation).
  SpecialPowers.pushPrefEnv({
    set: [
      ["extensions.update.url", `${BASE_URL}/updates.json`],
      ["xpinstall.signatures.required", false],
    ],
  });
  AddonManager.checkUpdateSecurity = false;
  registerCleanupFunction(() => {
    AddonManager.checkUpdateSecurity = true;
  });

  info(
    "Test incompatible xpi with a compatibility override that is still incompatible"
  );
  // Add the prefs to provide a compatibility range override which is still
  // incompatible.
  SpecialPowers.pushPrefEnv({
    set: [
      ["extensions.update.url", `${BASE_URL}/updates-still-incompatible.json`],
    ],
  });
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await installTestExtension({ expectIncompatible: true });
  });
  SpecialPowers.popPrefEnv();

  info(
    "Test incompatible xpi with a compatibility override that makes it compatible"
  );
  // Add the prefs to provide a compatibility range override which is
  // compatible.
  SpecialPowers.pushPrefEnv({
    set: [["extensions.update.url", `${BASE_URL}/updates-now-compatible.json`]],
  });
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await installTestExtension({ expectIncompatible: false });
  });
  SpecialPowers.popPrefEnv();

  SpecialPowers.popPrefEnv();
  AddonManager.checkUpdateSecurity = true;
});
