test_PreferencesBackupResource_Nimbus.js (8882B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { _ExperimentFeature: ExperimentFeature } = ChromeUtils.importESModule( 7 "resource://nimbus/ExperimentAPI.sys.mjs" 8 ); 9 const { NimbusTestUtils } = ChromeUtils.importESModule( 10 "resource://testing-common/NimbusTestUtils.sys.mjs" 11 ); 12 const { PreferencesBackupResource } = ChromeUtils.importESModule( 13 "resource:///modules/backup/PreferencesBackupResource.sys.mjs" 14 ); 15 16 NimbusTestUtils.init(this); 17 18 const USER_TYPED_FEATURE = new ExperimentFeature("test-typed-prefs", { 19 description: "Test feature that sets each type of pref", 20 owner: "test@test.test", 21 hasExposure: false, 22 variables: { 23 string: { 24 type: "string", 25 description: "test string variable", 26 setPref: { 27 branch: "user", 28 pref: "nimbus-test.types.string", 29 }, 30 }, 31 int: { 32 type: "int", 33 description: "test int variable", 34 setPref: { 35 branch: "user", 36 pref: "nimbus-test.types.int", 37 }, 38 }, 39 boolean: { 40 type: "boolean", 41 description: "test boolean variable", 42 setPref: { 43 branch: "user", 44 pref: "nimbus-test.types.boolean", 45 }, 46 }, 47 json: { 48 type: "json", 49 description: "test json variable", 50 setPref: { 51 branch: "user", 52 pref: "nimbus-test.types.json", 53 }, 54 }, 55 }, 56 }); 57 58 function getPrefsFromMap(prefs) { 59 return prefs.map(([pref, value]) => { 60 if (!Services.prefs.prefHasUserValue(pref)) { 61 return [pref, undefined]; 62 } 63 switch (typeof value) { 64 case "boolean": 65 return [pref, Services.prefs.getBoolPref(pref)]; 66 case "number": 67 return [pref, Services.prefs.getIntPref(pref)]; 68 case "string": 69 return [pref, Services.prefs.getStringPref(pref)]; 70 default: 71 throw new Error("Unsupported pref type!"); 72 } 73 }); 74 } 75 76 function setPrefsFromMap(prefs) { 77 for (let [pref, value] of prefs) { 78 if (value === undefined) { 79 Services.prefs.clearUserPref(pref); 80 continue; 81 } 82 83 switch (typeof value) { 84 case "boolean": 85 Services.prefs.setBoolPref(pref, value); 86 break; 87 case "number": 88 Services.prefs.setIntPref(pref, value); 89 break; 90 case "string": 91 Services.prefs.setStringPref(pref, value); 92 break; 93 default: 94 throw new Error("Unsupported pref type!"); 95 } 96 } 97 } 98 99 const fakeNimbusTestPrefs = [ 100 ["nimbus-test.types.boolean", false], 101 ["nimbus-test.types.int", 100], 102 ["nimbus-test.types.string", "bar"], 103 ["nimbus.all_prefs_with_nimbus._prefix_should_be_removed", "not serialized"], 104 ]; 105 106 // NB: Nimbus experiment setup is adapted from 107 // test_setPref_getOriginalPrefValuesForAllExperiments. 108 function initFakeNimbusExperimentPrefs() { 109 const originalPrefs = getPrefsFromMap(fakeNimbusTestPrefs); 110 setPrefsFromMap(fakeNimbusTestPrefs); 111 return originalPrefs; 112 } 113 114 function uninitFakeNimbusExperimentPrefs(originalPrefs) { 115 setPrefsFromMap(originalPrefs); 116 } 117 118 const jsonTestValue = { 119 foo: "foo", 120 bar: 12345, 121 baz: true, 122 qux: null, 123 quux: ["corge"], 124 }; 125 126 async function enrollFakeNimbusExperiment() { 127 const featureCleanup = NimbusTestUtils.addTestFeatures(USER_TYPED_FEATURE); 128 129 const { manager, cleanup } = await NimbusTestUtils.setupTest(); 130 131 const experimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig( 132 { 133 featureId: USER_TYPED_FEATURE.featureId, 134 value: { 135 string: "hello, world", 136 int: 12345, 137 boolean: true, 138 json: jsonTestValue, 139 }, 140 }, 141 { manager } 142 ); 143 144 return { featureCleanup, experimentCleanup, cleanup }; 145 } 146 147 async function unenrollFakeNimbusExperiment({ 148 featureCleanup, 149 experimentCleanup, 150 cleanup, 151 }) { 152 await experimentCleanup(); 153 featureCleanup(); 154 await cleanup(); 155 } 156 157 function checkNimbusHasSetPrefs() { 158 Assert.equal( 159 Services.prefs.getPrefType("nimbus-test.types.string"), 160 Services.prefs.PREF_STRING 161 ); 162 Assert.equal( 163 Services.prefs.getStringPref("nimbus-test.types.string"), 164 "hello, world" 165 ); 166 167 Assert.equal( 168 Services.prefs.getPrefType("nimbus-test.types.int"), 169 Services.prefs.PREF_INT 170 ); 171 Assert.equal(Services.prefs.getIntPref("nimbus-test.types.int"), 12345); 172 173 Assert.equal( 174 Services.prefs.getPrefType("nimbus-test.types.boolean"), 175 Services.prefs.PREF_BOOL 176 ); 177 Assert.equal(Services.prefs.getBoolPref("nimbus-test.types.boolean"), true); 178 179 Assert.equal( 180 Services.prefs.getPrefType("nimbus-test.types.json"), 181 Services.prefs.PREF_STRING 182 ); 183 184 const parsedJson = JSON.parse( 185 Services.prefs.getStringPref("nimbus-test.types.json") 186 ); 187 188 Assert.deepEqual(jsonTestValue, parsedJson); 189 } 190 191 async function checkIgnoredBackupPrefs(backupPrefsFilePath) { 192 let contents = (await IOUtils.readUTF8(backupPrefsFilePath)) 193 .split("\n") 194 .map(line => line.trim()); 195 196 let checkPrefNotSerialized = pref => { 197 Assert.ok( 198 !contents.reduce((acc, line) => acc || line.includes(pref), false) 199 ); 200 }; 201 202 checkPrefNotSerialized("browser.backup."); 203 } 204 205 async function checkBackupIgnoredNimbusPrefs(backupPrefsFilePath) { 206 let contents = (await IOUtils.readUTF8(backupPrefsFilePath)) 207 .split("\n") 208 .map(line => line.trim()); 209 210 Assert.ok( 211 contents.includes('user_pref("nimbus-test.types.boolean", false);') 212 ); 213 Assert.ok(contents.includes('user_pref("nimbus-test.types.int", 100);')); 214 Assert.ok(contents.includes('user_pref("nimbus-test.types.string", "bar");')); 215 216 let checkPrefNotSerialized = pref => { 217 Assert.ok( 218 !contents.reduce((acc, line) => acc || line.includes(pref), false) 219 ); 220 }; 221 222 // nimbus-test.types.json had no prior value so it should not appear. 223 checkPrefNotSerialized("nimbus-test.types.json"); 224 225 // nimbus metadata should not appear. 226 checkPrefNotSerialized("app.normandy.user_id"); 227 checkPrefNotSerialized("toolkit.telemetry.cachedProfileGroupID"); 228 229 // Prefs that start with "nimbus." are assumed to be internal and should 230 // not be included. 231 checkPrefNotSerialized( 232 "nimbus.all_prefs_with_nimbus._prefix_should_be_removed" 233 ); 234 } 235 236 async function performBackup() { 237 let sandbox = sinon.createSandbox(); 238 239 let preferencesBackupResource = new PreferencesBackupResource(); 240 let sourcePath = await IOUtils.createUniqueDirectory( 241 PathUtils.tempDir, 242 "PreferencesBackupResource-source-test" 243 ); 244 let stagingPath = await IOUtils.createUniqueDirectory( 245 PathUtils.tempDir, 246 "PreferencesBackupResource-staging-test" 247 ); 248 249 const simpleCopyFiles = [ 250 { path: "xulstore.json" }, 251 { path: "containers.json" }, 252 { path: "handlers.json" }, 253 { path: "search.json.mozlz4" }, 254 { path: "user.js" }, 255 { path: ["chrome", "userChrome.css"] }, 256 { path: ["chrome", "userContent.css"] }, 257 { path: ["chrome", "childFolder", "someOtherStylesheet.css"] }, 258 ]; 259 await createTestFiles(sourcePath, simpleCopyFiles); 260 261 // Create our fake database files. We don't expect these to be copied to the 262 // staging directory in this test due to our stubbing of the backup method, so 263 // we don't include it in `simpleCopyFiles`. 264 await createTestFiles(sourcePath, [ 265 { path: "permissions.sqlite" }, 266 { path: "content-prefs.sqlite" }, 267 ]); 268 269 // We have no need to test that Sqlite.sys.mjs's backup method is working - 270 // this is something that is tested in Sqlite's own tests. We can just make 271 // sure that it's being called using sinon. Unfortunately, we cannot do the 272 // same thing with IOUtils.copy, as its methods are not stubbable. 273 let fakeConnection = { 274 backup: sandbox.stub().resolves(true), 275 close: sandbox.stub().resolves(true), 276 }; 277 sandbox.stub(Sqlite, "openConnection").returns(fakeConnection); 278 279 await preferencesBackupResource.backup(stagingPath, sourcePath); 280 281 return { sandbox, stagingPath, sourcePath }; 282 } 283 284 /** 285 * Test that the backup method correctly serializes preference values that are 286 * manipulated by Nimbus experiments. Nimbus-set prefs should ignore the 287 * Nimbus-set values. 288 */ 289 add_task(async function test_prefs_backup_does_not_backup_nimbus_prefs() { 290 // Pre-set some nimbus-test.* prefs so they have originalValues that 291 // should be backed up instead of the values Nimbus will set when we enroll 292 // in the experiment. 293 const originalPrefs = initFakeNimbusExperimentPrefs(); 294 const cleanup = await enrollFakeNimbusExperiment(); 295 checkNimbusHasSetPrefs(); 296 const { sandbox, stagingPath, sourcePath } = await performBackup(); 297 await checkBackupIgnoredNimbusPrefs(PathUtils.join(stagingPath, "prefs.js")); 298 await checkIgnoredBackupPrefs(PathUtils.join(stagingPath, "prefs.js")); 299 await maybeRemovePath(stagingPath); 300 await maybeRemovePath(sourcePath); 301 sandbox.restore(); 302 await unenrollFakeNimbusExperiment(cleanup); 303 uninitFakeNimbusExperimentPrefs(originalPrefs); 304 });