| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/lacros/account_manager/account_profile_mapper.h" |
| |
| #include <algorithm> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/scoped_observation.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_attributes_entry.h" |
| #include "chrome/browser/profiles/profile_attributes_init_params.h" |
| #include "chrome/browser/profiles/profile_attributes_storage.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/test/base/profile_waiter.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "components/account_manager_core/account.h" |
| #include "components/account_manager_core/account_manager_facade.h" |
| #include "components/account_manager_core/account_upsertion_result.h" |
| #include "components/account_manager_core/mock_account_manager_facade.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "google_apis/gaia/google_service_auth_error.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using account_manager::Account; |
| using account_manager::AccountKey; |
| using account_manager::AccountManagerFacade; |
| using account_manager::AccountUpsertionResult; |
| using testing::Field; |
| |
| namespace { |
| |
| const char kLacrosAccountIdsPref[] = |
| "profile.account_manager_lacros_account_ids"; |
| |
| constexpr account_manager::AccountType kGaiaType = |
| account_manager::AccountType::kGaia; |
| |
| // Map from profile path to a set of GaiaIds. |
| using AccountMapping = |
| base::flat_map<base::FilePath, base::flat_set<std::string>>; |
| |
| // Map from profile path to a vector of account error updates. |
| using AccountErrorMapping = |
| base::flat_map<base::FilePath, |
| std::vector<std::pair<std::string, GoogleServiceAuthError>>>; |
| |
| using MockAddAccountCallback = base::MockOnceCallback<void( |
| const std::optional<AccountProfileMapper::AddAccountResult>&)>; |
| |
| class MockAccountProfileMapperObserver : public AccountProfileMapper::Observer { |
| public: |
| MockAccountProfileMapperObserver() = default; |
| ~MockAccountProfileMapperObserver() override = default; |
| |
| MOCK_METHOD(void, |
| OnAccountUpserted, |
| (const base::FilePath&, const Account&), |
| (override)); |
| MOCK_METHOD(void, |
| OnAccountRemoved, |
| (const base::FilePath&, const Account&), |
| (override)); |
| MOCK_METHOD(void, |
| OnAuthErrorChanged, |
| (const base::FilePath&, |
| const account_manager::AccountKey&, |
| const GoogleServiceAuthError&), |
| (override)); |
| }; |
| |
| class ProfileAttributesStorageTestObserver |
| : public ProfileAttributesStorage::Observer { |
| public: |
| explicit ProfileAttributesStorageTestObserver( |
| ProfileAttributesStorage* storage) |
| : storage_(storage) {} |
| |
| void WaitForProfileBeingDeleted(const base::FilePath& profile_path) { |
| ProfileAttributesEntry* entry = |
| storage_->GetProfileAttributesWithPath(profile_path); |
| // Return immediately if the profile entry doesn't exist. |
| if (!entry) |
| return; |
| |
| storage_observation_.Observe(storage_.get()); |
| profile_path_ = profile_path; |
| run_loop_.Run(); |
| } |
| |
| void OnProfileWasRemoved(const base::FilePath& removed_profile_path, |
| const std::u16string& profile_name) override { |
| if (removed_profile_path != profile_path_) |
| return; |
| |
| storage_observation_.Reset(); |
| run_loop_.Quit(); |
| } |
| |
| private: |
| raw_ptr<ProfileAttributesStorage> storage_; |
| base::ScopedObservation<ProfileAttributesStorage, |
| ProfileAttributesStorage::Observer> |
| storage_observation_{this}; |
| base::FilePath profile_path_; |
| base::RunLoop run_loop_; |
| }; |
| |
| MATCHER_P(AddAccountResultEqual, |
| other, |
| "optional<AddAccountResult> equality matcher") { |
| if (arg == std::nullopt && other == std::nullopt) { |
| return true; |
| } |
| return arg->profile_path == other->profile_path && |
| arg->account.key == other->account.key && |
| arg->account.raw_email == other->account.raw_email; |
| } |
| |
| MATCHER_P(AccountEqual, other, "Account equality matcher") { |
| return arg.key == other.key && arg.raw_email == other.raw_email; |
| } |
| |
| // Synthetizes a non-Gaia `Account` from an id. |
| Account NonGaiaAccountFromID(const std::string& id) { |
| AccountKey key(id, account_manager::AccountType::kActiveDirectory); |
| return {key, id + std::string("@example.com")}; |
| } |
| |
| // Synthetizes a `Account` from a Gaia ID, with a dummy email. |
| Account AccountFromGaiaID(const std::string& gaia_id) { |
| AccountKey key(gaia_id, kGaiaType); |
| return {key, gaia_id + std::string("@gmail.com")}; |
| } |
| |
| // Similar to `AccountFromGaiaID()`, but operates on vectors. |
| std::vector<Account> AccountsFromGaiaIDs( |
| const std::vector<std::string>& gaia_ids) { |
| std::vector<Account> accounts; |
| for (const auto& id : gaia_ids) |
| accounts.push_back(AccountFromGaiaID(id)); |
| return accounts; |
| } |
| |
| } // namespace |
| |
| class AccountProfileMapperTest : public testing::Test { |
| public: |
| AccountProfileMapperTest() |
| : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) { |
| CHECK(testing_profile_manager_.SetUp()); |
| main_path_ = GetProfilePath("Default"); |
| ON_CALL(mock_facade_, GetPersistentErrorForAccount) |
| .WillByDefault( |
| [](const AccountKey&, |
| base::OnceCallback<void(const GoogleServiceAuthError&)> |
| callback) { |
| std::move(callback).Run(GoogleServiceAuthError::AuthErrorNone()); |
| }); |
| } |
| |
| ~AccountProfileMapperTest() override { |
| EXPECT_TRUE(facade_get_accounts_completion_callbacks_.empty()) |
| << "The test has " << facade_get_accounts_completion_callbacks_.size() |
| << " unsatisfied GetAccounts() callbacks"; |
| } |
| |
| TestingProfileManager* testing_profile_manager() { |
| return &testing_profile_manager_; |
| } |
| |
| ProfileAttributesStorage* attributes_storage() { |
| return &testing_profile_manager_.profile_manager() |
| ->GetProfileAttributesStorage(); |
| } |
| |
| account_manager::MockAccountManagerFacade* mock_facade() { |
| return &mock_facade_; |
| } |
| |
| TestingPrefServiceSimple* local_state() { |
| return testing_profile_manager_.local_state()->Get(); |
| } |
| |
| const base::FilePath& main_path() { return main_path_; } |
| |
| base::FilePath GetProfilePath(const std::string& name) { |
| return testing_profile_manager_.profiles_dir().AppendASCII(name); |
| } |
| |
| // Helper function, similar to TestMapperUpdate(), but assumes all accounts |
| // are Gaia. |
| void TestMapperUpdateGaia( |
| AccountProfileMapper* mapper, |
| const std::vector<std::string>& gaia_accounts_in_facade, |
| const AccountMapping& expected_accounts_upserted, |
| const AccountMapping& expected_accounts_removed, |
| const AccountMapping& expected_accounts_in_prefs) { |
| TestMapperUpdate(mapper, AccountsFromGaiaIDs(gaia_accounts_in_facade), |
| expected_accounts_upserted, expected_accounts_removed, |
| expected_accounts_in_prefs); |
| } |
| |
| // Triggers an update of the accounts and checks observer calls, and the end |
| // state of the prefs. |
| void TestMapperUpdate(AccountProfileMapper* mapper, |
| const std::vector<Account>& accounts_in_facade, |
| const AccountMapping& expected_accounts_upserted, |
| const AccountMapping& expected_accounts_removed, |
| const AccountMapping& expected_accounts_in_prefs) { |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, |
| AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| ExpectOnAccountUpserted(&mock_observer, expected_accounts_upserted); |
| ExpectOnAccountRemoved(&mock_observer, expected_accounts_removed); |
| |
| // Trigger a `GetAccounts()` call. |
| ExpectFacadeGetAccountsCalled(); |
| mapper->OnAccountUpserted(AccountFromGaiaID("Dummy")); |
| CompleteFacadeGetAccounts(accounts_in_facade); |
| |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| VerifyAccountsInPrefs(expected_accounts_in_prefs); |
| } |
| |
| AccountProfileMapper* CreateMapperNonInitialized( |
| const AccountMapping& accounts) { |
| ExpectFacadeGetAccountsCalled(); |
| testing_profile_manager_.SetAccountProfileMapper( |
| std::make_unique<AccountProfileMapper>( |
| mock_facade(), attributes_storage(), local_state())); |
| CreateProfilesAndSetAccountsInPrefs(accounts); |
| VerifyAccountsInPrefs(accounts); |
| return testing_profile_manager_.profile_manager() |
| ->GetAccountProfileMapper(); |
| } |
| |
| AccountProfileMapper* CreateMapper(const AccountMapping& accounts) { |
| AccountProfileMapper* mapper = CreateMapperNonInitialized(accounts); |
| // Initialize the mapper by completing the `GetAccounts()` call on the |
| // facade. |
| std::vector<std::string> accounts_in_facade; |
| for (const auto& path_accounts_pair : accounts) { |
| for (const std::string& id : path_accounts_pair.second) { |
| accounts_in_facade.push_back(id); |
| } |
| } |
| CompleteFacadeGetAccountsGaia(accounts_in_facade); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| return mapper; |
| } |
| |
| // Setup gMock expectations for `OnAccountUpserted()` calls. |
| void ExpectOnAccountUpserted(MockAccountProfileMapperObserver* mock_observer, |
| const AccountMapping& accounts_map) { |
| if (accounts_map.empty()) { |
| EXPECT_CALL(*mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| return; |
| } |
| for (const auto& path_accounts_pair : accounts_map) { |
| const base::FilePath profile_path = path_accounts_pair.first; |
| for (const std::string& gaia_id : path_accounts_pair.second) { |
| AccountKey key = {gaia_id, kGaiaType}; |
| EXPECT_CALL(*mock_observer, |
| OnAccountUpserted(profile_path, Field(&Account::key, key))); |
| } |
| } |
| } |
| |
| // Setup gMock expectations for `OnAccountRemoved()` calls. |
| void ExpectOnAccountRemoved(MockAccountProfileMapperObserver* mock_observer, |
| const AccountMapping& accounts_map) { |
| if (accounts_map.empty()) { |
| EXPECT_CALL(*mock_observer, OnAccountRemoved(testing::_, testing::_)) |
| .Times(0); |
| return; |
| } |
| for (const auto& path_accounts_pair : accounts_map) { |
| const base::FilePath profile_path = path_accounts_pair.first; |
| for (const std::string& gaia_id : path_accounts_pair.second) { |
| AccountKey key = {gaia_id, kGaiaType}; |
| EXPECT_CALL(*mock_observer, |
| OnAccountRemoved(profile_path, Field(&Account::key, key))); |
| } |
| } |
| } |
| |
| // Setup gMock expectations for `OnAuthErrorChanged()` calls. |
| void ExpectOnAuthErrorChanged(MockAccountProfileMapperObserver* mock_observer, |
| const AccountErrorMapping& account_errors_map) { |
| if (account_errors_map.empty()) { |
| EXPECT_CALL(*mock_observer, |
| OnAuthErrorChanged(testing::_, testing::_, testing::_)) |
| .Times(0); |
| return; |
| } |
| for (const auto& path_account_errors : account_errors_map) { |
| const base::FilePath profile_path = path_account_errors.first; |
| |
| for (const std::pair<std::string, GoogleServiceAuthError>& account_error : |
| path_account_errors.second) { |
| const AccountKey account_key{account_error.first, kGaiaType}; |
| const GoogleServiceAuthError error = account_error.second; |
| EXPECT_CALL(*mock_observer, |
| OnAuthErrorChanged(profile_path, account_key, error)); |
| } |
| } |
| } |
| |
| // Checks that the `ProfileAttributesStorage` matches `accounts_map`. |
| // Tests should normally use `VerifyAccountsInPrefs()` instead to verify local |
| // state as well. |
| void VerifyAccountsInStorage(const AccountMapping& accounts_map) { |
| auto entries = attributes_storage()->GetAllProfilesAttributes(); |
| // Count profiles in the map. |
| size_t profiles_in_map = accounts_map.size(); |
| if (accounts_map.contains(base::FilePath())) |
| --profiles_in_map; // Unassigned accounts. |
| EXPECT_EQ(entries.size(), profiles_in_map); |
| bool main_profile_found = false; |
| for (const ProfileAttributesEntry* entry : entries) { |
| const base::FilePath path = entry->GetPath(); |
| if (Profile::IsMainProfilePath(path)) { |
| EXPECT_FALSE(main_profile_found) << "Duplicate main profile: " << path; |
| main_profile_found = true; |
| } |
| if (accounts_map.contains(path)) { |
| EXPECT_EQ(entry->GetGaiaIds(), accounts_map.at(path)) |
| << "Accounts don't match"; |
| } else { |
| ADD_FAILURE() << "Profile \"" << path << "\" not found"; |
| } |
| } |
| EXPECT_TRUE(main_profile_found) << "No main profile"; |
| } |
| |
| // Checks that the `ProfileAttributesStorage` and the list of accounts in |
| // local state match `accounts_map`. |
| void VerifyAccountsInPrefs(const AccountMapping& accounts_map) { |
| VerifyAccountsInStorage(accounts_map); |
| |
| // Check accounts in local state. |
| base::flat_set<std::string> accounts_set; |
| for (const auto& path_and_accounts_pair : accounts_map) { |
| const auto& profile_accounts_set = path_and_accounts_pair.second; |
| accounts_set.insert(profile_accounts_set.begin(), |
| profile_accounts_set.end()); |
| } |
| EXPECT_EQ(GetLacrosAccountsFromLocalState(), accounts_set); |
| } |
| |
| // Sets an expectation that `GetAccounts()` is called on the facade at least |
| // once, and stores the callbacks for later use in |
| // `CompleteFacadeGetAccounts()`. |
| // `CompleteFacadeGetAccounts()` must be called the exact same number of times |
| // as `GetAccounts()`. |
| void ExpectFacadeGetAccountsCalled() { |
| EXPECT_CALL(mock_facade_, GetAccounts(testing::_)) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly([this](base::OnceCallback<void( |
| const std::vector<Account>&)> callback) { |
| facade_get_accounts_completion_callbacks_.push(std::move(callback)); |
| }); |
| } |
| |
| // Sets an expectation that `ShowAddAccountDialog()` is called on the facade, |
| // and immediately returns with a new account. |
| void ExpectFacadeShowAddAccountDialogCalled( |
| AccountManagerFacade::AccountAdditionSource source, |
| const std::optional<Account>& new_account) { |
| EXPECT_CALL(mock_facade_, ShowAddAccountDialog(source, testing::_)) |
| .WillOnce([new_account]( |
| AccountManagerFacade::AccountAdditionSource, |
| base::OnceCallback<void(const AccountUpsertionResult&)> |
| callback) { |
| std::move(callback).Run( |
| new_account.has_value() |
| ? AccountUpsertionResult::FromAccount(new_account.value()) |
| : AccountUpsertionResult::FromStatus( |
| AccountUpsertionResult::Status::kCancelledByUser)); |
| }); |
| } |
| |
| void CompleteFacadeGetAccountsGaia(const std::vector<std::string>& gaia_ids) { |
| CompleteFacadeGetAccounts(AccountsFromGaiaIDs(gaia_ids)); |
| } |
| |
| void CompleteFacadeGetAccounts(const std::vector<Account>& accounts) { |
| ASSERT_FALSE(facade_get_accounts_completion_callbacks_.empty()); |
| auto callback = |
| std::move(facade_get_accounts_completion_callbacks_.front()); |
| facade_get_accounts_completion_callbacks_.pop(); |
| std::move(callback).Run(accounts); |
| } |
| |
| // Creates profiles that are listed in `accounts_map` and sets the accounts in |
| // `ProfileAttributesStorage` and in local state. |
| // `accounts_map` is a map from profile path to a vector of GaiaIds. One of |
| // the profiles must be the main profile. |
| void CreateProfilesAndSetAccountsInPrefs(const AccountMapping& accounts_map) { |
| // Clear all profiles. |
| testing_profile_manager_.DeleteAllTestingProfiles(); |
| // Create new profiles. |
| for (const auto& path_accounts_pair : accounts_map) { |
| const base::FilePath path = path_accounts_pair.first; |
| if (path.empty()) |
| continue; // Account is unassigned. |
| testing_profile_manager_.CreateTestingProfile( |
| path.BaseName().MaybeAsASCII()); |
| } |
| SetAccountsInStorage(accounts_map); |
| base::flat_set<std::string> accounts_set; |
| for (const auto& path_and_accounts_pair : accounts_map) { |
| const auto& profile_accounts_set = path_and_accounts_pair.second; |
| accounts_set.insert(profile_accounts_set.begin(), |
| profile_accounts_set.end()); |
| } |
| SetLacrosAccountsInLocalState(accounts_set); |
| } |
| |
| // Imports accounts from `accounts_map` to `ProfileAttributesStorage`. |
| void SetAccountsInStorage(const AccountMapping& accounts_map) { |
| ProfileAttributesStorage* storage = attributes_storage(); |
| for (const auto& path_accounts_pair : accounts_map) { |
| const base::FilePath& path = path_accounts_pair.first; |
| if (path.empty()) |
| continue; // Account is unassigned. |
| storage->GetProfileAttributesWithPath(path)->SetGaiaIds( |
| path_accounts_pair.second); |
| } |
| } |
| |
| void SetLacrosAccountsInLocalState( |
| const base::flat_set<std::string>& account_ids) { |
| base::Value::List list; |
| for (const auto& gaia_id : account_ids) |
| list.Append(gaia_id); |
| local_state()->SetList(kLacrosAccountIdsPref, std::move(list)); |
| } |
| |
| base::flat_set<std::string> GetLacrosAccountsFromLocalState() { |
| const base::Value& list = local_state()->GetValue(kLacrosAccountIdsPref); |
| EXPECT_TRUE(list.is_list()); |
| return base::MakeFlatSet<std::string>( |
| list.GetList(), {}, |
| [](const base::Value& value) { return value.GetString(); }); |
| } |
| |
| void SetPrimaryAccountForProfile(const base::FilePath& profile_path, |
| const std::string& primary_gaia_id, |
| bool is_consented_primary_account = true, |
| bool is_managed = false) { |
| ProfileAttributesStorage* storage = attributes_storage(); |
| ProfileAttributesEntry* entry = |
| storage->GetProfileAttributesWithPath(profile_path); |
| ASSERT_TRUE(entry); |
| entry->SetAuthInfo(primary_gaia_id, u"Test", is_consented_primary_account); |
| if (is_managed) |
| entry->SetHostedDomain("managed.com"); |
| } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| account_manager::MockAccountManagerFacade mock_facade_; |
| std::queue<base::OnceCallback<void(const std::vector<Account>&)>> |
| facade_get_accounts_completion_callbacks_; |
| TestingProfileManager testing_profile_manager_; |
| base::FilePath main_path_; |
| }; |
| |
| // Test basic functionality for `GetAccounts()`: |
| // - returns expected accounts when called on a valid profile |
| // - returns no accounts when called on non-existing profile |
| // - does not trigger a call to GetAccounts() on the facade. |
| TEST_F(AccountProfileMapperTest, GetAccounts) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| |
| base::MockRepeatingCallback<void(const std::vector<Account>&)> mock_callback; |
| |
| // `GetAccounts()` does not go through the facade, but directly reads from |
| // storage. |
| EXPECT_CALL(*mock_facade(), GetAccounts(testing::_)).Times(0); |
| |
| // Non-existing profile. |
| EXPECT_CALL(mock_callback, Run(testing::IsEmpty())); |
| mapper->GetAccounts(GetProfilePath("MissingAccount"), mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| // Existing profile. |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"B", kGaiaType}), |
| Field(&Account::key, AccountKey{"C", kGaiaType})))); |
| mapper->GetAccounts(other_path, mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| // No call to the facade. |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| } |
| |
| // Test basic functionality for `GetAccountsMap()` with unassigned accounts: |
| // - returns the complete map, incl. unassigned accounts, |
| // - does not trigger a call to GetAccounts() on the facade. |
| TEST_F(AccountProfileMapperTest, GetAccountsMapWithUnassigned) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| base::FilePath empty_path; |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {other_path, {"B", "C"}}, {empty_path, {"D"}}}); |
| |
| // `GetAccountsMap()` does not go through the facade, but directly reads from |
| // storage. |
| EXPECT_CALL(*mock_facade(), GetAccounts(testing::_)).Times(0); |
| |
| base::MockRepeatingCallback<void( |
| const std::map<base::FilePath, std::vector<account_manager::Account>>&)> |
| mock_callback; |
| EXPECT_CALL( |
| mock_callback, |
| Run(testing::UnorderedElementsAre( |
| testing::Pair(main_path(), |
| testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}))), |
| testing::Pair(other_path, |
| testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"B", kGaiaType}), |
| Field(&Account::key, AccountKey{"C", kGaiaType}))), |
| testing::Pair(empty_path, |
| testing::UnorderedElementsAre(Field( |
| &Account::key, AccountKey{"D", kGaiaType})))))); |
| mapper->GetAccountsMap(mock_callback.Get()); |
| } |
| |
| // Test basic functionality for `GetAccountsMap()` without unassigned accounts: |
| // - returns the complete map, without the entry for unassigned accounts, |
| // - does not trigger a call to GetAccounts() on the facade. |
| TEST_F(AccountProfileMapperTest, GetAccountsMapWithoutUnassigned) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| |
| // `GetAccountsMap()` does not go through the facade, but directly reads from |
| // storage. |
| EXPECT_CALL(*mock_facade(), GetAccounts(testing::_)).Times(0); |
| |
| base::MockRepeatingCallback<void( |
| const std::map<base::FilePath, std::vector<account_manager::Account>>&)> |
| mock_callback; |
| EXPECT_CALL( |
| mock_callback, |
| Run(testing::UnorderedElementsAre( |
| testing::Pair(main_path(), |
| testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}))), |
| testing::Pair( |
| other_path, |
| testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"B", kGaiaType}), |
| Field(&Account::key, AccountKey{"C", kGaiaType})))))); |
| mapper->GetAccountsMap(mock_callback.Get()); |
| } |
| |
| // Tests that accounts are added by default to the main profile when there is |
| // only one profile. |
| TEST_F(AccountProfileMapperTest, UpdateSingleProfile) { |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A", "B"}}}); |
| TestMapperUpdateGaia(mapper, |
| /*accounts_in_facade=*/{"A", "C"}, |
| /*expected_accounts_upserted=*/{{main_path(), {"C"}}}, |
| /*expected_accounts_removed=*/{{main_path(), {"B"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A", "C"}}}); |
| } |
| |
| // Tests that at AccountProfileMapper initialization when there is only one |
| // profile: |
| // - a new account is added to the main profile storage |
| // - a no longer existing account is removed form the profile storage |
| TEST_F(AccountProfileMapperTest, |
| UpdateSingleProfile_AtInitialization_EmptyLocalState) { |
| CreateMapperNonInitialized({{main_path(), {"A", "B"}}}); |
| // Clean local state. |
| SetLacrosAccountsInLocalState({}); |
| // B is removed and C is added. |
| CompleteFacadeGetAccountsGaia({"A", "C"}); |
| VerifyAccountsInPrefs({{main_path(), {"A", "C"}}}); |
| } |
| |
| // Tests that at AccountProfileMapper initialization when there is only one |
| // profile: |
| // - an unassigned account is not added to the main profile storage |
| // - a no longer existing account is removed from the profile storage |
| TEST_F(AccountProfileMapperTest, UpdateSingleProfile_AtInitialization) { |
| CreateMapperNonInitialized( |
| {{main_path(), {"A", "B"}}, {base::FilePath(), {"C"}}}); |
| // B is removed. |
| CompleteFacadeGetAccountsGaia({"A", "C"}); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {base::FilePath(), {"C"}}}); |
| } |
| |
| // Tests that new accounts are left unassigned when there are multiple profiles. |
| TEST_F(AccountProfileMapperTest, UpdateMultiProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| TestMapperUpdateGaia( |
| mapper, |
| /*accounts_in_facade=*/{"A", "B", "D"}, |
| /*expected_accounts_upserted=*/{{base::FilePath(), {"D"}}}, |
| /*expected_accounts_removed=*/{{other_path, {"C"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A"}}, {other_path, {"B"}}, {base::FilePath(), {"D"}}}); |
| } |
| |
| // Tests that at AccountProfileMapper initialization when there are multiple |
| // profiles: |
| // - a new account is not added to the main profile storage |
| // - a no longer existing account is removed form the profile storage |
| TEST_F(AccountProfileMapperTest, |
| UpdateMultiProfile_AtInitialization_EmptyLocalState) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| CreateMapperNonInitialized({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| // Clean local state. |
| SetLacrosAccountsInLocalState({}); |
| // C is removed and D is added. |
| CompleteFacadeGetAccountsGaia({"A", "B", "D"}); |
| VerifyAccountsInPrefs( |
| {{main_path(), {"A"}}, {other_path, {"B"}}, {base::FilePath(), {"D"}}}); |
| } |
| |
| // Tests that at AccountProfileMapper initialization when there are multiple |
| // profiles: |
| // - an unassigned account is not added to the main profile storage |
| // - a no longer existing account is removed from the profile storage |
| TEST_F(AccountProfileMapperTest, UpdateMultiProfile_AtInitialization) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| CreateMapperNonInitialized({{main_path(), {"A"}}, |
| {other_path, {"B", "C"}}, |
| {base::FilePath(), {"D"}}}); |
| // C is removed. |
| CompleteFacadeGetAccountsGaia({"A", "B", "D"}); |
| VerifyAccountsInPrefs( |
| {{main_path(), {"A"}}, {other_path, {"B"}}, {base::FilePath(), {"D"}}}); |
| } |
| |
| // Checks that `GetPersistentErrorForAccount()` returns an error when the |
| // account is not in this profile. |
| TEST_F(AccountProfileMapperTest, GetPersistentErrorForAccount) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| base::MockRepeatingCallback<void(const GoogleServiceAuthError&)> |
| mock_callback; |
| |
| // Account exists in the profile: success. |
| EXPECT_CALL(mock_callback, Run(GoogleServiceAuthError::AuthErrorNone())); |
| mapper->GetPersistentErrorForAccount(main_path(), {"A", kGaiaType}, |
| mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| // Account does not exist in the profile: failure. |
| EXPECT_CALL( |
| mock_callback, |
| Run(GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP))); |
| mapper->GetPersistentErrorForAccount(main_path(), {"B", kGaiaType}, |
| mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| } |
| |
| // Tests that consumer callbacks are delayed until initialization completes. |
| TEST_F(AccountProfileMapperTest, WaitForInitialization) { |
| AccountProfileMapper* mapper = |
| CreateMapperNonInitialized({{main_path(), {"A", "B"}}}); |
| base::MockOnceCallback<void(const GoogleServiceAuthError&)> error_callback; |
| base::MockOnceCallback<void(const std::vector<Account>&)> accounts_callback; |
| // Call the mapper before initialization, callback not invoked. |
| EXPECT_CALL(error_callback, Run(testing::_)).Times(0); |
| EXPECT_CALL(accounts_callback, Run(testing::_)).Times(0); |
| mapper->GetPersistentErrorForAccount(main_path(), {"A", kGaiaType}, |
| error_callback.Get()); |
| mapper->GetAccounts(main_path(), accounts_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&error_callback); |
| testing::Mock::VerifyAndClearExpectations(&accounts_callback); |
| // Complete initialization: callback is invoked. |
| EXPECT_CALL(error_callback, Run(GoogleServiceAuthError::AuthErrorNone())); |
| EXPECT_CALL(accounts_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}), |
| Field(&Account::key, AccountKey{"B", kGaiaType})))); |
| CompleteFacadeGetAccountsGaia({"A", "B"}); |
| testing::Mock::VerifyAndClearExpectations(&error_callback); |
| testing::Mock::VerifyAndClearExpectations(&accounts_callback); |
| } |
| |
| TEST_F(AccountProfileMapperTest, NoObserversAtInitialization) { |
| AccountProfileMapper* mapper = |
| CreateMapperNonInitialized({{main_path(), {"A"}}}); |
| // Change the prefs, so that observers would normally trigger. |
| SetAccountsInStorage({{main_path(), {"A", "B"}}}); |
| SetLacrosAccountsInLocalState({"A", "B"}); |
| |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| |
| // Observers were not called even though the storage was updated. |
| VerifyAccountsInPrefs({{main_path(), {"A", "B"}}}); |
| CompleteFacadeGetAccountsGaia({"A"}); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}}); |
| } |
| |
| TEST_F(AccountProfileMapperTest, NonGaia) { |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A"}}}); |
| // Addition of non-Gaia account is ignored. |
| TestMapperUpdate(mapper, {AccountFromGaiaID("A"), NonGaiaAccountFromID("B")}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{}, |
| /*expected_accounts_in_prefs=*/{{main_path(), {"A"}}}); |
| // Removal is ignored as well. |
| TestMapperUpdate(mapper, {AccountFromGaiaID("A")}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{}, |
| /*expected_accounts_in_prefs=*/{{main_path(), {"A"}}}); |
| } |
| |
| // Tests that observers are notified when an existing account receives an |
| // update. |
| TEST_F(AccountProfileMapperTest, ObserveAccountUpdate) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {second_path, {"B"}}, {third_path, {"A", "B"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| |
| ExpectOnAccountUpserted(&mock_observer, |
| {{main_path(), {"A"}}, {third_path, {"A"}}}); |
| mapper->OnAccountUpserted(AccountFromGaiaID("A")); |
| } |
| |
| // Tests that observers are not notified when a non-Gaia account receives an |
| // update. |
| TEST_F(AccountProfileMapperTest, ObserveAccountUpdate_NonGaia) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {second_path, {"B"}}, {third_path, {"A", "B"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| |
| ExpectOnAccountUpserted(&mock_observer, {}); |
| mapper->OnAccountUpserted(NonGaiaAccountFromID("A")); |
| } |
| |
| // Tests that observers are notified when an existing unassigned Gaia account |
| // receives an update. |
| TEST_F(AccountProfileMapperTest, ObserveAccountUpdate_Unassigned) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A", "B"}}, |
| {other_path, {"B"}}, |
| {base::FilePath(), {"C"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| |
| ExpectOnAccountUpserted(&mock_observer, {{base::FilePath(), {"C"}}}); |
| mapper->OnAccountUpserted(AccountFromGaiaID("C")); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| TestMapperUpdateGaia( |
| mapper, |
| /*accounts_in_facade=*/{"A", "B"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{base::FilePath(), {"C"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A", "B"}}, {other_path, {"B"}}}); |
| } |
| |
| // Tests that observers are notified when an existing account receives an |
| // update before the AccountProfileMapper was initialized. |
| TEST_F(AccountProfileMapperTest, ObserveAccountUpdate_AtInitialization) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapperNonInitialized( |
| {{main_path(), {"A"}}, {second_path, {"B"}}, {third_path, {"A", "B"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| |
| EXPECT_CALL(*mock_facade(), GetAccounts(testing::_)).Times(0); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| mapper->OnAccountUpserted(AccountFromGaiaID("A")); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| ExpectOnAccountUpserted(&mock_observer, |
| {{main_path(), {"A"}}, {third_path, {"A"}}}); |
| CompleteFacadeGetAccountsGaia({"A", "B"}); |
| } |
| |
| // Tests that observers are notified in the edge-case scenario when an account |
| // is removed and instantly re-added to the system. |
| TEST_F(AccountProfileMapperTest, ObserveAccountReadded) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A", "B"}}, |
| {second_path, {"A"}}, |
| {third_path, {"A", "B"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| |
| ExpectFacadeGetAccountsCalled(); |
| mapper->OnAccountRemoved(AccountFromGaiaID("B")); |
| |
| // Account B gets re-added before `mapper` receives GetAccounts() callback. |
| ExpectOnAccountUpserted(&mock_observer, |
| {{main_path(), {"B"}}, {third_path, {"B"}}}); |
| mapper->OnAccountUpserted(AccountFromGaiaID("B")); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| // The callback triggered by `OnAccountRemoved()` returns stale data that |
| // contains only one account. |
| ExpectOnAccountRemoved(&mock_observer, |
| {{main_path(), {"B"}}, {third_path, {"B"}}}); |
| CompleteFacadeGetAccountsGaia({"A"}); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| // Account B gets re-added as an unassigned account. |
| ExpectOnAccountUpserted(&mock_observer, {{base::FilePath(), {"B"}}}); |
| CompleteFacadeGetAccountsGaia({"A", "B"}); |
| } |
| |
| // Tests that observers are notified about changes to accounts' error status. |
| TEST_F(AccountProfileMapperTest, ObserveAuthErrorChanged) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A", "B"}}, {second_path, {"A"}}, {third_path, {"B"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| |
| GoogleServiceAuthError error = |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_SERVER); |
| ExpectOnAuthErrorChanged(&mock_observer, |
| {{main_path(), {std::make_pair("A", error)}}, |
| {second_path, {std::make_pair("A", error)}}}); |
| mapper->OnAuthErrorChanged(account_manager::AccountKey{"A", kGaiaType}, |
| error); |
| } |
| |
| // Tests that a managed syncing secondary profile gets deleted after its primary |
| // account is removed from the system. |
| // A secondary account of the deleted profile stays unassigned. |
| TEST_F(AccountProfileMapperTest, |
| RemovePrimaryAccountFromSecondaryProfile_MultipleProfiles) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {second_path, {"B", "C"}}, {third_path, {"D"}}}); |
| SetPrimaryAccountForProfile(second_path, "B", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| TestMapperUpdateGaia( |
| mapper, |
| /*accounts_in_facade=*/{"A", "C", "D"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{second_path, {"B", "C"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A"}}, {third_path, {"D"}}, {base::FilePath(), {"C"}}}); |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(second_path); |
| } |
| |
| // Local profiles are not deleted. |
| TEST_F(AccountProfileMapperTest, LocalProfileNotRemoved) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {second_path, {"B"}}, {third_path, {}}}); |
| SetPrimaryAccountForProfile(second_path, "B", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| TestMapperUpdateGaia(mapper, |
| /*accounts_in_facade=*/{"A"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{second_path, {"B"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A"}}, {third_path, {}}}); |
| |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(second_path); |
| |
| // Third profile was not deleted as it was already a local profile. |
| EXPECT_TRUE(attributes_storage()->GetProfileAttributesWithPath(third_path)); |
| } |
| |
| // Tests that a managed syncing profile gets deleted after its sync account |
| // is removed from the system. A secondary account of the deleted profile stays |
| // unassigned. |
| TEST_F(AccountProfileMapperTest, |
| RemovePrimaryAccount_ManagedSecondaryProfile_Syncing) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {second_path, {"B"}}}); |
| SetPrimaryAccountForProfile(second_path, "B", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| TestMapperUpdateGaia(mapper, |
| /*accounts_in_facade=*/{"A"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{second_path, {"B"}}}, |
| /*expected_accounts_in_prefs=*/{{main_path(), {"A"}}}); |
| |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(second_path); |
| } |
| |
| // Tests that a managed non syncing profile does not get deleted after its |
| // primary account is removed from the system. |
| TEST_F(AccountProfileMapperTest, |
| RemovePrimaryAccount_ManagedSecondaryProfile_NotSyncing) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {second_path, {"B"}}}); |
| SetPrimaryAccountForProfile(second_path, "B", |
| /*is_consented_primary_account=*/false, |
| /*is_managed=*/true); |
| TestMapperUpdateGaia(mapper, |
| /*accounts_in_facade=*/{"A"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{second_path, {"B"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A"}}, {second_path, {}}}); |
| |
| base::RunLoop().RunUntilIdle(); |
| // Only managed syncing profiles are deleted. |
| EXPECT_TRUE(attributes_storage()->GetProfileAttributesWithPath(second_path)); |
| } |
| |
| // Tests that a consumer profile does not get deleted after its sync account |
| // is removed from the system. |
| TEST_F(AccountProfileMapperTest, |
| RemovePrimaryAccount_ConsumerSecondaryProfile) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {second_path, {"B", "C"}}, {third_path, {"D"}}}); |
| SetPrimaryAccountForProfile(second_path, "B"); |
| TestMapperUpdateGaia( |
| mapper, |
| /*accounts_in_facade=*/{"A", "C", "D"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{second_path, {"B"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A"}}, {second_path, {"C"}}, {third_path, {"D"}}}); |
| |
| base::RunLoop().RunUntilIdle(); |
| // Only managed syncing profiles are deleted. |
| // The `SigninManager` will detect as soon the second profile is loaded that |
| // its primary account does not have a refresh token and will completely |
| // signout the profile. |
| EXPECT_TRUE(attributes_storage()->GetProfileAttributesWithPath(second_path)); |
| EXPECT_TRUE(attributes_storage()->GetProfileAttributesWithPath(third_path)); |
| } |
| |
| // Tests that a manged syncing secondary profile gets deleted after its sync |
| // account was removed from the system before startup. |
| // A secondary account of the deleted profile gets moved to the primary profile |
| // since local state doesn't contain lacros accounts and there is only one |
| // profile left. |
| TEST_F( |
| AccountProfileMapperTest, |
| RemovePrimaryAccountFromSecondaryProfile_AtInitialization_EmptyLocalState) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| CreateMapperNonInitialized({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| // Clean local state. |
| SetLacrosAccountsInLocalState({}); |
| SetPrimaryAccountForProfile(other_path, "B", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| CompleteFacadeGetAccountsGaia({"A", "C"}); |
| VerifyAccountsInPrefs({{main_path(), {"A", "C"}}}); |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(other_path); |
| } |
| |
| // Tests that a managed secondary profile gets deleted after its sync account |
| // was removed from the system before startup. |
| // A secondary account of the deleted profile remains unassigned. |
| TEST_F(AccountProfileMapperTest, |
| RemovePrimaryAccountFromSecondaryProfile_AtInitialization) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| CreateMapperNonInitialized({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| SetPrimaryAccountForProfile(other_path, "B", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| CompleteFacadeGetAccountsGaia({"A", "C"}); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {base::FilePath(), {"C"}}}); |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(other_path); |
| } |
| |
| // Tests that a managed syncing secondary profile doesn't get deleted after its |
| // secondary account is removed from the system. |
| TEST_F(AccountProfileMapperTest, RemoveSecondaryAccountFromSecondaryProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| SetPrimaryAccountForProfile(other_path, "B", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| TestMapperUpdateGaia(mapper, |
| /*accounts_in_facade=*/{"A", "B"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{other_path, {"C"}}}, |
| /*expected_accounts_in_prefs=*/ |
| {{main_path(), {"A"}}, {other_path, {"B"}}}); |
| } |
| |
| // Tests that the primary profile doesn't get deleted even after its primary |
| // account is removed from the system. |
| TEST_F(AccountProfileMapperTest, RemovePrimaryAccountFromPrimaryProfile) { |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A", "B"}}}); |
| SetPrimaryAccountForProfile(main_path(), "A", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| TestMapperUpdateGaia(mapper, |
| /*accounts_in_facade=*/{"B"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{main_path(), {"A"}}}, |
| /*expected_accounts_in_prefs=*/{{main_path(), {"B"}}}); |
| } |
| |
| // Tests removing all accounts from a secondary profile (User signed out from |
| // chrome or primary account removed from the OS) before initialization. |
| TEST_F(AccountProfileMapperTest, |
| RemoveAllAccountsFromSecondaryProfile_BeforeInitialization) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = CreateMapperNonInitialized( |
| {{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| ExpectOnAccountRemoved(&mock_observer, {{other_path, {"B", "C"}}}); |
| mapper->RemoveAllAccounts(other_path); |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInStorage({{main_path(), {"A"}}, {other_path, {}}}); |
| } |
| |
| // Tests removing all accounts from a secondary profile and account removed from |
| // the OS before initialization. |
| TEST_F( |
| AccountProfileMapperTest, |
| RemoveAllAccountsFromSecondaryProfile_OSAccountsChanged_BeforeInitialization) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = CreateMapperNonInitialized( |
| {{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| // "C" is removed from the ProfileAttributeEntry by RemoveStaleAccounts. |
| ExpectOnAccountRemoved(&mock_observer, {{other_path, {"B"}}}); |
| mapper->RemoveAllAccounts(other_path); |
| CompleteFacadeGetAccountsGaia({"A", "B"}); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInStorage({{main_path(), {"A"}}, {other_path, {}}}); |
| } |
| |
| // Tests removing all accounts from a secondary profile. |
| TEST_F(AccountProfileMapperTest, RemoveAllAccountsFromSecondaryProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| ExpectOnAccountRemoved(&mock_observer, {{other_path, {"B", "C"}}}); |
| mapper->RemoveAllAccounts(other_path); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInStorage({{main_path(), {"A"}}, {other_path, {}}}); |
| } |
| |
| // Tests removing all accounts from main profile is not allowed. |
| TEST_F(AccountProfileMapperTest, RemoveAllAccountsFromPrimaryProfile) { |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A", "B"}}}); |
| SetPrimaryAccountForProfile(main_path(), "A"); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| ExpectOnAccountRemoved(&mock_observer, {}); |
| mapper->RemoveAllAccounts(main_path()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInStorage({{main_path(), {"A", "B"}}}); |
| } |
| |
| // Tests removing accounts from secondary profile. |
| TEST_F(AccountProfileMapperTest, RemoveAccountSecondaryProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| SetPrimaryAccountForProfile(other_path, "B"); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| // Remove account C (secondary account). |
| ExpectOnAccountRemoved(&mock_observer, {{other_path, {"C"}}}); |
| mapper->RemoveAccount(other_path, AccountFromGaiaID("C").key); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInStorage({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| // Remove account B (main account). |
| ExpectOnAccountRemoved(&mock_observer, {{other_path, {"B"}}}); |
| mapper->RemoveAccount(other_path, AccountFromGaiaID("B").key); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInStorage({{main_path(), {"A"}}, {other_path, {}}}); |
| } |
| |
| // Tests removing accounts from main profile. |
| TEST_F(AccountProfileMapperTest, RemoveAccountPrimaryProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A", "B"}}, {other_path, {"B"}}}); |
| SetPrimaryAccountForProfile(main_path(), "A"); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| // Remove account B. |
| ExpectOnAccountRemoved(&mock_observer, {{main_path(), {"B"}}}); |
| mapper->RemoveAccount(main_path(), AccountFromGaiaID("B").key); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInStorage({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| // Try removing account A: this does nothing. |
| mapper->RemoveAccount(main_path(), AccountFromGaiaID("A").key); |
| VerifyAccountsInStorage({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| } |
| |
| // Tests removing all accounts from profile before initialization but profile |
| // is deleted during initialization. |
| TEST_F( |
| AccountProfileMapperTest, |
| RemoveAllAccountsFromSecondaryProfile_ProfileDeletedDuringInitialization) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = CreateMapperNonInitialized( |
| {{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| SetPrimaryAccountForProfile(other_path, "B", |
| /*is_consented_primary_account=*/true, |
| /*is_managed=*/true); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| ExpectOnAccountRemoved(&mock_observer, {{other_path, {"C"}}}); |
| mapper->RemoveAllAccounts(other_path); |
| CompleteFacadeGetAccountsGaia({"A", "C"}); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {base::FilePath(), {"C"}}}); |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(other_path); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| } |
| |
| // Tests that accounts from deleted profile remain unassigned. |
| TEST_F(AccountProfileMapperTest, DeleteProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| ExpectOnAccountRemoved(&mock_observer, {{other_path, {"B", "C"}}}); |
| |
| testing_profile_manager()->DeleteTestingProfile("Other"); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {base::FilePath(), {"B", "C"}}}); |
| } |
| |
| TEST_F(AccountProfileMapperTest, ShowAddAccountDialogBeforeInit) { |
| AccountProfileMapper* mapper = |
| CreateMapperNonInitialized({{main_path(), {"A"}}}); |
| AccountManagerFacade::AccountAdditionSource source = |
| AccountManagerFacade::AccountAdditionSource::kOgbAddAccount; |
| // The facade is not called before initialization. |
| EXPECT_CALL(*mock_facade(), ShowAddAccountDialog(testing::_, testing::_)) |
| .Times(0); |
| |
| mapper->ShowAddAccountDialog(main_path(), source, |
| AccountProfileMapper::AddAccountCallback()); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| // Complete initialization, and check that the facade was called. |
| EXPECT_CALL(*mock_facade(), ShowAddAccountDialog(source, testing::_)); |
| CompleteFacadeGetAccountsGaia({"A"}); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| } |
| |
| TEST_F(AccountProfileMapperTest, ShowAddAccountDialog) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| |
| MockAddAccountCallback account_added_callback; |
| Account account_c = AccountFromGaiaID("C"); |
| std::optional<AccountProfileMapper::AddAccountResult> result = |
| AccountProfileMapper::AddAccountResult{other_path, account_c}; |
| AccountManagerFacade::AccountAdditionSource source = |
| AccountManagerFacade::AccountAdditionSource::kOgbAddAccount; |
| |
| // Success: Add account to existing profile. |
| ExpectFacadeShowAddAccountDialogCalled(source, account_c); |
| ExpectFacadeGetAccountsCalled(); |
| EXPECT_CALL(account_added_callback, Run(AddAccountResultEqual(result))); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(other_path, AccountEqual(account_c))); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| mapper->OnAccountUpserted(account_c); |
| // The first `GetAccounts()` call is generated by `OnAccountUpserted()`. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| // The second `GetAccounts()` call is generated when an `AddAccountHelper` |
| // completes. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| |
| // Success: Add account to existing profile (with no callback provided). |
| Account account_d = AccountFromGaiaID("D"); |
| ExpectFacadeShowAddAccountDialogCalled(source, account_d); |
| ExpectFacadeGetAccountsCalled(); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(other_path, AccountEqual(account_d))); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->ShowAddAccountDialog(other_path, source, |
| AccountProfileMapper::AddAccountCallback()); |
| mapper->OnAccountUpserted(account_d); |
| // The first `GetAccounts()` call is generated by `OnAccountUpserted()`. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C", "D"}); |
| // The second `GetAccounts()` call is generated when an `AddAccountHelper` |
| // completes. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C", "D"}); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {other_path, {"B", "C", "D"}}}); |
| |
| // Failure: Add account that already exists. |
| ExpectFacadeShowAddAccountDialogCalled(source, account_c); |
| EXPECT_CALL(account_added_callback, Run(testing::Eq(std::nullopt))); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| // Failure: Add account to non-existing profile, account is unassigned. |
| Account account_e = AccountFromGaiaID("E"); |
| base::FilePath unknown_path = GetProfilePath("UnknownProfile"); |
| ExpectFacadeShowAddAccountDialogCalled(source, account_e); |
| ExpectFacadeGetAccountsCalled(); |
| result = {base::FilePath(), account_e}; |
| EXPECT_CALL(account_added_callback, Run(AddAccountResultEqual(result))); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(base::FilePath(), AccountEqual(account_e))); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->ShowAddAccountDialog(unknown_path, source, |
| account_added_callback.Get()); |
| mapper->OnAccountUpserted(account_e); |
| // The first `GetAccounts()` call is generated by `OnAccountUpserted()`. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C", "D", "E"}); |
| // The second `GetAccounts()` call is generated when an `AddAccountHelper` |
| // completes. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C", "D", "E"}); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| // `account_e` is added as an unassigned account. |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, |
| {other_path, {"B", "C", "D"}}, |
| {base::FilePath(), {"E"}}}); |
| |
| // Failure: Non-Gaia account. |
| Account account_f = NonGaiaAccountFromID("F"); |
| ExpectFacadeShowAddAccountDialogCalled(source, account_f); |
| EXPECT_CALL(account_added_callback, Run(testing::Eq(std::nullopt))); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| mapper->OnAccountUpserted(account_f); |
| |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| // Failure: Flow aborted. |
| ExpectFacadeShowAddAccountDialogCalled(source, std::nullopt); |
| EXPECT_CALL(account_added_callback, Run(testing::Eq(std::nullopt))); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| // No account was assigned by any the failures above. |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, |
| {other_path, {"B", "C", "D"}}, |
| {base::FilePath(), {"E"}}}); |
| } |
| |
| // Tests that an account is fully added only after the account manager called |
| // both the account added callback and the `OnAccountUpserted()` method. |
| // The account added callback is called first in this test. |
| TEST_F(AccountProfileMapperTest, |
| ShowAddAccountDialogTwoPhase_AccountAddedCallbackFirst) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| MockAddAccountCallback account_added_callback; |
| Account account_c = AccountFromGaiaID("C"); |
| std::optional<AccountProfileMapper::AddAccountResult> result = |
| AccountProfileMapper::AddAccountResult{other_path, account_c}; |
| AccountManagerFacade::AccountAdditionSource source = |
| AccountManagerFacade::AccountAdditionSource::kOgbAddAccount; |
| |
| // No events fire before the account manager upserts an account: |
| EXPECT_CALL(account_added_callback, Run(testing::_)).Times(0); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| ExpectFacadeShowAddAccountDialogCalled(source, account_c); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| |
| // An account is added after the account manager upserts it: |
| EXPECT_CALL(account_added_callback, Run(AddAccountResultEqual(result))); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(other_path, AccountEqual(account_c))); |
| ExpectFacadeGetAccountsCalled(); |
| mapper->OnAccountUpserted(account_c); |
| // The first `GetAccounts()` call is generated by `OnAccountUpserted()`. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| // The second `GetAccounts()` call is generated when an `AddAccountHelper` |
| // completes. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| } |
| |
| // Tests that an account is fully added only after the account manager called |
| // both the account added callback and the `OnAccountUpserted()` method. |
| // The `OnAccountUpserted()` method is called first in this test. |
| TEST_F(AccountProfileMapperTest, |
| ShowAddAccountDialogTwoPhase_AccountUpsertedFirst) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| MockAddAccountCallback account_added_callback; |
| Account account_c = AccountFromGaiaID("C"); |
| std::optional<AccountProfileMapper::AddAccountResult> result = |
| AccountProfileMapper::AddAccountResult{other_path, account_c}; |
| AccountManagerFacade::AccountAdditionSource source = |
| AccountManagerFacade::AccountAdditionSource::kOgbAddAccount; |
| base::OnceCallback<void(const AccountUpsertionResult&)> |
| show_add_account_dialog_facade_callback; |
| |
| // No events fire before the account manager invokes the account added |
| // callback: |
| EXPECT_CALL(account_added_callback, Run(testing::_)).Times(0); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(*mock_facade(), ShowAddAccountDialog(source, testing::_)) |
| .WillOnce([&show_add_account_dialog_facade_callback]( |
| AccountManagerFacade::AccountAdditionSource, |
| base::OnceCallback<void(const AccountUpsertionResult&)> |
| callback) { |
| show_add_account_dialog_facade_callback = std::move(callback); |
| }); |
| ExpectFacadeGetAccountsCalled(); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| mapper->OnAccountUpserted(account_c); |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| EXPECT_TRUE(show_add_account_dialog_facade_callback); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| |
| // An account is added after the account manager invokes the account added |
| // callback: |
| EXPECT_CALL(account_added_callback, Run(AddAccountResultEqual(result))); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(other_path, AccountEqual(account_c))); |
| ExpectFacadeGetAccountsCalled(); |
| std::move(show_add_account_dialog_facade_callback) |
| .Run(AccountUpsertionResult::FromAccount(account_c)); |
| // `mapper` updates the account list after it adds an account. |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| } |
| |
| TEST_F(AccountProfileMapperTest, ShowAddAccountDialogNewProfile) { |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| MockAddAccountCallback account_added_callback; |
| |
| // Set expectations: a new profile should be created for the new account. |
| AccountManagerFacade::AccountAdditionSource source = |
| AccountManagerFacade::AccountAdditionSource::kChromeProfileCreation; |
| Account account_b = AccountFromGaiaID("B"); |
| ExpectFacadeShowAddAccountDialogCalled(source, account_b); |
| ExpectFacadeGetAccountsCalled(); |
| base::FilePath new_profile_path = GetProfilePath("Profile 1"); |
| std::optional<AccountProfileMapper::AddAccountResult> result = |
| AccountProfileMapper::AddAccountResult{new_profile_path, account_b}; |
| EXPECT_CALL(account_added_callback, Run(AddAccountResultEqual(result))); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(new_profile_path, AccountEqual(account_b))); |
| ProfileWaiter profile_waiter; |
| |
| // Create the profile. |
| mapper->ShowAddAccountDialogAndCreateNewProfile(source, |
| account_added_callback.Get()); |
| mapper->OnAccountUpserted(account_b); |
| // The first `GetAccounts()` call is generated by `OnAccountUpserted()`. |
| CompleteFacadeGetAccountsGaia({"A", "B"}); |
| Profile* new_profile = profile_waiter.WaitForProfileAdded(); |
| // The second `GetAccounts()` call is generated when an `AddAccountHelper` |
| // completes after a new profile is created. |
| CompleteFacadeGetAccountsGaia({"A", "B"}); |
| |
| // Check that the profile was created and configured. |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| EXPECT_EQ(new_profile->GetPath(), new_profile_path); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}, {new_profile_path, {"B"}}}); |
| ProfileAttributesEntry* entry = |
| attributes_storage()->GetProfileAttributesWithPath(new_profile_path); |
| ASSERT_TRUE(entry); |
| EXPECT_TRUE(entry->IsOmitted()); |
| EXPECT_TRUE(entry->IsEphemeral()); |
| EXPECT_EQ(entry->GetSigninState(), SigninState::kNotSignedIn); |
| } |
| |
| TEST_F(AccountProfileMapperTest, AddAccount) { |
| // Start with account "C" unassigned. |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {other_path, {"B"}}, {base::FilePath(), {"C"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| MockAddAccountCallback account_added_callback; |
| |
| // Set expectations. |
| Account account_c = AccountFromGaiaID("C"); |
| std::optional<AccountProfileMapper::AddAccountResult> result = |
| AccountProfileMapper::AddAccountResult{main_path(), account_c}; |
| EXPECT_CALL(account_added_callback, Run(AddAccountResultEqual(result))); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(main_path(), AccountEqual(account_c))); |
| EXPECT_CALL(*mock_facade(), ShowAddAccountDialog(testing::_, testing::_)) |
| .Times(0); |
| |
| // Add the account. |
| mapper->AddAccount(main_path(), account_c.key, account_added_callback.Get()); |
| |
| // Check that the account was added. |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInPrefs({{main_path(), {"A", "C"}}, {other_path, {"B"}}}); |
| |
| // Failure: Non-Gaia account (with no callback provided). |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->AddAccount(main_path(), NonGaiaAccountFromID("D").key, |
| AccountProfileMapper::AddAccountCallback()); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| // Failure: Non-existing account (with no callback provided). |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(mock_observer, OnAccountRemoved(testing::_, testing::_)).Times(0); |
| mapper->AddAccount(main_path(), AccountFromGaiaID("E").key, |
| AccountProfileMapper::AddAccountCallback()); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| |
| VerifyAccountsInPrefs({{main_path(), {"A", "C"}}, {other_path, {"B"}}}); |
| } |
| |
| // Tries adding an account "B" to the profile, when the account "B" does not |
| // exist. |
| TEST_F(AccountProfileMapperTest, AddUnknownAccount) { |
| AccountProfileMapper* mapper = CreateMapper({{main_path(), {"A"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| MockAddAccountCallback account_added_callback; |
| |
| // Set expectations: the operation fails. |
| Account account_b = AccountFromGaiaID("B"); |
| EXPECT_CALL(account_added_callback, Run(testing::Eq(std::nullopt))); |
| EXPECT_CALL(mock_observer, OnAccountUpserted(testing::_, testing::_)) |
| .Times(0); |
| EXPECT_CALL(*mock_facade(), |
| ShowAddAccountDialog( |
| AccountManagerFacade::AccountAdditionSource::kOgbAddAccount, |
| testing::_)) |
| .Times(0); |
| |
| // Add the account. |
| mapper->AddAccount(main_path(), account_b.key, account_added_callback.Get()); |
| |
| // Check expectations. |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| VerifyAccountsInPrefs({{main_path(), {"A"}}}); |
| } |
| |
| TEST_F(AccountProfileMapperTest, CreateNewProfileWithAccount) { |
| // Start with account "C" unassigned. |
| base::FilePath other_path = GetProfilePath("Other"); |
| AccountProfileMapper* mapper = CreateMapper( |
| {{main_path(), {"A"}}, {other_path, {"B"}}, {base::FilePath(), {"C"}}}); |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper); |
| MockAddAccountCallback account_added_callback; |
| |
| // Set expectations: a new profile should be created for the account. |
| Account account_c = AccountFromGaiaID("C"); |
| EXPECT_CALL(*mock_facade(), ShowAddAccountDialog(testing::_, testing::_)) |
| .Times(0); |
| base::FilePath new_profile_path = GetProfilePath("Profile 1"); |
| std::optional<AccountProfileMapper::AddAccountResult> result = |
| AccountProfileMapper::AddAccountResult{new_profile_path, account_c}; |
| EXPECT_CALL(account_added_callback, Run(AddAccountResultEqual(result))); |
| EXPECT_CALL(mock_observer, |
| OnAccountUpserted(new_profile_path, AccountEqual(account_c))); |
| ProfileWaiter profile_waiter; |
| |
| // Create the profile. |
| mapper->CreateNewProfileWithAccount(account_c.key, |
| account_added_callback.Get()); |
| Profile* new_profile = profile_waiter.WaitForProfileAdded(); |
| |
| // Check that the profile was created and configured. |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| EXPECT_EQ(new_profile->GetPath(), new_profile_path); |
| VerifyAccountsInPrefs( |
| {{main_path(), {"A"}}, {other_path, {"B"}}, {new_profile_path, {"C"}}}); |
| ProfileAttributesEntry* entry = |
| attributes_storage()->GetProfileAttributesWithPath(new_profile_path); |
| ASSERT_TRUE(entry); |
| EXPECT_TRUE(entry->IsOmitted()); |
| EXPECT_TRUE(entry->IsEphemeral()); |
| EXPECT_EQ(entry->GetSigninState(), SigninState::kNotSignedIn); |
| } |
| |
| TEST_F(AccountProfileMapperTest, FixProfilesAtStartupWithLocalProfiles) { |
| base::FilePath syncing_path = GetProfilePath("Syncing"); |
| base::FilePath signed_out_path = GetProfilePath("SignedOut"); |
| base::FilePath unconsented_path = GetProfilePath("Unconsented"); |
| |
| // Create profiles without gaia ids. |
| CreateProfilesAndSetAccountsInPrefs({{main_path(), {}}, |
| {syncing_path, {}}, |
| {unconsented_path, {}}, |
| {signed_out_path, {}}}); |
| // Set profiles in various signin states. |
| attributes_storage() |
| ->GetProfileAttributesWithPath(syncing_path) |
| ->SetAuthInfo( |
| /*gaia_id=*/"A", /*user_name=*/u"A", |
| /*is_consented_primary_account=*/true); |
| attributes_storage() |
| ->GetProfileAttributesWithPath(unconsented_path) |
| ->SetAuthInfo( |
| /*gaia_id=*/"B", /*user_name=*/u"B", |
| /*is_consented_primary_account=*/false); |
| |
| auto mapper = std::make_unique<AccountProfileMapper>( |
| mock_facade(), attributes_storage(), local_state()); |
| |
| // The main profile is not deleted, even though it does not have an account. |
| // Other profiles are not deleted either. The missing gaia IDs are added to |
| // the storage. |
| VerifyAccountsInStorage({{main_path(), {}}, |
| {syncing_path, {"A"}}, |
| {unconsented_path, {"B"}}, |
| {signed_out_path, {}}}); |
| } |
| |
| // Checks that profiles are correctly imported from Ash-based Chrome. |
| TEST_F(AccountProfileMapperTest, MigrateAshProfile) { |
| // On Ash, the accounts are not explicitly assigned to the profile in |
| // `ProfileAttributesStorage`. |
| CreateMapperNonInitialized( |
| {{main_path(), {}}, {base::FilePath(), {"A", "B", "C"}}}); |
| // Local state is empty before the profile is migrated. |
| SetLacrosAccountsInLocalState({}); |
| CompleteFacadeGetAccountsGaia({"A", "B", "C"}); |
| |
| // All accounts have been assigned to the main profile. |
| VerifyAccountsInPrefs({{main_path(), {"A", "B", "C"}}}); |
| } |
| |
| TEST_F(AccountProfileMapperTest, ReportAuthError) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A", "B"}}, {second_path, {"A"}}}); |
| |
| const account_manager::AccountKey account_key{"A", kGaiaType}; |
| const GoogleServiceAuthError error = |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_SERVER); |
| EXPECT_CALL(*mock_facade(), ReportAuthError(account_key, error)); |
| |
| mapper->ReportAuthError(second_path, account_key, error); |
| } |
| |
| TEST_F(AccountProfileMapperTest, |
| ReportAuthErrorForUnknownProfileAccountMapping) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| AccountProfileMapper* mapper = |
| CreateMapper({{main_path(), {"A", "B"}}, {second_path, {"B"}}}); |
| |
| const account_manager::AccountKey account_key{"A", kGaiaType}; |
| const GoogleServiceAuthError error = |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_SERVER); |
| EXPECT_CALL(*mock_facade(), ReportAuthError(account_key, error)).Times(0); |
| |
| mapper->ReportAuthError(second_path, account_key, error); |
| } |