test_addons_engine.js (7987B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { AddonManager } = ChromeUtils.importESModule( 7 "resource://gre/modules/AddonManager.sys.mjs" 8 ); 9 const { CHANGE_INSTALLED } = ChromeUtils.importESModule( 10 "resource://services-sync/addonsreconciler.sys.mjs" 11 ); 12 const { AddonsEngine } = ChromeUtils.importESModule( 13 "resource://services-sync/engines/addons.sys.mjs" 14 ); 15 const { Service } = ChromeUtils.importESModule( 16 "resource://services-sync/service.sys.mjs" 17 ); 18 19 Services.prefs.setStringPref( 20 "extensions.getAddons.get.url", 21 "http://localhost:8888/search/guid:%IDS%" 22 ); 23 Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false); 24 25 let engine; 26 let syncID; 27 let reconciler; 28 let tracker; 29 30 AddonTestUtils.init(this); 31 32 const ADDON_ID = "addon1@tests.mozilla.org"; 33 const XPI = AddonTestUtils.createTempWebExtensionFile({ 34 manifest: { 35 name: "Test 1", 36 description: "Test Description", 37 browser_specific_settings: { gecko: { id: ADDON_ID } }, 38 }, 39 }); 40 41 async function resetReconciler() { 42 reconciler._addons = {}; 43 reconciler._changes = []; 44 45 await reconciler.saveState(); 46 47 await tracker.clearChangedIDs(); 48 } 49 50 add_task(async function setup() { 51 AddonTestUtils.createAppInfo( 52 "xpcshell@tests.mozilla.org", 53 "XPCShell", 54 "1", 55 "1.9.2" 56 ); 57 AddonTestUtils.overrideCertDB(); 58 await AddonTestUtils.promiseStartupManager(); 59 60 await Service.engineManager.register(AddonsEngine); 61 engine = Service.engineManager.get("addons"); 62 syncID = await engine.resetLocalSyncID(); 63 reconciler = engine._reconciler; 64 tracker = engine._tracker; 65 66 reconciler.startListening(); 67 68 // Don't flush to disk in the middle of an event listener! 69 // This causes test hangs on WinXP. 70 reconciler._shouldPersist = false; 71 72 await resetReconciler(); 73 }); 74 75 // This is a basic sanity test for the unit test itself. If this breaks, the 76 // add-ons API likely changed upstream. 77 add_task(async function test_addon_install() { 78 _("Ensure basic add-on APIs work as expected."); 79 80 let install = await AddonManager.getInstallForFile(XPI); 81 Assert.notEqual(install, null); 82 Assert.equal(install.type, "extension"); 83 Assert.equal(install.name, "Test 1"); 84 85 await resetReconciler(); 86 }); 87 88 add_task(async function test_find_dupe() { 89 _("Ensure the _findDupe() implementation is sane."); 90 91 // This gets invoked at the top of sync, which is bypassed by this 92 // test, so we do it manually. 93 await engine._refreshReconcilerState(); 94 95 let addon = await installAddon(XPI, reconciler); 96 97 let record = { 98 id: Utils.makeGUID(), 99 addonID: ADDON_ID, 100 enabled: true, 101 applicationID: Services.appinfo.ID, 102 source: "amo", 103 }; 104 105 let dupe = await engine._findDupe(record); 106 Assert.equal(addon.syncGUID, dupe); 107 108 record.id = addon.syncGUID; 109 dupe = await engine._findDupe(record); 110 Assert.equal(null, dupe); 111 112 await uninstallAddon(addon, reconciler); 113 await resetReconciler(); 114 }); 115 116 add_task(async function test_get_changed_ids() { 117 let timerPrecision = Services.prefs.getBoolPref( 118 "privacy.reduceTimerPrecision" 119 ); 120 Services.prefs.setBoolPref("privacy.reduceTimerPrecision", false); 121 122 registerCleanupFunction(function () { 123 Services.prefs.setBoolPref("privacy.reduceTimerPrecision", timerPrecision); 124 }); 125 126 _("Ensure getChangedIDs() has the appropriate behavior."); 127 128 _("Ensure getChangedIDs() returns an empty object by default."); 129 let changes = await engine.getChangedIDs(); 130 Assert.equal("object", typeof changes); 131 Assert.equal(0, Object.keys(changes).length); 132 133 _("Ensure tracker changes are populated."); 134 let now = new Date(); 135 let changeTime = now.getTime() / 1000; 136 let guid1 = Utils.makeGUID(); 137 await tracker.addChangedID(guid1, changeTime); 138 139 changes = await engine.getChangedIDs(); 140 Assert.equal("object", typeof changes); 141 Assert.equal(1, Object.keys(changes).length); 142 Assert.ok(guid1 in changes); 143 Assert.equal(changeTime, changes[guid1]); 144 145 await tracker.clearChangedIDs(); 146 147 _("Ensure reconciler changes are populated."); 148 let addon = await installAddon(XPI, reconciler); 149 await tracker.clearChangedIDs(); // Just in case. 150 changes = await engine.getChangedIDs(); 151 Assert.equal("object", typeof changes); 152 Assert.equal(1, Object.keys(changes).length); 153 Assert.ok(addon.syncGUID in changes); 154 _( 155 "Change time: " + changeTime + ", addon change: " + changes[addon.syncGUID] 156 ); 157 Assert.greaterOrEqual(changes[addon.syncGUID], changeTime); 158 159 let oldTime = changes[addon.syncGUID]; 160 let guid2 = addon.syncGUID; 161 await uninstallAddon(addon, reconciler); 162 changes = await engine.getChangedIDs(); 163 Assert.equal(1, Object.keys(changes).length); 164 Assert.ok(guid2 in changes); 165 Assert.greater(changes[guid2], oldTime); 166 167 _("Ensure non-syncable add-ons aren't picked up by reconciler changes."); 168 reconciler._addons = {}; 169 reconciler._changes = []; 170 let record = { 171 id: "DUMMY", 172 guid: Utils.makeGUID(), 173 enabled: true, 174 installed: true, 175 modified: new Date(), 176 type: "UNSUPPORTED", 177 scope: 0, 178 foreignInstall: false, 179 }; 180 reconciler.addons.DUMMY = record; 181 await reconciler._addChange(record.modified, CHANGE_INSTALLED, record); 182 183 changes = await engine.getChangedIDs(); 184 _(JSON.stringify(changes)); 185 Assert.equal(0, Object.keys(changes).length); 186 187 await resetReconciler(); 188 }); 189 190 add_task(async function test_disabled_install_semantics() { 191 _("Ensure that syncing a disabled add-on preserves proper state."); 192 193 // This is essentially a test for bug 712542, which snuck into the original 194 // add-on sync drop. It ensures that when an add-on is installed that the 195 // disabled state and incoming syncGUID is preserved, even on the next sync. 196 const USER = "foo"; 197 const PASSWORD = "password"; 198 199 let server = new SyncServer(); 200 server.start(); 201 await SyncTestingInfrastructure(server, USER, PASSWORD); 202 203 await generateNewKeys(Service.collectionKeys); 204 205 let contents = { 206 meta: { 207 global: { engines: { addons: { version: engine.version, syncID } } }, 208 }, 209 crypto: {}, 210 addons: {}, 211 }; 212 213 server.registerUser(USER, "password"); 214 server.createContents(USER, contents); 215 216 let amoServer = new HttpServer(); 217 amoServer.registerFile( 218 "/search/guid:addon1%40tests.mozilla.org", 219 do_get_file("addon1-search.json") 220 ); 221 222 amoServer.registerFile("/addon1.xpi", XPI); 223 amoServer.start(8888); 224 225 // Insert an existing record into the server. 226 let id = Utils.makeGUID(); 227 let now = Date.now() / 1000; 228 229 let record = encryptPayload({ 230 id, 231 applicationID: Services.appinfo.ID, 232 addonID: ADDON_ID, 233 enabled: false, 234 deleted: false, 235 source: "amo", 236 }); 237 let wbo = new ServerWBO(id, record, now - 2); 238 server.insertWBO(USER, "addons", wbo); 239 240 _("Performing sync of add-ons engine."); 241 await engine._sync(); 242 243 // At this point the non-restartless extension should be staged for install. 244 245 // Don't need this server any more. 246 await promiseStopServer(amoServer); 247 248 // We ensure the reconciler has recorded the proper ID and enabled state. 249 let addon = reconciler.getAddonStateFromSyncGUID(id); 250 Assert.notEqual(null, addon); 251 Assert.equal(false, addon.enabled); 252 253 // We fake an app restart and perform another sync, just to make sure things 254 // are sane. 255 await AddonTestUtils.promiseRestartManager(); 256 257 let collection = server.getCollection(USER, "addons"); 258 engine.lastModified = collection.timestamp; 259 await engine._sync(); 260 261 // The client should not upload a new record. The old record should be 262 // retained and unmodified. 263 Assert.equal(1, collection.count()); 264 265 let payload = collection.payloads()[0]; 266 Assert.notEqual(null, collection.wbo(id)); 267 Assert.equal(ADDON_ID, payload.addonID); 268 Assert.ok(!payload.enabled); 269 270 await promiseStopServer(server); 271 }); 272 273 add_test(function cleanup() { 274 // There's an xpcom-shutdown hook for this, but let's give this a shot. 275 reconciler.stopListening(); 276 run_next_test(); 277 });