1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/tests/unit/test_addons_store.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,474 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +"use strict"; 1.8 + 1.9 +Cu.import("resource://gre/modules/Preferences.jsm"); 1.10 +Cu.import("resource://services-sync/addonutils.js"); 1.11 +Cu.import("resource://services-sync/engines/addons.js"); 1.12 +Cu.import("resource://services-sync/service.js"); 1.13 +Cu.import("resource://services-sync/util.js"); 1.14 + 1.15 +const HTTP_PORT = 8888; 1.16 + 1.17 +let prefs = new Preferences(); 1.18 + 1.19 +prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%"); 1.20 +loadAddonTestFunctions(); 1.21 +startupManager(); 1.22 + 1.23 +Service.engineManager.register(AddonsEngine); 1.24 +let engine = Service.engineManager.get("addons"); 1.25 +let tracker = engine._tracker; 1.26 +let store = engine._store; 1.27 +let reconciler = engine._reconciler; 1.28 + 1.29 +/** 1.30 + * Create a AddonsRec for this application with the fields specified. 1.31 + * 1.32 + * @param id Sync GUID of record 1.33 + * @param addonId ID of add-on 1.34 + * @param enabled Boolean whether record is enabled 1.35 + * @param deleted Boolean whether record was deleted 1.36 + */ 1.37 +function createRecordForThisApp(id, addonId, enabled, deleted) { 1.38 + return { 1.39 + id: id, 1.40 + addonID: addonId, 1.41 + enabled: enabled, 1.42 + deleted: !!deleted, 1.43 + applicationID: Services.appinfo.ID, 1.44 + source: "amo" 1.45 + }; 1.46 +} 1.47 + 1.48 +function createAndStartHTTPServer(port) { 1.49 + try { 1.50 + let server = new HttpServer(); 1.51 + 1.52 + let bootstrap1XPI = ExtensionsTestPath("/addons/test_bootstrap1_1.xpi"); 1.53 + 1.54 + server.registerFile("/search/guid:bootstrap1%40tests.mozilla.org", 1.55 + do_get_file("bootstrap1-search.xml")); 1.56 + server.registerFile("/bootstrap1.xpi", do_get_file(bootstrap1XPI)); 1.57 + 1.58 + server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org", 1.59 + do_get_file("missing-xpi-search.xml")); 1.60 + 1.61 + server.start(port); 1.62 + 1.63 + return server; 1.64 + } catch (ex) { 1.65 + _("Got exception starting HTTP server on port " + port); 1.66 + _("Error: " + Utils.exceptionStr(ex)); 1.67 + do_throw(ex); 1.68 + } 1.69 +} 1.70 + 1.71 +function run_test() { 1.72 + initTestLogging("Trace"); 1.73 + Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace; 1.74 + Log.repository.getLogger("Sync.AddonsRepository").level = 1.75 + Log.Level.Trace; 1.76 + 1.77 + reconciler.startListening(); 1.78 + 1.79 + // Don't flush to disk in the middle of an event listener! 1.80 + // This causes test hangs on WinXP. 1.81 + reconciler._shouldPersist = false; 1.82 + 1.83 + run_next_test(); 1.84 +} 1.85 + 1.86 +add_test(function test_remove() { 1.87 + _("Ensure removing add-ons from deleted records works."); 1.88 + 1.89 + let addon = installAddon("test_bootstrap1_1"); 1.90 + let record = createRecordForThisApp(addon.syncGUID, addon.id, true, true); 1.91 + 1.92 + let failed = store.applyIncomingBatch([record]); 1.93 + do_check_eq(0, failed.length); 1.94 + 1.95 + let newAddon = getAddonFromAddonManagerByID(addon.id); 1.96 + do_check_eq(null, newAddon); 1.97 + 1.98 + run_next_test(); 1.99 +}); 1.100 + 1.101 +add_test(function test_apply_enabled() { 1.102 + _("Ensures that changes to the userEnabled flag apply."); 1.103 + 1.104 + let addon = installAddon("test_bootstrap1_1"); 1.105 + do_check_true(addon.isActive); 1.106 + do_check_false(addon.userDisabled); 1.107 + 1.108 + _("Ensure application of a disable record works as expected."); 1.109 + let records = []; 1.110 + records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false)); 1.111 + let failed = store.applyIncomingBatch(records); 1.112 + do_check_eq(0, failed.length); 1.113 + addon = getAddonFromAddonManagerByID(addon.id); 1.114 + do_check_true(addon.userDisabled); 1.115 + records = []; 1.116 + 1.117 + _("Ensure enable record works as expected."); 1.118 + records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, false)); 1.119 + failed = store.applyIncomingBatch(records); 1.120 + do_check_eq(0, failed.length); 1.121 + addon = getAddonFromAddonManagerByID(addon.id); 1.122 + do_check_false(addon.userDisabled); 1.123 + records = []; 1.124 + 1.125 + _("Ensure enabled state updates don't apply if the ignore pref is set."); 1.126 + records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false)); 1.127 + Svc.Prefs.set("addons.ignoreUserEnabledChanges", true); 1.128 + failed = store.applyIncomingBatch(records); 1.129 + do_check_eq(0, failed.length); 1.130 + addon = getAddonFromAddonManagerByID(addon.id); 1.131 + do_check_false(addon.userDisabled); 1.132 + records = []; 1.133 + 1.134 + uninstallAddon(addon); 1.135 + Svc.Prefs.reset("addons.ignoreUserEnabledChanges"); 1.136 + run_next_test(); 1.137 +}); 1.138 + 1.139 +add_test(function test_ignore_different_appid() { 1.140 + _("Ensure that incoming records with a different application ID are ignored."); 1.141 + 1.142 + // We test by creating a record that should result in an update. 1.143 + let addon = installAddon("test_bootstrap1_1"); 1.144 + do_check_false(addon.userDisabled); 1.145 + 1.146 + let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false); 1.147 + record.applicationID = "FAKE_ID"; 1.148 + 1.149 + let failed = store.applyIncomingBatch([record]); 1.150 + do_check_eq(0, failed.length); 1.151 + 1.152 + let newAddon = getAddonFromAddonManagerByID(addon.id); 1.153 + do_check_false(addon.userDisabled); 1.154 + 1.155 + uninstallAddon(addon); 1.156 + 1.157 + run_next_test(); 1.158 +}); 1.159 + 1.160 +add_test(function test_ignore_unknown_source() { 1.161 + _("Ensure incoming records with unknown source are ignored."); 1.162 + 1.163 + let addon = installAddon("test_bootstrap1_1"); 1.164 + 1.165 + let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false); 1.166 + record.source = "DUMMY_SOURCE"; 1.167 + 1.168 + let failed = store.applyIncomingBatch([record]); 1.169 + do_check_eq(0, failed.length); 1.170 + 1.171 + let newAddon = getAddonFromAddonManagerByID(addon.id); 1.172 + do_check_false(addon.userDisabled); 1.173 + 1.174 + uninstallAddon(addon); 1.175 + 1.176 + run_next_test(); 1.177 +}); 1.178 + 1.179 +add_test(function test_apply_uninstall() { 1.180 + _("Ensures that uninstalling an add-on from a record works."); 1.181 + 1.182 + let addon = installAddon("test_bootstrap1_1"); 1.183 + 1.184 + let records = []; 1.185 + records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, true)); 1.186 + let failed = store.applyIncomingBatch(records); 1.187 + do_check_eq(0, failed.length); 1.188 + 1.189 + addon = getAddonFromAddonManagerByID(addon.id); 1.190 + do_check_eq(null, addon); 1.191 + 1.192 + run_next_test(); 1.193 +}); 1.194 + 1.195 +add_test(function test_addon_syncability() { 1.196 + _("Ensure isAddonSyncable functions properly."); 1.197 + 1.198 + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); 1.199 + Svc.Prefs.set("addons.trustedSourceHostnames", 1.200 + "addons.mozilla.org,other.example.com"); 1.201 + 1.202 + do_check_false(store.isAddonSyncable(null)); 1.203 + 1.204 + let addon = installAddon("test_bootstrap1_1"); 1.205 + do_check_true(store.isAddonSyncable(addon)); 1.206 + 1.207 + let dummy = {}; 1.208 + const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; 1.209 + for each (let k in KEYS) { 1.210 + dummy[k] = addon[k]; 1.211 + } 1.212 + 1.213 + do_check_true(store.isAddonSyncable(dummy)); 1.214 + 1.215 + dummy.type = "UNSUPPORTED"; 1.216 + do_check_false(store.isAddonSyncable(dummy)); 1.217 + dummy.type = addon.type; 1.218 + 1.219 + dummy.scope = 0; 1.220 + do_check_false(store.isAddonSyncable(dummy)); 1.221 + dummy.scope = addon.scope; 1.222 + 1.223 + dummy.foreignInstall = true; 1.224 + do_check_false(store.isAddonSyncable(dummy)); 1.225 + dummy.foreignInstall = false; 1.226 + 1.227 + uninstallAddon(addon); 1.228 + 1.229 + do_check_false(store.isSourceURITrusted(null)); 1.230 + 1.231 + function createURI(s) { 1.232 + let service = Components.classes["@mozilla.org/network/io-service;1"] 1.233 + .getService(Components.interfaces.nsIIOService); 1.234 + return service.newURI(s, null, null); 1.235 + } 1.236 + 1.237 + let trusted = [ 1.238 + "https://addons.mozilla.org/foo", 1.239 + "https://other.example.com/foo" 1.240 + ]; 1.241 + 1.242 + let untrusted = [ 1.243 + "http://addons.mozilla.org/foo", // non-https 1.244 + "ftps://addons.mozilla.org/foo", // non-https 1.245 + "https://untrusted.example.com/foo", // non-trusted hostname` 1.246 + ]; 1.247 + 1.248 + for each (let uri in trusted) { 1.249 + do_check_true(store.isSourceURITrusted(createURI(uri))); 1.250 + } 1.251 + 1.252 + for each (let uri in untrusted) { 1.253 + do_check_false(store.isSourceURITrusted(createURI(uri))); 1.254 + } 1.255 + 1.256 + Svc.Prefs.set("addons.trustedSourceHostnames", ""); 1.257 + for each (let uri in trusted) { 1.258 + do_check_false(store.isSourceURITrusted(createURI(uri))); 1.259 + } 1.260 + 1.261 + Svc.Prefs.set("addons.trustedSourceHostnames", "addons.mozilla.org"); 1.262 + do_check_true(store.isSourceURITrusted(createURI("https://addons.mozilla.org/foo"))); 1.263 + 1.264 + Svc.Prefs.reset("addons.trustedSourceHostnames"); 1.265 + 1.266 + run_next_test(); 1.267 +}); 1.268 + 1.269 +add_test(function test_ignore_hotfixes() { 1.270 + _("Ensure that hotfix extensions are ignored."); 1.271 + 1.272 + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); 1.273 + 1.274 + // A hotfix extension is one that has the id the same as the 1.275 + // extensions.hotfix.id pref. 1.276 + let prefs = new Preferences("extensions."); 1.277 + 1.278 + let addon = installAddon("test_bootstrap1_1"); 1.279 + do_check_true(store.isAddonSyncable(addon)); 1.280 + 1.281 + let dummy = {}; 1.282 + const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; 1.283 + for each (let k in KEYS) { 1.284 + dummy[k] = addon[k]; 1.285 + } 1.286 + 1.287 + // Basic sanity check. 1.288 + do_check_true(store.isAddonSyncable(dummy)); 1.289 + 1.290 + prefs.set("hotfix.id", dummy.id); 1.291 + do_check_false(store.isAddonSyncable(dummy)); 1.292 + 1.293 + // Verify that int values don't throw off checking. 1.294 + let prefSvc = Cc["@mozilla.org/preferences-service;1"] 1.295 + .getService(Ci.nsIPrefService) 1.296 + .getBranch("extensions."); 1.297 + // Need to delete pref before changing type. 1.298 + prefSvc.deleteBranch("hotfix.id"); 1.299 + prefSvc.setIntPref("hotfix.id", 0xdeadbeef); 1.300 + 1.301 + do_check_true(store.isAddonSyncable(dummy)); 1.302 + 1.303 + uninstallAddon(addon); 1.304 + 1.305 + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); 1.306 + prefs.reset("hotfix.id"); 1.307 + 1.308 + run_next_test(); 1.309 +}); 1.310 + 1.311 + 1.312 +add_test(function test_get_all_ids() { 1.313 + _("Ensures that getAllIDs() returns an appropriate set."); 1.314 + 1.315 + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); 1.316 + 1.317 + _("Installing two addons."); 1.318 + let addon1 = installAddon("test_install1"); 1.319 + let addon2 = installAddon("test_bootstrap1_1"); 1.320 + 1.321 + _("Ensure they're syncable."); 1.322 + do_check_true(store.isAddonSyncable(addon1)); 1.323 + do_check_true(store.isAddonSyncable(addon2)); 1.324 + 1.325 + let ids = store.getAllIDs(); 1.326 + 1.327 + do_check_eq("object", typeof(ids)); 1.328 + do_check_eq(2, Object.keys(ids).length); 1.329 + do_check_true(addon1.syncGUID in ids); 1.330 + do_check_true(addon2.syncGUID in ids); 1.331 + 1.332 + addon1.install.cancel(); 1.333 + uninstallAddon(addon2); 1.334 + 1.335 + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); 1.336 + run_next_test(); 1.337 +}); 1.338 + 1.339 +add_test(function test_change_item_id() { 1.340 + _("Ensures that changeItemID() works properly."); 1.341 + 1.342 + let addon = installAddon("test_bootstrap1_1"); 1.343 + 1.344 + let oldID = addon.syncGUID; 1.345 + let newID = Utils.makeGUID(); 1.346 + 1.347 + store.changeItemID(oldID, newID); 1.348 + 1.349 + let newAddon = getAddonFromAddonManagerByID(addon.id); 1.350 + do_check_neq(null, newAddon); 1.351 + do_check_eq(newID, newAddon.syncGUID); 1.352 + 1.353 + uninstallAddon(newAddon); 1.354 + 1.355 + run_next_test(); 1.356 +}); 1.357 + 1.358 +add_test(function test_create() { 1.359 + _("Ensure creating/installing an add-on from a record works."); 1.360 + 1.361 + // Set this so that getInstallFromSearchResult doesn't end up 1.362 + // failing the install due to an insecure source URI scheme. 1.363 + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); 1.364 + let server = createAndStartHTTPServer(HTTP_PORT); 1.365 + 1.366 + let addon = installAddon("test_bootstrap1_1"); 1.367 + let id = addon.id; 1.368 + uninstallAddon(addon); 1.369 + 1.370 + let guid = Utils.makeGUID(); 1.371 + let record = createRecordForThisApp(guid, id, true, false); 1.372 + 1.373 + let failed = store.applyIncomingBatch([record]); 1.374 + do_check_eq(0, failed.length); 1.375 + 1.376 + let newAddon = getAddonFromAddonManagerByID(id); 1.377 + do_check_neq(null, newAddon); 1.378 + do_check_eq(guid, newAddon.syncGUID); 1.379 + do_check_false(newAddon.userDisabled); 1.380 + 1.381 + uninstallAddon(newAddon); 1.382 + 1.383 + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); 1.384 + server.stop(run_next_test); 1.385 +}); 1.386 + 1.387 +add_test(function test_create_missing_search() { 1.388 + _("Ensures that failed add-on searches are handled gracefully."); 1.389 + 1.390 + let server = createAndStartHTTPServer(HTTP_PORT); 1.391 + 1.392 + // The handler for this ID is not installed, so a search should 404. 1.393 + const id = "missing@tests.mozilla.org"; 1.394 + let guid = Utils.makeGUID(); 1.395 + let record = createRecordForThisApp(guid, id, true, false); 1.396 + 1.397 + let failed = store.applyIncomingBatch([record]); 1.398 + do_check_eq(1, failed.length); 1.399 + do_check_eq(guid, failed[0]); 1.400 + 1.401 + let addon = getAddonFromAddonManagerByID(id); 1.402 + do_check_eq(null, addon); 1.403 + 1.404 + server.stop(run_next_test); 1.405 +}); 1.406 + 1.407 +add_test(function test_create_bad_install() { 1.408 + _("Ensures that add-ons without a valid install are handled gracefully."); 1.409 + 1.410 + let server = createAndStartHTTPServer(HTTP_PORT); 1.411 + 1.412 + // The handler returns a search result but the XPI will 404. 1.413 + const id = "missing-xpi@tests.mozilla.org"; 1.414 + let guid = Utils.makeGUID(); 1.415 + let record = createRecordForThisApp(guid, id, true, false); 1.416 + 1.417 + let failed = store.applyIncomingBatch([record]); 1.418 + do_check_eq(1, failed.length); 1.419 + do_check_eq(guid, failed[0]); 1.420 + 1.421 + let addon = getAddonFromAddonManagerByID(id); 1.422 + do_check_eq(null, addon); 1.423 + 1.424 + server.stop(run_next_test); 1.425 +}); 1.426 + 1.427 +add_test(function test_wipe() { 1.428 + _("Ensures that wiping causes add-ons to be uninstalled."); 1.429 + 1.430 + let addon1 = installAddon("test_bootstrap1_1"); 1.431 + 1.432 + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); 1.433 + store.wipe(); 1.434 + 1.435 + let addon = getAddonFromAddonManagerByID(addon1.id); 1.436 + do_check_eq(null, addon); 1.437 + 1.438 + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); 1.439 + 1.440 + run_next_test(); 1.441 +}); 1.442 + 1.443 +add_test(function test_wipe_and_install() { 1.444 + _("Ensure wipe followed by install works."); 1.445 + 1.446 + // This tests the reset sync flow where remote data is replaced by local. The 1.447 + // receiving client will see a wipe followed by a record which should undo 1.448 + // the wipe. 1.449 + let installed = installAddon("test_bootstrap1_1"); 1.450 + 1.451 + let record = createRecordForThisApp(installed.syncGUID, installed.id, true, 1.452 + false); 1.453 + 1.454 + Svc.Prefs.set("addons.ignoreRepositoryChecking", true); 1.455 + store.wipe(); 1.456 + 1.457 + let deleted = getAddonFromAddonManagerByID(installed.id); 1.458 + do_check_null(deleted); 1.459 + 1.460 + // Re-applying the record can require re-fetching the XPI. 1.461 + let server = createAndStartHTTPServer(HTTP_PORT); 1.462 + 1.463 + store.applyIncoming(record); 1.464 + 1.465 + let fetched = getAddonFromAddonManagerByID(record.addonID); 1.466 + do_check_true(!!fetched); 1.467 + 1.468 + Svc.Prefs.reset("addons.ignoreRepositoryChecking"); 1.469 + server.stop(run_next_test); 1.470 +}); 1.471 + 1.472 +add_test(function cleanup() { 1.473 + // There's an xpcom-shutdown hook for this, but let's give this a shot. 1.474 + reconciler.stopListening(); 1.475 + run_next_test(); 1.476 +}); 1.477 +