Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: !sync OR appname == 'thunderbird'
- This test runs only with pattern: os != 'android'
- 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",
PREF_LAST_FXA_USER_UID: "resource://gre/modules/FxAccountsCommon.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.setPreviousAccountHashPref("test_uid");
// Mock methods
helpers._selectableProfilesEnabled = () =>
Services.prefs.getBoolPref("browser.profiles.enabled");
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({
email: "testuser2@test.com",
uid: "test2",
});
// 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._selectableProfilesEnabled = () =>
Services.prefs.getBoolPref("browser.profiles.enabled");
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", uid: "uid" },
};
}
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({
email: "testuser2@test.com",
uid: "uid",
});
// 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", uid: "user" },
version: 1,
},
[PathUtils.join(PathUtils.tempDir, "profile2", "signedInUser.json")]: {
accountData: { email: "other@example.com", uid: "other" },
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");
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");
Assert.ok(
associatedProfile,
"Should return a profile when account email is in another profile."
);
Assert.equal(
associatedProfile.name,
"Profile2",
"Returned profile should be 'Profile2'."
);
});
// Test need-relink-warning.
add_task(
async function test_previously_signed_in_dialog_variants_result_and_telemetry() {
let helpers = new FxAccountsWebChannelHelpers();
// We "pretend" there was another account previously logged in
helpers.setPreviousAccountHashPref("test_uid");
Assert.ok(
!helpers._needRelinkWarning({
email: "testuser2@test.com",
uid: "test_uid",
})
);
Assert.ok(
helpers._needRelinkWarning({
email: "testuser2@test.com",
uid: "different_uid",
})
);
// missing uid == "new account" == "always need the warning if anyone was previously logged in"
Assert.ok(helpers._needRelinkWarning({ email: "testuser2@test.com" }));
Services.prefs.clearUserPref(PREF_LAST_FXA_USER_UID);
Assert.ok(!helpers._needRelinkWarning({ email: "testuser2@test.com" }));
}
);