blob: a5a56107266ac3afe846b1810a446bcfa567ffa3 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <string>
#include <unordered_set>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/avatar_menu.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_init_params.h"
#include "chrome/browser/profiles/profile_avatar_downloader.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/supervised_user/supervised_user_constants.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/account_id/account_id.h"
#include "components/profile_metrics/state.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/native_theme/native_theme.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/signin/profile_colors_util.h"
#endif
using ::testing::Mock;
using ::testing::_;
namespace {
// The ProfileMetadataEntry accessors aren't just plain old accessors to local
// members so they'll be tested. The following helpers will make the testing
// code less verbose.
#define TEST_ACCESSORS(entry_type, entry, member, first_value, second_value) \
TestAccessors(&entry, \
&entry_type::Get ## member, \
&entry_type::Set ## member, \
first_value, \
second_value);
#define TEST_STRING16_ACCESSORS(entry_type, entry, member) \
TEST_ACCESSORS(entry_type, entry, member, \
std::u16string(u"first_" #member "_value"), \
std::u16string(u"second_" #member "_value"));
#define TEST_STRING_ACCESSORS(entry_type, entry, member) \
TEST_ACCESSORS(entry_type, entry, member, \
std::string("first_" #member "_value"), \
std::string("second_" #member "_value"));
#define TEST_BOOL_ACCESSORS(entry_type, entry, member) \
TestAccessors(&entry, &entry_type::member, &entry_type::Set##member, true, \
false);
template<typename TValue, typename TGetter, typename TSetter>
void TestAccessors(ProfileAttributesEntry** entry,
TGetter getter_func,
TSetter setter_func,
TValue first_value,
TValue second_value) {
(*entry->*setter_func)(first_value);
EXPECT_EQ(first_value, (*entry->*getter_func)());
(*entry->*setter_func)(second_value);
EXPECT_EQ(second_value, (*entry->*getter_func)());
}
void VerifyInitialValues(ProfileAttributesEntry* entry,
const base::FilePath& profile_path,
const std::u16string& profile_name,
const std::string& gaia_id,
const std::u16string& user_name,
bool is_consented_primary_account,
size_t icon_index,
const std::string& supervised_user_id,
bool is_ephemeral,
bool is_omitted,
bool is_signed_in_with_credential_provider) {
ASSERT_NE(entry, nullptr);
EXPECT_EQ(profile_path, entry->GetPath());
EXPECT_EQ(profile_name, entry->GetName());
EXPECT_EQ(gaia_id, entry->GetGAIAId());
EXPECT_EQ(user_name, entry->GetUserName());
EXPECT_EQ(is_consented_primary_account, entry->IsAuthenticated());
EXPECT_EQ(icon_index, entry->GetAvatarIconIndex());
EXPECT_EQ(supervised_user_id, entry->GetSupervisedUserId());
EXPECT_EQ(is_ephemeral, entry->IsEphemeral());
EXPECT_EQ(is_omitted, entry->IsOmitted());
EXPECT_EQ(is_signed_in_with_credential_provider,
entry->IsSignedInWithCredentialProvider());
EXPECT_EQ(std::string(), entry->GetHostedDomain());
}
class ProfileAttributesTestObserver
: public ProfileAttributesStorage::Observer {
public:
MOCK_METHOD1(OnProfileAdded, void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileWillBeRemoved,
void(const base::FilePath& profile_path));
MOCK_METHOD2(OnProfileWasRemoved,
void(const base::FilePath& profile_path,
const std::u16string& profile_name));
MOCK_METHOD2(OnProfileNameChanged,
void(const base::FilePath& profile_path,
const std::u16string& old_profile_name));
MOCK_METHOD1(OnProfileAuthInfoChanged,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileAvatarChanged,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileHighResAvatarLoaded,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileSigninRequiredChanged,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileSupervisedUserIdChanged,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileIsOmittedChanged,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileThemeColorsChanged,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileHostedDomainChanged,
void(const base::FilePath& profile_path));
MOCK_METHOD1(OnProfileUserManagementAcceptanceChanged,
void(const base::FilePath& profile_path));
};
size_t GetDefaultAvatarIconResourceIDAtIndex(int index) {
#if BUILDFLAG(IS_WIN)
return profiles::GetOldDefaultAvatar2xIconResourceIDAtIndex(index);
#else
return profiles::GetDefaultAvatarIconResourceIDAtIndex(index);
#endif // BUILDFLAG(IS_WIN)
}
std::u16string ConcatenateGaiaAndProfileNames(
const std::u16string& gaia_name,
const std::u16string& profile_name) {
return base::StrCat({gaia_name, u" (", profile_name, u")"});
}
} // namespace
class ProfileAttributesStorageTest : public testing::Test {
public:
ProfileAttributesStorageTest()
: testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {}
~ProfileAttributesStorageTest() override {}
protected:
void SetUp() override {
ASSERT_TRUE(testing_profile_manager_.SetUp());
VerifyAndResetCallExpectations();
EnableObserver();
}
base::FilePath GetProfilePath(const std::string& base_name) {
return testing_profile_manager_.profile_manager()->user_data_dir().
AppendASCII(base_name);
}
void VerifyAndResetCallExpectations() {
Mock::VerifyAndClear(&observer_);
EXPECT_CALL(observer_, OnProfileAdded(_)).Times(0);
EXPECT_CALL(observer_, OnProfileWillBeRemoved(_)).Times(0);
EXPECT_CALL(observer_, OnProfileWasRemoved(_, _)).Times(0);
EXPECT_CALL(observer_, OnProfileNameChanged(_, _)).Times(0);
EXPECT_CALL(observer_, OnProfileAuthInfoChanged(_)).Times(0);
EXPECT_CALL(observer_, OnProfileAvatarChanged(_)).Times(0);
EXPECT_CALL(observer_, OnProfileHighResAvatarLoaded(_)).Times(0);
EXPECT_CALL(observer_, OnProfileSigninRequiredChanged(_)).Times(0);
EXPECT_CALL(observer_, OnProfileSupervisedUserIdChanged(_)).Times(0);
EXPECT_CALL(observer_, OnProfileIsOmittedChanged(_)).Times(0);
EXPECT_CALL(observer_, OnProfileThemeColorsChanged(_)).Times(0);
EXPECT_CALL(observer_, OnProfileHostedDomainChanged(_)).Times(0);
}
void EnableObserver() { scoped_observation_.Observe(storage()); }
void DisableObserver() { scoped_observation_.Reset(); }
void AddCallExpectationsForRemoveProfile(size_t profile_number) {
base::FilePath profile_path = GetProfilePath(
base::StringPrintf("testing_profile_path%" PRIuS, profile_number));
std::u16string profile_name = base::ASCIIToUTF16(
base::StringPrintf("testing_profile_name%" PRIuS, profile_number));
::testing::InSequence dummy;
EXPECT_CALL(observer(), OnProfileWillBeRemoved(profile_path)).Times(1);
EXPECT_CALL(observer(), OnProfileWasRemoved(profile_path, profile_name))
.Times(1);
}
ProfileAttributesStorage* storage() {
return testing_profile_manager_.profile_attributes_storage();
}
ProfileAttributesTestObserver& observer() { return observer_; }
void AddTestingProfile() {
size_t number_of_profiles = storage()->GetNumberOfProfiles();
base::FilePath profile_path = GetProfilePath(
base::StringPrintf("testing_profile_path%" PRIuS, number_of_profiles));
EXPECT_CALL(observer(), OnProfileAdded(profile_path))
.Times(1)
.RetiresOnSaturation();
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = base::ASCIIToUTF16(
base::StringPrintf("testing_profile_name%" PRIuS, number_of_profiles));
params.gaia_id =
base::StringPrintf("testing_profile_gaia%" PRIuS, number_of_profiles);
params.user_name = base::ASCIIToUTF16(
base::StringPrintf("testing_profile_user%" PRIuS, number_of_profiles));
params.is_consented_primary_account = true;
params.icon_index = number_of_profiles;
storage()->AddProfile(std::move(params));
EXPECT_EQ(number_of_profiles + 1, storage()->GetNumberOfProfiles());
}
void ResetProfileAttributesStorage() {
bool was_observing = scoped_observation_.IsObserving();
DisableObserver();
testing_profile_manager_.DeleteProfileAttributesStorage();
// Restore observation if there was any.
if (was_observing)
EnableObserver();
}
TestingProfileManager& testing_profile_manager() {
return testing_profile_manager_;
}
private:
content::BrowserTaskEnvironment task_environment_;
TestingProfileManager testing_profile_manager_;
ProfileAttributesTestObserver observer_;
base::ScopedObservation<ProfileAttributesStorage,
ProfileAttributesStorage::Observer>
scoped_observation_{&observer_};
};
TEST_F(ProfileAttributesStorageTest, ProfileNotFound) {
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
ASSERT_EQ(storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0")),
nullptr);
AddTestingProfile();
EXPECT_EQ(1U, storage()->GetNumberOfProfiles());
ASSERT_NE(storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0")),
nullptr);
ASSERT_EQ(storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path1")),
nullptr);
}
TEST_F(ProfileAttributesStorageTest, AddProfile) {
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
EXPECT_CALL(observer(), OnProfileAdded(GetProfilePath("new_profile_path_1")))
.Times(1);
ProfileAttributesInitParams params;
params.profile_path = GetProfilePath("new_profile_path_1");
params.profile_name = u"new_profile_name_1";
params.gaia_id = "new_profile_gaia_1";
params.user_name = u"new_profile_username_1";
params.is_consented_primary_account = true;
params.icon_index = 1;
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
EXPECT_EQ(1U, storage()->GetNumberOfProfiles());
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("new_profile_path_1"));
ASSERT_NE(entry, nullptr);
EXPECT_EQ(u"new_profile_name_1", entry->GetName());
}
TEST_F(ProfileAttributesStorageTest, AddProfiles) {
DisableObserver(); // This test doesn't test observers.
EXPECT_EQ(0u, storage()->GetNumberOfProfiles());
// Avatar icons not used on Android.
#if !BUILDFLAG(IS_ANDROID)
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
#endif
for (size_t i = 0; i < 4u; ++i) {
base::FilePath profile_path =
GetProfilePath(base::StringPrintf("path_%zu", i));
std::u16string profile_name =
base::ASCIIToUTF16(base::StringPrintf("name_%zu", i));
#if !BUILDFLAG(IS_ANDROID)
size_t icon_id = GetDefaultAvatarIconResourceIDAtIndex(i);
const SkBitmap* icon = rb.GetImageNamed(icon_id).ToSkBitmap();
#endif // !BUILDFLAG(IS_ANDROID)
std::string supervised_user_id;
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
if (i == 3u)
supervised_user_id = supervised_users::kChildAccountSUID;
#endif
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = profile_name;
params.icon_index = i;
params.supervised_user_id = supervised_user_id;
storage()->AddProfile(std::move(params));
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
entry->SetBackgroundStatus(true);
std::u16string gaia_name =
base::ASCIIToUTF16(base::StringPrintf("gaia_%zu", i));
entry->SetGAIAName(gaia_name);
EXPECT_EQ(i + 1, storage()->GetNumberOfProfiles());
std::u16string expected_profile_name =
ConcatenateGaiaAndProfileNames(gaia_name, profile_name);
EXPECT_EQ(expected_profile_name, entry->GetName());
EXPECT_EQ(profile_path, entry->GetPath());
#if !BUILDFLAG(IS_ANDROID)
const SkBitmap* actual_icon = entry->GetAvatarIcon().ToSkBitmap();
EXPECT_EQ(icon->width(), actual_icon->width());
EXPECT_EQ(icon->height(), actual_icon->height());
#endif
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
EXPECT_EQ(i == 3u, entry->IsSupervised());
#else
EXPECT_FALSE(entry->IsSupervised());
EXPECT_FALSE(entry->IsOmitted());
#endif // BUILDFLAG(ENABLE_SUPERVISED_USERS)
EXPECT_EQ(supervised_user_id, entry->GetSupervisedUserId());
}
// Reset the storage and test the it reloads correctly.
ResetProfileAttributesStorage();
EXPECT_EQ(4u, storage()->GetNumberOfProfiles());
for (size_t i = 0; i < 4u; ++i) {
base::FilePath profile_path =
GetProfilePath(base::StringPrintf("path_%zu", i));
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
std::u16string profile_name =
base::ASCIIToUTF16(base::StringPrintf("name_%zu", i));
std::u16string gaia_name =
base::ASCIIToUTF16(base::StringPrintf("gaia_%zu", i));
std::u16string expected_profile_name =
ConcatenateGaiaAndProfileNames(gaia_name, profile_name);
EXPECT_EQ(expected_profile_name, entry->GetName());
#if !BUILDFLAG(IS_ANDROID)
EXPECT_EQ(i, entry->GetAvatarIconIndex());
#endif
EXPECT_EQ(true, entry->GetBackgroundStatus());
EXPECT_EQ(gaia_name, entry->GetGAIAName());
}
}
TEST_F(ProfileAttributesStorageTest, RemoveProfile) {
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_EQ(entry, nullptr);
AddTestingProfile();
EXPECT_EQ(1U, storage()->GetNumberOfProfiles());
entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(entry, nullptr);
EXPECT_EQ(u"testing_profile_name0", entry->GetName());
// Deleting an existing profile. This should call observers and make the entry
// un-retrievable.
AddCallExpectationsForRemoveProfile(0);
storage()->RemoveProfile(GetProfilePath("testing_profile_path0"));
VerifyAndResetCallExpectations();
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
EXPECT_EQ(entry, nullptr);
}
TEST_F(ProfileAttributesStorageTest, RemoveProfileByAccountId) {
DisableObserver(); // This test doesn't test observers.
EXPECT_EQ(0u, storage()->GetNumberOfProfiles());
const struct {
const char* profile_path;
const char* profile_name;
AccountId account_id;
bool is_consented_primary_account;
} kTestCases[] = {
{"path_1", "name_1", AccountId::FromUserEmailGaiaId("email1", "111111"),
true},
{"path_2", "name_3", AccountId::FromUserEmailGaiaId("email2", "222222"),
true},
{"path_3", "name_3", AccountId::FromUserEmailGaiaId("email3", "333333"),
false},
{"path_4", "name_4", AccountId::FromUserEmailGaiaId("email4", "444444"),
false}};
for (size_t i = 0; i < std::size(kTestCases); ++i) {
ProfileAttributesInitParams params;
params.profile_path = GetProfilePath(kTestCases[i].profile_path);
params.profile_name = base::ASCIIToUTF16(kTestCases[i].profile_name);
params.gaia_id = kTestCases[i].account_id.GetGaiaId();
params.user_name =
base::UTF8ToUTF16(kTestCases[i].account_id.GetUserEmail());
params.is_consented_primary_account =
kTestCases[i].is_consented_primary_account;
storage()->AddProfile(std::move(params));
EXPECT_EQ(i + 1, storage()->GetNumberOfProfiles());
}
storage()->RemoveProfileByAccountId(kTestCases[2].account_id);
EXPECT_EQ(3u, storage()->GetNumberOfProfiles());
storage()->RemoveProfileByAccountId(kTestCases[0].account_id);
EXPECT_EQ(2u, storage()->GetNumberOfProfiles());
// This profile is already deleted.
storage()->RemoveProfileByAccountId(kTestCases[2].account_id);
EXPECT_EQ(2u, storage()->GetNumberOfProfiles());
// Remove profile by partial match.
storage()->RemoveProfileByAccountId(
AccountId::FromUserEmail(kTestCases[1].account_id.GetUserEmail()));
EXPECT_EQ(1u, storage()->GetNumberOfProfiles());
// Remove last profile.
storage()->RemoveProfileByAccountId(kTestCases[3].account_id);
EXPECT_EQ(0u, storage()->GetNumberOfProfiles());
}
TEST_F(ProfileAttributesStorageTest, MultipleProfiles) {
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
for (size_t i = 0; i < 5; ++i) {
AddTestingProfile();
EXPECT_EQ(i + 1, storage()->GetNumberOfProfiles());
EXPECT_EQ(i + 1, storage()->GetAllProfilesAttributes().size());
EXPECT_EQ(i + 1, storage()->GetAllProfilesAttributesSortedByName().size());
}
EXPECT_EQ(5U, storage()->GetNumberOfProfiles());
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(entry, nullptr);
EXPECT_EQ(u"testing_profile_name0", entry->GetName());
AddCallExpectationsForRemoveProfile(0);
storage()->RemoveProfile(GetProfilePath("testing_profile_path0"));
VerifyAndResetCallExpectations();
entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_EQ(entry, nullptr);
EXPECT_EQ(4U, storage()->GetNumberOfProfiles());
std::vector<ProfileAttributesEntry*> entries =
storage()->GetAllProfilesAttributes();
for (auto* attributes_entry : entries) {
EXPECT_NE(GetProfilePath("testing_profile_path0"),
attributes_entry->GetPath());
}
}
TEST_F(ProfileAttributesStorageTest, AddStubProfile) {
DisableObserver(); // This test doesn't test observers.
EXPECT_EQ(0u, storage()->GetNumberOfProfiles());
// Add some profiles with and without a '.' in their paths.
const struct {
const char* profile_path;
const char* profile_name;
} kTestCases[] = {
{"path.test0", "name_0"},
{"path_test1", "name_1"},
{"path.test2", "name_2"},
{"path_test3", "name_3"},
};
const size_t kNumProfiles = std::size(kTestCases);
for (auto test_case : kTestCases) {
base::FilePath profile_path = GetProfilePath(test_case.profile_path);
std::u16string profile_name = base::ASCIIToUTF16(test_case.profile_name);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = profile_name;
storage()->AddProfile(std::move(params));
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
EXPECT_TRUE(entry);
EXPECT_EQ(profile_name, entry->GetName());
}
ASSERT_EQ(kNumProfiles, storage()->GetNumberOfProfiles());
// Check that the profiles can be extracted from the local state.
std::vector<std::string> names;
PrefService* local_state = g_browser_process->local_state();
const base::Value::Dict& attributes =
local_state->GetDict(prefs::kProfileAttributes);
for (const auto kv : attributes) {
const base::Value& info = kv.second;
const std::string* name = info.FindStringKey("name");
names.push_back(*name);
}
for (size_t i = 0; i < kNumProfiles; i++)
ASSERT_FALSE(names[i].empty());
}
TEST_F(ProfileAttributesStorageTest, InitialValues) {
#if BUILDFLAG(IS_ANDROID)
// Android has only one default avatar.
size_t kIconIndex = 0;
#else
size_t kIconIndex = 1;
#endif
base::FilePath profile_path = GetProfilePath("testing_profile_path");
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"testing_profile_name";
params.gaia_id = "testing_profile_gaia";
params.user_name = u"testing_profile_username";
params.is_consented_primary_account = true;
params.icon_index = kIconIndex;
params.supervised_user_id = "testing_supervised_user_id";
params.account_id = AccountId::FromUserEmailGaiaId(
base::UTF16ToUTF8(params.user_name), params.gaia_id);
params.is_ephemeral = true;
params.is_omitted = true;
params.is_signed_in_with_credential_provider = true;
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
VerifyInitialValues(
entry, profile_path, /*profile_name=*/u"testing_profile_name",
/*gaia_id=*/"testing_profile_gaia",
/*user_name=*/u"testing_profile_username",
/*is_consented_primary_account=*/true, /*icon_index=*/kIconIndex,
/*supervised_user_id=*/"testing_supervised_user_id",
/*is_ephemeral=*/true, /*is_omitted=*/true,
/*is_signed_in_with_credential_provider=*/true);
}
TEST_F(ProfileAttributesStorageTest, InitialValues_Defaults) {
base::FilePath profile_path = GetProfilePath("testing_profile_path");
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
// Verify defaults of ProfileAttributesInitParams.
EXPECT_TRUE(params.profile_name.empty());
EXPECT_TRUE(params.gaia_id.empty());
EXPECT_TRUE(params.user_name.empty());
EXPECT_FALSE(params.is_consented_primary_account);
EXPECT_EQ(0U, params.icon_index);
EXPECT_TRUE(params.supervised_user_id.empty());
EXPECT_TRUE(params.account_id.empty());
EXPECT_FALSE(params.is_ephemeral);
EXPECT_FALSE(params.is_omitted);
EXPECT_FALSE(params.is_signed_in_with_credential_provider);
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
VerifyInitialValues(entry, profile_path, /*profile_name=*/std::u16string(),
/*gaia_id=*/std::string(), /*user_name=*/std::u16string(),
/*is_consented_primary_account=*/false, /*icon_index=*/0,
/*supervised_user_id=*/std::string(),
/*is_ephemeral=*/false, /*is_omitted=*/false,
/*is_signed_in_with_credential_provider=*/false);
}
// Checks that ProfileAttributesStorage doesn't crash when
// ProfileAttributesEntry initialization modifies an attributes entry.
// This is a regression test for https://crbug.com/1180497.
TEST_F(ProfileAttributesStorageTest, ModifyEntryWhileInitializing) {
DisableObserver(); // This test doesn't test observers.
base::FilePath profile_path = GetProfilePath("test");
{
signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
AccountId account_id = AccountId::FromUserEmailGaiaId("email", "111111");
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"Test";
params.gaia_id = account_id.GetGaiaId();
params.user_name = base::UTF8ToUTF16(account_id.GetUserEmail());
storage()->AddProfile(std::move(params));
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
// Set up the state so that ProfileAttributesEntry::Initialize() will modify
// the entry.
entry->LockForceSigninProfile(true);
}
// Reinitialize ProfileAttributesStorage.
ResetProfileAttributesStorage();
storage(); // Should not crash.
// The IsSigninRequired attribute should be cleaned up.
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
EXPECT_FALSE(entry->IsSigninRequired());
}
TEST_F(ProfileAttributesStorageTest, ProfileNamesOnInit) {
DisableObserver(); // This test doesn't test observers.
// Set up the storage with two profiles having the same GAIA given name.
// The second profile also has a profile name matching the GAIA given name.
std::u16string kDefaultProfileName = u"Person 1";
std::u16string kCommonName = u"Joe";
// Create and initialize the first profile.
base::FilePath path_1 = GetProfilePath("path_1");
ProfileAttributesInitParams params_1;
params_1.profile_path = path_1;
params_1.profile_name = kDefaultProfileName;
storage()->AddProfile(std::move(params_1));
ProfileAttributesEntry* entry_1 =
storage()->GetProfileAttributesWithPath(path_1);
entry_1->SetGAIAGivenName(kCommonName);
EXPECT_EQ(entry_1->GetName(), kCommonName);
// Create and initialize the second profile.
base::FilePath path_2 = GetProfilePath("path_2");
ProfileAttributesInitParams params_2;
params_2.profile_path = path_2;
params_2.profile_name = kCommonName;
storage()->AddProfile(std::move(params_2));
ProfileAttributesEntry* entry_2 =
storage()->GetProfileAttributesWithPath(path_2);
entry_2->SetGAIAGivenName(kCommonName);
EXPECT_EQ(entry_2->GetName(), kCommonName);
// The first profile name should be modified.
EXPECT_EQ(entry_1->GetName(),
ConcatenateGaiaAndProfileNames(kCommonName, kDefaultProfileName));
// Reset the storage to test profile names set on initialization.
ResetProfileAttributesStorage();
entry_1 = storage()->GetProfileAttributesWithPath(path_1);
entry_2 = storage()->GetProfileAttributesWithPath(path_2);
// Freshly initialized entries should not report name changes.
EXPECT_FALSE(entry_1->HasProfileNameChanged());
EXPECT_FALSE(entry_2->HasProfileNameChanged());
EXPECT_EQ(entry_1->GetName(),
ConcatenateGaiaAndProfileNames(kCommonName, kDefaultProfileName));
EXPECT_EQ(entry_2->GetName(), kCommonName);
}
TEST_F(ProfileAttributesStorageTest, EntryAccessors) {
AddTestingProfile();
base::FilePath path = GetProfilePath("testing_profile_path0");
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(path);
ASSERT_NE(entry, nullptr);
EXPECT_EQ(path, entry->GetPath());
EXPECT_CALL(observer(), OnProfileNameChanged(path, _)).Times(2);
entry->SetLocalProfileName(u"first_value", true);
EXPECT_EQ(u"first_value", entry->GetLocalProfileName());
EXPECT_TRUE(entry->IsUsingDefaultName());
entry->SetLocalProfileName(u"second_value", false);
EXPECT_EQ(u"second_value", entry->GetLocalProfileName());
EXPECT_FALSE(entry->IsUsingDefaultName());
VerifyAndResetCallExpectations();
// GaiaIds.
EXPECT_TRUE(entry->GetGaiaIds().empty());
base::flat_set<std::string> accounts1({"a"});
base::flat_set<std::string> accounts2({"b", "c"});
entry->SetGaiaIds(accounts1);
EXPECT_EQ(accounts1, entry->GetGaiaIds());
entry->SetGaiaIds(accounts2);
EXPECT_EQ(accounts2, entry->GetGaiaIds());
entry->SetGaiaIds({});
EXPECT_TRUE(entry->GetGaiaIds().empty());
TEST_STRING16_ACCESSORS(ProfileAttributesEntry, entry, ShortcutName);
TEST_ACCESSORS(ProfileAttributesEntry, entry, BackgroundStatus, true, false);
EXPECT_CALL(observer(), OnProfileNameChanged(path, _)).Times(4);
TEST_STRING16_ACCESSORS(ProfileAttributesEntry, entry, GAIAName);
TEST_STRING16_ACCESSORS(ProfileAttributesEntry, entry, GAIAGivenName);
VerifyAndResetCallExpectations();
EXPECT_CALL(observer(), OnProfileAvatarChanged(path)).Times(2);
TEST_BOOL_ACCESSORS(ProfileAttributesEntry, entry, IsUsingGAIAPicture);
VerifyAndResetCallExpectations();
// IsOmitted() should be set only on ephemeral profiles.
entry->SetIsEphemeral(true);
EXPECT_CALL(observer(), OnProfileIsOmittedChanged(path)).Times(2);
TEST_BOOL_ACCESSORS(ProfileAttributesEntry, entry, IsOmitted);
VerifyAndResetCallExpectations();
entry->SetIsEphemeral(false);
EXPECT_CALL(observer(), OnProfileHostedDomainChanged(path)).Times(2);
TEST_STRING_ACCESSORS(ProfileAttributesEntry, entry, HostedDomain);
VerifyAndResetCallExpectations();
TEST_BOOL_ACCESSORS(ProfileAttributesEntry, entry, IsEphemeral);
TEST_BOOL_ACCESSORS(ProfileAttributesEntry, entry, IsUsingDefaultAvatar);
TEST_STRING_ACCESSORS(ProfileAttributesEntry, entry,
LastDownloadedGAIAPictureUrlWithSize);
EXPECT_CALL(observer(), OnProfileUserManagementAcceptanceChanged(_)).Times(2);
TEST_BOOL_ACCESSORS(ProfileAttributesEntry, entry,
UserAcceptedAccountManagement);
VerifyAndResetCallExpectations();
}
TEST_F(ProfileAttributesStorageTest, EntryInternalAccessors) {
AddTestingProfile();
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(entry, nullptr);
EXPECT_EQ(GetProfilePath("testing_profile_path0"), entry->GetPath());
const char key[] = "test";
// Tests whether the accessors store and retrieve values correctly.
EXPECT_TRUE(entry->SetString(key, std::string("abcd")));
ASSERT_TRUE(entry->GetValue(key));
EXPECT_EQ(base::Value::Type::STRING, entry->GetValue(key)->type());
EXPECT_EQ(std::string("abcd"), entry->GetString(key));
EXPECT_EQ(u"abcd", entry->GetString16(key));
EXPECT_EQ(0.0, entry->GetDouble(key));
EXPECT_FALSE(entry->GetBool(key));
EXPECT_FALSE(entry->IsDouble(key));
EXPECT_TRUE(entry->SetString16(key, u"efgh"));
ASSERT_TRUE(entry->GetValue(key));
EXPECT_EQ(base::Value::Type::STRING, entry->GetValue(key)->type());
EXPECT_EQ(std::string("efgh"), entry->GetString(key));
EXPECT_EQ(u"efgh", entry->GetString16(key));
EXPECT_EQ(0.0, entry->GetDouble(key));
EXPECT_FALSE(entry->GetBool(key));
EXPECT_FALSE(entry->IsDouble(key));
EXPECT_TRUE(entry->SetDouble(key, 12.5));
ASSERT_TRUE(entry->GetValue(key));
EXPECT_EQ(base::Value::Type::DOUBLE, entry->GetValue(key)->type());
EXPECT_EQ(std::string(), entry->GetString(key));
EXPECT_EQ(u"", entry->GetString16(key));
EXPECT_EQ(12.5, entry->GetDouble(key));
EXPECT_FALSE(entry->GetBool(key));
EXPECT_TRUE(entry->IsDouble(key));
EXPECT_TRUE(entry->SetBool(key, true));
ASSERT_TRUE(entry->GetValue(key));
EXPECT_EQ(base::Value::Type::BOOLEAN, entry->GetValue(key)->type());
EXPECT_EQ(std::string(), entry->GetString(key));
EXPECT_EQ(u"", entry->GetString16(key));
EXPECT_EQ(0.0, entry->GetDouble(key));
EXPECT_TRUE(entry->GetBool(key));
EXPECT_FALSE(entry->IsDouble(key));
// Test whether the setters returns correctly. Setters should return true if
// the previously stored value is different from the new value.
entry->SetBool(key, true);
EXPECT_TRUE(entry->SetString(key, std::string("abcd")));
EXPECT_FALSE(entry->SetString(key, std::string("abcd")));
EXPECT_FALSE(entry->SetString16(key, u"abcd"));
EXPECT_TRUE(entry->SetString16(key, u"efgh"));
EXPECT_FALSE(entry->SetString16(key, u"efgh"));
EXPECT_FALSE(entry->SetString(key, std::string("efgh")));
EXPECT_TRUE(entry->SetDouble(key, 12.5));
EXPECT_FALSE(entry->SetDouble(key, 12.5));
EXPECT_TRUE(entry->SetDouble(key, 15.0));
EXPECT_TRUE(entry->SetString(key, std::string("abcd")));
EXPECT_TRUE(entry->SetBool(key, true));
EXPECT_FALSE(entry->SetBool(key, true));
EXPECT_TRUE(entry->SetBool(key, false));
EXPECT_TRUE(entry->SetString16(key, u"efgh"));
// If previous data is not there, setters should returns false even if the
// defaults (empty string, 0.0, or false) are written.
EXPECT_FALSE(entry->SetString("test1", std::string()));
EXPECT_FALSE(entry->SetString16("test2", std::u16string()));
EXPECT_FALSE(entry->SetDouble("test3", 0.0));
EXPECT_FALSE(entry->SetBool("test4", false));
// If previous data is in a wrong type, setters should returns false even if
// the defaults (empty string, 0.0, or false) are written.
EXPECT_FALSE(entry->SetString("test3", std::string()));
EXPECT_FALSE(entry->SetString16("test4", std::u16string()));
EXPECT_FALSE(entry->SetDouble("test1", 0.0));
EXPECT_FALSE(entry->SetBool("test2", false));
}
TEST_F(ProfileAttributesStorageTest, ProfileActiveTime) {
AddTestingProfile();
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(entry, nullptr);
// Check the state before active time is stored.
const char kActiveTimeKey[] = "active_time";
EXPECT_FALSE(entry->IsDouble(kActiveTimeKey));
EXPECT_EQ(base::Time(), entry->GetActiveTime());
// Store the time and check for the result. Allow for a difference one second
// because the 64-bit integral representation in base::Time is rounded off to
// a double, which is what base::Value stores. http://crbug.com/346827
base::Time lower_bound = base::Time::Now() - base::Seconds(1);
entry->SetActiveTimeToNow();
base::Time upper_bound = base::Time::Now() + base::Seconds(1);
EXPECT_TRUE(entry->IsDouble(kActiveTimeKey));
EXPECT_LE(lower_bound, entry->GetActiveTime());
EXPECT_GE(upper_bound, entry->GetActiveTime());
// If the active time was less than one hour ago, SetActiveTimeToNow should do
// nothing.
base::Time past = base::Time::Now() - base::Minutes(10);
lower_bound = past - base::Seconds(1);
upper_bound = past + base::Seconds(1);
ASSERT_TRUE(entry->SetDouble(kActiveTimeKey, past.ToDoubleT()));
base::Time stored_time = entry->GetActiveTime();
ASSERT_LE(lower_bound, stored_time);
ASSERT_GE(upper_bound, stored_time);
entry->SetActiveTimeToNow();
EXPECT_EQ(stored_time, entry->GetActiveTime());
}
TEST_F(ProfileAttributesStorageTest, AuthInfo) {
AddTestingProfile();
base::FilePath path = GetProfilePath("testing_profile_path0");
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(path);
ASSERT_NE(entry, nullptr);
EXPECT_CALL(observer(), OnProfileAuthInfoChanged(path)).Times(1);
entry->SetAuthInfo("", std::u16string(), false);
VerifyAndResetCallExpectations();
ASSERT_EQ(entry->GetSigninState(), SigninState::kNotSignedIn);
EXPECT_EQ(std::u16string(), entry->GetUserName());
EXPECT_EQ("", entry->GetGAIAId());
EXPECT_CALL(observer(), OnProfileAuthInfoChanged(path)).Times(1);
entry->SetAuthInfo("foo", u"bar", true);
VerifyAndResetCallExpectations();
ASSERT_TRUE(entry->IsAuthenticated());
EXPECT_EQ(u"bar", entry->GetUserName());
EXPECT_EQ("foo", entry->GetGAIAId());
}
TEST_F(ProfileAttributesStorageTest, GAIAName) {
DisableObserver(); // This test doesn't test observers.
base::FilePath profile_path_1 = GetProfilePath("path_1");
ProfileAttributesInitParams params_1;
params_1.profile_path = profile_path_1;
params_1.profile_name = u"Person 1";
storage()->AddProfile(std::move(params_1));
ProfileAttributesEntry* entry_1 =
storage()->GetProfileAttributesWithPath(profile_path_1);
base::FilePath profile_path_2 = GetProfilePath("path_2");
ProfileAttributesInitParams params_2;
params_2.profile_path = profile_path_2;
params_2.profile_name = u"Person 2";
storage()->AddProfile(std::move(params_2));
ProfileAttributesEntry* entry_2 =
storage()->GetProfileAttributesWithPath(profile_path_2);
// Sanity check.
EXPECT_TRUE(entry_1->GetGAIAName().empty());
EXPECT_TRUE(entry_2->GetGAIAName().empty());
// Set GAIA name.
std::u16string gaia_name(u"Pat Smith");
entry_2->SetGAIAName(gaia_name);
// Since there is a GAIA name, we use that as a display name.
EXPECT_TRUE(entry_1->GetGAIAName().empty());
EXPECT_EQ(gaia_name, entry_2->GetGAIAName());
EXPECT_EQ(gaia_name, entry_2->GetName());
std::u16string custom_name(u"Custom name");
entry_2->SetLocalProfileName(custom_name, false);
std::u16string expected_profile_name =
ConcatenateGaiaAndProfileNames(gaia_name, custom_name);
EXPECT_EQ(expected_profile_name, entry_2->GetName());
EXPECT_EQ(gaia_name, entry_2->GetGAIAName());
}
TEST_F(ProfileAttributesStorageTest, ConcatenateGaiaNameAndProfileName) {
DisableObserver(); // This test doesn't test observers.
// We should only append the profile name to the GAIA name if:
// - The user has chosen a profile name on purpose.
// - Two profiles has the sama GAIA name and we need to show it to
// clear ambiguity.
// If one of the two conditions hold, we will show the profile name in this
// format |GAIA name (Profile local name)|
// Single profile.
base::FilePath profile_path_1 = GetProfilePath("path_1");
ProfileAttributesInitParams params_1;
params_1.profile_path = profile_path_1;
params_1.profile_name = u"Person 1";
storage()->AddProfile(std::move(params_1));
ProfileAttributesEntry* entry_1 =
storage()->GetProfileAttributesWithPath(profile_path_1);
EXPECT_EQ(u"Person 1", entry_1->GetName());
entry_1->SetGAIAName(u"Patt Smith");
EXPECT_EQ(u"Patt Smith", entry_1->GetName());
entry_1->SetGAIAGivenName(u"Patt");
EXPECT_EQ(u"Patt", entry_1->GetName());
// Set a custom profile name.
entry_1->SetLocalProfileName(u"Work", false);
EXPECT_EQ(u"Patt (Work)", entry_1->GetName());
// Set the profile name to be equal to GAIA name.
entry_1->SetLocalProfileName(u"patt", false);
EXPECT_EQ(u"Patt", entry_1->GetName());
// Multiple profiles.
// Add another profile with the same GAIA name and a default profile name.
base::FilePath profile_path_2 = GetProfilePath("path_2");
ProfileAttributesInitParams params_2;
params_2.profile_path = profile_path_2;
params_2.profile_name = u"Person 2";
storage()->AddProfile(std::move(params_2));
ProfileAttributesEntry* entry_2 =
storage()->GetProfileAttributesWithPath(profile_path_2);
EXPECT_EQ(u"Patt", entry_1->GetName());
EXPECT_EQ(u"Person 2", entry_2->GetName());
entry_1->SetLocalProfileName(u"Work", false);
EXPECT_EQ(u"Patt (Work)", entry_1->GetName());
EXPECT_EQ(u"Person 2", entry_2->GetName());
// A second profile with a different GAIA name should not affect the first
// profile.
entry_2->SetGAIAGivenName(u"Olly");
EXPECT_EQ(u"Patt (Work)", entry_1->GetName());
EXPECT_EQ(u"Olly", entry_2->GetName());
// Mark profile name as default.
entry_1->SetLocalProfileName(u"Person 1", true);
EXPECT_EQ(u"Patt", entry_1->GetName());
EXPECT_EQ(u"Olly", entry_2->GetName());
// Add a third profile with the same GAIA name as the first.
// The two profiles are marked as using default profile names.
base::FilePath profile_path_3 = GetProfilePath("path_3");
ProfileAttributesInitParams params_3;
params_3.profile_path = profile_path_3;
params_3.profile_name = u"Person 3";
storage()->AddProfile(std::move(params_3));
ProfileAttributesEntry* entry_3 =
storage()->GetProfileAttributesWithPath(profile_path_3);
entry_3->SetGAIAName(u"Patt Smith");
EXPECT_EQ(u"Patt", entry_1->GetName());
EXPECT_EQ(u"Patt Smith", entry_3->GetName());
// Two profiles with same GAIA name and default profile name.
// Empty GAIA given name.
entry_3->SetGAIAName(u"Patt");
EXPECT_EQ(u"Patt (Person 1)", entry_1->GetName());
EXPECT_EQ(u"Patt (Person 3)", entry_3->GetName());
// Set GAIA given name.
entry_3->SetGAIAGivenName(u"Patt");
EXPECT_EQ(u"Patt (Person 1)", entry_1->GetName());
EXPECT_EQ(u"Patt (Person 3)", entry_3->GetName());
// Customize the profile name for one of the two profiles.
entry_3->SetLocalProfileName(u"Personal", false);
EXPECT_EQ(u"Patt", entry_1->GetName());
EXPECT_EQ(u"Patt (Personal)", entry_3->GetName());
// Set one of the profile names to be equal to GAIA name, we should show
// the profile name even if it is Person n to clear ambiguity.
entry_3->SetLocalProfileName(u"patt", false);
EXPECT_EQ(u"Patt (Person 1)", entry_1->GetName());
EXPECT_EQ(u"Patt", entry_3->GetName());
// Never show the profile name if it is equal GAIA name.
entry_1->SetLocalProfileName(u"Patt", false);
EXPECT_EQ(u"Patt", entry_1->GetName());
EXPECT_EQ(u"Patt", entry_3->GetName());
EXPECT_EQ(u"Olly", entry_2->GetName());
}
TEST_F(ProfileAttributesStorageTest, SupervisedUsersAccessors) {
AddTestingProfile();
base::FilePath path = GetProfilePath("testing_profile_path0");
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(entry, nullptr);
entry->SetSupervisedUserId("");
ASSERT_FALSE(entry->IsSupervised());
ASSERT_FALSE(entry->IsChild());
EXPECT_CALL(observer(), OnProfileSupervisedUserIdChanged(path)).Times(1);
entry->SetSupervisedUserId("some_supervised_user_id");
VerifyAndResetCallExpectations();
ASSERT_TRUE(entry->IsSupervised());
ASSERT_FALSE(entry->IsChild());
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
EXPECT_CALL(observer(), OnProfileSupervisedUserIdChanged(path)).Times(1);
entry->SetSupervisedUserId(supervised_users::kChildAccountSUID);
VerifyAndResetCallExpectations();
ASSERT_TRUE(entry->IsSupervised());
ASSERT_TRUE(entry->IsChild());
#endif // BUILDFLAG(ENABLE_SUPERVISED_USERS)
}
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
TEST_F(ProfileAttributesStorageTest, CreateSupervisedTestingProfile) {
DisableObserver(); // This test doesn't test observers.
base::FilePath path_1 =
testing_profile_manager().CreateTestingProfile("default")->GetPath();
std::u16string supervised_user_name = u"Supervised User";
base::FilePath path_2 =
testing_profile_manager()
.CreateTestingProfile(
"test1", std::unique_ptr<sync_preferences::PrefServiceSyncable>(),
supervised_user_name, 0, TestingProfile::TestingFactories(),
/*is_supervised_profile=*/true)
->GetPath();
base::FilePath profile_paths[] = {path_1, path_2};
for (const base::FilePath& path : profile_paths) {
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(path);
bool is_supervised = entry->GetName() == supervised_user_name;
EXPECT_EQ(is_supervised, entry->IsSupervised());
std::string supervised_user_id =
is_supervised ? supervised_users::kChildAccountSUID : "";
EXPECT_EQ(supervised_user_id, entry->GetSupervisedUserId());
}
}
#endif
TEST_F(ProfileAttributesStorageTest, ReSortTriggered) {
DisableObserver(); // No need to test observers in this test.
ProfileAttributesInitParams alpha_params;
alpha_params.profile_path = GetProfilePath("alpha_path");
alpha_params.profile_name = u"alpha";
alpha_params.gaia_id = "alpha_gaia";
alpha_params.user_name = u"alpha_username";
alpha_params.is_consented_primary_account = true;
alpha_params.icon_index = 1;
storage()->AddProfile(std::move(alpha_params));
ProfileAttributesInitParams lima_params;
lima_params.profile_path = GetProfilePath("lime_path");
lima_params.profile_name = u"lima";
lima_params.gaia_id = "lima_gaia";
lima_params.user_name = u"lima_username";
lima_params.is_consented_primary_account = true;
lima_params.icon_index = 1;
storage()->AddProfile(std::move(lima_params));
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(GetProfilePath("alpha_path"));
ASSERT_NE(entry, nullptr);
// Trigger a ProfileAttributesStorage re-sort.
entry->SetLocalProfileName(u"zulu_name",
/*is_default_name=*/false);
EXPECT_EQ(GetProfilePath("alpha_path"), entry->GetPath());
}
TEST_F(ProfileAttributesStorageTest, RemoveOtherProfile) {
AddTestingProfile();
AddTestingProfile();
EXPECT_EQ(2U, storage()->GetNumberOfProfiles());
ProfileAttributesEntry* first_entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(first_entry, nullptr);
ProfileAttributesEntry* second_entry =
storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path1"));
ASSERT_NE(second_entry, nullptr);
EXPECT_EQ(u"testing_profile_name0", first_entry->GetName());
AddCallExpectationsForRemoveProfile(1);
storage()->RemoveProfile(GetProfilePath("testing_profile_path1"));
VerifyAndResetCallExpectations();
second_entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path1"));
ASSERT_EQ(second_entry, nullptr);
EXPECT_EQ(GetProfilePath("testing_profile_path0"), first_entry->GetPath());
EXPECT_EQ(u"testing_profile_name0", first_entry->GetName());
}
TEST_F(ProfileAttributesStorageTest, AccessFromElsewhere) {
AddTestingProfile();
DisableObserver(); // No need to test observers in this test.
ProfileAttributesEntry* first_entry = storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(first_entry, nullptr);
ProfileAttributesEntry* second_entry =
storage()->GetProfileAttributesWithPath(
GetProfilePath("testing_profile_path0"));
ASSERT_NE(second_entry, nullptr);
first_entry->SetLocalProfileName(u"NewName",
/*is_default_name=*/false);
EXPECT_EQ(u"NewName", second_entry->GetName());
EXPECT_EQ(first_entry, second_entry);
second_entry->SetLocalProfileName(u"OtherNewName",
/*is_default_name=*/false);
EXPECT_EQ(u"OtherNewName", first_entry->GetName());
}
TEST_F(ProfileAttributesStorageTest, ChooseAvatarIconIndexForNewProfile) {
size_t total_icon_count = profiles::GetDefaultAvatarIconCount() -
profiles::GetModernAvatarIconStartIndex();
// Run ChooseAvatarIconIndexForNewProfile |num_iterations| times before using
// the final |icon_index| to add a profile. Multiple checks are needed because
// ChooseAvatarIconIndexForNewProfile is non-deterministic.
const int num_iterations = 10;
std::unordered_set<int> used_icon_indices;
for (size_t i = 0; i < total_icon_count; ++i) {
EXPECT_EQ(i, storage()->GetNumberOfProfiles());
size_t icon_index = 0;
for (int iter = 0; iter < num_iterations; ++iter) {
icon_index = storage()->ChooseAvatarIconIndexForNewProfile();
// Icon must not be used.
ASSERT_EQ(0u, used_icon_indices.count(icon_index));
ASSERT_TRUE(profiles::IsModernAvatarIconIndex(icon_index));
}
used_icon_indices.insert(icon_index);
base::FilePath profile_path =
GetProfilePath(base::StringPrintf("testing_profile_path%" PRIuS, i));
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.icon_index = icon_index;
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
}
for (int iter = 0; iter < num_iterations; ++iter) {
// All icons are used up, expect any valid icon.
ASSERT_TRUE(profiles::IsModernAvatarIconIndex(
storage()->ChooseAvatarIconIndexForNewProfile()));
}
}
TEST_F(ProfileAttributesStorageTest, IsSigninRequiredOnInit_NotAuthenticated) {
signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
base::FilePath profile_path = GetProfilePath("testing_profile_path");
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"testing_profile_name";
params.is_consented_primary_account = false;
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
EXPECT_FALSE(entry->IsAuthenticated());
EXPECT_TRUE(entry->IsSigninRequired());
}
TEST_F(ProfileAttributesStorageTest, IsSigninRequiredOnInit_Authenticated) {
signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
base::FilePath profile_path = GetProfilePath("testing_profile_path");
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"testing_profile_name";
params.gaia_id = "testing_profile_gaia";
params.user_name = u"testing_profile_username";
params.is_consented_primary_account = true;
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
EXPECT_TRUE(entry->IsAuthenticated());
EXPECT_FALSE(entry->IsSigninRequired());
}
TEST_F(ProfileAttributesStorageTest,
IsSigninRequiredOnInit_FromPreviousSession) {
base::FilePath profile_path = GetProfilePath("testing_profile_path");
{
signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"testing_profile_name";
params.gaia_id = "testing_profile_gaia";
params.user_name = u"testing_profile_username";
params.is_consented_primary_account = true;
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
// IsSigninRequired() cannot be set as an init parameter. Set it after an
// entry is initialized and reset the storage to reinitialize an entry from
// prefs.
EXPECT_CALL(observer(), OnProfileSigninRequiredChanged(profile_path))
.Times(1);
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
entry->LockForceSigninProfile(true);
VerifyAndResetCallExpectations();
ResetProfileAttributesStorage();
entry = storage()->GetProfileAttributesWithPath(profile_path);
ASSERT_NE(entry, nullptr);
EXPECT_TRUE(entry->IsAuthenticated());
EXPECT_TRUE(entry->IsSigninRequired());
}
// Reset the storage once more after the policy has been disabled and check
// that sign-in is no longer required.
ResetProfileAttributesStorage();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
ASSERT_NE(entry, nullptr);
EXPECT_TRUE(entry->IsAuthenticated());
EXPECT_FALSE(entry->IsSigninRequired());
}
TEST_F(ProfileAttributesStorageTest, ProfileForceSigninLock) {
signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true);
AddTestingProfile();
base::FilePath path = GetProfilePath("testing_profile_path0");
ProfileAttributesEntry* entry = storage()->GetProfileAttributesWithPath(path);
ASSERT_NE(entry, nullptr);
ASSERT_FALSE(entry->IsSigninRequired());
entry->LockForceSigninProfile(false);
ASSERT_FALSE(entry->IsSigninRequired());
EXPECT_CALL(observer(), OnProfileSigninRequiredChanged(path)).Times(1);
entry->LockForceSigninProfile(true);
VerifyAndResetCallExpectations();
ASSERT_TRUE(entry->IsSigninRequired());
EXPECT_CALL(observer(), OnProfileSigninRequiredChanged(path)).Times(1);
entry->LockForceSigninProfile(false);
VerifyAndResetCallExpectations();
ASSERT_FALSE(entry->IsSigninRequired());
}
// Avatar icons not used on Android.
#if !BUILDFLAG(IS_ANDROID)
TEST_F(ProfileAttributesStorageTest, AvatarIconIndex) {
AddTestingProfile();
base::FilePath profile_path = GetProfilePath("testing_profile_path0");
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
ASSERT_NE(entry, nullptr);
ASSERT_EQ(0U, entry->GetAvatarIconIndex());
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
entry->SetAvatarIconIndex(2U);
VerifyAndResetCallExpectations();
ASSERT_EQ(2U, entry->GetAvatarIconIndex());
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
entry->SetAvatarIconIndex(3U);
VerifyAndResetCallExpectations();
ASSERT_EQ(3U, entry->GetAvatarIconIndex());
}
#endif
// High res avatar downloading is only supported on desktop.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(ProfileAttributesStorageTest, DownloadHighResAvatarTest) {
storage()->set_disable_avatar_download_for_testing(false);
const size_t kIconIndex = 0;
base::FilePath icon_path =
profiles::GetPathOfHighResAvatarAtIndex(kIconIndex);
ASSERT_EQ(0U, storage()->GetNumberOfProfiles());
base::FilePath profile_path = GetProfilePath("path_1");
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"name_1";
params.icon_index = kIconIndex;
storage()->AddProfile(std::move(params));
ASSERT_EQ(1U, storage()->GetNumberOfProfiles());
VerifyAndResetCallExpectations();
// Make sure there are no avatars already on disk.
content::RunAllTasksUntilIdle();
ASSERT_FALSE(base::PathExists(icon_path));
// We haven't downloaded any high-res avatars yet.
EXPECT_EQ(0U, storage()->cached_avatar_images_.size());
// After adding a new profile, the download of high-res avatar will be
// triggered. But the downloader won't ever call OnFetchComplete in the test.
EXPECT_EQ(1U, storage()->avatar_images_downloads_in_progress_.size());
// |GetHighResAvater| does not contain a cached avatar, so it should return
// null.
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
ASSERT_NE(entry, nullptr);
EXPECT_FALSE(entry->GetHighResAvatar());
// The previous |GetHighResAvater| starts |LoadAvatarPictureFromPath| async.
// The async code will end up at |OnAvatarPictureLoaded| storing an empty
// image in the storage.
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
content::RunAllTasksUntilIdle();
VerifyAndResetCallExpectations();
std::string icon_filename =
profiles::GetDefaultAvatarIconFileNameAtIndex(kIconIndex);
EXPECT_EQ(1U, storage()->cached_avatar_images_.size());
EXPECT_TRUE(storage()->cached_avatar_images_[icon_filename].IsEmpty());
// Simulate downloading a high-res avatar.
ProfileAvatarDownloader avatar_downloader(
kIconIndex,
base::BindOnce(&ProfileAttributesStorage::SaveAvatarImageAtPathNoCallback,
base::Unretained(storage()), entry->GetPath()));
// Put a real bitmap into "bitmap": a 2x2 bitmap of green 32 bit pixels.
SkBitmap bitmap;
bitmap.allocN32Pixels(2, 2);
bitmap.eraseColor(SK_ColorGREEN);
avatar_downloader.OnFetchComplete(GURL("http://www.google.com/avatar.png"),
&bitmap);
// Now the download should not be in progress anymore.
EXPECT_EQ(0U, storage()->avatar_images_downloads_in_progress_.size());
// The image should have been cached.
EXPECT_EQ(1U, storage()->cached_avatar_images_.size());
EXPECT_FALSE(storage()->cached_avatar_images_[icon_filename].IsEmpty());
EXPECT_EQ(&storage()->cached_avatar_images_[icon_filename],
entry->GetHighResAvatar());
// Since we are not using GAIA image, |GetAvatarIcon| should return the same
// image as |GetHighResAvatar| in desktop. Since it returns a copy, the
// backing object needs to get checked.
const gfx::ImageSkia* avatar_icon = entry->GetAvatarIcon().ToImageSkia();
const gfx::ImageSkia* cached_icon =
storage()->cached_avatar_images_[icon_filename].ToImageSkia();
EXPECT_TRUE(avatar_icon->BackedBySameObjectAs(*cached_icon));
// Finish the async calls that save the image to the disk.
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
content::RunAllTasksUntilIdle();
VerifyAndResetCallExpectations();
// Clean up.
EXPECT_NE(std::string::npos, icon_path.MaybeAsASCII().find(icon_filename));
ASSERT_TRUE(base::PathExists(icon_path));
EXPECT_TRUE(base::DeleteFile(icon_path));
EXPECT_FALSE(base::PathExists(icon_path));
}
TEST_F(ProfileAttributesStorageTest, NothingToDownloadHighResAvatarTest) {
storage()->set_disable_avatar_download_for_testing(false);
const size_t kIconIndex = profiles::GetPlaceholderAvatarIndex();
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
base::FilePath profile_path = GetProfilePath("path_1");
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"name_1";
params.icon_index = kIconIndex;
storage()->AddProfile(std::move(params));
EXPECT_EQ(1U, storage()->GetNumberOfProfiles());
content::RunAllTasksUntilIdle();
// We haven't tried to download any high-res avatars as the specified icon is
// just a placeholder.
EXPECT_EQ(0U, storage()->cached_avatar_images_.size());
EXPECT_EQ(0U, storage()->avatar_images_downloads_in_progress_.size());
}
TEST_F(ProfileAttributesStorageTest, LoadAvatarFromDiskTest) {
const size_t kIconIndex = 0;
base::FilePath icon_path =
profiles::GetPathOfHighResAvatarAtIndex(kIconIndex);
// Create the avatar on the disk, which is a valid 1x1 transparent png.
base::FilePath dir = icon_path.DirName();
ASSERT_FALSE(base::DirectoryExists(dir));
ASSERT_TRUE(base::CreateDirectory(dir));
ASSERT_FALSE(base::PathExists(icon_path));
const char bitmap[] =
"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
"\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x37\x6E\xF9"
"\x24\x00\x00\x00\x0A\x49\x44\x41\x54\x08\x1D\x63\x60\x00\x00\x00"
"\x02\x00\x01\xCF\xC8\x35\xE5\x00\x00\x00\x00\x49\x45\x4E\x44\xAE"
"\x42\x60\x82";
base::WriteFile(icon_path, bitmap, sizeof(bitmap));
ASSERT_TRUE(base::PathExists(icon_path));
// Add a new profile.
ASSERT_EQ(0U, storage()->GetNumberOfProfiles());
base::FilePath profile_path = GetProfilePath("path_1");
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"name_1";
params.icon_index = kIconIndex;
storage()->AddProfile(std::move(params));
EXPECT_EQ(1U, storage()->GetNumberOfProfiles());
VerifyAndResetCallExpectations();
// Load the avatar image.
storage()->set_disable_avatar_download_for_testing(false);
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
ASSERT_NE(entry, nullptr);
ASSERT_FALSE(entry->IsUsingGAIAPicture());
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
entry->GetAvatarIcon();
// Wait until the avatar image finish loading.
content::RunAllTasksUntilIdle();
VerifyAndResetCallExpectations();
// Clean up.
EXPECT_TRUE(base::DeleteFile(icon_path));
EXPECT_FALSE(base::PathExists(icon_path));
}
#endif
TEST_F(ProfileAttributesStorageTest, ProfilesState_ActiveMultiProfile) {
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
for (size_t i = 0; i < 5; ++i)
AddTestingProfile();
EXPECT_EQ(5U, storage()->GetNumberOfProfiles());
std::vector<ProfileAttributesEntry*> entries =
storage()->GetAllProfilesAttributes();
entries[0]->SetActiveTimeToNow();
entries[1]->SetActiveTimeToNow();
base::HistogramTester histogram_tester;
storage()->RecordProfilesState();
// There are 5 profiles all together.
histogram_tester.ExpectTotalCount("Profile.State.Avatar_All", 5);
histogram_tester.ExpectTotalCount("Profile.State.Avatar_ActiveMultiProfile",
5);
// Other user segments get 0 records.
histogram_tester.ExpectTotalCount("Profile.State.Avatar_SingleProfile", 0);
histogram_tester.ExpectTotalCount("Profile.State.Avatar_LatentMultiProfile",
0);
histogram_tester.ExpectTotalCount(
"Profile.State.Avatar_LatentMultiProfileActive", 0);
histogram_tester.ExpectTotalCount(
"Profile.State.Avatar_LatentMultiProfileOthers", 0);
}
// On Android (at least on KitKat), all profiles are considered active (because
// ActiveTime is not set in production). Thus, these test does not work.
#if !BUILDFLAG(IS_ANDROID)
TEST_F(ProfileAttributesStorageTest, ProfilesState_LatentMultiProfile) {
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
for (size_t i = 0; i < 5; ++i)
AddTestingProfile();
EXPECT_EQ(5U, storage()->GetNumberOfProfiles());
std::vector<ProfileAttributesEntry*> entries =
storage()->GetAllProfilesAttributes();
entries[0]->SetActiveTimeToNow();
base::HistogramTester histogram_tester;
storage()->RecordProfilesState();
// There are 5 profiles all together.
histogram_tester.ExpectTotalCount("Profile.State.Name_All", 5);
histogram_tester.ExpectTotalCount("Profile.State.Name_LatentMultiProfile", 5);
histogram_tester.ExpectTotalCount(
"Profile.State.Name_LatentMultiProfileActive", 1);
histogram_tester.ExpectTotalCount(
"Profile.State.Name_LatentMultiProfileOthers", 4);
// Other user segments get 0 records.
histogram_tester.ExpectTotalCount("Profile.State.Name_SingleProfile", 0);
histogram_tester.ExpectTotalCount("Profile.State.Name_ActiveMultiProfile", 0);
}
#endif
TEST_F(ProfileAttributesStorageTest, ProfilesState_SingleProfile) {
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
AddTestingProfile();
EXPECT_EQ(1U, storage()->GetNumberOfProfiles());
base::HistogramTester histogram_tester;
storage()->RecordProfilesState();
// There is 1 profile all together.
histogram_tester.ExpectTotalCount("Profile.State.LastUsed_All", 1);
histogram_tester.ExpectTotalCount("Profile.State.LastUsed_SingleProfile", 1);
// Other user segments get 0 records.
histogram_tester.ExpectTotalCount("Profile.State.LastUsed_ActiveMultiProfile",
0);
histogram_tester.ExpectTotalCount("Profile.State.LastUsed_LatentMultiProfile",
0);
histogram_tester.ExpectTotalCount(
"Profile.State.LastUsed_LatentMultiProfileActive", 0);
histogram_tester.ExpectTotalCount(
"Profile.State.LastUsed_LatentMultiProfileOthers", 0);
}
// Themes aren't used on Android
#if !BUILDFLAG(IS_ANDROID)
TEST_F(ProfileAttributesStorageTest, ProfileThemeColors) {
ui::NativeTheme::GetInstanceForNativeUi()->set_use_dark_colors(false);
AddTestingProfile();
base::FilePath profile_path = GetProfilePath("testing_profile_path0");
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
ASSERT_NE(entry, nullptr);
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
entry->SetAvatarIconIndex(profiles::GetPlaceholderAvatarIndex());
VerifyAndResetCallExpectations();
ProfileThemeColors light_colors = GetDefaultProfileThemeColors();
EXPECT_EQ(entry->GetProfileThemeColors(), light_colors);
auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
native_theme->set_use_dark_colors(true);
EXPECT_EQ(entry->GetProfileThemeColors(), GetDefaultProfileThemeColors());
EXPECT_NE(entry->GetProfileThemeColors(), light_colors);
ProfileThemeColors colors = {SK_ColorTRANSPARENT, SK_ColorBLACK,
SK_ColorWHITE};
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
EXPECT_CALL(observer(), OnProfileThemeColorsChanged(profile_path)).Times(1);
entry->SetProfileThemeColors(colors);
EXPECT_EQ(entry->GetProfileThemeColors(), colors);
VerifyAndResetCallExpectations();
// Colors shouldn't change after switching back to the light mode.
native_theme->set_use_dark_colors(false);
EXPECT_EQ(entry->GetProfileThemeColors(), colors);
// absl::nullopt resets the colors to default.
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
EXPECT_CALL(observer(), OnProfileThemeColorsChanged(profile_path)).Times(1);
entry->SetProfileThemeColors(absl::nullopt);
EXPECT_EQ(entry->GetProfileThemeColors(), GetDefaultProfileThemeColors());
VerifyAndResetCallExpectations();
}
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(ProfileAttributesStorageTest, GAIAPicture) {
const int kDefaultAvatarIndex = 0;
const int kOtherAvatarIndex = 1;
const int kGaiaPictureSize = 256; // Standard size of a Gaia account picture.
base::FilePath profile_path = GetProfilePath("path_1");
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"name_1";
params.icon_index = kDefaultAvatarIndex;
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
// Sanity check.
EXPECT_EQ(nullptr, entry->GetGAIAPicture());
EXPECT_FALSE(entry->IsUsingGAIAPicture());
// The profile icon should be the default one.
EXPECT_TRUE(entry->IsUsingDefaultAvatar());
size_t default_avatar_id =
GetDefaultAvatarIconResourceIDAtIndex(kDefaultAvatarIndex);
const gfx::Image& default_avatar_image(
ui::ResourceBundle::GetSharedInstance().GetImageNamed(default_avatar_id));
EXPECT_TRUE(
gfx::test::AreImagesEqual(default_avatar_image, entry->GetAvatarIcon()));
// Set GAIA picture.
gfx::Image gaia_image(
gfx::test::CreateImage(kGaiaPictureSize, kGaiaPictureSize));
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
entry->SetGAIAPicture("GAIA_IMAGE_URL_WITH_SIZE_1", gaia_image);
VerifyAndResetCallExpectations();
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, *entry->GetGAIAPicture()));
// Since we're still using the default avatar, the GAIA image should be
// preferred over the generic avatar image.
EXPECT_TRUE(entry->IsUsingDefaultAvatar());
EXPECT_TRUE(entry->IsUsingGAIAPicture());
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, entry->GetAvatarIcon()));
// Set a non-default avatar. This should be preferred over the GAIA image.
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
entry->SetAvatarIconIndex(kOtherAvatarIndex);
entry->SetIsUsingDefaultAvatar(false);
VerifyAndResetCallExpectations();
EXPECT_FALSE(entry->IsUsingDefaultAvatar());
EXPECT_FALSE(entry->IsUsingGAIAPicture());
// Avatar icons not used on Android.
#if !BUILDFLAG(IS_ANDROID)
size_t other_avatar_id =
GetDefaultAvatarIconResourceIDAtIndex(kOtherAvatarIndex);
const gfx::Image& other_avatar_image(
ui::ResourceBundle::GetSharedInstance().GetImageNamed(other_avatar_id));
EXPECT_TRUE(
gfx::test::AreImagesEqual(other_avatar_image, entry->GetAvatarIcon()));
#endif // !BUILDFLAG(IS_ANDROID)
// Explicitly setting the GAIA picture should make it preferred again.
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
entry->SetIsUsingGAIAPicture(true);
VerifyAndResetCallExpectations();
EXPECT_TRUE(entry->IsUsingGAIAPicture());
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, *entry->GetGAIAPicture()));
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, entry->GetAvatarIcon()));
// Clearing the IsUsingGAIAPicture flag should result in the generic image
// being used again.
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
entry->SetIsUsingGAIAPicture(false);
VerifyAndResetCallExpectations();
EXPECT_FALSE(entry->IsUsingGAIAPicture());
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, *entry->GetGAIAPicture()));
#if !BUILDFLAG(IS_ANDROID)
EXPECT_TRUE(
gfx::test::AreImagesEqual(other_avatar_image, entry->GetAvatarIcon()));
#endif
}
TEST_F(ProfileAttributesStorageTest, PersistGAIAPicture) {
base::FilePath profile_path = GetProfilePath("path_1");
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"name_1";
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
gfx::Image gaia_image(gfx::test::CreateImage());
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
entry->SetGAIAPicture("GAIA_IMAGE_URL_WITH_SIZE_0", gaia_image);
// Make sure everything has completed, and the file has been written to disk.
content::RunAllTasksUntilIdle();
VerifyAndResetCallExpectations();
EXPECT_EQ(entry->GetLastDownloadedGAIAPictureUrlWithSize(),
"GAIA_IMAGE_URL_WITH_SIZE_0");
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, *entry->GetGAIAPicture()));
ResetProfileAttributesStorage();
// Try to get the GAIA picture. This should return NULL until the read from
// disk is done.
entry = storage()->GetProfileAttributesWithPath(profile_path);
EXPECT_EQ(nullptr, entry->GetGAIAPicture());
EXPECT_EQ(entry->GetLastDownloadedGAIAPictureUrlWithSize(),
"GAIA_IMAGE_URL_WITH_SIZE_0");
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
content::RunAllTasksUntilIdle();
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, *entry->GetGAIAPicture()));
}
TEST_F(ProfileAttributesStorageTest, EmptyGAIAInfo) {
std::u16string profile_name = u"name_1";
size_t id = GetDefaultAvatarIconResourceIDAtIndex(0);
const gfx::Image& profile_image(
ui::ResourceBundle::GetSharedInstance().GetImageNamed(id));
base::FilePath profile_path = GetProfilePath("path_1");
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = profile_name;
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
gfx::Image gaia_image(gfx::test::CreateImage());
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
entry->SetGAIAPicture("GAIA_IMAGE_URL_WITH_SIZE_0", gaia_image);
// Make sure everything has completed, and the file has been written to disk.
content::RunAllTasksUntilIdle();
VerifyAndResetCallExpectations();
// Set empty GAIA info.
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(2);
entry->SetGAIAName(std::u16string());
entry->SetGAIAPicture(std::string(), gfx::Image());
entry->SetIsUsingGAIAPicture(true);
EXPECT_TRUE(entry->GetLastDownloadedGAIAPictureUrlWithSize().empty());
// Verify that the profile name and picture are not empty.
EXPECT_EQ(profile_name, entry->GetName());
EXPECT_TRUE(gfx::test::AreImagesEqual(profile_image, entry->GetAvatarIcon()));
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(ProfileAttributesStorageTest, GetGaiaImageForAvatarMenu) {
storage()->set_disable_avatar_download_for_testing(false);
base::FilePath profile_path = GetProfilePath("path_1");
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = u"name_1";
EXPECT_CALL(observer(), OnProfileAdded(profile_path)).Times(1);
storage()->AddProfile(std::move(params));
VerifyAndResetCallExpectations();
ProfileAttributesEntry* entry =
storage()->GetProfileAttributesWithPath(profile_path);
gfx::Image gaia_image(gfx::test::CreateImage());
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
entry->SetGAIAPicture("GAIA_IMAGE_URL_WITH_SIZE_0", gaia_image);
// Make sure everything has completed, and the file has been written to disk.
content::RunAllTasksUntilIdle();
VerifyAndResetCallExpectations();
// Make sure this profile is using GAIA picture.
EXPECT_TRUE(entry->IsUsingGAIAPicture());
ResetProfileAttributesStorage();
entry = storage()->GetProfileAttributesWithPath(profile_path);
// We need to explicitly set the GAIA usage flag after resetting the storage.
EXPECT_CALL(observer(), OnProfileAvatarChanged(profile_path)).Times(1);
EXPECT_CALL(observer(), OnProfileHighResAvatarLoaded(profile_path)).Times(1);
entry->SetIsUsingGAIAPicture(true);
EXPECT_TRUE(entry->IsUsingGAIAPicture());
gfx::Image image_loaded;
// Try to get the GAIA image. For the first time, it triggers an async image
// load from disk. The load status indicates the image is still being loaded.
constexpr int kArbitraryPreferredSize = 96;
EXPECT_EQ(AvatarMenu::ImageLoadStatus::LOADING,
AvatarMenu::GetImageForMenuButton(profile_path, &image_loaded,
kArbitraryPreferredSize));
EXPECT_FALSE(gfx::test::AreImagesEqual(gaia_image, image_loaded));
// Wait until the async image load finishes.
content::RunAllTasksUntilIdle();
// Since the GAIA image is loaded now, we can get it this time.
EXPECT_EQ(AvatarMenu::ImageLoadStatus::LOADED,
AvatarMenu::GetImageForMenuButton(profile_path, &image_loaded,
kArbitraryPreferredSize));
EXPECT_TRUE(gfx::test::AreImagesEqual(gaia_image, image_loaded));
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(ProfileAttributesStorageTest,
MigrateLegacyProfileNamesAndRecomputeIfNeeded) {
DisableObserver(); // This test doesn't test observers.
EXPECT_EQ(0U, storage()->GetNumberOfProfiles());
// Mimic a pre-existing Directory with profiles that has legacy profile
// names.
const struct {
const char* profile_path;
const char* profile_name;
bool is_using_default_name;
} kTestCases[] = {
{"path_1", "Default Profile", true}, {"path_2", "First user", true},
{"path_3", "Lemonade", true}, {"path_4", "Batman", true},
{"path_5", "Batman", false}, {"path_6", "Person 2", true},
{"path_7", "Person 3", true}, {"path_8", "Person 1", true},
{"path_9", "Person 2", true}, {"path_10", "Person 1", true},
{"path_11", "Smith", false}, {"path_12", "Person 2", true}};
const size_t kNumProfiles = std::size(kTestCases);
ProfileAttributesEntry* entry = nullptr;
for (size_t i = 0; i < kNumProfiles; ++i) {
base::FilePath profile_path = GetProfilePath(kTestCases[i].profile_path);
ProfileAttributesInitParams params;
params.profile_path = profile_path;
params.profile_name = base::ASCIIToUTF16(kTestCases[i].profile_name);
params.icon_index = i;
storage()->AddProfile(std::move(params));
entry = storage()->GetProfileAttributesWithPath(profile_path);
EXPECT_TRUE(entry);
entry->SetLocalProfileName(entry->GetLocalProfileName(),
kTestCases[i].is_using_default_name);
}
EXPECT_EQ(kNumProfiles, storage()->GetNumberOfProfiles());
ResetProfileAttributesStorage();
ProfileAttributesStorage::SetLegacyProfileMigrationForTesting(true);
storage();
ProfileAttributesStorage::SetLegacyProfileMigrationForTesting(false);
entry = storage()->GetProfileAttributesWithPath(
GetProfilePath(kTestCases[4].profile_path));
EXPECT_EQ(base::ASCIIToUTF16(kTestCases[4].profile_name), entry->GetName());
entry = storage()->GetProfileAttributesWithPath(
GetProfilePath(kTestCases[10].profile_path));
EXPECT_EQ(base::ASCIIToUTF16(kTestCases[10].profile_name), entry->GetName());
// Legacy profile names like "Default Profile" and "First user" should be
// migrated to "Person %n" type names, i.e. any permutation of "Person %n".
std::set<std::u16string> expected_profile_names{
u"Person 1", u"Person 2", u"Person 3", u"Person 4", u"Person 5",
u"Person 6", u"Person 7", u"Person 8", u"Person 9", u"Person 10"};
const char* profile_paths[] = {
kTestCases[0].profile_path, kTestCases[1].profile_path,
kTestCases[2].profile_path, kTestCases[3].profile_path,
kTestCases[5].profile_path, kTestCases[6].profile_path,
kTestCases[7].profile_path, kTestCases[8].profile_path,
kTestCases[9].profile_path, kTestCases[11].profile_path};
std::set<std::u16string> actual_profile_names;
for (auto* path : profile_paths) {
entry = storage()->GetProfileAttributesWithPath(GetProfilePath(path));
actual_profile_names.insert(entry->GetName());
}
EXPECT_EQ(actual_profile_names, expected_profile_names);
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)