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

// Tests AddonRepository.jsm

Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm");

Components.utils.import("resource://testing-common/httpd.js");
var gServer = new HttpServer();
gServer.start(-1);

const PREF_GETADDONS_BROWSEADDONS        = "extensions.getAddons.browseAddons";
const PREF_GETADDONS_BROWSERECOMMENDED   = "extensions.getAddons.recommended.browseURL";
const PREF_GETADDONS_GETRECOMMENDED      = "extensions.getAddons.recommended.url";
const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL";
const PREF_GETADDONS_GETSEARCHRESULTS    = "extensions.getAddons.search.url";

const PORT          = gServer.identity.primaryPort;
const BASE_URL      = "http://localhost:" + PORT;
const DEFAULT_URL   = "about:blank";

gPort = PORT;

// Path to source URI of installed add-on
const INSTALL_URL1  = "/addons/test_AddonRepository_1.xpi";
// Path to source URI of installing add-on
const INSTALL_URL2  = "/addons/test_AddonRepository_2.xpi";
// Path to source URI of non-active add-on (state = STATE_AVAILABLE)
const INSTALL_URL3  = "/addons/test_AddonRepository_3.xpi";

// Properties of an individual add-on that should be checked
// Note: name is checked separately
var ADDON_PROPERTIES = ["id", "type", "version", "creator", "developers",
                        "description", "fullDescription", "developerComments",
                        "eula", "iconURL", "icons", "screenshots", "homepageURL",
                        "supportURL", "contributionURL", "contributionAmount",
                        "averageRating", "reviewCount", "reviewURL",
                        "totalDownloads", "weeklyDownloads", "dailyUsers",
                        "sourceURI", "repositoryStatus", "size", "updateDate",
                        "purchaseURL", "purchaseAmount", "purchaseDisplayAmount",
                        "compatibilityOverrides"];

// Results of getAddonsByIDs
var GET_RESULTS = [{
  id:                     "test1@tests.mozilla.org",
  type:                   "extension",
  version:                "1.1",
  creator:                {
                            name: "Test Creator 1",
                            url:  BASE_URL + "/creator1.html"
                          },
  developers:             [{
                            name: "Test Developer 1",
                            url:  BASE_URL + "/developer1.html"
                          }],
  description:            "Test Summary 1",
  fullDescription:        "Test Description 1",
  developerComments:      "Test Developer Comments 1",
  eula:                   "Test EULA 1",
  iconURL:                BASE_URL + "/icon1.png",
  icons:                  { "32": BASE_URL + "/icon1.png" },
  screenshots:            [{
                            url:             BASE_URL + "/full1-1.png",
                            width:           400,
                            height:          300,
                            thumbnailURL:    BASE_URL + "/thumbnail1-1.png",
                            thumbnailWidth:  200,
                            thumbnailHeight: 150,
                            caption:         "Caption 1 - 1"
                          }, {
                            url:          BASE_URL + "/full2-1.png",
                            thumbnailURL: BASE_URL + "/thumbnail2-1.png",
                            caption:      "Caption 2 - 1"
                          }],
  homepageURL:            BASE_URL + "/learnmore1.html",
  learnmoreURL:           BASE_URL + "/learnmore1.html",
  supportURL:             BASE_URL + "/support1.html",
  contributionURL:        BASE_URL + "/meetDevelopers1.html",
  contributionAmount:     "$11.11",
  averageRating:          4,
  reviewCount:            1111,
  reviewURL:              BASE_URL + "/review1.html",
  totalDownloads:         2222,
  weeklyDownloads:        3333,
  dailyUsers:             4444,
  sourceURI:              BASE_URL + INSTALL_URL2,
  repositoryStatus:       8,
  size:                   5555,
  updateDate:             new Date(1265033045000),
  compatibilityOverrides: [{
                            type: "incompatible",
                            minVersion: 0.1,
                            maxVersion: 0.2,
                            appID: "xpcshell@tests.mozilla.org",
                            appMinVersion: 3.0,
                            appMaxVersion: 4.0
                          }, {
                            type: "incompatible",
                            minVersion: 0.2,
                            maxVersion: 0.3,
                            appID: "xpcshell@tests.mozilla.org",
                            appMinVersion: 5.0,
                            appMaxVersion: 6.0
                          }]
}, {
  id:                     "test_AddonRepository_1@tests.mozilla.org",
  type:                   "theme",
  version:                "1.4",
  repositoryStatus:       9999,
  icons:                  {}
}];

// Results of retrieveRecommendedAddons and searchAddons
var SEARCH_RESULTS = [{
  id:                     "test1@tests.mozilla.org",
  type:                   "extension",
  version:                "1.1",
  creator:                {
                            name: "Test Creator 1",
                            url:  BASE_URL + "/creator1.html"
                          },
  repositoryStatus:       8,
  sourceURI:              BASE_URL + "/test1.xpi",
  icons:                  {}
}, {
  id:                     "test2@tests.mozilla.org",
  type:                   "extension",
  version:                "1.2",
  creator:                {
                            name: "Test Creator 2",
                            url:  BASE_URL + "/creator2.html"
                          },
  developers:             [{
                            name: "Test Developer 2",
                            url:  BASE_URL + "/developer2.html"
                          }],
  description:            "Test Summary 2\n\nparagraph",
  fullDescription:        "Test Description 2\nnewline",
  developerComments:      "Test Developer\nComments 2",
  eula:                   "Test EULA 2",
  iconURL:                BASE_URL + "/icon2-32.png",
  icons:                  {
                            "32": BASE_URL + "/icon2-32.png",
                            "48": BASE_URL + "/icon2-48.png",
                            "64": BASE_URL + "/icon2-64.png"
                          },
  screenshots:            [{
                            url:          BASE_URL + "/full1-2.png",
                            thumbnailURL: BASE_URL + "/thumbnail1-2.png"
                          }, {
                            url:          BASE_URL + "/full2-2.png",
                            thumbnailURL: BASE_URL + "/thumbnail2-2.png",
                            caption:      "Caption 2"
                          }],
  homepageURL:            BASE_URL + "/learnmore2.html",
  supportURL:             BASE_URL + "/support2.html",
  learnmoreURL:           BASE_URL + "/learnmore2.html",
  contributionURL:        BASE_URL + "/meetDevelopers2.html",
  contributionAmount:     null,
  repositoryStatus:       4,
  sourceURI:              BASE_URL + "/test2.xpi"
}, {
  id:                     "test3@tests.mozilla.org",
  type:                   "theme",
  version:                "1.3",
  creator:                {
                            name: "Test Creator 3",
                            url:  BASE_URL + "/creator3.html"
                          },
  developers:             [{
                            name: "First Test Developer 3",
                            url:  BASE_URL + "/developer1-3.html"
                          }, {
                            name: "Second Test Developer 3",
                            url:  BASE_URL + "/developer2-3.html"
                          }],
  description:            "Test Summary 3",
  fullDescription:        "Test Description 3\n\n    List item 1\n    List item 2",
  developerComments:      "Test Developer Comments 3",
  eula:                   "Test EULA 3",
  iconURL:                BASE_URL + "/icon3.png",
  icons:                  { "32": BASE_URL + "/icon3.png" },
  screenshots:            [{
                            url:          BASE_URL + "/full1-3.png",
                            thumbnailURL: BASE_URL + "/thumbnail1-3.png",
                            caption:      "Caption 1 - 3"
                          }, {
                            url:          BASE_URL + "/full2-3.png",
                            caption:      "Caption 2 - 3"
                          }, {
                            url:          BASE_URL + "/full3-3.png",
                            thumbnailURL: BASE_URL + "/thumbnail3-3.png",
                            caption:      "Caption 3 - 3"
                          }],
  homepageURL:            BASE_URL + "/homepage3.html",
  supportURL:             BASE_URL + "/support3.html",
  learnmoreURL:           BASE_URL + "/learnmore3.html",
  contributionURL:        BASE_URL + "/meetDevelopers3.html",
  contributionAmount:     "$11.11",
  averageRating:          2,
  reviewCount:            1111,
  reviewURL:              BASE_URL + "/review3.html",
  totalDownloads:         2222,
  weeklyDownloads:        3333,
  dailyUsers:             4444,
  sourceURI:              BASE_URL + "/test3.xpi",
  repositoryStatus:       8,
  size:                   5555,
  updateDate:             new Date(1265033045000),

}, {
  id:                     "purchase1@tests.mozilla.org",
  type:                   "extension",
  version:                "2.0",
  creator:                {
                            name: "Test Creator - Last Passing",
                            url:  BASE_URL + "/creatorLastPassing.html"
                          },
  averageRating:          5,
  repositoryStatus:       4,
  purchaseURL:            "http://localhost:" + PORT + "/purchaseURL1",
  purchaseAmount:         5,
  purchaseDisplayAmount:  "$5",
  icons:                  {}
}, {
  id:                     "purchase2@tests.mozilla.org",
  type:                   "extension",
  version:                "2.0",
  creator:                {
                            name: "Test Creator - Last Passing",
                            url:  BASE_URL + "/creatorLastPassing.html"
                          },
  averageRating:          5,
  repositoryStatus:       4,
  purchaseURL:            "http://localhost:" + PORT + "/purchaseURL2",
  purchaseAmount:         10,
  purchaseDisplayAmount:  "$10",
  icons:                  {}
}, {
  id:                     "test-lastPassing@tests.mozilla.org",
  type:                   "extension",
  version:                "2.0",
  creator:                {
                            name: "Test Creator - Last Passing",
                            url:  BASE_URL + "/creatorLastPassing.html"
                          },
  averageRating:          5,
  repositoryStatus:       4,
  sourceURI:              BASE_URL + "/addons/test_AddonRepository_3.xpi",
  icons:                  {}
}];

const TOTAL_RESULTS = 1111;
const MAX_RESULTS = SEARCH_RESULTS.length;

// Used to differentiate between testing that a search success
// or a search failure for retrieveRecommendedAddons and searchAddons
const FAILED_MAX_RESULTS  = 9999;

// Values for testing AddonRepository.getAddonsByIDs()
var GET_TEST = {
  preference:       PREF_GETADDONS_BYIDS,
  preferenceValue:  BASE_URL + "/%OS%/%VERSION%/%API_VERSION%/" +
                    "%API_VERSION%/%IDS%",
  failedIDs:      ["test1@tests.mozilla.org"],
  failedURL:        "/XPCShell/1/1.5/1.5/test1%40tests.mozilla.org",
  successfulIDs:  ["test1@tests.mozilla.org",
                     "{00000000-1111-2222-3333-444444444444}",
                     "test_AddonRepository_1@tests.mozilla.org"],
  successfulURL:    "/XPCShell/1/1.5/1.5/test1%40tests.mozilla.org," +
                    "%7B00000000-1111-2222-3333-444444444444%7D," +
                    "test_AddonRepository_1%40tests.mozilla.org"
};

// Values for testing AddonRepository.retrieveRecommendedAddons()
var RECOMMENDED_TEST = {
  preference:       PREF_GETADDONS_GETRECOMMENDED,
  preferenceValue:  BASE_URL + "/%OS%/%VERSION%/%API_VERSION%/" +
                    "%API_VERSION%/%MAX_RESULTS%",
  failedURL:        "/XPCShell/1/1.5/1.5/" + (2 * FAILED_MAX_RESULTS),
  successfulURL:    "/XPCShell/1/1.5/1.5/" + (2 * MAX_RESULTS)
};

// Values for testing AddonRepository.searchAddons()
var SEARCH_TEST = {
  searchTerms:      "odd=search:with&weird\"characters",
  preference:       PREF_GETADDONS_GETSEARCHRESULTS,
  preferenceValue:  BASE_URL + "/%OS%/%VERSION%/%API_VERSION%/" +
                    "%API_VERSION%/%MAX_RESULTS%/%TERMS%",
  failedURL:        "/XPCShell/1/1.5/1.5/" + (2 * FAILED_MAX_RESULTS) +
                    "/odd%3Dsearch%3Awith%26weird%22characters",
  successfulURL:    "/XPCShell/1/1.5/1.5/" + (2 * MAX_RESULTS) +
                    "/odd%3Dsearch%3Awith%26weird%22characters"
};

// Test that actual results and expected results are equal
function check_results(aActualAddons, aExpectedAddons, aAddonCount, aInstallNull) {
  do_check_false(AddonRepository.isSearching);

  do_check_eq(aActualAddons.length, aAddonCount);
  do_check_addons(aActualAddons, aExpectedAddons, ADDON_PROPERTIES);

  // Additional tests
  aActualAddons.forEach(function check_each_addon(aActualAddon) {
    // Separately check name so better messages are output when test fails
    if (aActualAddon.name == "FAIL")
      do_throw(aActualAddon.id + " - " + aActualAddon.description);
    if (aActualAddon.name != "PASS")
      do_throw(aActualAddon.id + " - " + "invalid add-on name " + aActualAddon.name);

    do_check_eq(aActualAddon.install == null, !!aInstallNull || !aActualAddon.sourceURI);

    // Check that sourceURI property consistent within actual addon
    if (aActualAddon.install)
      do_check_eq(aActualAddon.install.sourceURI.spec, aActualAddon.sourceURI.spec);
  });
}

// Complete a search, also testing cancelSearch() and isSearching
function complete_search(aSearch, aSearchCallback) {
  var failCallback = {
    searchSucceeded: function(addons, length, total) {
      do_throw("failCallback.searchSucceeded should not be called");
      end_test();
    },

    searchFailed: function() {
      do_throw("failCallback.searchFailed should not be called");
      end_test();
    }
  };

  var callbackCalled = false;
  var testCallback = {
    searchSucceeded: function(addons, length, total) {
      do_throw("testCallback.searchSucceeded should not be called");
      end_test();
    },

    searchFailed: function() {
      callbackCalled = true;
    }
  };

  // Should fail because cancelled it immediately
  aSearch(failCallback);
  do_check_true(AddonRepository.isSearching);
  AddonRepository.cancelSearch();
  do_check_false(AddonRepository.isSearching);

  aSearch(aSearchCallback);
  do_check_true(AddonRepository.isSearching);

  // searchFailed should be called immediately because already searching
  aSearch(testCallback);
  do_check_true(callbackCalled);
  do_check_true(AddonRepository.isSearching);
}


function run_test() {
  // Setup for test
  do_test_pending();
  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");

  startupManager();

  // Install an add-on so can check that it isn't returned in the results
  installAllFiles([do_get_addon("test_AddonRepository_1")], function addon_1_install_callback() {
    restartManager();

    // Register other add-on XPI files
    gServer.registerFile(INSTALL_URL2,
                        do_get_addon("test_AddonRepository_2"));
    gServer.registerFile(INSTALL_URL3,
                        do_get_addon("test_AddonRepository_3"));

    // Register files used to test search failure
    mapUrlToFile(GET_TEST.failedURL,
                 do_get_file("data/test_AddonRepository_failed.xml"),
                 gServer);
    mapUrlToFile(RECOMMENDED_TEST.failedURL,
                 do_get_file("data/test_AddonRepository_failed.xml"),
                 gServer);
    mapUrlToFile(SEARCH_TEST.failedURL,
                 do_get_file("data/test_AddonRepository_failed.xml"),
                 gServer);

    // Register files used to test search success
    mapUrlToFile(GET_TEST.successfulURL,
                 do_get_file("data/test_AddonRepository_getAddonsByIDs.xml"),
                 gServer);
    mapUrlToFile(RECOMMENDED_TEST.successfulURL,
                 do_get_file("data/test_AddonRepository.xml"),
                 gServer);
    mapUrlToFile(SEARCH_TEST.successfulURL,
                 do_get_file("data/test_AddonRepository.xml"),
                 gServer);

    // Create an active AddonInstall so can check that it isn't returned in the results
    AddonManager.getInstallForURL(BASE_URL + INSTALL_URL2, function addon_2_get(aInstall) {
      try {
        aInstall.install();
      }
      catch (e) {
        do_print("Failed to install add-on " + aInstall.sourceURI.spec);
        do_report_unexpected_exception(e);
      }

      // Create a non-active AddonInstall so can check that it is returned in the results
      AddonManager.getInstallForURL(BASE_URL + INSTALL_URL3,
                                    run_test_1, "application/x-xpinstall");
    }, "application/x-xpinstall");
  });
}

function end_test() {
  let testDir = gProfD.clone();
  testDir.append("extensions");
  testDir.append("staged");
  gServer.stop(function() {
    function loop() {
      if (!testDir.exists()) {
        do_print("Staged directory has been cleaned up");
        do_test_finished();
      }
      do_print("Waiting 1 second until cleanup is complete");
      do_timeout(1000, loop);
    }
    loop();
  });
}

// Tests homepageURL, getRecommendedURL() and getSearchURL()
function run_test_1() {
  function check_urls(aPreference, aGetURL, aTests) {
    aTests.forEach(function(aTest) {
      Services.prefs.setCharPref(aPreference, aTest.preferenceValue);
      do_check_eq(aGetURL(aTest), aTest.expectedURL);
    });
  }

  var urlTests = [{
    preferenceValue:  BASE_URL,
    expectedURL:      BASE_URL
  }, {
    preferenceValue:  BASE_URL + "/%OS%/%VERSION%",
    expectedURL:      BASE_URL + "/XPCShell/1"
  }];

  // Extra tests for AddonRepository.getSearchURL();
  var searchURLTests = [{
    searchTerms:      "test",
    preferenceValue:  BASE_URL + "/search?q=%TERMS%",
    expectedURL:      BASE_URL + "/search?q=test"
  }, {
    searchTerms:      "test search",
    preferenceValue:  BASE_URL + "/%TERMS%",
    expectedURL:      BASE_URL + "/test%20search"
  }, {
    searchTerms:      "odd=search:with&weird\"characters",
    preferenceValue:  BASE_URL + "/%TERMS%",
    expectedURL:      BASE_URL + "/odd%3Dsearch%3Awith%26weird%22characters"
  }];

  // Setup tests for homepageURL, getRecommendedURL() and getSearchURL()
  var tests = [{
    initiallyUndefined: true,
    preference:         PREF_GETADDONS_BROWSEADDONS,
    urlTests:           urlTests,
    getURL:             () => AddonRepository.homepageURL
  }, {
    initiallyUndefined: true,
    preference:         PREF_GETADDONS_BROWSERECOMMENDED,
    urlTests:           urlTests,
    getURL:             () => AddonRepository.getRecommendedURL()
  }, {
    initiallyUndefined: false,
    preference:         PREF_GETADDONS_BROWSESEARCHRESULTS,
    urlTests:           urlTests.concat(searchURLTests),
    getURL:             function getSearchURL(aTest) {
                          var searchTerms = aTest && aTest.searchTerms ? aTest.searchTerms
                                                                       : "unused terms";
                          return AddonRepository.getSearchURL(searchTerms);
                        }
  }];

  tests.forEach(function url_test(aTest) {
    if (aTest.initiallyUndefined) {
      // Preference is not defined by default
      do_check_eq(Services.prefs.getPrefType(aTest.preference),
                  Services.prefs.PREF_INVALID);
      do_check_eq(aTest.getURL(), DEFAULT_URL);
    }

    check_urls(aTest.preference, aTest.getURL, aTest.urlTests);
  });

  run_test_getAddonsByID_fails();
}

// Tests failure of AddonRepository.getAddonsByIDs()
function run_test_getAddonsByID_fails() {
  Services.prefs.setCharPref(GET_TEST.preference, GET_TEST.preferenceValue);
  var callback = {
    searchSucceeded: function(aAddonsList, aAddonCount, aTotalResults) {
      do_throw("searchAddons should not have succeeded");
      end_test();
    },

    searchFailed: function() {
      do_check_false(AddonRepository.isSearching);
      run_test_getAddonsByID_succeeds();
    }
  };

  complete_search(function complete_search_fail_callback(aCallback) {
    AddonRepository.getAddonsByIDs(GET_TEST.failedIDs, aCallback);
  }, callback);
}

// Tests success of AddonRepository.getAddonsByIDs()
function run_test_getAddonsByID_succeeds() {
  var callback = {
    searchSucceeded: function(aAddonsList, aAddonCount, aTotalResults) {
      do_check_eq(aTotalResults, -1);
      check_results(aAddonsList, GET_RESULTS, aAddonCount, true);
      run_test_retrieveRecommended_fails();
    },

    searchFailed: function() {
      do_throw("searchAddons should not have failed");
      end_test();
    }
  };

  complete_search(function complete_search_succeed_callback(aCallback) {
    AddonRepository.getAddonsByIDs(GET_TEST.successfulIDs, aCallback);
  }, callback);
}

// Tests failure of AddonRepository.retrieveRecommendedAddons()
function run_test_retrieveRecommended_fails() {
  Services.prefs.setCharPref(RECOMMENDED_TEST.preference,
                             RECOMMENDED_TEST.preferenceValue);
  var callback = {
    searchSucceeded: function(aAddonsList, aAddonCount, aTotalResults) {
      do_throw("retrieveRecommendedAddons should not have succeeded");
      end_test();
    },

    searchFailed: function() {
      do_check_false(AddonRepository.isSearching);
      run_test_retrieveRecommended_succeed();
    }
  };

  complete_search(function retrieveRecommended_failing_callback(aCallback) {
    AddonRepository.retrieveRecommendedAddons(FAILED_MAX_RESULTS, aCallback);
  }, callback);
}

// Tests success of AddonRepository.retrieveRecommendedAddons()
function run_test_retrieveRecommended_succeed() {
  var callback = {
    searchSucceeded: function(aAddonsList, aAddonCount, aTotalResults) {
      do_check_eq(aTotalResults, -1);
      check_results(aAddonsList, SEARCH_RESULTS, aAddonCount);
      run_test_searchAddons_fails();
    },

    searchFailed: function() {
      do_throw("retrieveRecommendedAddons should not have failed");
      end_test();
    }
  };

  complete_search(function retrieveRecommended_succeed_callback(aCallback) {
    AddonRepository.retrieveRecommendedAddons(MAX_RESULTS, aCallback);
  }, callback);
}

// Tests failure of AddonRepository.searchAddons()
function run_test_searchAddons_fails() {
  Services.prefs.setCharPref(SEARCH_TEST.preference, SEARCH_TEST.preferenceValue);
  var callback = {
    searchSucceeded: function(aAddonsList, aAddonCount, aTotalResults) {
      do_throw("searchAddons should not have succeeded");
      end_test();
    },

    searchFailed: function() {
      do_check_false(AddonRepository.isSearching);
      run_test_searchAddons_succeeds();
    }
  };

  complete_search(function(aCallback) {
    var searchTerms = SEARCH_TEST.searchTerms;
    AddonRepository.searchAddons(searchTerms, FAILED_MAX_RESULTS, aCallback);
  }, callback);
}

// Tests success of AddonRepository.searchAddons()
function run_test_searchAddons_succeeds() {
  var callback = {
    searchSucceeded: function(aAddonsList, aAddonCount, aTotalResults) {
      do_check_eq(aTotalResults, TOTAL_RESULTS);
      check_results(aAddonsList, SEARCH_RESULTS, aAddonCount);
      end_test();
    },

    searchFailed: function() {
      do_throw("searchAddons should not have failed");
      end_test();
    }
  };

  complete_search(function(aCallback) {
    var searchTerms = SEARCH_TEST.searchTerms;
    AddonRepository.searchAddons(searchTerms, MAX_RESULTS, aCallback);
  }, callback);
}