text: // Copyright 2018 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 "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.h"
  
  #include <limits>
  #include <memory>
  #include <set>
  #include <string>
  #include <utility>
  
  #include "ash/components/account_manager/account_manager.h"
  #include "ash/components/account_manager/account_manager_ash.h"
  #include "base/bind.h"
  #include "base/containers/contains.h"
  #include "base/files/scoped_temp_dir.h"
  #include "base/macros.h"
  #include "base/memory/scoped_refptr.h"
  #include "base/test/gmock_callback_support.h"
  #include "base/test/task_environment.h"
  #include "components/account_manager_core/account.h"
  #include "components/account_manager_core/account_manager_facade.h"
  #include "components/account_manager_core/account_manager_facade_impl.h"
  #include "components/signin/internal/identity_manager/account_tracker_service.h"
  #include "components/signin/internal/identity_manager/profile_oauth2_token_service_observer.h"
  #include "components/signin/public/base/signin_pref_names.h"
  #include "components/signin/public/base/test_signin_client.h"
  #include "components/signin/public/identity_manager/account_info.h"
  #include "components/sync_preferences/testing_pref_service_syncable.h"
  #include "google_apis/gaia/gaia_urls.h"
  #include "google_apis/gaia/oauth2_access_token_consumer.h"
  #include "google_apis/gaia/oauth2_access_token_fetcher.h"
  #include "google_apis/gaia/oauth2_access_token_manager_test_util.h"
  #include "services/network/test/test_network_connection_tracker.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace signin {
  
  namespace {
  
  using ::account_manager::AccountManagerFacade;
  using ::ash::AccountManager;
  
  constexpr char kGaiaId[] = "gaia-id";
  constexpr char kGaiaToken[] = "gaia-token";
  constexpr char kUserEmail[] = "user@gmail.com";
  
  class AccessTokenConsumer : public OAuth2AccessTokenConsumer {
   public:
    AccessTokenConsumer() = default;
    ~AccessTokenConsumer() override = default;
  
    void OnGetTokenSuccess(const TokenResponse& token_response) override {
      ++num_access_token_fetch_success_;
    }
  
    void OnGetTokenFailure(const GoogleServiceAuthError& error) override {
      ++num_access_token_fetch_failure_;
    }
  
    int num_access_token_fetch_success_ = 0;
    int num_access_token_fetch_failure_ = 0;
  
   private:
    DISALLOW_COPY_AND_ASSIGN(AccessTokenConsumer);
  };
  
  class TestOAuth2TokenServiceObserver
      : public ProfileOAuth2TokenServiceObserver {
   public:
    // |delegate| is a non-owning pointer to an
    // |ProfileOAuth2TokenServiceDelegate| that MUST outlive |this| instance.
    explicit TestOAuth2TokenServiceObserver(
        ProfileOAuth2TokenServiceDelegate* delegate)
        : delegate_(delegate) {
      delegate_->AddObserver(this);
    }
  
    ~TestOAuth2TokenServiceObserver() override {
      delegate_->RemoveObserver(this);
    }
  
    void StartBatchChanges() {
      EXPECT_FALSE(is_inside_batch_);
      is_inside_batch_ = true;
  
      // Start a new batch
      batch_change_records_.emplace_back(std::vector<CoreAccountId>());
    }
  
    void OnEndBatchChanges() override {
      EXPECT_TRUE(is_inside_batch_);
      is_inside_batch_ = false;
    }
  
    void OnRefreshTokenAvailable(const CoreAccountId& account_id) override {
      if (!is_inside_batch_)
        StartBatchChanges();
  
      // We should not be seeing any cached errors for a freshly updated account,
      // except when they have been generated by us (i.e.
      // CREDENTIALS_REJECTED_BY_CLIENT).
      const GoogleServiceAuthError error = delegate_->GetAuthError(account_id);
      EXPECT_TRUE((error == GoogleServiceAuthError::AuthErrorNone()) ||
                  (error.state() ==
                       GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS &&
                   error.GetInvalidGaiaCredentialsReason() ==
                       GoogleServiceAuthError::InvalidGaiaCredentialsReason::
                           CREDENTIALS_REJECTED_BY_CLIENT));
  
      account_ids_.insert(account_id);
  
      // Record the |account_id| in the last batch.
      batch_change_records_.rbegin()->emplace_back(account_id);
    }
  
    void OnRefreshTokensLoaded() override { refresh_tokens_loaded_ = true; }
  
    void OnRefreshTokenRevoked(const CoreAccountId& account_id) override {
      if (!is_inside_batch_)
        StartBatchChanges();
  
      account_ids_.erase(account_id);
      // Record the |account_id| in the last batch.
      batch_change_records_.rbegin()->emplace_back(account_id);
    }
  
    void OnAuthErrorChanged(const CoreAccountId& account_id,
                            const GoogleServiceAuthError& auth_error) override {
      last_err_account_id_ = account_id;
      last_err_ = auth_error;
      on_auth_error_changed_calls++;
    }
  
    int on_auth_error_changed_calls = 0;
  
    CoreAccountId last_err_account_id_;
    GoogleServiceAuthError last_err_;
    std::set<CoreAccountId> account_ids_;
    bool is_inside_batch_ = false;
    bool refresh_tokens_loaded_ = false;
  
    // Records batch changes for later verification. Each index of this vector
    // represents a batch change. Each batch change is a vector of account ids for
    // which |OnRefreshTokenAvailable| is called.
    std::vector<std::vector<CoreAccountId>> batch_change_records_;
  
    // Non-owning pointer.
    ProfileOAuth2TokenServiceDelegate* const delegate_;
  };
  
  class MockProfileOAuth2TokenServiceObserver
      : public ProfileOAuth2TokenServiceObserver {
   public:
    explicit MockProfileOAuth2TokenServiceObserver(
        ProfileOAuth2TokenServiceDelegate* delegate)
        : delegate_(delegate) {
      delegate_->AddObserver(this);
    }
  
    ~MockProfileOAuth2TokenServiceObserver() override {
      delegate_->RemoveObserver(this);
    }
  
    MockProfileOAuth2TokenServiceObserver(
        const MockProfileOAuth2TokenServiceObserver&) = delete;
    MockProfileOAuth2TokenServiceObserver& operator=(
        const MockProfileOAuth2TokenServiceObserver&) = delete;
  
    MOCK_METHOD(void, OnRefreshTokensLoaded, (), (override));
    MOCK_METHOD(void,
                OnRefreshTokenAvailable,
                (const CoreAccountId&),
                (override));
    MOCK_METHOD(void, OnRefreshTokenRevoked, (const CoreAccountId&), (override));
  
   private:
    ProfileOAuth2TokenServiceDelegate* const delegate_;
  };
  
  class MockAccountManagerFacadeObserver : public AccountManagerFacade::Observer {
   public:
    MockAccountManagerFacadeObserver() = default;
    MockAccountManagerFacadeObserver(const MockAccountManagerFacadeObserver&) =
        delete;
    MockAccountManagerFacadeObserver& operator=(
        const MockAccountManagerFacadeObserver&) = delete;
    ~MockAccountManagerFacadeObserver() override = default;
  
    MOCK_METHOD(void,
                OnAccountUpserted,
                (const account_manager::Account& account),
                (override));
    MOCK_METHOD(void,
                OnAccountRemoved,
                (const account_manager::Account& account),
                (override));
  };
  
  }  // namespace
  
  class ProfileOAuth2TokenServiceDelegateChromeOSTest : public testing::Test {
   public:
    ProfileOAuth2TokenServiceDelegateChromeOSTest() {}
    ~ProfileOAuth2TokenServiceDelegateChromeOSTest() override = default;
  
   protected:
    void SetUp() override {
      ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
      AccountTrackerService::RegisterPrefs(pref_service_.registry());
      AccountManager::RegisterPrefs(pref_service_.registry());
  
      client_ = std::make_unique<TestSigninClient>(&pref_service_);
      account_manager_.Initialize(tmp_dir_.GetPath(),
                                  client_->GetURLLoaderFactory(),
                                  immediate_callback_runner_);
      account_manager_.SetPrefService(&pref_service_);
      task_environment_.RunUntilIdle();
  
      account_manager_ash_ =
          std::make_unique<crosapi::AccountManagerAsh>(&account_manager_);
      account_manager_facade_ =
          CreateAccountManagerFacade(account_manager_ash_.get());
  
      account_tracker_service_.Initialize(&pref_service_, base::FilePath());
  
      account_info_ = CreateAccountInfoTestFixture(kGaiaId, kUserEmail);
      account_tracker_service_.SeedAccountInfo(account_info_);
      gaia_account_key_ = {account_info_.gaia,
                           account_manager::AccountType::kGaia};
      ad_account_key_ = {"object-guid",
                         account_manager::AccountType::kActiveDirectory};
  
      delegate_ = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
          &account_tracker_service_,
          network::TestNetworkConnectionTracker::GetInstance(),
          account_manager_facade_.get(), true /* is_regular_profile */);
  
      LoadCredentialsAndWaitForCompletion(
          account_info_.account_id /* primary_account_id */);
    }
  
    AccountInfo CreateAccountInfoTestFixture(const std::string& gaia_id,
                                             const std::string& email) {
      AccountInfo account_info;
  
      account_info.gaia = gaia_id;
      account_info.email = email;
      account_info.full_name = "name";
      account_info.given_name = "name";
      account_info.hosted_domain = "example.com";
      account_info.locale = "en";
      account_info.picture_url = "https://example.com";
      account_info.is_child_account = false;
      account_info.account_id = account_tracker_service_.PickAccountIdForAccount(
          account_info.gaia, account_info.email);
  
      // Cannot use |ASSERT_TRUE| due to a |void| return type in an |ASSERT_TRUE|
      // branch.
      EXPECT_TRUE(account_info.IsValid());
  
      return account_info;
    }
  
    void AddSuccessfulOAuthTokenResponse() {
      client_->GetTestURLLoaderFactory()->AddResponse(
          GaiaUrls::GetInstance()->oauth2_token_url().spec(),
          GetValidTokenResponse("token", 3600));
    }
  
    void LoadCredentialsAndWaitForCompletion(
        const CoreAccountId& primary_account_id) {
      MockProfileOAuth2TokenServiceObserver observer(delegate_.get());
      base::RunLoop run_loop;
      EXPECT_CALL(observer, OnRefreshTokensLoaded())
          .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
      delegate_->LoadCredentials(primary_account_id);
      run_loop.Run();
    }
  
    void UpsertAccountAndWaitForCompletion(
        const ::account_manager::AccountKey& account_key,
        const std::string& raw_email,
        const std::string& token) {
      ASSERT_EQ(account_key.account_type, account_manager::AccountType::kGaia);
  
      // `ProfileOAuth2TokenServiceDelegateChromeOS` asynchronously obtains error
      // statuses for Gaia accounts, so we have to wait for a notification from
      // the delegate itself here.
      MockProfileOAuth2TokenServiceObserver observer(delegate_.get());
      base::RunLoop run_loop;
      EXPECT_CALL(observer, OnRefreshTokenAvailable(testing::_))
          .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
      account_manager_.UpsertAccount(account_key, raw_email, token);
      run_loop.Run();
    }
  
    void RemoveAccountAndWaitForCompletion(
        const ::account_manager::AccountKey& account_key) {
      ASSERT_EQ(account_key.account_type, account_manager::AccountType::kGaia);
      MockProfileOAuth2TokenServiceObserver observer(delegate_.get());
      base::RunLoop run_loop;
      EXPECT_CALL(observer, OnRefreshTokenRevoked(testing::_))
          .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
      account_manager_.RemoveAccount(account_key);
      run_loop.Run();
    }
  
    void UpsertActiveDirectoryAccountAndWaitForCompletion(
        const ::account_manager::AccountKey& account_key,
        const std::string& raw_email,
        const std::string& token) {
      ASSERT_EQ(account_key.account_type,
                account_manager::AccountType::kActiveDirectory);
      MockAccountManagerFacadeObserver observer;
      account_manager_facade_->AddObserver(&observer);
  
      base::RunLoop run_loop;
      EXPECT_CALL(observer, OnAccountUpserted(testing::_))
          .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
      account_manager_.UpsertAccount(account_key, raw_email, token);
      run_loop.Run();
  
      account_manager_facade_->RemoveObserver(&observer);
    }
  
    void RemoveActiveDirectoryAccountAndWaitForCompletion(
        const ::account_manager::AccountKey& account_key) {
      ASSERT_EQ(account_key.account_type,
                account_manager::AccountType::kActiveDirectory);
      MockAccountManagerFacadeObserver observer;
      account_manager_facade_->AddObserver(&observer);
  
      base::RunLoop run_loop;
      EXPECT_CALL(observer, OnAccountRemoved(testing::_))
          .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
      account_manager_.RemoveAccount(account_key);
      run_loop.Run();
  
      account_manager_facade_->RemoveObserver(&observer);
    }
  
    std::unique_ptr<AccountManagerFacade> CreateAccountManagerFacade(
        crosapi::AccountManagerAsh* account_manager_ash) {
      DCHECK(account_manager_ash);
      mojo::Remote<crosapi::mojom::AccountManager> remote;
      account_manager_ash->BindReceiver(remote.BindNewPipeAndPassReceiver());
      return std::make_unique<account_manager::AccountManagerFacadeImpl>(
          std::move(remote),
          std::numeric_limits<uint32_t>::max() /* remote_version */);
    }
  
    base::test::TaskEnvironment task_environment_;
  
    base::ScopedTempDir tmp_dir_;
    AccountInfo account_info_;
    account_manager::AccountKey gaia_account_key_;
    account_manager::AccountKey ad_account_key_;
    AccountTrackerService account_tracker_service_;
    AccountManager account_manager_;
    std::unique_ptr<crosapi::AccountManagerAsh> account_manager_ash_;
    std::unique_ptr<account_manager::AccountManagerFacade>
        account_manager_facade_;
    std::unique_ptr<ProfileOAuth2TokenServiceDelegateChromeOS> delegate_;
    AccountManager::DelayNetworkCallRunner immediate_callback_runner_ =
        base::BindRepeating(
            [](base::OnceClosure closure) -> void { std::move(closure).Run(); });
    sync_preferences::TestingPrefServiceSyncable pref_service_;
    std::unique_ptr<TestSigninClient> client_;
  
   private:
    DISALLOW_COPY_AND_ASSIGN(ProfileOAuth2TokenServiceDelegateChromeOSTest);
  };
  
  // Refresh tokens should load successfully for non-regular (Signin and Lock
  // Screen) Profiles.
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         RefreshTokensAreLoadedForNonRegularProfiles) {
    // Create an instance of Account Manager but do not
    // |AccountManager::Initialize| it. This mimics Signin and Lock Screen Profile
    // behaviour.
    AccountManager account_manager;
  
    auto delegate = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
        &account_tracker_service_,
        network::TestNetworkConnectionTracker::GetInstance(),
        account_manager_facade_.get(), false /* is_regular_profile */);
    TestOAuth2TokenServiceObserver observer(delegate.get());
  
    // Test that LoadCredentials works as expected.
    EXPECT_FALSE(observer.refresh_tokens_loaded_);
    delegate->LoadCredentials(CoreAccountId() /* primary_account_id */);
    EXPECT_TRUE(observer.refresh_tokens_loaded_);
    EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
              delegate->load_credentials_state());
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         RefreshTokenIsAvailableReturnsTrueForValidGaiaTokens) {
    EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
              delegate_->load_credentials_state());
  
    EXPECT_FALSE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
    EXPECT_FALSE(
        base::Contains(delegate_->GetAccounts(), account_info_.account_id));
  
    UpsertAccountAndWaitForCompletion(gaia_account_key_, kUserEmail, kGaiaToken);
  
    EXPECT_TRUE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
    EXPECT_TRUE(
        base::Contains(delegate_->GetAccounts(), account_info_.account_id));
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         RefreshTokenIsAvailableReturnsTrueForInvalidGaiaTokens) {
    EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
              delegate_->load_credentials_state());
  
    EXPECT_FALSE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
    EXPECT_FALSE(
        base::Contains(delegate_->GetAccounts(), account_info_.account_id));
  
    UpsertAccountAndWaitForCompletion(gaia_account_key_, kUserEmail,
                                      AccountManager::kInvalidToken);
  
    EXPECT_TRUE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
    EXPECT_TRUE(
        base::Contains(delegate_->GetAccounts(), account_info_.account_id));
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversAreNotifiedOnAuthErrorChange) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    auto error =
        GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);
  
    delegate_->UpdateAuthError(account_info_.account_id, error);
    EXPECT_EQ(error, delegate_->GetAuthError(account_info_.account_id));
    EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
    EXPECT_EQ(error, observer.last_err_);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversAreNotNotifiedIfErrorDidntChange) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    auto error =
        GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);
  
    delegate_->UpdateAuthError(account_info_.account_id, error);
    EXPECT_EQ(1, observer.on_auth_error_changed_calls);
    delegate_->UpdateAuthError(account_info_.account_id, error);
    EXPECT_EQ(1, observer.on_auth_error_changed_calls);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversAreNotifiedIfErrorDidChange) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    delegate_->UpdateAuthError(
        account_info_.account_id,
        GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR));
    EXPECT_EQ(1, observer.on_auth_error_changed_calls);
  
    delegate_->UpdateAuthError(
        account_info_.account_id,
        GoogleServiceAuthError(
            GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
    EXPECT_EQ(2, observer.on_auth_error_changed_calls);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversAreNotifiedOnCredentialsInsertion) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
  
    EXPECT_EQ(1UL, observer.account_ids_.size());
    EXPECT_EQ(account_info_.account_id, *observer.account_ids_.begin());
    EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
    EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), observer.last_err_);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversDoNotSeeCachedErrorsOnCredentialsUpdate) {
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
  
    // Deliberately add an error.
    auto error =
        GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);
    delegate_->UpdateAuthError(account_info_.account_id, error);
  
    // Update credentials. The delegate will check if see cached errors.
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      "new-token");
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversDoNotSeeCachedErrorsOnAccountRemoval) {
    auto error =
        GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
    // Deliberately add an error.
    delegate_->UpdateAuthError(account_info_.account_id, error);
    RemoveAccountAndWaitForCompletion(gaia_account_key_);
    EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
              delegate_->GetAuthError(account_info_.account_id));
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         DummyTokensArePreEmptivelyRejected) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      AccountManager::kInvalidToken);
  
    const GoogleServiceAuthError error =
        delegate_->GetAuthError(account_info_.account_id);
    EXPECT_EQ(GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS,
              error.state());
    EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason::
                  CREDENTIALS_REJECTED_BY_CLIENT,
              error.GetInvalidGaiaCredentialsReason());
  
    // Observer notification should also have notified about the same error.
    EXPECT_EQ(error, observer.last_err_);
    EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversAreNotifiedOnCredentialsUpdate) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
  
    EXPECT_EQ(1UL, observer.account_ids_.size());
    EXPECT_EQ(account_info_.account_id, *observer.account_ids_.begin());
    EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
    EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), observer.last_err_);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversAreNotNotifiedIfCredentialsAreNotUpdated) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
  
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
    observer.account_ids_.clear();
    observer.last_err_account_id_ = CoreAccountId();
    // UpsertAccountAndWaitForCompletion can't be used here, as it uses an
    // observer to wait for completion. Observers aren't called in this flow, so
    // UpsertAccountAndWaitForCompletion would hang here.
    account_manager_.UpsertAccount(gaia_account_key_, account_info_.email,
                                   kGaiaToken);
    task_environment_.RunUntilIdle();
  
    EXPECT_TRUE(observer.account_ids_.empty());
    EXPECT_TRUE(observer.last_err_account_id_.empty());
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         BatchChangeObserversAreNotifiedOnCredentialsUpdate) {
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
  
    EXPECT_EQ(1UL, observer.batch_change_records_.size());
    EXPECT_EQ(1UL, observer.batch_change_records_[0].size());
    EXPECT_EQ(account_info_.account_id, observer.batch_change_records_[0][0]);
  }
  
  // If observers register themselves with |ProfileOAuth2TokenServiceDelegate|
  // before |AccountManager| has been initialized, they should receive all the
  // accounts stored in |AccountManager| in a single batch.
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         BatchChangeObserversAreNotifiedOncePerBatch) {
    // Setup
    AccountInfo account1 = CreateAccountInfoTestFixture(
        "1" /* gaia_id */, "user1@example.com" /* email */);
    AccountInfo account2 = CreateAccountInfoTestFixture(
        "2" /* gaia_id */, "user2@example.com" /* email */);
  
    account_tracker_service_.SeedAccountInfo(account1);
    account_tracker_service_.SeedAccountInfo(account2);
    account_manager_.UpsertAccount(
        account_manager::AccountKey{account1.gaia,
                                    account_manager::AccountType::kGaia},
        "user1@example.com", "token1");
    account_manager_.UpsertAccount(
        account_manager::AccountKey{account2.gaia,
                                    account_manager::AccountType::kGaia},
        "user2@example.com", "token2");
    task_environment_.RunUntilIdle();
  
    AccountManager account_manager;
    // AccountManager will not be fully initialized until
    // |task_environment_.RunUntilIdle()| is called.
    account_manager.Initialize(tmp_dir_.GetPath(), client_->GetURLLoaderFactory(),
                               immediate_callback_runner_);
    account_manager.SetPrefService(&pref_service_);
  
    auto account_manager_ash =
        std::make_unique<crosapi::AccountManagerAsh>(&account_manager);
    auto account_manager_facade =
        CreateAccountManagerFacade(account_manager_ash.get());
  
    // Register callbacks before AccountManager has been fully initialized.
    auto delegate = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
        &account_tracker_service_,
        network::TestNetworkConnectionTracker::GetInstance(),
        account_manager_facade.get(), true /* is_regular_profile */);
    delegate->LoadCredentials(account1.account_id /* primary_account_id */);
    TestOAuth2TokenServiceObserver observer(delegate.get());
    // Wait until AccountManager is fully initialized.
    task_environment_.RunUntilIdle();
  
    // Tests
  
    // The observer should receive at least one batch change callback: batch of
    // all accounts stored in AccountManager: because of the delegate's
    // invocation of |AccountManagerFacade::GetAccounts| in |LoadCredentials|.
    EXPECT_FALSE(observer.batch_change_records_.empty());
    const std::vector<CoreAccountId>& first_batch =
        observer.batch_change_records_[0];
    EXPECT_EQ(2UL, first_batch.size());
    EXPECT_TRUE(base::Contains(first_batch, account1.account_id));
    EXPECT_TRUE(base::Contains(first_batch, account2.account_id));
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         GetAccountsShouldNotReturnAdAccounts) {
    EXPECT_TRUE(delegate_->GetAccounts().empty());
  
    // Insert an Active Directory account into AccountManager.
    UpsertActiveDirectoryAccountAndWaitForCompletion(
        ad_account_key_, kUserEmail, AccountManager::kActiveDirectoryDummyToken);
  
    // OAuth delegate should not return Active Directory accounts.
    EXPECT_TRUE(delegate_->GetAccounts().empty());
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         GetAccountsReturnsGaiaAccounts) {
    EXPECT_TRUE(delegate_->GetAccounts().empty());
  
    UpsertAccountAndWaitForCompletion(gaia_account_key_, kUserEmail, kGaiaToken);
  
    std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
    EXPECT_EQ(1UL, accounts.size());
    EXPECT_EQ(account_info_.account_id, accounts[0]);
  }
  
  // |GetAccounts| should return all known Gaia accounts, whether or not they have
  // a "valid" refresh token stored against them.
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         GetAccountsReturnsGaiaAccountsWithInvalidTokens) {
    EXPECT_TRUE(delegate_->GetAccounts().empty());
  
    UpsertAccountAndWaitForCompletion(gaia_account_key_, kUserEmail,
                                      AccountManager::kInvalidToken);
  
    std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
    EXPECT_EQ(1UL, accounts.size());
    EXPECT_EQ(account_info_.account_id, accounts[0]);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         RefreshTokenMustBeAvailableForAllAccountsReturnedByGetAccounts) {
    EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
              delegate_->load_credentials_state());
    EXPECT_TRUE(delegate_->GetAccounts().empty());
    const std::string kUserEmail2 = "random-email2@example.com";
    const std::string kUserEmail3 = "random-email3@example.com";
  
    // Insert 2 Gaia accounts and 1 Active Directory Account. Of the 2 Gaia
    // accounts, 1 has a valid refresh token and 1 has a dummy token.
    UpsertAccountAndWaitForCompletion(gaia_account_key_, kUserEmail, kGaiaToken);
  
    account_manager::AccountKey gaia_account_key2{
        "random-gaia-id", account_manager::AccountType::kGaia};
    account_tracker_service_.SeedAccountInfo(
        CreateAccountInfoTestFixture(gaia_account_key2.id, kUserEmail2));
    UpsertAccountAndWaitForCompletion(gaia_account_key2, kUserEmail2,
                                      AccountManager::kInvalidToken);
  
    UpsertActiveDirectoryAccountAndWaitForCompletion(
        ad_account_key_, kUserEmail3, AccountManager::kActiveDirectoryDummyToken);
  
    // Verify.
    const std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
    // 2 Gaia accounts should be returned.
    EXPECT_EQ(2UL, accounts.size());
    // And |RefreshTokenIsAvailable| should return true for these accounts.
    for (const CoreAccountId& account : accounts) {
      EXPECT_TRUE(delegate_->RefreshTokenIsAvailable(account));
    }
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         UpdateCredentialsSucceeds) {
    EXPECT_TRUE(delegate_->GetAccounts().empty());
  
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
  
    std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
    EXPECT_EQ(1UL, accounts.size());
    EXPECT_EQ(account_info_.account_id, accounts[0]);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         ObserversAreNotifiedOnAccountRemoval) {
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
  
    TestOAuth2TokenServiceObserver observer(delegate_.get());
    RemoveAccountAndWaitForCompletion(gaia_account_key_);
  
    EXPECT_EQ(1UL, observer.batch_change_records_.size());
    EXPECT_EQ(1UL, observer.batch_change_records_[0].size());
    EXPECT_EQ(account_info_.account_id, observer.batch_change_records_[0][0]);
    EXPECT_TRUE(observer.account_ids_.empty());
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         AccountRemovedRightAfterAccountUpserted) {
    // Use StrictMock to verify that no observer methods are invoked.
    testing::StrictMock<MockProfileOAuth2TokenServiceObserver> observer(
        delegate_.get());
  
    // `UpsertAccount` will asynchronously send a notification through
    // `AccountManagerFacade`, so `RemoveAccount` should remove the account before
    // `ProfileOAuth2TokenServiceDelegateChromeOS` can add this account.
    account_manager_.UpsertAccount(gaia_account_key_, account_info_.email,
                                   kGaiaToken);
    account_manager_.RemoveAccount(gaia_account_key_);
  
    task_environment_.RunUntilIdle();
  
    EXPECT_EQ(0UL, delegate_->GetAccounts().size());
    // Destroying the mock will verify no observer methods were called.
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         PreexistingAccountRemovedRightAfterAccountTokenUpdate) {
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
    EXPECT_EQ(1UL, delegate_->GetAccounts().size());
  
    base::RunLoop run_loop;
    MockProfileOAuth2TokenServiceObserver observer(delegate_.get());
  
    // Since this account already existed, `RemoveAccount` should trigger
    // `OnRefreshTokenRevoked` call to observers.
    EXPECT_CALL(observer, OnRefreshTokenRevoked(account_info_.account_id))
        .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
  
    account_manager_.UpsertAccount(gaia_account_key_, account_info_.email,
                                   AccountManager::kInvalidToken);
    account_manager_.RemoveAccount(gaia_account_key_);
  
    run_loop.Run();
  
    EXPECT_EQ(0UL, delegate_->GetAccounts().size());
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         SigninErrorObserversAreNotifiedOnAuthErrorChange) {
    auto error =
        GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);
  
    delegate_->UpdateAuthError(account_info_.account_id, error);
  
    EXPECT_EQ(error, delegate_->GetAuthError(account_info_.account_id));
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         TransientErrorsAreNotShown) {
    auto transient_error = GoogleServiceAuthError(
        GoogleServiceAuthError::State::SERVICE_UNAVAILABLE);
    EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
              delegate_->GetAuthError(account_info_.account_id));
  
    delegate_->UpdateAuthError(account_info_.account_id, transient_error);
  
    EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
              delegate_->GetAuthError(account_info_.account_id));
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         BackOffIsTriggerredForTransientErrors) {
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
    auto transient_error = GoogleServiceAuthError(
        GoogleServiceAuthError::State::SERVICE_UNAVAILABLE);
    delegate_->UpdateAuthError(account_info_.account_id, transient_error);
    // Add a dummy success response. The actual network call has not been made
    // yet.
    AddSuccessfulOAuthTokenResponse();
  
    // Transient error should repeat until backoff period expires.
    AccessTokenConsumer access_token_consumer;
    EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
    EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_failure_);
    std::vector<std::string> scopes{"scope"};
    std::unique_ptr<OAuth2AccessTokenFetcher> fetcher =
        delegate_->CreateAccessTokenFetcher(account_info_.account_id,
                                            delegate_->GetURLLoaderFactory(),
                                            &access_token_consumer);
    task_environment_.RunUntilIdle();
    fetcher->Start("client_id", "client_secret", scopes);
    task_environment_.RunUntilIdle();
    EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
    EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
    // Expect a positive backoff time.
    EXPECT_GT(delegate_->backoff_entry_.GetTimeUntilRelease(), base::TimeDelta());
  
    // Pretend that backoff has expired and try again.
    delegate_->backoff_entry_.SetCustomReleaseTime(base::TimeTicks());
    fetcher = delegate_->CreateAccessTokenFetcher(
        account_info_.account_id, delegate_->GetURLLoaderFactory(),
        &access_token_consumer);
    fetcher->Start("client_id", "client_secret", scopes);
    task_environment_.RunUntilIdle();
    EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_success_);
    EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
  }
  
  TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
         BackOffIsResetOnNetworkChange) {
    UpsertAccountAndWaitForCompletion(gaia_account_key_, account_info_.email,
                                      kGaiaToken);
    auto transient_error = GoogleServiceAuthError(
        GoogleServiceAuthError::State::SERVICE_UNAVAILABLE);
    delegate_->UpdateAuthError(account_info_.account_id, transient_error);
    // Add a dummy success response. The actual network call has not been made
    // yet.
    AddSuccessfulOAuthTokenResponse();
  
    // Transient error should repeat until backoff period expires.
    AccessTokenConsumer access_token_consumer;
    EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
    EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_failure_);
    std::vector<std::string> scopes{"scope"};
    std::unique_ptr<OAuth2AccessTokenFetcher> fetcher =
        delegate_->CreateAccessTokenFetcher(account_info_.account_id,
                                            delegate_->GetURLLoaderFactory(),
                                            &access_token_consumer);
    task_environment_.RunUntilIdle();
    fetcher->Start("client_id", "client_secret", scopes);
    task_environment_.RunUntilIdle();
    EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
    EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
    // Expect a positive backoff time.
    EXPECT_GT(delegate_->backoff_entry_.GetTimeUntilRelease(), base::TimeDelta());
  
    // Notify of network change and ensure that request now runs.
    delegate_->OnConnectionChanged(
        network::mojom::ConnectionType::CONNECTION_WIFI);
    fetcher = delegate_->CreateAccessTokenFetcher(
        account_info_.account_id, delegate_->GetURLLoaderFactory(),
        &access_token_consumer);
    fetcher->Start("client_id", "client_secret", scopes);
    task_environment_.RunUntilIdle();
    EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_success_);
    EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
  }
  
  }  // namespace signin
  