Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: appname == 'thunderbird' && !nightly_build OR appname == 'thunderbird'
- Manifest: services/fxaccounts/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
ChromeUtils.defineESModuleGetters(this, {
FxAccountsWebChannelHelpers:
"resource://gre/modules/FxAccountsWebChannel.sys.mjs",
SelectableProfileService:
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
});
// Set up mocked profiles
const mockedProfiles = [
{
name: "Profile1",
path: PathUtils.join(PathUtils.tempDir, "current-profile"),
email: "testuser1@test.com",
},
{
name: "Profile2",
path: PathUtils.join(PathUtils.tempDir, "other-profile"),
email: "testuser2@test.com",
},
];
// Emulates the response from the user
let gResponse = 1;
(function replacePromptService() {
let originalPromptService = Services.prompt;
Services.prompt = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]),
confirmEx: () => gResponse,
};
registerCleanupFunction(() => {
Services.prompt = originalPromptService;
});
})();
add_setup(function setup() {
// FOG needs a profile directory to put its data in.
do_get_profile();
// FOG needs to be initialized in order for data to flow.
Services.fog.initializeFOG();
// The profile service requires the directory service to have been initialized.
Cc["@mozilla.org/xre/directory-provider;1"].getService(Ci.nsIXREDirProvider);
// The normal isEnabled getter relies on there being a properly working toolkit
// profile service. For the purposes of this test just mirror the state of the
// preference.
Object.defineProperty(SelectableProfileService, "isEnabled", {
get() {
return Services.prefs.getBoolPref("browser.profiles.enabled");
},
});
});
const dialogVariants = [
{
description: "A previous account was signed into this profile",
prefs: {
"browser.profiles.enabled": true,
"browser.profiles.sync.allow-danger-merge": false,
},
expectedResponses: [
{
responseVal: 0,
expectedResult: { action: "create-profile" },
expectedTelemetry: {
variant_shown: "merge-warning",
option_clicked: "create-profile",
},
},
{
responseVal: 1,
expectedResult: { action: "cancel" },
expectedTelemetry: {
variant_shown: "merge-warning",
option_clicked: "cancel",
},
},
],
},
{
description:
"A previous account was signed into this profile, with merge allowed",
prefs: {
"browser.profiles.enabled": true,
"browser.profiles.sync.allow-danger-merge": true,
},
expectedResponses: [
{
responseVal: 0,
expectedResult: { action: "continue" },
expectedTelemetry: {
variant_shown: "merge-warning-allow-merge",
option_clicked: "continue",
},
},
{
responseVal: 1,
expectedResult: { action: "create-profile" },
expectedTelemetry: {
variant_shown: "merge-warning-allow-merge",
option_clicked: "create-profile",
},
},
{
responseVal: 2,
expectedResult: { action: "cancel" },
expectedTelemetry: {
option_clicked: "cancel",
variant_shown: "merge-warning-allow-merge",
},
},
],
},
];
add_task(
async function test_previously_signed_in_dialog_variants_result_and_telemetry() {
// Create a helper instance
let helpers = new FxAccountsWebChannelHelpers();
// We "pretend" there was another account previously logged in
helpers.setPreviousAccountNameHashPref("testuser@testuser.com");
// Mock methods
helpers._getAllProfiles = async () => mockedProfiles;
helpers._getCurrentProfileName = () => mockedProfiles[0].name;
helpers._readJSONFileAsync = async function (_filePath) {
return null;
};
for (let variant of dialogVariants) {
info(`Testing variant: ${variant.description}`);
// Set the preferences for this variant
for (let [prefName, prefValue] of Object.entries(variant.prefs)) {
Services.prefs.setBoolPref(prefName, prefValue);
}
for (let i = 0; i < variant.expectedResponses.length; i++) {
let { responseVal, expectedResult, expectedTelemetry } =
variant.expectedResponses[i];
gResponse = responseVal;
let result =
await helpers.promptProfileSyncWarningIfNeeded("testuser2@test.com");
//Verify we returned the expected result
Assert.deepEqual(result, expectedResult);
let gleanValue = Glean.syncMergeDialog.clicked.testGetValue();
// Verify the telemetry is shaped as expected
Assert.equal(
gleanValue[i].extra.variant_shown,
expectedTelemetry.variant_shown,
"Correctly logged which dialog variant was shown to the user"
);
Assert.equal(
gleanValue[i].extra.option_clicked,
expectedTelemetry.option_clicked,
"Correctly logged which option the user selected"
);
}
// Reset Glean for next iteration
Services.fog.testResetFOG();
}
// Clean up preferences
Services.prefs.clearUserPref("browser.profiles.enabled");
Services.prefs.clearUserPref("browser.profiles.sync.allow-danger-merge");
}
);
/**
* Testing the dialog variants where another profile is signed into the account
* we're trying to sign into
*/
const anotherProfileDialogVariants = [
{
description:
"Another profile is logged into the account we're trying to sign into",
prefs: {
"browser.profiles.enabled": true,
"browser.profiles.sync.allow-danger-merge": false,
},
expectedResponses: [
{
responseVal: 0,
// switch-profile also returns what the profile we switch to
expectedResult: {
action: "switch-profile",
data: {
name: "Profile2",
path: PathUtils.join(PathUtils.tempDir, "other-profile"),
email: "testuser2@test.com",
},
},
expectedTelemetry: {
option_clicked: "switch-profile",
variant_shown: "sync-warning",
},
},
{
responseVal: 1,
expectedResult: { action: "cancel" },
expectedTelemetry: {
option_clicked: "cancel",
variant_shown: "sync-warning",
},
},
],
},
{
description:
"Another profile is logged into the account we're trying to sign into, with merge allowed",
prefs: {
"browser.profiles.enabled": true,
"browser.profiles.sync.allow-danger-merge": true,
},
expectedResponses: [
{
responseVal: 0,
expectedResult: { action: "continue" },
expectedTelemetry: {
option_clicked: "continue",
variant_shown: "sync-warning-allow-merge",
},
},
{
responseVal: 1,
// switch-profile also returns what the profile we switch to
expectedResult: {
action: "switch-profile",
data: {
name: "Profile2",
path: PathUtils.join(PathUtils.tempDir, "other-profile"),
email: "testuser2@test.com",
},
},
expectedTelemetry: {
option_clicked: "switch-profile",
variant_shown: "sync-warning-allow-merge",
},
},
{
responseVal: 2,
expectedResult: { action: "cancel" },
expectedTelemetry: {
option_clicked: "cancel",
variant_shown: "sync-warning-allow-merge",
},
},
],
},
];
add_task(
async function test_another_profile_signed_in_variants_result_and_telemetry() {
// Create a helper instance
let helpers = new FxAccountsWebChannelHelpers();
// Mock methods
helpers._getAllProfiles = async () => mockedProfiles;
helpers._getCurrentProfileName = () => mockedProfiles[0].name;
// Mock the file reading to simulate the account being signed into the other profile
helpers._readJSONFileAsync = async function (filePath) {
if (filePath.includes("current-profile")) {
// No signed-in user in the current profile
return null;
} else if (filePath.includes("other-profile")) {
// The account is signed into the other profile
return {
version: 1,
accountData: { email: "testuser2@test.com" },
};
}
return null;
};
for (let variant of anotherProfileDialogVariants) {
info(`Testing variant: ${variant.description}`);
// Set the preferences for this variant
for (let [prefName, prefValue] of Object.entries(variant.prefs)) {
Services.prefs.setBoolPref(prefName, prefValue);
}
for (let i = 0; i < variant.expectedResponses.length; i++) {
let { responseVal, expectedResult, expectedTelemetry } =
variant.expectedResponses[i];
gResponse = responseVal;
let result =
await helpers.promptProfileSyncWarningIfNeeded("testuser2@test.com");
//Verify we returned the expected result
Assert.deepEqual(result, expectedResult);
let gleanValue = Glean.syncMergeDialog.clicked.testGetValue();
// Verify the telemetry is shaped as expected
Assert.equal(
gleanValue[i].extra.variant_shown,
expectedTelemetry.variant_shown,
"Correctly logged which dialog variant was shown to the user"
);
Assert.equal(
gleanValue[i].extra.option_clicked,
expectedTelemetry.option_clicked,
"Correctly logged which option the user selected"
);
}
// Reset Glean for next iteration
Services.fog.testResetFOG();
}
// Clean up preferences
Services.prefs.clearUserPref("browser.profiles.enabled");
Services.prefs.clearUserPref("browser.profiles.sync.allow-danger-merge");
}
);
add_task(async function test_current_profile_is_correctly_skipped() {
// Define two profiles.
const fakeProfiles = [
{ name: "Profile1", path: PathUtils.join(PathUtils.tempDir, "profile1") },
{ name: "Profile2", path: PathUtils.join(PathUtils.tempDir, "profile2") },
];
// Fake signedInUser.json content for each profile.
// Profile1 (the current profile) is signed in with user@example.com.
// Profile2 is signed in with other@example.com.
const fakeSignedInUsers = {
[PathUtils.join(PathUtils.tempDir, "profile1", "signedInUser.json")]: {
accountData: { email: "user@example.com" },
version: 1,
},
[PathUtils.join(PathUtils.tempDir, "profile2", "signedInUser.json")]: {
accountData: { email: "other@example.com" },
version: 1,
},
};
// Create an instance of the FxAccountsWebChannelHelpers.
let channel = new FxAccountsWebChannelHelpers();
// Override the methods to return our fake data.
channel._getAllProfiles = async () => fakeProfiles;
channel._getCurrentProfileName = () => "Profile1";
channel._readJSONFileAsync = async filePath =>
fakeSignedInUsers[filePath] || null;
// Case 1: The account email is in the current profile.
let associatedProfile =
await channel._getProfileAssociatedWithAcct("user@example.com");
Assert.equal(
associatedProfile,
null,
"Should not return the current profile."
);
// Case 2: The account email is in a different profile.
associatedProfile =
await channel._getProfileAssociatedWithAcct("other@example.com");
Assert.ok(
associatedProfile,
"Should return a profile when account email is in another profile."
);
Assert.equal(
associatedProfile.name,
"Profile2",
"Returned profile should be 'Profile2'."
);
});