tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 );