|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 "use strict"; |
|
5 |
|
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"); |
|
11 |
|
12 const HTTP_PORT = 8888; |
|
13 |
|
14 let prefs = new Preferences(); |
|
15 |
|
16 prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%"); |
|
17 loadAddonTestFunctions(); |
|
18 startupManager(); |
|
19 |
|
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; |
|
25 |
|
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 } |
|
44 |
|
45 function createAndStartHTTPServer(port) { |
|
46 try { |
|
47 let server = new HttpServer(); |
|
48 |
|
49 let bootstrap1XPI = ExtensionsTestPath("/addons/test_bootstrap1_1.xpi"); |
|
50 |
|
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)); |
|
54 |
|
55 server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org", |
|
56 do_get_file("missing-xpi-search.xml")); |
|
57 |
|
58 server.start(port); |
|
59 |
|
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 } |
|
67 |
|
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; |
|
73 |
|
74 reconciler.startListening(); |
|
75 |
|
76 // Don't flush to disk in the middle of an event listener! |
|
77 // This causes test hangs on WinXP. |
|
78 reconciler._shouldPersist = false; |
|
79 |
|
80 run_next_test(); |
|
81 } |
|
82 |
|
83 add_test(function test_remove() { |
|
84 _("Ensure removing add-ons from deleted records works."); |
|
85 |
|
86 let addon = installAddon("test_bootstrap1_1"); |
|
87 let record = createRecordForThisApp(addon.syncGUID, addon.id, true, true); |
|
88 |
|
89 let failed = store.applyIncomingBatch([record]); |
|
90 do_check_eq(0, failed.length); |
|
91 |
|
92 let newAddon = getAddonFromAddonManagerByID(addon.id); |
|
93 do_check_eq(null, newAddon); |
|
94 |
|
95 run_next_test(); |
|
96 }); |
|
97 |
|
98 add_test(function test_apply_enabled() { |
|
99 _("Ensures that changes to the userEnabled flag apply."); |
|
100 |
|
101 let addon = installAddon("test_bootstrap1_1"); |
|
102 do_check_true(addon.isActive); |
|
103 do_check_false(addon.userDisabled); |
|
104 |
|
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 = []; |
|
113 |
|
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 = []; |
|
121 |
|
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 = []; |
|
130 |
|
131 uninstallAddon(addon); |
|
132 Svc.Prefs.reset("addons.ignoreUserEnabledChanges"); |
|
133 run_next_test(); |
|
134 }); |
|
135 |
|
136 add_test(function test_ignore_different_appid() { |
|
137 _("Ensure that incoming records with a different application ID are ignored."); |
|
138 |
|
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); |
|
142 |
|
143 let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false); |
|
144 record.applicationID = "FAKE_ID"; |
|
145 |
|
146 let failed = store.applyIncomingBatch([record]); |
|
147 do_check_eq(0, failed.length); |
|
148 |
|
149 let newAddon = getAddonFromAddonManagerByID(addon.id); |
|
150 do_check_false(addon.userDisabled); |
|
151 |
|
152 uninstallAddon(addon); |
|
153 |
|
154 run_next_test(); |
|
155 }); |
|
156 |
|
157 add_test(function test_ignore_unknown_source() { |
|
158 _("Ensure incoming records with unknown source are ignored."); |
|
159 |
|
160 let addon = installAddon("test_bootstrap1_1"); |
|
161 |
|
162 let record = createRecordForThisApp(addon.syncGUID, addon.id, false, false); |
|
163 record.source = "DUMMY_SOURCE"; |
|
164 |
|
165 let failed = store.applyIncomingBatch([record]); |
|
166 do_check_eq(0, failed.length); |
|
167 |
|
168 let newAddon = getAddonFromAddonManagerByID(addon.id); |
|
169 do_check_false(addon.userDisabled); |
|
170 |
|
171 uninstallAddon(addon); |
|
172 |
|
173 run_next_test(); |
|
174 }); |
|
175 |
|
176 add_test(function test_apply_uninstall() { |
|
177 _("Ensures that uninstalling an add-on from a record works."); |
|
178 |
|
179 let addon = installAddon("test_bootstrap1_1"); |
|
180 |
|
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); |
|
185 |
|
186 addon = getAddonFromAddonManagerByID(addon.id); |
|
187 do_check_eq(null, addon); |
|
188 |
|
189 run_next_test(); |
|
190 }); |
|
191 |
|
192 add_test(function test_addon_syncability() { |
|
193 _("Ensure isAddonSyncable functions properly."); |
|
194 |
|
195 Svc.Prefs.set("addons.ignoreRepositoryChecking", true); |
|
196 Svc.Prefs.set("addons.trustedSourceHostnames", |
|
197 "addons.mozilla.org,other.example.com"); |
|
198 |
|
199 do_check_false(store.isAddonSyncable(null)); |
|
200 |
|
201 let addon = installAddon("test_bootstrap1_1"); |
|
202 do_check_true(store.isAddonSyncable(addon)); |
|
203 |
|
204 let dummy = {}; |
|
205 const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; |
|
206 for each (let k in KEYS) { |
|
207 dummy[k] = addon[k]; |
|
208 } |
|
209 |
|
210 do_check_true(store.isAddonSyncable(dummy)); |
|
211 |
|
212 dummy.type = "UNSUPPORTED"; |
|
213 do_check_false(store.isAddonSyncable(dummy)); |
|
214 dummy.type = addon.type; |
|
215 |
|
216 dummy.scope = 0; |
|
217 do_check_false(store.isAddonSyncable(dummy)); |
|
218 dummy.scope = addon.scope; |
|
219 |
|
220 dummy.foreignInstall = true; |
|
221 do_check_false(store.isAddonSyncable(dummy)); |
|
222 dummy.foreignInstall = false; |
|
223 |
|
224 uninstallAddon(addon); |
|
225 |
|
226 do_check_false(store.isSourceURITrusted(null)); |
|
227 |
|
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 } |
|
233 |
|
234 let trusted = [ |
|
235 "https://addons.mozilla.org/foo", |
|
236 "https://other.example.com/foo" |
|
237 ]; |
|
238 |
|
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 ]; |
|
244 |
|
245 for each (let uri in trusted) { |
|
246 do_check_true(store.isSourceURITrusted(createURI(uri))); |
|
247 } |
|
248 |
|
249 for each (let uri in untrusted) { |
|
250 do_check_false(store.isSourceURITrusted(createURI(uri))); |
|
251 } |
|
252 |
|
253 Svc.Prefs.set("addons.trustedSourceHostnames", ""); |
|
254 for each (let uri in trusted) { |
|
255 do_check_false(store.isSourceURITrusted(createURI(uri))); |
|
256 } |
|
257 |
|
258 Svc.Prefs.set("addons.trustedSourceHostnames", "addons.mozilla.org"); |
|
259 do_check_true(store.isSourceURITrusted(createURI("https://addons.mozilla.org/foo"))); |
|
260 |
|
261 Svc.Prefs.reset("addons.trustedSourceHostnames"); |
|
262 |
|
263 run_next_test(); |
|
264 }); |
|
265 |
|
266 add_test(function test_ignore_hotfixes() { |
|
267 _("Ensure that hotfix extensions are ignored."); |
|
268 |
|
269 Svc.Prefs.set("addons.ignoreRepositoryChecking", true); |
|
270 |
|
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."); |
|
274 |
|
275 let addon = installAddon("test_bootstrap1_1"); |
|
276 do_check_true(store.isAddonSyncable(addon)); |
|
277 |
|
278 let dummy = {}; |
|
279 const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"]; |
|
280 for each (let k in KEYS) { |
|
281 dummy[k] = addon[k]; |
|
282 } |
|
283 |
|
284 // Basic sanity check. |
|
285 do_check_true(store.isAddonSyncable(dummy)); |
|
286 |
|
287 prefs.set("hotfix.id", dummy.id); |
|
288 do_check_false(store.isAddonSyncable(dummy)); |
|
289 |
|
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); |
|
297 |
|
298 do_check_true(store.isAddonSyncable(dummy)); |
|
299 |
|
300 uninstallAddon(addon); |
|
301 |
|
302 Svc.Prefs.reset("addons.ignoreRepositoryChecking"); |
|
303 prefs.reset("hotfix.id"); |
|
304 |
|
305 run_next_test(); |
|
306 }); |
|
307 |
|
308 |
|
309 add_test(function test_get_all_ids() { |
|
310 _("Ensures that getAllIDs() returns an appropriate set."); |
|
311 |
|
312 Svc.Prefs.set("addons.ignoreRepositoryChecking", true); |
|
313 |
|
314 _("Installing two addons."); |
|
315 let addon1 = installAddon("test_install1"); |
|
316 let addon2 = installAddon("test_bootstrap1_1"); |
|
317 |
|
318 _("Ensure they're syncable."); |
|
319 do_check_true(store.isAddonSyncable(addon1)); |
|
320 do_check_true(store.isAddonSyncable(addon2)); |
|
321 |
|
322 let ids = store.getAllIDs(); |
|
323 |
|
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); |
|
328 |
|
329 addon1.install.cancel(); |
|
330 uninstallAddon(addon2); |
|
331 |
|
332 Svc.Prefs.reset("addons.ignoreRepositoryChecking"); |
|
333 run_next_test(); |
|
334 }); |
|
335 |
|
336 add_test(function test_change_item_id() { |
|
337 _("Ensures that changeItemID() works properly."); |
|
338 |
|
339 let addon = installAddon("test_bootstrap1_1"); |
|
340 |
|
341 let oldID = addon.syncGUID; |
|
342 let newID = Utils.makeGUID(); |
|
343 |
|
344 store.changeItemID(oldID, newID); |
|
345 |
|
346 let newAddon = getAddonFromAddonManagerByID(addon.id); |
|
347 do_check_neq(null, newAddon); |
|
348 do_check_eq(newID, newAddon.syncGUID); |
|
349 |
|
350 uninstallAddon(newAddon); |
|
351 |
|
352 run_next_test(); |
|
353 }); |
|
354 |
|
355 add_test(function test_create() { |
|
356 _("Ensure creating/installing an add-on from a record works."); |
|
357 |
|
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); |
|
362 |
|
363 let addon = installAddon("test_bootstrap1_1"); |
|
364 let id = addon.id; |
|
365 uninstallAddon(addon); |
|
366 |
|
367 let guid = Utils.makeGUID(); |
|
368 let record = createRecordForThisApp(guid, id, true, false); |
|
369 |
|
370 let failed = store.applyIncomingBatch([record]); |
|
371 do_check_eq(0, failed.length); |
|
372 |
|
373 let newAddon = getAddonFromAddonManagerByID(id); |
|
374 do_check_neq(null, newAddon); |
|
375 do_check_eq(guid, newAddon.syncGUID); |
|
376 do_check_false(newAddon.userDisabled); |
|
377 |
|
378 uninstallAddon(newAddon); |
|
379 |
|
380 Svc.Prefs.reset("addons.ignoreRepositoryChecking"); |
|
381 server.stop(run_next_test); |
|
382 }); |
|
383 |
|
384 add_test(function test_create_missing_search() { |
|
385 _("Ensures that failed add-on searches are handled gracefully."); |
|
386 |
|
387 let server = createAndStartHTTPServer(HTTP_PORT); |
|
388 |
|
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); |
|
393 |
|
394 let failed = store.applyIncomingBatch([record]); |
|
395 do_check_eq(1, failed.length); |
|
396 do_check_eq(guid, failed[0]); |
|
397 |
|
398 let addon = getAddonFromAddonManagerByID(id); |
|
399 do_check_eq(null, addon); |
|
400 |
|
401 server.stop(run_next_test); |
|
402 }); |
|
403 |
|
404 add_test(function test_create_bad_install() { |
|
405 _("Ensures that add-ons without a valid install are handled gracefully."); |
|
406 |
|
407 let server = createAndStartHTTPServer(HTTP_PORT); |
|
408 |
|
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); |
|
413 |
|
414 let failed = store.applyIncomingBatch([record]); |
|
415 do_check_eq(1, failed.length); |
|
416 do_check_eq(guid, failed[0]); |
|
417 |
|
418 let addon = getAddonFromAddonManagerByID(id); |
|
419 do_check_eq(null, addon); |
|
420 |
|
421 server.stop(run_next_test); |
|
422 }); |
|
423 |
|
424 add_test(function test_wipe() { |
|
425 _("Ensures that wiping causes add-ons to be uninstalled."); |
|
426 |
|
427 let addon1 = installAddon("test_bootstrap1_1"); |
|
428 |
|
429 Svc.Prefs.set("addons.ignoreRepositoryChecking", true); |
|
430 store.wipe(); |
|
431 |
|
432 let addon = getAddonFromAddonManagerByID(addon1.id); |
|
433 do_check_eq(null, addon); |
|
434 |
|
435 Svc.Prefs.reset("addons.ignoreRepositoryChecking"); |
|
436 |
|
437 run_next_test(); |
|
438 }); |
|
439 |
|
440 add_test(function test_wipe_and_install() { |
|
441 _("Ensure wipe followed by install works."); |
|
442 |
|
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"); |
|
447 |
|
448 let record = createRecordForThisApp(installed.syncGUID, installed.id, true, |
|
449 false); |
|
450 |
|
451 Svc.Prefs.set("addons.ignoreRepositoryChecking", true); |
|
452 store.wipe(); |
|
453 |
|
454 let deleted = getAddonFromAddonManagerByID(installed.id); |
|
455 do_check_null(deleted); |
|
456 |
|
457 // Re-applying the record can require re-fetching the XPI. |
|
458 let server = createAndStartHTTPServer(HTTP_PORT); |
|
459 |
|
460 store.applyIncoming(record); |
|
461 |
|
462 let fetched = getAddonFromAddonManagerByID(record.addonID); |
|
463 do_check_true(!!fetched); |
|
464 |
|
465 Svc.Prefs.reset("addons.ignoreRepositoryChecking"); |
|
466 server.stop(run_next_test); |
|
467 }); |
|
468 |
|
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 }); |
|
474 |