test_sync_warning_dialogs.js (12999B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 ChromeUtils.defineESModuleGetters(this, { 7 FxAccountsWebChannelHelpers: 8 "resource://gre/modules/FxAccountsWebChannel.sys.mjs", 9 SelectableProfileService: 10 "resource:///modules/profiles/SelectableProfileService.sys.mjs", 11 PREF_LAST_FXA_USER_UID: "resource://gre/modules/FxAccountsCommon.sys.mjs", 12 }); 13 14 // Set up mocked profiles 15 const mockedProfiles = [ 16 { 17 name: "Profile1", 18 path: PathUtils.join(PathUtils.tempDir, "current-profile"), 19 email: "testuser1@test.com", 20 }, 21 { 22 name: "Profile2", 23 path: PathUtils.join(PathUtils.tempDir, "other-profile"), 24 email: "testuser2@test.com", 25 }, 26 ]; 27 28 // Emulates the response from the user 29 let gResponse = 1; 30 (function replacePromptService() { 31 let originalPromptService = Services.prompt; 32 Services.prompt = { 33 QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]), 34 confirmEx: () => gResponse, 35 }; 36 registerCleanupFunction(() => { 37 Services.prompt = originalPromptService; 38 }); 39 })(); 40 41 add_setup(function setup() { 42 // FOG needs a profile directory to put its data in. 43 do_get_profile(); 44 // FOG needs to be initialized in order for data to flow. 45 Services.fog.initializeFOG(); 46 47 // The profile service requires the directory service to have been initialized. 48 Cc["@mozilla.org/xre/directory-provider;1"].getService(Ci.nsIXREDirProvider); 49 50 // The normal isEnabled getter relies on there being a properly working toolkit 51 // profile service. For the purposes of this test just mirror the state of the 52 // preference. 53 Object.defineProperty(SelectableProfileService, "isEnabled", { 54 get() { 55 return Services.prefs.getBoolPref("browser.profiles.enabled"); 56 }, 57 }); 58 }); 59 60 const dialogVariants = [ 61 { 62 description: "A previous account was signed into this profile", 63 prefs: { 64 "browser.profiles.enabled": true, 65 "browser.profiles.sync.allow-danger-merge": false, 66 }, 67 expectedResponses: [ 68 { 69 responseVal: 0, 70 expectedResult: { action: "create-profile" }, 71 expectedTelemetry: { 72 variant_shown: "merge-warning", 73 option_clicked: "create-profile", 74 }, 75 }, 76 { 77 responseVal: 1, 78 expectedResult: { action: "cancel" }, 79 expectedTelemetry: { 80 variant_shown: "merge-warning", 81 option_clicked: "cancel", 82 }, 83 }, 84 ], 85 }, 86 { 87 description: 88 "A previous account was signed into this profile, with merge allowed", 89 prefs: { 90 "browser.profiles.enabled": true, 91 "browser.profiles.sync.allow-danger-merge": true, 92 }, 93 expectedResponses: [ 94 { 95 responseVal: 0, 96 expectedResult: { action: "continue" }, 97 expectedTelemetry: { 98 variant_shown: "merge-warning-allow-merge", 99 option_clicked: "continue", 100 }, 101 }, 102 { 103 responseVal: 1, 104 expectedResult: { action: "create-profile" }, 105 expectedTelemetry: { 106 variant_shown: "merge-warning-allow-merge", 107 option_clicked: "create-profile", 108 }, 109 }, 110 { 111 responseVal: 2, 112 expectedResult: { action: "cancel" }, 113 expectedTelemetry: { 114 option_clicked: "cancel", 115 variant_shown: "merge-warning-allow-merge", 116 }, 117 }, 118 ], 119 }, 120 ]; 121 122 add_task( 123 async function test_previously_signed_in_dialog_variants_result_and_telemetry() { 124 // Create a helper instance 125 let helpers = new FxAccountsWebChannelHelpers(); 126 127 // We "pretend" there was another account previously logged in 128 helpers.setPreviousAccountHashPref("test_uid"); 129 130 // Mock methods 131 helpers._selectableProfilesEnabled = () => 132 Services.prefs.getBoolPref("browser.profiles.enabled"); 133 helpers._getAllProfiles = async () => mockedProfiles; 134 helpers._getCurrentProfileName = () => mockedProfiles[0].name; 135 helpers._readJSONFileAsync = async function (_filePath) { 136 return null; 137 }; 138 139 for (let variant of dialogVariants) { 140 info(`Testing variant: ${variant.description}`); 141 // Set the preferences for this variant 142 for (let [prefName, prefValue] of Object.entries(variant.prefs)) { 143 Services.prefs.setBoolPref(prefName, prefValue); 144 } 145 146 for (let i = 0; i < variant.expectedResponses.length; i++) { 147 let { responseVal, expectedResult, expectedTelemetry } = 148 variant.expectedResponses[i]; 149 150 gResponse = responseVal; 151 let result = await helpers.promptProfileSyncWarningIfNeeded({ 152 email: "testuser2@test.com", 153 uid: "test2", 154 }); 155 // Verify we returned the expected result 156 Assert.deepEqual(result, expectedResult); 157 158 let gleanValue = Glean.syncMergeDialog.clicked.testGetValue(); 159 // Verify the telemetry is shaped as expected 160 Assert.equal( 161 gleanValue[i].extra.variant_shown, 162 expectedTelemetry.variant_shown, 163 "Correctly logged which dialog variant was shown to the user" 164 ); 165 Assert.equal( 166 gleanValue[i].extra.option_clicked, 167 expectedTelemetry.option_clicked, 168 "Correctly logged which option the user selected" 169 ); 170 } 171 // Reset Glean for next iteration 172 Services.fog.testResetFOG(); 173 } 174 175 // Clean up preferences 176 Services.prefs.clearUserPref("browser.profiles.enabled"); 177 Services.prefs.clearUserPref("browser.profiles.sync.allow-danger-merge"); 178 } 179 ); 180 181 /** 182 * Testing the dialog variants where another profile is signed into the account 183 * we're trying to sign into 184 */ 185 const anotherProfileDialogVariants = [ 186 { 187 description: 188 "Another profile is logged into the account we're trying to sign into", 189 prefs: { 190 "browser.profiles.enabled": true, 191 "browser.profiles.sync.allow-danger-merge": false, 192 }, 193 expectedResponses: [ 194 { 195 responseVal: 0, 196 // switch-profile also returns what the profile we switch to 197 expectedResult: { 198 action: "switch-profile", 199 data: { 200 name: "Profile2", 201 path: PathUtils.join(PathUtils.tempDir, "other-profile"), 202 email: "testuser2@test.com", 203 }, 204 }, 205 expectedTelemetry: { 206 option_clicked: "switch-profile", 207 variant_shown: "sync-warning", 208 }, 209 }, 210 { 211 responseVal: 1, 212 expectedResult: { action: "cancel" }, 213 expectedTelemetry: { 214 option_clicked: "cancel", 215 variant_shown: "sync-warning", 216 }, 217 }, 218 ], 219 }, 220 { 221 description: 222 "Another profile is logged into the account we're trying to sign into, with merge allowed", 223 prefs: { 224 "browser.profiles.enabled": true, 225 "browser.profiles.sync.allow-danger-merge": true, 226 }, 227 expectedResponses: [ 228 { 229 responseVal: 0, 230 expectedResult: { action: "continue" }, 231 expectedTelemetry: { 232 option_clicked: "continue", 233 variant_shown: "sync-warning-allow-merge", 234 }, 235 }, 236 { 237 responseVal: 1, 238 // switch-profile also returns what the profile we switch to 239 expectedResult: { 240 action: "switch-profile", 241 data: { 242 name: "Profile2", 243 path: PathUtils.join(PathUtils.tempDir, "other-profile"), 244 email: "testuser2@test.com", 245 }, 246 }, 247 expectedTelemetry: { 248 option_clicked: "switch-profile", 249 variant_shown: "sync-warning-allow-merge", 250 }, 251 }, 252 { 253 responseVal: 2, 254 expectedResult: { action: "cancel" }, 255 expectedTelemetry: { 256 option_clicked: "cancel", 257 variant_shown: "sync-warning-allow-merge", 258 }, 259 }, 260 ], 261 }, 262 ]; 263 264 add_task( 265 async function test_another_profile_signed_in_variants_result_and_telemetry() { 266 // Create a helper instance 267 let helpers = new FxAccountsWebChannelHelpers(); 268 269 // Mock methods 270 helpers._selectableProfilesEnabled = () => 271 Services.prefs.getBoolPref("browser.profiles.enabled"); 272 helpers._getAllProfiles = async () => mockedProfiles; 273 helpers._getCurrentProfileName = () => mockedProfiles[0].name; 274 // Mock the file reading to simulate the account being signed into the other profile 275 helpers._readJSONFileAsync = async function (filePath) { 276 if (filePath.includes("current-profile")) { 277 // No signed-in user in the current profile 278 return null; 279 } else if (filePath.includes("other-profile")) { 280 // The account is signed into the other profile 281 return { 282 version: 1, 283 accountData: { email: "testuser2@test.com", uid: "uid" }, 284 }; 285 } 286 return null; 287 }; 288 289 for (let variant of anotherProfileDialogVariants) { 290 info(`Testing variant: ${variant.description}`); 291 // Set the preferences for this variant 292 for (let [prefName, prefValue] of Object.entries(variant.prefs)) { 293 Services.prefs.setBoolPref(prefName, prefValue); 294 } 295 296 for (let i = 0; i < variant.expectedResponses.length; i++) { 297 let { responseVal, expectedResult, expectedTelemetry } = 298 variant.expectedResponses[i]; 299 300 gResponse = responseVal; 301 let result = await helpers.promptProfileSyncWarningIfNeeded({ 302 email: "testuser2@test.com", 303 uid: "uid", 304 }); 305 // Verify we returned the expected result 306 Assert.deepEqual(result, expectedResult); 307 308 let gleanValue = Glean.syncMergeDialog.clicked.testGetValue(); 309 // Verify the telemetry is shaped as expected 310 Assert.equal( 311 gleanValue[i].extra.variant_shown, 312 expectedTelemetry.variant_shown, 313 "Correctly logged which dialog variant was shown to the user" 314 ); 315 Assert.equal( 316 gleanValue[i].extra.option_clicked, 317 expectedTelemetry.option_clicked, 318 "Correctly logged which option the user selected" 319 ); 320 } 321 // Reset Glean for next iteration 322 Services.fog.testResetFOG(); 323 } 324 325 // Clean up preferences 326 Services.prefs.clearUserPref("browser.profiles.enabled"); 327 Services.prefs.clearUserPref("browser.profiles.sync.allow-danger-merge"); 328 } 329 ); 330 331 add_task(async function test_current_profile_is_correctly_skipped() { 332 // Define two profiles. 333 const fakeProfiles = [ 334 { name: "Profile1", path: PathUtils.join(PathUtils.tempDir, "profile1") }, 335 { name: "Profile2", path: PathUtils.join(PathUtils.tempDir, "profile2") }, 336 ]; 337 338 // Fake signedInUser.json content for each profile. 339 // Profile1 (the current profile) is signed in with user@example.com. 340 // Profile2 is signed in with other@example.com. 341 const fakeSignedInUsers = { 342 [PathUtils.join(PathUtils.tempDir, "profile1", "signedInUser.json")]: { 343 accountData: { email: "user@example.com", uid: "user" }, 344 version: 1, 345 }, 346 [PathUtils.join(PathUtils.tempDir, "profile2", "signedInUser.json")]: { 347 accountData: { email: "other@example.com", uid: "other" }, 348 version: 1, 349 }, 350 }; 351 352 // Create an instance of the FxAccountsWebChannelHelpers. 353 let channel = new FxAccountsWebChannelHelpers(); 354 355 // Override the methods to return our fake data. 356 channel._getAllProfiles = async () => fakeProfiles; 357 channel._getCurrentProfileName = () => "Profile1"; 358 channel._readJSONFileAsync = async filePath => 359 fakeSignedInUsers[filePath] || null; 360 361 // Case 1: The account email is in the current profile. 362 let associatedProfile = await channel._getProfileAssociatedWithAcct("user"); 363 Assert.equal( 364 associatedProfile, 365 null, 366 "Should not return the current profile." 367 ); 368 369 // Case 2: The account email is in a different profile. 370 associatedProfile = await channel._getProfileAssociatedWithAcct("other"); 371 Assert.ok( 372 associatedProfile, 373 "Should return a profile when account email is in another profile." 374 ); 375 Assert.equal( 376 associatedProfile.name, 377 "Profile2", 378 "Returned profile should be 'Profile2'." 379 ); 380 }); 381 382 // Test need-relink-warning. 383 add_task( 384 async function test_previously_signed_in_dialog_variants_result_and_telemetry() { 385 let helpers = new FxAccountsWebChannelHelpers(); 386 387 // We "pretend" there was another account previously logged in 388 helpers.setPreviousAccountHashPref("test_uid"); 389 390 Assert.ok( 391 !helpers._needRelinkWarning({ 392 email: "testuser2@test.com", 393 uid: "test_uid", 394 }) 395 ); 396 Assert.ok( 397 helpers._needRelinkWarning({ 398 email: "testuser2@test.com", 399 uid: "different_uid", 400 }) 401 ); 402 // missing uid == "new account" == "always need the warning if anyone was previously logged in" 403 Assert.ok(helpers._needRelinkWarning({ email: "testuser2@test.com" })); 404 Services.prefs.clearUserPref(PREF_LAST_FXA_USER_UID); 405 Assert.ok(!helpers._needRelinkWarning({ email: "testuser2@test.com" })); 406 } 407 );