Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 "use strict";
6 Cu.import("resource://gre/modules/Preferences.jsm");
7 Cu.import("resource://services-sync/addonutils.js");
8 Cu.import("resource://services-sync/engines/addons.js");
9 Cu.import("resource://services-sync/service.js");
10 Cu.import("resource://services-sync/util.js");
12 const HTTP_PORT = 8888;
14 let prefs = new Preferences();
16 prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%");
17 loadAddonTestFunctions();
18 startupManager();
20 Service.engineManager.register(AddonsEngine);
21 let engine = Service.engineManager.get("addons");
22 let tracker = engine._tracker;
23 let store = engine._store;
24 let reconciler = engine._reconciler;
26 /**
27 * Create a AddonsRec for this application with the fields specified.
28 *
29 * @param id Sync GUID of record
30 * @param addonId ID of add-on
31 * @param enabled Boolean whether record is enabled
32 * @param deleted Boolean whether record was deleted
33 */
34 function createRecordForThisApp(id, addonId, enabled, deleted) {
35 return {
36 id: id,
37 addonID: addonId,
38 enabled: enabled,
39 deleted: !!deleted,
40 applicationID: Services.appinfo.ID,
41 source: "amo"
42 };
43 }
45 function createAndStartHTTPServer(port) {
46 try {
47 let server = new HttpServer();
49 let bootstrap1XPI = ExtensionsTestPath("/addons/test_bootstrap1_1.xpi");
51 server.registerFile("/search/guid:bootstrap1%40tests.mozilla.org",
52 do_get_file("bootstrap1-search.xml"));
53 server.registerFile("/bootstrap1.xpi", do_get_file(bootstrap1XPI));
55 server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org",
56 do_get_file("missing-xpi-search.xml"));
58 server.start(port);
60 return server;
61 } catch (ex) {
62 _("Got exception starting HTTP server on port " + port);
63 _("Error: " + Utils.exceptionStr(ex));
64 do_throw(ex);
65 }
66 }
68 function run_test() {
69 initTestLogging("Trace");
70 Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace;
71 Log.repository.getLogger("Sync.AddonsRepository").level =
72 Log.Level.Trace;
74 reconciler.startListening();
76 // Don't flush to disk in the middle of an event listener!
77 // This causes test hangs on WinXP.
78 reconciler._shouldPersist = false;
80 run_next_test();
81 }
83 add_test(function test_remove() {
84 _("Ensure removing add-ons from deleted records works.");
86 let addon = installAddon("test_bootstrap1_1");
87 let record = createRecordForThisApp(addon.syncGUID, addon.id, true, true);
89 let failed = store.applyIncomingBatch([record]);
90 do_check_eq(0, failed.length);
92 let newAddon = getAddonFromAddonManagerByID(addon.id);
93 do_check_eq(null, newAddon);
95 run_next_test();
96 });
98 add_test(function test_apply_enabled() {
99 _("Ensures that changes to the userEnabled flag apply.");
101 let addon = installAddon("test_bootstrap1_1");
102 do_check_true(addon.isActive);
103 do_check_false(addon.userDisabled);
105 _("Ensure application of a disable record works as expected.");
106 let records = [];
107 records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false));
108 let failed = store.applyIncomingBatch(records);
109 do_check_eq(0, failed.length);
110 addon = getAddonFromAddonManagerByID(addon.id);
111 do_check_true(addon.userDisabled);
112 records = [];
114 _("Ensure enable record works as expected.");
115 records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, false));
116 failed = store.applyIncomingBatch(records);
117 do_check_eq(0, failed.length);
118 addon = getAddonFromAddonManagerByID(addon.id);
119 do_check_false(addon.userDisabled);
120 records = [];
122 _("Ensure enabled state updates don't apply if the ignore pref is set.");
123 records.push(createRecordForThisApp(addon.syncGUID, addon.id, false, false));
124 Svc.Prefs.set("addons.ignoreUserEnabledChanges", true);
125 failed = store.applyIncomingBatch(records);
126 do_check_eq(0, failed.length);
127 addon = getAddonFromAddonManagerByID(addon.id);
128 do_check_false(addon.userDisabled);
129 records = [];
131 uninstallAddon(addon);
132 Svc.Prefs.reset("addons.ignoreUserEnabledChanges");
133 run_next_test();
134 });
136 add_test(function test_ignore_different_appid() {
137 _("Ensure that incoming records with a different application ID are ignored.");
139 // We test by creating a record that should result in an update.
140 let addon = installAddon("test_bootstrap1_1");
141 do_check_false(addon.userDisabled);
143 let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false);
144 record.applicationID = "FAKE_ID";
146 let failed = store.applyIncomingBatch([record]);
147 do_check_eq(0, failed.length);
149 let newAddon = getAddonFromAddonManagerByID(addon.id);
150 do_check_false(addon.userDisabled);
152 uninstallAddon(addon);
154 run_next_test();
155 });
157 add_test(function test_ignore_unknown_source() {
158 _("Ensure incoming records with unknown source are ignored.");
160 let addon = installAddon("test_bootstrap1_1");
162 let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false);
163 record.source = "DUMMY_SOURCE";
165 let failed = store.applyIncomingBatch([record]);
166 do_check_eq(0, failed.length);
168 let newAddon = getAddonFromAddonManagerByID(addon.id);
169 do_check_false(addon.userDisabled);
171 uninstallAddon(addon);
173 run_next_test();
174 });
176 add_test(function test_apply_uninstall() {
177 _("Ensures that uninstalling an add-on from a record works.");
179 let addon = installAddon("test_bootstrap1_1");
181 let records = [];
182 records.push(createRecordForThisApp(addon.syncGUID, addon.id, true, true));
183 let failed = store.applyIncomingBatch(records);
184 do_check_eq(0, failed.length);
186 addon = getAddonFromAddonManagerByID(addon.id);
187 do_check_eq(null, addon);
189 run_next_test();
190 });
192 add_test(function test_addon_syncability() {
193 _("Ensure isAddonSyncable functions properly.");
195 Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
196 Svc.Prefs.set("addons.trustedSourceHostnames",
197 "addons.mozilla.org,other.example.com");
199 do_check_false(store.isAddonSyncable(null));
201 let addon = installAddon("test_bootstrap1_1");
202 do_check_true(store.isAddonSyncable(addon));
204 let dummy = {};
205 const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"];
206 for each (let k in KEYS) {
207 dummy[k] = addon[k];
208 }
210 do_check_true(store.isAddonSyncable(dummy));
212 dummy.type = "UNSUPPORTED";
213 do_check_false(store.isAddonSyncable(dummy));
214 dummy.type = addon.type;
216 dummy.scope = 0;
217 do_check_false(store.isAddonSyncable(dummy));
218 dummy.scope = addon.scope;
220 dummy.foreignInstall = true;
221 do_check_false(store.isAddonSyncable(dummy));
222 dummy.foreignInstall = false;
224 uninstallAddon(addon);
226 do_check_false(store.isSourceURITrusted(null));
228 function createURI(s) {
229 let service = Components.classes["@mozilla.org/network/io-service;1"]
230 .getService(Components.interfaces.nsIIOService);
231 return service.newURI(s, null, null);
232 }
234 let trusted = [
235 "https://addons.mozilla.org/foo",
236 "https://other.example.com/foo"
237 ];
239 let untrusted = [
240 "http://addons.mozilla.org/foo", // non-https
241 "ftps://addons.mozilla.org/foo", // non-https
242 "https://untrusted.example.com/foo", // non-trusted hostname`
243 ];
245 for each (let uri in trusted) {
246 do_check_true(store.isSourceURITrusted(createURI(uri)));
247 }
249 for each (let uri in untrusted) {
250 do_check_false(store.isSourceURITrusted(createURI(uri)));
251 }
253 Svc.Prefs.set("addons.trustedSourceHostnames", "");
254 for each (let uri in trusted) {
255 do_check_false(store.isSourceURITrusted(createURI(uri)));
256 }
258 Svc.Prefs.set("addons.trustedSourceHostnames", "addons.mozilla.org");
259 do_check_true(store.isSourceURITrusted(createURI("https://addons.mozilla.org/foo")));
261 Svc.Prefs.reset("addons.trustedSourceHostnames");
263 run_next_test();
264 });
266 add_test(function test_ignore_hotfixes() {
267 _("Ensure that hotfix extensions are ignored.");
269 Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
271 // A hotfix extension is one that has the id the same as the
272 // extensions.hotfix.id pref.
273 let prefs = new Preferences("extensions.");
275 let addon = installAddon("test_bootstrap1_1");
276 do_check_true(store.isAddonSyncable(addon));
278 let dummy = {};
279 const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"];
280 for each (let k in KEYS) {
281 dummy[k] = addon[k];
282 }
284 // Basic sanity check.
285 do_check_true(store.isAddonSyncable(dummy));
287 prefs.set("hotfix.id", dummy.id);
288 do_check_false(store.isAddonSyncable(dummy));
290 // Verify that int values don't throw off checking.
291 let prefSvc = Cc["@mozilla.org/preferences-service;1"]
292 .getService(Ci.nsIPrefService)
293 .getBranch("extensions.");
294 // Need to delete pref before changing type.
295 prefSvc.deleteBranch("hotfix.id");
296 prefSvc.setIntPref("hotfix.id", 0xdeadbeef);
298 do_check_true(store.isAddonSyncable(dummy));
300 uninstallAddon(addon);
302 Svc.Prefs.reset("addons.ignoreRepositoryChecking");
303 prefs.reset("hotfix.id");
305 run_next_test();
306 });
309 add_test(function test_get_all_ids() {
310 _("Ensures that getAllIDs() returns an appropriate set.");
312 Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
314 _("Installing two addons.");
315 let addon1 = installAddon("test_install1");
316 let addon2 = installAddon("test_bootstrap1_1");
318 _("Ensure they're syncable.");
319 do_check_true(store.isAddonSyncable(addon1));
320 do_check_true(store.isAddonSyncable(addon2));
322 let ids = store.getAllIDs();
324 do_check_eq("object", typeof(ids));
325 do_check_eq(2, Object.keys(ids).length);
326 do_check_true(addon1.syncGUID in ids);
327 do_check_true(addon2.syncGUID in ids);
329 addon1.install.cancel();
330 uninstallAddon(addon2);
332 Svc.Prefs.reset("addons.ignoreRepositoryChecking");
333 run_next_test();
334 });
336 add_test(function test_change_item_id() {
337 _("Ensures that changeItemID() works properly.");
339 let addon = installAddon("test_bootstrap1_1");
341 let oldID = addon.syncGUID;
342 let newID = Utils.makeGUID();
344 store.changeItemID(oldID, newID);
346 let newAddon = getAddonFromAddonManagerByID(addon.id);
347 do_check_neq(null, newAddon);
348 do_check_eq(newID, newAddon.syncGUID);
350 uninstallAddon(newAddon);
352 run_next_test();
353 });
355 add_test(function test_create() {
356 _("Ensure creating/installing an add-on from a record works.");
358 // Set this so that getInstallFromSearchResult doesn't end up
359 // failing the install due to an insecure source URI scheme.
360 Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
361 let server = createAndStartHTTPServer(HTTP_PORT);
363 let addon = installAddon("test_bootstrap1_1");
364 let id = addon.id;
365 uninstallAddon(addon);
367 let guid = Utils.makeGUID();
368 let record = createRecordForThisApp(guid, id, true, false);
370 let failed = store.applyIncomingBatch([record]);
371 do_check_eq(0, failed.length);
373 let newAddon = getAddonFromAddonManagerByID(id);
374 do_check_neq(null, newAddon);
375 do_check_eq(guid, newAddon.syncGUID);
376 do_check_false(newAddon.userDisabled);
378 uninstallAddon(newAddon);
380 Svc.Prefs.reset("addons.ignoreRepositoryChecking");
381 server.stop(run_next_test);
382 });
384 add_test(function test_create_missing_search() {
385 _("Ensures that failed add-on searches are handled gracefully.");
387 let server = createAndStartHTTPServer(HTTP_PORT);
389 // The handler for this ID is not installed, so a search should 404.
390 const id = "missing@tests.mozilla.org";
391 let guid = Utils.makeGUID();
392 let record = createRecordForThisApp(guid, id, true, false);
394 let failed = store.applyIncomingBatch([record]);
395 do_check_eq(1, failed.length);
396 do_check_eq(guid, failed[0]);
398 let addon = getAddonFromAddonManagerByID(id);
399 do_check_eq(null, addon);
401 server.stop(run_next_test);
402 });
404 add_test(function test_create_bad_install() {
405 _("Ensures that add-ons without a valid install are handled gracefully.");
407 let server = createAndStartHTTPServer(HTTP_PORT);
409 // The handler returns a search result but the XPI will 404.
410 const id = "missing-xpi@tests.mozilla.org";
411 let guid = Utils.makeGUID();
412 let record = createRecordForThisApp(guid, id, true, false);
414 let failed = store.applyIncomingBatch([record]);
415 do_check_eq(1, failed.length);
416 do_check_eq(guid, failed[0]);
418 let addon = getAddonFromAddonManagerByID(id);
419 do_check_eq(null, addon);
421 server.stop(run_next_test);
422 });
424 add_test(function test_wipe() {
425 _("Ensures that wiping causes add-ons to be uninstalled.");
427 let addon1 = installAddon("test_bootstrap1_1");
429 Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
430 store.wipe();
432 let addon = getAddonFromAddonManagerByID(addon1.id);
433 do_check_eq(null, addon);
435 Svc.Prefs.reset("addons.ignoreRepositoryChecking");
437 run_next_test();
438 });
440 add_test(function test_wipe_and_install() {
441 _("Ensure wipe followed by install works.");
443 // This tests the reset sync flow where remote data is replaced by local. The
444 // receiving client will see a wipe followed by a record which should undo
445 // the wipe.
446 let installed = installAddon("test_bootstrap1_1");
448 let record = createRecordForThisApp(installed.syncGUID, installed.id, true,
449 false);
451 Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
452 store.wipe();
454 let deleted = getAddonFromAddonManagerByID(installed.id);
455 do_check_null(deleted);
457 // Re-applying the record can require re-fetching the XPI.
458 let server = createAndStartHTTPServer(HTTP_PORT);
460 store.applyIncoming(record);
462 let fetched = getAddonFromAddonManagerByID(record.addonID);
463 do_check_true(!!fetched);
465 Svc.Prefs.reset("addons.ignoreRepositoryChecking");
466 server.stop(run_next_test);
467 });
469 add_test(function cleanup() {
470 // There's an xpcom-shutdown hook for this, but let's give this a shot.
471 reconciler.stopListening();
472 run_next_test();
473 });