| // 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_manager_util.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_forward.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_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_manager_facade.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 testing::Field; |
| using testing::UnorderedElementsAre; |
| |
| 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>>; |
| |
| // 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")}; |
| } |
| |
| } // namespace |
| |
| class AccountManagerUtilTest : public testing::Test { |
| public: |
| AccountManagerUtilTest() |
| : 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()); |
| }); |
| } |
| |
| ~AccountManagerUtilTest() override = default; |
| |
| 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); |
| } |
| |
| std::unique_ptr<AccountProfileMapper> CreateMapper( |
| const AccountMapping& accounts) { |
| // Mapper asks the facade for accounts upon construction. |
| std::vector<Account> accounts_in_facade; |
| for (const auto& path_accounts_pair : accounts) { |
| for (const std::string& id : path_accounts_pair.second) { |
| accounts_in_facade.push_back(AccountFromGaiaID(id)); |
| } |
| } |
| ON_CALL(mock_facade_, GetAccounts(testing::_)) |
| .WillByDefault( |
| [&accounts_in_facade]( |
| base::OnceCallback<void(const std::vector<Account>&)> |
| callback) { std::move(callback).Run(accounts_in_facade); }); |
| auto mapper = std::make_unique<AccountProfileMapper>( |
| mock_facade(), attributes_storage(), |
| testing_profile_manager_.local_state()->Get()); |
| SetAccountsInStorage(accounts); |
| return mapper; |
| } |
| |
| // 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; |
| if (path.empty()) |
| continue; // Account is unassigned. |
| 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; |
| if (path.empty()) |
| continue; // Account is unassigned. |
| 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_; |
| testing::NiceMock<account_manager::MockAccountManagerFacade> mock_facade_; |
| TestingProfileManager testing_profile_manager_; |
| base::FilePath main_path_; |
| }; |
| |
| TEST_F(AccountManagerUtilTest, GetAllAvailableAccounts) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath third_path = GetProfilePath("Third"); |
| base::FilePath unassigned = base::FilePath(); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| CreateMapper({{main_path(), {"A"}}, |
| {second_path, {"B", "C"}}, |
| {third_path, {"D"}}, |
| {unassigned, {"E"}}}); |
| |
| base::MockRepeatingCallback<void(const std::vector<Account>&)> mock_callback; |
| |
| // Accounts from all other profiles are returned, incl. unassigned. |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"B", kGaiaType}), |
| Field(&Account::key, AccountKey{"C", kGaiaType}), |
| Field(&Account::key, AccountKey{"D", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}), |
| Field(&Account::key, AccountKey{"D", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| // Syncing status does not change anything here. |
| SetPrimaryAccountForProfile(main_path(), "A"); |
| SetPrimaryAccountForProfile(second_path, "B"); |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"B", kGaiaType}), |
| Field(&Account::key, AccountKey{"C", kGaiaType}), |
| Field(&Account::key, AccountKey{"D", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}), |
| Field(&Account::key, AccountKey{"D", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| // Non existing profile path or empty profile path returns all accounts. |
| base::FilePath non_existing_path = base::FilePath("Foo"); |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}), |
| Field(&Account::key, AccountKey{"B", kGaiaType}), |
| Field(&Account::key, AccountKey{"C", kGaiaType}), |
| Field(&Account::key, AccountKey{"D", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))) |
| .Times(2); |
| GetAllAvailableAccounts(mapper.get(), non_existing_path, mock_callback.Get()); |
| GetAllAvailableAccounts(mapper.get(), base::FilePath(), mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| } |
| |
| TEST_F(AccountManagerUtilTest, GetAccountsAvailableAsSecondary_Overlapping) { |
| base::FilePath second_path = GetProfilePath("Second"); |
| base::FilePath unassigned = base::FilePath(); |
| std::unique_ptr<AccountProfileMapper> mapper = |
| CreateMapper({{main_path(), {"A", "B"}}, |
| {second_path, {"B", "C"}}, |
| {unassigned, {"E"}}}); |
| |
| base::MockRepeatingCallback<void(const std::vector<Account>&)> mock_callback; |
| |
| // All accounts not in the current profile are returned, incl. unassigned. |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"C", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| // Syncing status does not change anything here. |
| SetPrimaryAccountForProfile(main_path(), "A"); |
| SetPrimaryAccountForProfile(second_path, "B"); |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"C", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| |
| EXPECT_CALL(mock_callback, |
| Run(testing::UnorderedElementsAre( |
| Field(&Account::key, AccountKey{"A", kGaiaType}), |
| Field(&Account::key, AccountKey{"E", kGaiaType})))); |
| GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get()); |
| testing::Mock::VerifyAndClearExpectations(&mock_callback); |
| } |