chromium/chrome/browser/lacros/account_manager/account_profile_mapper_unittest.cc

// 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);
}