| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // 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 <vector> |
| |
| #include "base/callback.h" |
| #include "base/callback_forward.h" |
| #include "base/containers/flat_map.h" |
| #include "base/files/file_path.h" |
| #include "base/scoped_observation.h" |
| #include "base/test/mock_callback.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/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_addition_result.h" |
| #include "components/account_manager_core/account_manager_facade.h" |
| #include "components/account_manager_core/mock_account_manager_facade.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "google_apis/gaia/google_service_auth_error.h" |
| #include "google_apis/gaia/oauth2_access_token_fetcher.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| using account_manager::Account; |
| using account_manager::AccountAdditionResult; |
| using account_manager::AccountKey; |
| using account_manager::AccountManagerFacade; |
| using testing::Field; |
| |
| namespace { |
| |
| constexpr account_manager::AccountType kGaiaType = |
| account_manager::AccountType::kGaia; |
| |
| // Map from profile path to a vector of GaiaIds. |
| using AccountMapping = |
| base::flat_map<base::FilePath, base::flat_set<std::string>>; |
| |
| class MockAccountProfileMapperObserver : public AccountProfileMapper::Observer { |
| public: |
| MockAccountProfileMapperObserver() = default; |
| ~MockAccountProfileMapperObserver() override = default; |
| |
| MOCK_METHOD(void, |
| OnAccountUpserted, |
| (const base::FilePath& profile_path, const Account&), |
| (override)); |
| MOCK_METHOD(void, |
| OnAccountRemoved, |
| (const base::FilePath& profile_path, const Account&), |
| (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_); |
| 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: |
| ProfileAttributesStorage* storage_; |
| base::ScopedObservation<ProfileAttributesStorage, |
| ProfileAttributesStorage::Observer> |
| storage_observation_{this}; |
| base::FilePath profile_path_; |
| base::RunLoop run_loop_; |
| }; |
| |
| MATCHER_P(OptionalAccountEqual, other, "optional<Account> equality matcher") { |
| if (arg == absl::nullopt && other == absl::nullopt) |
| return true; |
| 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()); |
| }); |
| } |
| |
| ProfileAttributesStorage* attributes_storage() { |
| return &testing_profile_manager_.profile_manager() |
| ->GetProfileAttributesStorage(); |
| } |
| |
| account_manager::MockAccountManagerFacade* mock_facade() { |
| return &mock_facade_; |
| } |
| |
| 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_storage) { |
| TestMapperUpdate(mapper, AccountsFromGaiaIDs(gaia_accounts_in_facade), |
| expected_accounts_upserted, expected_accounts_removed, |
| expected_accounts_in_storage); |
| } |
| |
| // Triggers an update of the accounts and checks observer calls, and the end |
| // state of the storage. |
| 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_storage) { |
| 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()); |
| ExpectAccountsInStorage(expected_accounts_in_storage); |
| } |
| |
| std::unique_ptr<AccountProfileMapper> GetMapperNonInitialized( |
| const AccountMapping& accounts) { |
| SetAccountsInStorage(accounts); |
| ExpectAccountsInStorage(accounts); |
| ExpectFacadeGetAccountsCalled(); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| std::make_unique<AccountProfileMapper>(mock_facade(), |
| attributes_storage()); |
| return mapper; |
| } |
| |
| std::unique_ptr<AccountProfileMapper> GetMapper( |
| const AccountMapping& accounts) { |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapperNonInitialized(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); |
| 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))); |
| } |
| } |
| } |
| |
| // Checks that the `ProfileAttributesStorage` matches `accounts_map`. |
| void ExpectAccountsInStorage(const AccountMapping& accounts_map) { |
| auto entries = attributes_storage()->GetAllProfilesAttributes(); |
| EXPECT_EQ(entries.size(), accounts_map.size()); |
| 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"; |
| } |
| |
| // Sets an expectation that `GetAccounts()` is called on the facade, and |
| // stores the callback for later use in `CompleteFacadeGetAccounts()`. |
| void ExpectFacadeGetAccountsCalled() { |
| EXPECT_CALL(mock_facade_, GetAccounts(testing::_)) |
| .WillOnce([this](base::OnceCallback<void(const std::vector<Account>&)> |
| callback) { |
| DCHECK(!facade_get_accounts_completion_); |
| facade_get_accounts_completion_ = 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 absl::optional<Account>& new_account) { |
| EXPECT_CALL(mock_facade_, ShowAddAccountDialog(source, testing::_)) |
| .WillOnce( |
| [new_account](AccountManagerFacade::AccountAdditionSource, |
| base::OnceCallback<void(const AccountAdditionResult&)> |
| callback) { |
| std::move(callback).Run( |
| new_account.has_value() |
| ? AccountAdditionResult::FromAccount(new_account.value()) |
| : AccountAdditionResult::FromStatus( |
| AccountAdditionResult::Status::kCancelledByUser)); |
| }); |
| } |
| |
| void CompleteFacadeGetAccountsGaia(const std::vector<std::string>& gaia_ids) { |
| CompleteFacadeGetAccounts(AccountsFromGaiaIDs(gaia_ids)); |
| } |
| |
| void CompleteFacadeGetAccounts(const std::vector<Account>& accounts) { |
| std::move(facade_get_accounts_completion_).Run(accounts); |
| } |
| |
| // Sets the accounts in `ProfileAttributesStorage`. `accounts_map` is a map |
| // from profile path to a vector of GaiaIds. One of the profiles must be the |
| // main profile. |
| void SetAccountsInStorage(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; |
| testing_profile_manager_.CreateTestingProfile( |
| path.BaseName().MaybeAsASCII()); |
| } |
| // Import accounts from the map. |
| ProfileAttributesStorage* storage = attributes_storage(); |
| for (const auto& path_accounts_pair : accounts_map) { |
| const base::FilePath path = path_accounts_pair.first; |
| storage->GetProfileAttributesWithPath(path)->SetGaiaIds( |
| path_accounts_pair.second); |
| } |
| } |
| |
| void SetPrimaryAccountForProfile(const base::FilePath& profile_path, |
| const std::string& primary_gaia_id) { |
| ProfileAttributesStorage* storage = attributes_storage(); |
| ProfileAttributesEntry* entry = |
| storage->GetProfileAttributesWithPath(profile_path); |
| ASSERT_TRUE(entry); |
| entry->SetAuthInfo(primary_gaia_id, u"Test", |
| /*is_consented_primary_account=*/true); |
| } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| TestingProfileManager testing_profile_manager_; |
| base::FilePath main_path_; |
| account_manager::MockAccountManagerFacade mock_facade_; |
| base::OnceCallback<void(const std::vector<Account>&)> |
| facade_get_accounts_completion_; |
| }; |
| |
| // 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"); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{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()); |
| } |
| |
| // Tests that accounts are added by default to the main profile when there is |
| // only one profile. |
| TEST_F(AccountProfileMapperTest, UpdateSingleProfile) { |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{main_path(), {"A", "B"}}}); |
| TestMapperUpdateGaia(mapper.get(), |
| /*accounts_in_facade=*/{"A", "C"}, |
| /*expected_accounts_upserted=*/{{main_path(), {"C"}}}, |
| /*expected_accounts_removed=*/{{main_path(), {"B"}}}, |
| /*expected_accounts_in_storage=*/ |
| {{main_path(), {"A", "C"}}}); |
| } |
| |
| // Tests that new accounts are left unassigned when there are multiple profiles. |
| TEST_F(AccountProfileMapperTest, UpdateMulltiProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| TestMapperUpdateGaia( |
| mapper.get(), |
| /*accounts_in_facade=*/{"A", "B", "D"}, |
| /*expected_accounts_upserted=*/{{base::FilePath(), {"D"}}}, |
| /*expected_accounts_removed=*/{{other_path, {"C"}}}, |
| /*expected_accounts_in_storage=*/ |
| {{main_path(), {"A"}}, {other_path, {"B"}}}); |
| } |
| |
| // Checks that `GetPersistentErrorForAccount()` returns an error when the |
| // account is not in this profile. |
| TEST_F(AccountProfileMapperTest, GetPersistentErrorForAccount) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{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) { |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapperNonInitialized({{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) { |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapperNonInitialized({{main_path(), {"A"}}}); |
| // Change the storage, so that observers would normally trigger. |
| SetAccountsInStorage({{main_path(), {"A", "B"}}}); |
| |
| MockAccountProfileMapperObserver mock_observer; |
| base::ScopedObservation<AccountProfileMapper, AccountProfileMapper::Observer> |
| observation{&mock_observer}; |
| observation.Observe(mapper.get()); |
| 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. |
| ExpectAccountsInStorage({{main_path(), {"A", "B"}}}); |
| CompleteFacadeGetAccountsGaia({"A"}); |
| testing::Mock::VerifyAndClearExpectations(&mock_observer); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| ExpectAccountsInStorage({{main_path(), {"A"}}}); |
| } |
| |
| TEST_F(AccountProfileMapperTest, NonGaia) { |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{main_path(), {"A"}}}); |
| // Addition of non-Gaia account is ignored. |
| TestMapperUpdate(mapper.get(), |
| {AccountFromGaiaID("A"), NonGaiaAccountFromID("B")}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{}, |
| /*expected_accounts_in_storage=*/ |
| {{main_path(), {"A"}}}); |
| // Removal is ignored as well. |
| TestMapperUpdate(mapper.get(), {AccountFromGaiaID("A")}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{}, |
| /*expected_accounts_in_storage=*/ |
| {{main_path(), {"A"}}}); |
| } |
| |
| // Tests that a secondary profile gets deleted after its primary account is |
| // removed from the system. |
| // A secondary account of the deleted profile gets moved to the primary profile |
| // since it's an only remaining profile. |
| TEST_F(AccountProfileMapperTest, RemovePrimaryAccountFromSecondaryProfile) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| SetPrimaryAccountForProfile(other_path, "B"); |
| TestMapperUpdateGaia(mapper.get(), |
| /*accounts_in_facade=*/{"A", "C"}, |
| /*expected_accounts_upserted=*/{{main_path(), {"C"}}}, |
| /*expected_accounts_removed=*/{{other_path, {"B"}}}, |
| /*expected_accounts_in_storage=*/ |
| {{main_path(), {"A", "C"}}}); |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(other_path); |
| } |
| |
| // Tests that a secondary profile gets deleted after its primary account is |
| // removed from the system. |
| // A secondary account of the deleted profile stays unassigned since there are |
| // still several profiles. |
| TEST_F(AccountProfileMapperTest, |
| RemovePrimaryAccountFromSecondaryProfile_MultipleProfiles) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| std::unique_ptr<AccountProfileMapper> mapper = GetMapper( |
| {{main_path(), {"A"}}, {second_path, {"B", "C"}}, {third_path, {"D"}}}); |
| SetPrimaryAccountForProfile(second_path, "B"); |
| TestMapperUpdateGaia( |
| mapper.get(), |
| /*accounts_in_facade=*/{"A", "C", "D"}, |
| /*expected_accounts_upserted=*/{{base::FilePath(), {"C"}}}, |
| /*expected_accounts_removed=*/{{second_path, {"B"}}}, |
| /*expected_accounts_in_storage=*/ |
| {{main_path(), {"A"}}, {third_path, {"D"}}}); |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(second_path); |
| } |
| |
| // Tests that a secondary profile gets deleted after its primary account was |
| // removed from the system before startup. |
| // A secondary account of the deleted profile gets moved to the primary profile |
| // since it's an only remaining profile. |
| TEST_F(AccountProfileMapperTest, |
| RemovePrimaryAccountFromSecondaryProfile_AtInitialization) { |
| base::FilePath other_path = GetProfilePath("Other"); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapperNonInitialized({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| SetPrimaryAccountForProfile(other_path, "B"); |
| CompleteFacadeGetAccountsGaia({"A", "C"}); |
| ExpectAccountsInStorage({{main_path(), {"A", "C"}}}); |
| ProfileAttributesStorageTestObserver(attributes_storage()) |
| .WaitForProfileBeingDeleted(other_path); |
| } |
| |
| // Tests that a 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"); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}}); |
| SetPrimaryAccountForProfile(other_path, "B"); |
| TestMapperUpdateGaia(mapper.get(), |
| /*accounts_in_facade=*/{"A", "B"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{other_path, {"C"}}}, |
| /*expected_accounts_in_storage=*/ |
| {{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) { |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{main_path(), {"A", "B"}}}); |
| SetPrimaryAccountForProfile(main_path(), "A"); |
| TestMapperUpdateGaia(mapper.get(), |
| /*accounts_in_facade=*/{"B"}, |
| /*expected_accounts_upserted=*/{}, |
| /*expected_accounts_removed=*/{{main_path(), {"A"}}}, |
| /*expected_accounts_in_storage=*/ |
| {{main_path(), {"B"}}}); |
| } |
| |
| TEST_F(AccountProfileMapperTest, ShowAddAccountDialogBeforeInit) { |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapperNonInitialized({{main_path(), {"A"}}}); |
| AccountManagerFacade::AccountAdditionSource source = |
| AccountManagerFacade::AccountAdditionSource::kArc; |
| // The facade is not called before initialization. |
| EXPECT_CALL(*mock_facade(), ShowAddAccountDialog(testing::_, testing::_)) |
| .Times(0); |
| mapper->ShowAddAccountDialog( |
| main_path(), source, |
| base::OnceCallback<void(const absl::optional<Account>&)>()); |
| 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"); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| GetMapper({{main_path(), {"A"}}, {other_path, {"B"}}}); |
| base::MockOnceCallback<void(const absl::optional<Account>&)> |
| account_added_callback; |
| AccountManagerFacade::AccountAdditionSource source = |
| AccountManagerFacade::AccountAdditionSource::kArc; |
| Account account_c = AccountFromGaiaID("C"); |
| // Add account to existing profile. |
| ExpectFacadeShowAddAccountDialogCalled(source, account_c); |
| EXPECT_CALL(account_added_callback, |
| Run(OptionalAccountEqual(absl::make_optional(account_c)))); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| // Add account that already exists. |
| ExpectFacadeShowAddAccountDialogCalled(source, account_c); |
| EXPECT_CALL(account_added_callback, |
| Run(OptionalAccountEqual(absl::optional<Account>()))); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| // Add account to non-existing profile. |
| ExpectFacadeShowAddAccountDialogCalled(source, AccountFromGaiaID("D")); |
| EXPECT_CALL(account_added_callback, |
| Run(OptionalAccountEqual(absl::optional<Account>()))); |
| mapper->ShowAddAccountDialog(GetProfilePath("UnknownProfile"), source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| // Non-Gaia account. |
| ExpectFacadeShowAddAccountDialogCalled(source, NonGaiaAccountFromID("D")); |
| EXPECT_CALL(account_added_callback, |
| Run(OptionalAccountEqual(absl::optional<Account>()))); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| // Flow aborted. |
| ExpectFacadeShowAddAccountDialogCalled(source, absl::nullopt); |
| EXPECT_CALL(account_added_callback, |
| Run(OptionalAccountEqual(absl::optional<Account>()))); |
| mapper->ShowAddAccountDialog(other_path, source, |
| account_added_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&account_added_callback); |
| testing::Mock::VerifyAndClearExpectations(mock_facade()); |
| } |