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

// Tests that various operations with file pointers work and do not affect the
// source files

const ID1 = "addon1@tests.mozilla.org";
const ID2 = "addon2@tests.mozilla.org";

const profileDir = gProfD.clone();
profileDir.append("extensions");
profileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);

const sourceDir = gProfD.clone();
sourceDir.append("source");

function promiseWriteWebExtension(path, data) {
  let files = ExtensionTestCommon.generateFiles(data);
  return AddonTestUtils.promiseWriteFilesToDir(path, files);
}

function promiseWritePointer(aId, aName) {
  let path = PathUtils.join(profileDir.path, aName || aId);

  let target = PathUtils.join(sourceDir.path, do_get_expected_addon_name(aId));

  return IOUtils.writeUTF8(path, target);
}

function promiseWriteRelativePointer(aId, aName) {
  let path = PathUtils.join(profileDir.path, aName || aId);

  let absTarget = sourceDir.clone();
  absTarget.append(do_get_expected_addon_name(aId));

  let relTarget = absTarget.getRelativeDescriptor(profileDir);

  return IOUtils.writeUTF8(path, relTarget);
}

add_task(async function setup() {
  ok(TEST_UNPACKED, "Pointer files only work with unpacked directories");

  // Unpacked extensions are never signed, so this can only run with
  // signature checks disabled.
  Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);

  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
});

// Tests that installing a new add-on by pointer works
add_task(async function test_new_pointer_install() {
  let target = PathUtils.join(sourceDir.path, ID1);
  await promiseWriteWebExtension(target, {
    manifest: {
      version: "1.0",
      browser_specific_settings: { gecko: { id: ID1 } },
    },
  });
  await promiseWritePointer(ID1);
  await promiseStartupManager();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "1.0");

  let file = getAddonFile(addon);
  equal(file.parent.path, sourceDir.path);

  let rootUri = do_get_addon_root_uri(sourceDir, ID1);
  let uri = addon.getResourceURI();
  equal(uri.spec, rootUri);

  // Check that upgrade is disabled for addons installed by file-pointers.
  equal(addon.permissions & AddonManager.PERM_CAN_UPGRADE, 0);
});

// Tests that installing the addon from some other source doesn't clobber
// the original sources
add_task(async function test_addon_over_pointer() {
  let xpi = AddonTestUtils.createTempWebExtensionFile({
    manifest: {
      version: "2.0",
      browser_specific_settings: { gecko: { id: ID1 } },
    },
  });

  let install = await AddonManager.getInstallForFile(
    xpi,
    "application/x-xpinstall"
  );
  await install.install();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "2.0");

  let url = addon.getResourceURI();
  if (url instanceof Ci.nsIJARURI) {
    url = url.JARFile;
  }
  let { file } = url.QueryInterface(Ci.nsIFileURL);
  equal(file.parent.path, profileDir.path);

  let rootUri = do_get_addon_root_uri(profileDir, ID1);
  let uri = addon.getResourceURI();
  equal(uri.spec, rootUri);

  let source = sourceDir.clone();
  source.append(ID1);
  ok(source.exists());

  await addon.uninstall();
});

// Tests that uninstalling doesn't clobber the original sources
add_task(async function test_uninstall_pointer() {
  await promiseWritePointer(ID1);
  await promiseRestartManager();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "1.0");

  await addon.uninstall();

  let source = sourceDir.clone();
  source.append(ID1);
  ok(source.exists());
});

// Tests that misnaming a pointer doesn't clobber the sources
add_task(async function test_bad_pointer() {
  await promiseWritePointer(ID2, ID1);

  let [a1, a2] = await AddonManager.getAddonsByIDs([ID1, ID2]);
  equal(a1, null);
  equal(a2, null);

  let source = sourceDir.clone();
  source.append(ID1);
  ok(source.exists());

  let pointer = profileDir.clone();
  pointer.append(ID2);
  ok(!pointer.exists());
});

// Tests that changing the ID of an existing add-on doesn't clobber the sources
add_task(async function test_bad_pointer_id() {
  let dir = sourceDir.clone();
  dir.append(ID1);

  // Make sure the modification time changes enough to be detected.
  setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000);
  await promiseWritePointer(ID1);
  await promiseRestartManager();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "1.0");

  await promiseWriteWebExtension(dir.path, {
    manifest: {
      version: "1.0",
      browser_specific_settings: { gecko: { id: ID2 } },
    },
  });
  setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000);

  await promiseRestartManager();

  let [a1, a2] = await AddonManager.getAddonsByIDs([ID1, ID2]);
  equal(a1, null);
  equal(a2, null);

  let source = sourceDir.clone();
  source.append(ID1);
  ok(source.exists());

  let pointer = profileDir.clone();
  pointer.append(ID1);
  ok(!pointer.exists());
});

// Removing the pointer file should uninstall the add-on
add_task(async function test_remove_pointer() {
  let dir = sourceDir.clone();
  dir.append(ID1);

  await promiseWriteWebExtension(dir.path, {
    manifest: {
      version: "1.0",
      browser_specific_settings: { gecko: { id: ID1 } },
    },
  });

  setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000);
  await promiseWritePointer(ID1);

  await promiseRestartManager();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "1.0");

  let pointer = profileDir.clone();
  pointer.append(ID1);
  pointer.remove(false);

  await promiseRestartManager();

  addon = await AddonManager.getAddonByID(ID1);
  equal(addon, null);
});

// Removing the pointer file and replacing it with a directory should work
add_task(async function test_replace_pointer() {
  await promiseWritePointer(ID1);
  await promiseRestartManager();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "1.0");

  let pointer = profileDir.clone();
  pointer.append(ID1);
  pointer.remove(false);

  await promiseWriteWebExtension(PathUtils.join(profileDir.path, ID1), {
    manifest: {
      version: "2.0",
      browser_specific_settings: { gecko: { id: ID1 } },
    },
  });

  await promiseRestartManager();

  addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "2.0");

  await addon.uninstall();
});

// Changes to the source files should be detected
add_task(async function test_change_pointer_sources() {
  await promiseWritePointer(ID1);
  await promiseRestartManager();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "1.0");

  let dir = sourceDir.clone();
  dir.append(ID1);
  await promiseWriteWebExtension(dir.path, {
    manifest: {
      version: "2.0",
      browser_specific_settings: { gecko: { id: ID1 } },
    },
  });
  setExtensionModifiedTime(dir, dir.lastModifiedTime - 5000);

  await promiseRestartManager();

  addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "2.0");

  await addon.uninstall();
});

// Removing the add-on the pointer file points at should uninstall the add-on
add_task(async function test_remove_pointer_target() {
  let target = PathUtils.join(sourceDir.path, ID1);
  await promiseWriteWebExtension(target, {
    manifest: {
      version: "1.0",
      browser_specific_settings: { gecko: { id: ID1 } },
    },
  });
  await promiseWritePointer(ID1);
  await promiseRestartManager();

  let addon = await AddonManager.getAddonByID(ID1);
  notEqual(addon, null);
  equal(addon.version, "1.0");

  await IOUtils.remove(target, { recursive: true });

  await promiseRestartManager();

  addon = await AddonManager.getAddonByID(ID1);
  equal(addon, null);

  let pointer = profileDir.clone();
  pointer.append(ID1);
  ok(!pointer.exists());
});

// Tests that installing a new add-on by pointer with a relative path works
add_task(async function test_new_relative_pointer() {
  let target = PathUtils.join(sourceDir.path, ID1);
  await promiseWriteWebExtension(target, {
    manifest: {
      version: "1.0",
      browser_specific_settings: { gecko: { id: ID1 } },
    },
  });
  await promiseWriteRelativePointer(ID1);
  await promiseRestartManager();

  let addon = await AddonManager.getAddonByID(ID1);
  equal(addon.version, "1.0");

  let { file } = addon.getResourceURI().QueryInterface(Ci.nsIFileURL);
  equal(file.parent.path, sourceDir.path);

  let rootUri = do_get_addon_root_uri(sourceDir, ID1);
  let uri = addon.getResourceURI();
  equal(uri.spec, rootUri);

  // Check that upgrade is disabled for addons installed by file-pointers.
  equal(addon.permissions & AddonManager.PERM_CAN_UPGRADE, 0);
});
