/* 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

var addon1 = {
  id: "addon1@tests.mozilla.org",
  version: "1.0",
  name: "Test 1",
  targetApplications: [{
    id: "xpcshell@tests.mozilla.org",
    minVersion: "1",
    maxVersion: "1"
  }]
};

var addon1_2 = {
  id: "addon1@tests.mozilla.org",
  version: "2.0",
  name: "Test 1",
  targetApplications: [{
    id: "xpcshell@tests.mozilla.org",
    minVersion: "1",
    maxVersion: "1"
  }]
};

var addon2 = {
  id: "addon2@tests.mozilla.org",
  version: "1.0",
  name: "Test 2",
  targetApplications: [{
    id: "xpcshell@tests.mozilla.org",
    minVersion: "1",
    maxVersion: "1"
  }]
};

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

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

ChromeUtils.import("resource://testing-common/httpd.js");
var testserver;

function writePointer(aId, aName) {
  let file = profileDir.clone();
  file.append(aName ? aName : aId);

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

  var fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
            createInstance(AM_Ci.nsIFileOutputStream);
  fos.init(file,
           FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
           FileUtils.PERMS_FILE, 0);
  fos.write(target.path, target.path.length);
  fos.close();
}

function writeRelativePointer(aId, aName) {
  let file = profileDir.clone();
  file.append(aName ? aName : aId);

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

  var relTarget = absTarget.getRelativeDescriptor(profileDir);

  var fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
            createInstance(AM_Ci.nsIFileOutputStream);
  fos.init(file,
           FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
           FileUtils.PERMS_FILE, 0);
  fos.write(relTarget, relTarget.length);
  fos.close();
}

function run_test() {
  // pointer files only work with unpacked directories
  if (Services.prefs.getBoolPref("extensions.alwaysUnpack") == false)
    return;

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

  // Create and configure the HTTP server.
  testserver = new HttpServer();
  testserver.registerDirectory("/data/", do_get_file("data"));
  testserver.registerDirectory("/addons/", do_get_file("addons"));
  testserver.start(-1);
  gPort = testserver.identity.primaryPort;

  run_test_1();
}

function end_test() {
  testserver.stop(do_test_finished);
}

// Tests that installing a new add-on by pointer works
function run_test_1() {
  writeInstallRDFForExtension(addon1, sourceDir);
  writePointer(addon1.id);

  startupManager();

  AddonManager.getAddonByID(addon1.id, function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

    let file = a1.getResourceURI().QueryInterface(AM_Ci.nsIFileURL).file;
    Assert.equal(file.parent.path, sourceDir.path);

    let rootUri = do_get_addon_root_uri(sourceDir, addon1.id);
    let uri = a1.getResourceURI("/");
    Assert.equal(uri.spec, rootUri);
    uri = a1.getResourceURI("install.rdf");
    Assert.equal(uri.spec, rootUri + "install.rdf");

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

// Tests that installing the addon from some other source doesn't clobber
// the original sources
function run_test_2() {
  prepare_test({}, [
    "onNewInstall",
  ]);

  let url = "http://localhost:" + gPort + "/addons/test_filepointer.xpi";
  AddonManager.getInstallForURL(url, function(install) {
    ensure_test_completed();

    prepare_test({
      "addon1@tests.mozilla.org": [
        "onInstalling"
      ]
    }, [
      "onDownloadStarted",
      "onDownloadEnded",
      "onInstallStarted",
      "onInstallEnded"
    ], callback_soon(check_test_2));

    install.install();
  }, "application/x-xpinstall");
}

function check_test_2() {
  restartManager();

  AddonManager.getAddonByID(addon1.id, function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "2.0");

    let file = a1.getResourceURI().QueryInterface(AM_Ci.nsIFileURL).file;
    Assert.equal(file.parent.path, profileDir.path);

    let rootUri = do_get_addon_root_uri(profileDir, addon1.id);
    let uri = a1.getResourceURI("/");
    Assert.equal(uri.spec, rootUri);
    uri = a1.getResourceURI("install.rdf");
    Assert.equal(uri.spec, rootUri + "install.rdf");

    let source = sourceDir.clone();
    source.append(addon1.id);
    Assert.ok(source.exists());

    a1.uninstall();

    executeSoon(run_test_3);
  });
}

// Tests that uninstalling doesn't clobber the original sources
function run_test_3() {
  restartManager();

  writePointer(addon1.id);

  restartManager();

  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

    a1.uninstall();

    restartManager();

    let source = sourceDir.clone();
    source.append(addon1.id);
    Assert.ok(source.exists());

    executeSoon(run_test_4);
  }));
}

// Tests that misnaming a pointer doesn't clobber the sources
function run_test_4() {
  writePointer("addon2@tests.mozilla.org", addon1.id);

  restartManager();

  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                               "addon2@tests.mozilla.org"], function([a1, a2]) {
    Assert.equal(a1, null);
    Assert.equal(a2, null);

    let source = sourceDir.clone();
    source.append(addon1.id);
    Assert.ok(source.exists());

    let pointer = profileDir.clone();
    pointer.append("addon2@tests.mozilla.org");
    Assert.ok(!pointer.exists());

    executeSoon(run_test_5);
  });
}

// Tests that changing the ID of an existing add-on doesn't clobber the sources
function run_test_5() {
  var dest = writeInstallRDFForExtension(addon1, sourceDir);
  // Make sure the modification time changes enough to be detected.
  setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
  writePointer(addon1.id);

  restartManager();

  AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

    writeInstallRDFForExtension(addon2, sourceDir, addon1.id);

    restartManager();

    AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                 "addon2@tests.mozilla.org"], function([a1_2, a2_2]) {
      Assert.equal(a1_2, null);
      Assert.equal(a2_2, null);

      let source = sourceDir.clone();
      source.append(addon1.id);
      Assert.ok(source.exists());

      let pointer = profileDir.clone();
      pointer.append(addon1.id);
      Assert.ok(!pointer.exists());

      executeSoon(run_test_6);
    });
  }));
}

// Removing the pointer file should uninstall the add-on
function run_test_6() {
  var dest = writeInstallRDFForExtension(addon1, sourceDir);
  // Make sure the modification time changes enough to be detected in run_test_8.
  setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
  writePointer(addon1.id);

  restartManager();

  AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

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

    restartManager();

    AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1_2) {
      Assert.equal(a1_2, null);

      executeSoon(run_test_7);
    });
  }));
}

// Removing the pointer file and replacing it with a directory should work
function run_test_7() {
  writePointer(addon1.id);

  restartManager();

  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

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

    writeInstallRDFForExtension(addon1_2, profileDir);

    restartManager();

    AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1_2) {
      Assert.notEqual(a1_2, null);
      Assert.equal(a1_2.version, "2.0");

      a1_2.uninstall();

      executeSoon(run_test_8);
    });
  }));
}

// Changes to the source files should be detected
function run_test_8() {
  restartManager();

  writePointer(addon1.id);

  restartManager();

  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

    writeInstallRDFForExtension(addon1_2, sourceDir);

    restartManager();

    AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1_2) {
      Assert.notEqual(a1_2, null);
      Assert.equal(a1_2.version, "2.0");

      a1_2.uninstall();

      executeSoon(run_test_9);
    });
  }));
}

// Removing the add-on the pointer file points at should uninstall the add-on
function run_test_9() {
  restartManager();

  var dest = writeInstallRDFForExtension(addon1, sourceDir);
  writePointer(addon1.id);

  restartManager();

  AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

    dest.remove(true);

    restartManager();

    AddonManager.getAddonByID(addon1.id, function(a1_2) {
      Assert.equal(a1_2, null);

      let pointer = profileDir.clone();
      pointer.append(addon1.id);
      Assert.ok(!pointer.exists());

      executeSoon(run_test_10);
    });
  }));
}

// Tests that installing a new add-on by pointer with a relative path works
function run_test_10() {
  writeInstallRDFForExtension(addon1, sourceDir);
  writeRelativePointer(addon1.id);

  restartManager();

  AddonManager.getAddonByID(addon1.id, function(a1) {
    Assert.notEqual(a1, null);
    Assert.equal(a1.version, "1.0");

    let file = a1.getResourceURI().QueryInterface(AM_Ci.nsIFileURL).file;
    Assert.equal(file.parent.path, sourceDir.path);

    let rootUri = do_get_addon_root_uri(sourceDir, addon1.id);
    let uri = a1.getResourceURI("/");
    Assert.equal(uri.spec, rootUri);
    uri = a1.getResourceURI("install.rdf");
    Assert.equal(uri.spec, rootUri + "install.rdf");

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