// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sync/service/sync_auth_manager.h"

#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/account_managed_status_finder_outcome.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "components/sync/base/features.h"
#include "components/sync/engine/connection_status.h"
#include "components/sync/engine/sync_credentials.h"
#include "net/base/net_errors.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace syncer {

namespace {

using ::testing::Field;
using ::testing::Mock;

class MockDelegate : public SyncAuthManager::Delegate {
 public:
  MockDelegate() = default;
  ~MockDelegate() override = default;

  MOCK_METHOD(void, SyncAuthAccountStateChanged, (), (override));
  MOCK_METHOD(void, SyncAuthCredentialsChanged, (), (override));
};

class SyncAuthManagerTest
    : public testing::Test,
      public ::testing::WithParamInterface<signin::ConsentLevel> {
 protected:
  SyncAuthManagerTest() : identity_env_(&test_url_loader_factory_) {}

  ~SyncAuthManagerTest() override = default;

  std::unique_ptr<SyncAuthManager> CreateAuthManager(
      base::TimeDelta timeout = base::Seconds(5)) {
    return std::make_unique<SyncAuthManager>(identity_env_.identity_manager(),
                                             &delegate_, timeout);
  }

  std::unique_ptr<SyncAuthManager> CreateAuthManagerForLocalSync(
      base::TimeDelta timeout = base::Seconds(5)) {
    return std::make_unique<SyncAuthManager>(nullptr, &delegate_, timeout);
  }

  signin::IdentityTestEnvironment* identity_env() { return &identity_env_; }
  MockDelegate& delegate() { return delegate_; }

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  signin::IdentityTestEnvironment identity_env_;
  testing::NiceMock<MockDelegate> delegate_;
};

INSTANTIATE_TEST_SUITE_P(Dice_Mirror,
                         SyncAuthManagerTest,
                         ::testing::Values(signin::ConsentLevel::kSignin,
                                           signin::ConsentLevel::kSync));

TEST_P(SyncAuthManagerTest, ProvidesNothingInLocalSyncMode) {
  std::unique_ptr<SyncAuthManager> auth_manager =
      CreateAuthManagerForLocalSync();
  EXPECT_TRUE(auth_manager->GetActiveAccountInfo().account_info.IsEmpty());
  syncer::SyncCredentials credentials = auth_manager->GetCredentials();
  EXPECT_TRUE(credentials.access_token_info.token.empty());
  // Note: Calling RegisterForAuthNotifications or any of the Connection*()
  // methods is illegal in local Sync mode, so we don't test that.
}

TEST_P(SyncAuthManagerTest, IgnoresEventsIfNotRegistered) {
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();

  // Fire some auth events. We haven't called RegisterForAuthNotifications, so
  // none of this should result in any callback calls.
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  // Without RegisterForAuthNotifications, the active account should always be
  // reported as empty.
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());
  identity_env()->SetRefreshTokenForPrimaryAccount();
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());

// ChromeOS doesn't support sign-out.
#if !BUILDFLAG(IS_CHROMEOS)
  identity_env()->ClearPrimaryAccount();
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());
#endif  // !BUILDFLAG(IS_CHROMEOS)
}

// ChromeOS doesn't support sign-out.
#if !BUILDFLAG(IS_CHROMEOS)
TEST_P(SyncAuthManagerTest, ForwardsPrimaryAccountEvents) {
  // Start out already signed in before the SyncAuthManager is created.
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();

  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();

  auth_manager->RegisterForAuthNotifications();

  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // Sign out of the account.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged);
  // Note: The ordering of removing the refresh token and the actual sign-out is
  // undefined, see comment on IdentityManager::Observer. So we might or might
  // not get a `credentials_changed` call here.
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(testing::AtMost(1));
  identity_env()->ClearPrimaryAccount();
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());

  // Sign in to a different account.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged);
  CoreAccountId second_account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            second_account_id);
}

TEST_P(SyncAuthManagerTest, NotifiesOfSignoutBeforeAccessTokenIsGone) {
  // Start out already signed in before the SyncAuthManager is created.
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();

  auth_manager->RegisterForAuthNotifications();

  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();

  // Make sure an access token is available.
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Sign out of the account.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).WillOnce([&]() {
    // At the time the callback gets run, the access token should still be here.
    EXPECT_FALSE(
        auth_manager->GetCredentials().access_token_info.token.empty());
  });
  identity_env()->ClearPrimaryAccount();
  // After the signout is complete, the access token should be gone.
  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
  ASSERT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

// Unconsented primary accounts are only supported on Win/Mac/Linux.
// TODO(crbug.com/40066949): Remove once kSync becomes unreachable or is
// deleted from the codebase. See ConsentLevel::kSync documentation for
// details.
#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_P(SyncAuthManagerTest, ForwardsUnconsentedAccountEvents) {
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  ASSERT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());

  // Make a primary account available without Sync consent.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged);
  AccountInfo account_info = identity_env()->MakePrimaryAccountAvailable(
      "test@email.com", signin::ConsentLevel::kSignin);

  EXPECT_FALSE(auth_manager->GetActiveAccountInfo().is_sync_consented);
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.GetAccountId());

  // Make the account Sync-consented.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged);
  signin::PrimaryAccountMutator* primary_account_mutator =
      identity_env()->identity_manager()->GetPrimaryAccountMutator();
  primary_account_mutator->SetPrimaryAccount(
      account_info.GetAccountId(), signin::ConsentLevel::kSync,
      signin_metrics::AccessPoint::kStartPage);

  EXPECT_TRUE(auth_manager->GetActiveAccountInfo().is_sync_consented);
}
#endif  // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) &&
        // !BUILDFLAG(IS_IOS)

// ChromeOS doesn't support sign-out.
#if !BUILDFLAG(IS_CHROMEOS)
TEST_P(SyncAuthManagerTest, ClearsAuthErrorOnSignoutWithRefreshTokenRemoval) {
  // Start out already signed in before the SyncAuthManager is created.
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();

  auth_manager->RegisterForAuthNotifications();

  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);
  ASSERT_EQ(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);

  // Sign out of the account.
  // The ordering of removing the refresh token and the actual sign-out is
  // undefined, see comment on IdentityManager::Observer. Here, explicitly
  // revoke the refresh token first (see also other test below which does *not*
  // remove the refresh token first).
  identity_env()->RemoveRefreshTokenForPrimaryAccount();

  // Note: Things are now in an intermediate state, where the primary account
  // still exists, but doesn't have a refresh token anymore. It doesn't really
  // matter whether the auth error is still there at this point.

  // Actually signing out, i.e. removing the primary account, should clear the
  // auth error, since it's now not meaningful anymore.
  identity_env()->ClearPrimaryAccount();
  EXPECT_EQ(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);
}

TEST_P(SyncAuthManagerTest,
       ClearsAuthErrorOnSignoutWithoutRefreshTokenRemoval) {
  // Start out already signed in before the SyncAuthManager is created.
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();

  auth_manager->RegisterForAuthNotifications();

  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);
  ASSERT_EQ(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);

  // Sign out of the account.
  // The ordering of removing the refresh token and the actual sign-out is
  // undefined, see comment on IdentityManager::Observer. Here, do *not* remove
  // the refresh token first (see also other test above which does remove it).

  // Signing out, i.e. removing the primary account, should clear the auth
  // error, since it's now not meaningful anymore.
  identity_env()->ClearPrimaryAccount();
  EXPECT_EQ(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

TEST_P(SyncAuthManagerTest, DoesNotClearAuthErrorOnSyncDisable) {
  // Start out already signed in before the SyncAuthManager is created.
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();

  auth_manager->RegisterForAuthNotifications();

  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);
  ASSERT_EQ(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);

  auth_manager->ConnectionOpened();

  // Force an auth error by revoking the refresh token.
  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();
  ASSERT_NE(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);

  // Now Sync gets turned off, e.g. because the user disabled it.
  auth_manager->ConnectionClosed();

  // Since the user is still signed in, the auth error should have remained.
  EXPECT_NE(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);
}

TEST_P(SyncAuthManagerTest, ForwardsCredentialsEvents) {
  // Start out already signed in before the SyncAuthManager is created.
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();

  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();

  auth_manager->RegisterForAuthNotifications();

  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();

  // Once an access token is available, the callback should get run.
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Now the refresh token gets updated. The access token will get dropped, so
  // this should cause another notification.
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged);
  identity_env()->SetRefreshTokenForPrimaryAccount();
  ASSERT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());

  // Once a new token is available, there's another notification.
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token_2", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token_2");

  // Revoking the refresh token should also cause the access token to get
  // dropped.
  // Note: On ChromeOS-Ash, setting an invalid refresh token causes 2
  // "credentials changed" events, one for the token change itself, and another
  // one for the auth error caused by the invalid token.
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged)
      .Times(testing::AtLeast(1));
  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();
  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
}

TEST_P(SyncAuthManagerTest, RequestsAccessTokenOnSyncStartup) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));

  EXPECT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
}

TEST_P(SyncAuthManagerTest,
       RetriesAccessTokenFetchWithBackoffOnTransientFailure) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::FromConnectionError(net::ERR_TIMED_OUT));

  // The access token fetch should get retried (with backoff, hence no actual
  // request yet), without exposing an auth error.
  EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest());
  EXPECT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());
}

TEST_P(SyncAuthManagerTest,
       RetriesAccessTokenFetchWithoutBackoffOnceOnFirstCancelTransientFailure) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::CreateRequestCanceled());

  // Expect no backoff the first time the request is canceled.
  EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest());

  // Cancel the retry as well.
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::CreateRequestCanceled());

  // Expect retry with backoff when the first retry was also canceled.
  EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest());
}

TEST_P(SyncAuthManagerTest,
       RetriesAccessTokenFetchOnFirstCancelTransientFailure) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::CreateRequestCanceled());

  // Expect no backoff the first time the request is canceled.
  EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest());

  // Retry is a success.
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));

  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  // Don't expect any backoff when the retry is a success.
  EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest());
}

TEST_P(SyncAuthManagerTest, AbortsAccessTokenFetchOnPersistentFailure) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();

  GoogleServiceAuthError auth_error =
      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
          GoogleServiceAuthError::InvalidGaiaCredentialsReason::
              CREDENTIALS_REJECTED_BY_SERVER);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      auth_error);

  // Auth error should get exposed; no retry.
  EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest());
  EXPECT_EQ(auth_manager->GetLastAuthError(), auth_error);
}

TEST_P(SyncAuthManagerTest, FetchesNewAccessTokenWithBackoffOnServerError) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // The server is returning AUTH_ERROR - maybe something's wrong with the
  // token we got.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);

  // The access token fetch should get retried (with backoff, hence no actual
  // request yet), without exposing an auth error.
  EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest());
  EXPECT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());
}

TEST_P(SyncAuthManagerTest, DoesNotExposeServerError) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Now a server error happens.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_SERVER_ERROR);

  // The error should not be reported as it is transient.
  EXPECT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());
  EXPECT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
}

TEST_P(SyncAuthManagerTest, ClearsServerErrorOnSyncDisable) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // The server returns an auth error.
  GoogleServiceAuthError auth_error =
      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
          GoogleServiceAuthError::InvalidGaiaCredentialsReason::
              CREDENTIALS_REJECTED_BY_SERVER);
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      auth_error);

  ASSERT_NE(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // Now Sync gets turned off, e.g. because the user disabled it.
  auth_manager->ConnectionClosed();

  // This should not have cleared the auth error.
  EXPECT_NE(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());
}

TEST_P(SyncAuthManagerTest, RequestsNewAccessTokenOnExpiry) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  ASSERT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // But then the token expires, resulting in an auth error from the server.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);

  // Should immediately drop the access token and fetch a new one (no backoff).
  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token_2", base::Time::Now() + base::Hours(1));
  EXPECT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token_2");
}

TEST_P(SyncAuthManagerTest, RequestsNewAccessTokenOnRefreshTokenUpdate) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  ASSERT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // But then the refresh token changes.
  identity_env()->SetRefreshTokenForPrimaryAccount();

  // Should immediately drop the access token and fetch a new one (no backoff).
  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token_2", base::Time::Now() + base::Hours(1));
  EXPECT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token_2");
}

TEST_P(SyncAuthManagerTest, DoesNotRequestAccessTokenAutonomously) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // Do *not* call ConnectionStatusChanged here (which is what usually kicks off
  // the token fetch).

  // Now the refresh token gets updated. If we already had an access token
  // before, then this should trigger a new fetch. But since that initial fetch
  // never happened (e.g. because Sync is turned off), this should do nothing.
  base::MockCallback<base::OnceClosure> access_token_requested;
  EXPECT_CALL(access_token_requested, Run()).Times(0);
  identity_env()->SetCallbackForNextAccessTokenRequest(
      access_token_requested.Get());
  identity_env()->SetRefreshTokenForPrimaryAccount();

  // Make sure no access token request was sent. Since the request goes through
  // posted tasks, we have to spin the message loop.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
}

TEST_P(SyncAuthManagerTest, ClearsCredentialsOnRefreshTokenRemoval) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  ASSERT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // But then the refresh token gets revoked. No new access token should get
  // requested due to this.
  base::MockCallback<base::OnceClosure> access_token_requested;
  EXPECT_CALL(access_token_requested, Run()).Times(0);
  identity_env()->SetCallbackForNextAccessTokenRequest(
      access_token_requested.Get());
  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();

  // Should immediately drop the access token and expose an auth error.
  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
  EXPECT_NE(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // No new access token should have been requested. Since the request goes
  // through posted tasks, we have to spin the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_P(SyncAuthManagerTest, ClearsCredentialsOnInvalidRefreshToken) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  ASSERT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // But now an invalid refresh token gets set, i.e. we enter the "Sync/Signin
  // paused" state. No new access token should get requested due to this.
  base::MockCallback<base::OnceClosure> access_token_requested;
  EXPECT_CALL(access_token_requested, Run()).Times(0);
  identity_env()->SetCallbackForNextAccessTokenRequest(
      access_token_requested.Get());
  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();

  // Should immediately drop the access token and expose a special auth error.
  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
  GoogleServiceAuthError invalid_token_error =
      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
          GoogleServiceAuthError::InvalidGaiaCredentialsReason::
              CREDENTIALS_REJECTED_BY_CLIENT);
  EXPECT_EQ(auth_manager->GetLastAuthError(), invalid_token_error);
  EXPECT_TRUE(auth_manager->IsSyncPaused());

  // No new access token should have been requested. Since the request goes
  // through posted tasks, we have to spin the message loop.
  base::RunLoop().RunUntilIdle();
}

TEST_P(SyncAuthManagerTest, EntersPausedStateOnPersistentAuthError) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  ASSERT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // But now an auth error happens.
  identity_env()->UpdatePersistentErrorOfRefreshTokenForAccount(
      auth_manager->GetActiveAccountInfo().account_info.account_id,
      GoogleServiceAuthError::FromServiceError("Test error"));

  // Should immediately drop the access token and enter the sync-paused state.
  EXPECT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
  EXPECT_TRUE(auth_manager->GetLastAuthError().IsPersistentError());
  EXPECT_TRUE(auth_manager->IsSyncPaused());
}

TEST_P(SyncAuthManagerTest,
       RequestsAccessTokenWhenInvalidRefreshTokenResolved) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // Sync starts up normally.
  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token");
  ASSERT_EQ(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());

  // But now an invalid refresh token gets set, i.e. we enter the "Sync/Signin
  // paused" state.
  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();
  ASSERT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
  ASSERT_TRUE(auth_manager->IsSyncPaused());

  // Once the user signs in again and we have a valid refresh token, we should
  // also request a new access token.
  identity_env()->SetRefreshTokenForPrimaryAccount();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token_2", base::Time::Now() + base::Hours(1));
  ASSERT_EQ(auth_manager->GetCredentials().access_token_info.token,
            "access_token_2");
}

TEST_P(SyncAuthManagerTest, DoesNotRequestAccessTokenIfSyncInactive) {
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();

  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // Sync is *not* enabled; in particular we don't call ConnectionOpened().

  // An invalid refresh token gets set, i.e. we enter the "Sync/Signin paused"
  // state (only from SyncAuthManager's point of view - Sync as a whole is still
  // disabled).
  // Note: Depending on the exact sequence of IdentityManager::Observer calls
  // (refresh token changed and/or auth error changed), the credentials-changed
  // callback might get run multiple times.
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged)
      .Times(testing::AtLeast(1));
  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();
  ASSERT_TRUE(auth_manager->GetCredentials().access_token_info.token.empty());
  ASSERT_TRUE(auth_manager->IsSyncPaused());

  // Once the user signs in again and we have a valid refresh token, we should
  // *not* request a new access token, since Sync isn't active.
  base::MockCallback<base::OnceClosure> access_token_requested;
  EXPECT_CALL(access_token_requested, Run()).Times(0);
  identity_env()->SetCallbackForNextAccessTokenRequest(
      access_token_requested.Get());
  // This *should* notify about changed credentials though, so that the
  // SyncService can decide to start syncing.
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged);
  identity_env()->SetRefreshTokenForPrimaryAccount();
  ASSERT_FALSE(auth_manager->IsSyncPaused());

  // Since the access token request goes through posted tasks, we have to spin
  // the message loop to make sure it didn't happen.
  base::RunLoop().RunUntilIdle();
}

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Primary account with no sync consent is not supported on Android and iOS.
TEST_P(SyncAuthManagerTest, PrimaryAccountWithNoSyncConsent) {
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  ASSERT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());

  // Make a primary account with no sync consent available.
  AccountInfo account_info = identity_env()->MakePrimaryAccountAvailable(
      "test@email.com", signin::ConsentLevel::kSignin);

  // Since unconsented primary account support is enabled, SyncAuthManager
  // should have picked up this account.
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.GetAccountId());
}
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)

#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Primary account with no sync consent is not supported on Android and iOS.
// On CrOS the unconsented primary account can't be changed or removed, but can
// be granted sync consent.
TEST_P(SyncAuthManagerTest, PicksNewPrimaryAccountWithSyncConsent) {
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  ASSERT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());

  // Make a primary account with no sync consent available.
  AccountInfo unconsented_primary_account_info =
      identity_env()->MakePrimaryAccountAvailable(
          "test@email.com", signin::ConsentLevel::kSignin);
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            unconsented_primary_account_info.GetAccountId());

  // Once a primary account with sync consent becomes available, the unconsented
  // primary account should be overridden.
  AccountInfo primary_account_info =
      identity_env()->MakePrimaryAccountAvailable("primary@email.com",
                                                  /*consent_level=*/GetParam());
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            primary_account_info.GetAccountId());
}

TEST_P(SyncAuthManagerTest,
       DropsAccountWhenPrimaryAccountWithNoSyncConsentGoesAway) {
  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  // Make a primary account with no sync consent available.
  AccountInfo account_info = identity_env()->MakePrimaryAccountAvailable(
      "test@email.com", signin::ConsentLevel::kSignin);
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.GetAccountId());

  identity_env()->ClearPrimaryAccount();
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());
}
#endif  // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) &&
        // !BUILDFLAG(IS_IOS)

TEST_P(SyncAuthManagerTest, DetectsInvalidRefreshTokenAtStartup) {
  // There is a primary account, but it has an invalid refresh token (with a
  // persistent auth error).
  CoreAccountId account_id =
      identity_env()
          ->MakePrimaryAccountAvailable("test@email.com",
                                        /*consent_level=*/GetParam())
          .GetAccountId();
  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();

  // On initialization, SyncAuthManager should pick up the auth error. This
  // should not result in a notification.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  EXPECT_TRUE(auth_manager->GetLastAuthError().IsPersistentError());
}

TEST_F(SyncAuthManagerTest, DeterminesAccountTypeSynchronously) {
  // There is a primary account. It's @gmail.com so it's managed status can be
  // determined synchronously.
  AccountInfo account_info = identity_env()->MakePrimaryAccountAvailable(
      "test@gmail.com", signin::ConsentLevel::kSignin);
  ASSERT_EQ(account_info.IsManaged(), signin::Tribool::kUnknown);

  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);

  base::HistogramTester histograms;

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.GetAccountId());

  // The managed status should be known synchronously, with no notification.
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().managed_status,
            signin::AccountManagedStatusFinderOutcome::kConsumerGmail);
  histograms.ExpectBucketCount(
      "Sync.AccountManagedStatusSynchronousOutcome",
      signin::AccountManagedStatusFinderOutcome::kConsumerGmail, 1);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusAsynchronousOutcome",
                              0);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusDuration", 0);

  // Even when the hosted domain gets determined later, there should be no
  // notification (since nothing relevant changed).
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  account_info =
      AccountInfo::Builder(account_info).SetHostedDomain(std::string()).Build();
  identity_env()->UpdateAccountInfoForAccount(account_info);

  EXPECT_EQ(auth_manager->GetActiveAccountInfo().managed_status,
            signin::AccountManagedStatusFinderOutcome::kConsumerGmail);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusSynchronousOutcome", 1);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusAsynchronousOutcome",
                              0);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusDuration", 0);
}

TEST_F(SyncAuthManagerTest, DeterminesAccountTypeAsynchronously) {
  // There is a primary account, whose managed-ness status isn't known yet.
  AccountInfo account_info = identity_env()->MakePrimaryAccountAvailable(
      "test@consumer.com", signin::ConsentLevel::kSignin);
  ASSERT_EQ(account_info.IsManaged(), signin::Tribool::kUnknown);

  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);

  base::HistogramTester histograms;

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.GetAccountId());

  // The managed status should initially be unknown (pending).
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().managed_status,
            signin::AccountManagedStatusFinderOutcome::kPending);
  histograms.ExpectBucketCount(
      "Sync.AccountManagedStatusSynchronousOutcome",
      signin::AccountManagedStatusFinderOutcome::kPending, 1);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusAsynchronousOutcome",
                              0);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusDuration", 0);

  // Once the account's hosted domain is determined, the managed status should
  // become known too, and this should trigger a notification.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged);
  account_info =
      AccountInfo::Builder(account_info).SetHostedDomain(std::string()).Build();
  identity_env()->UpdateAccountInfoForAccount(account_info);

  EXPECT_EQ(auth_manager->GetActiveAccountInfo().managed_status,
            signin::AccountManagedStatusFinderOutcome::kConsumerNotWellKnown);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusSynchronousOutcome", 1);
  histograms.ExpectBucketCount(
      "Sync.AccountManagedStatusAsynchronousOutcome",
      signin::AccountManagedStatusFinderOutcome::kConsumerNotWellKnown, 1);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusDuration", 1);
}

#if !BUILDFLAG(IS_CHROMEOS)
TEST_F(SyncAuthManagerTest, AccountChangeWhileDeterminingAccountType) {
  // There is a primary account, whose managed-ness status isn't known yet.
  AccountInfo account_info = identity_env()->MakePrimaryAccountAvailable(
      "test@consumer.com", signin::ConsentLevel::kSignin);
  ASSERT_EQ(account_info.IsManaged(), signin::Tribool::kUnknown);

  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(0);
  EXPECT_CALL(delegate(), SyncAuthCredentialsChanged).Times(0);

  base::HistogramTester histograms;

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.GetAccountId());

  // The managed status should initially be unknown (pending).
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().managed_status,
            signin::AccountManagedStatusFinderOutcome::kPending);
  histograms.ExpectBucketCount(
      "Sync.AccountManagedStatusSynchronousOutcome",
      signin::AccountManagedStatusFinderOutcome::kPending, 1);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusAsynchronousOutcome",
                              0);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusDuration", 0);

  // Before the account type can be determined, the account signs out again, and
  // a different account signs in (whose status is also not known yet).
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged).Times(2);
  identity_env()->ClearPrimaryAccount();
  AccountInfo account_info2 = identity_env()->MakePrimaryAccountAvailable(
      "test2@consumer.com", signin::ConsentLevel::kSignin);
  ASSERT_EQ(account_info.IsManaged(), signin::Tribool::kUnknown);

  histograms.ExpectBucketCount(
      "Sync.AccountManagedStatusSynchronousOutcome",
      signin::AccountManagedStatusFinderOutcome::kPending, 2);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusAsynchronousOutcome",
                              0);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusDuration", 0);

  // Once the second account's hosted domain is determined, the managed status
  // should become known too, and this should trigger a notification.
  EXPECT_CALL(delegate(), SyncAuthAccountStateChanged);
  account_info2 = AccountInfo::Builder(account_info2)
                      .SetHostedDomain(std::string())
                      .Build();
  identity_env()->UpdateAccountInfoForAccount(account_info2);

  EXPECT_EQ(auth_manager->GetActiveAccountInfo().managed_status,
            signin::AccountManagedStatusFinderOutcome::kConsumerNotWellKnown);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusSynchronousOutcome", 2);
  histograms.ExpectBucketCount(
      "Sync.AccountManagedStatusAsynchronousOutcome",
      signin::AccountManagedStatusFinderOutcome::kConsumerNotWellKnown, 1);
  histograms.ExpectTotalCount("Sync.AccountManagedStatusDuration", 1);
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

TEST_P(SyncAuthManagerTest,
       FetchAccessTokenReturnsValidCachedTokenSynchronously) {
  base::test::ScopedFeatureList feature_list(kSyncUsePropagatedAccessToken);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  identity_env()->MakePrimaryAccountAvailable("test@email.com",
                                              /*consent_level=*/GetParam());

  auth_manager->ConnectionOpened();
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));

  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback;
  EXPECT_CALL(callback,
              Run(Field(&signin::AccessTokenInfo::token, "access_token")));
  auth_manager->FetchAccessToken(callback.Get());
}

TEST_P(SyncAuthManagerTest, FetchAccessTokenWhileFetchOngoing) {
  base::test::ScopedFeatureList feature_list(kSyncUsePropagatedAccessToken);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  identity_env()->MakePrimaryAccountAvailable("test@email.com",
                                              /*consent_level=*/GetParam());

  auth_manager->ConnectionOpened();

  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback1;
  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback2;
  // Verify that the callbacks are not invoked synchronously while the token
  // fetch is still ongoing.
  EXPECT_CALL(callback1, Run).Times(0);
  EXPECT_CALL(callback2, Run).Times(0);
  auth_manager->FetchAccessToken(callback1.Get());
  auth_manager->FetchAccessToken(callback2.Get());

  // Both callbacks should be invoked once the access token fetch completes.
  EXPECT_CALL(callback1,
              Run(Field(&signin::AccessTokenInfo::token, "access_token")));
  EXPECT_CALL(callback2,
              Run(Field(&signin::AccessTokenInfo::token, "access_token")));

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
}

TEST_P(SyncAuthManagerTest, FetchAccessTokenReturnsEmptyTokenWhenPaused) {
  base::test::ScopedFeatureList feature_list(kSyncUsePropagatedAccessToken);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  identity_env()->MakePrimaryAccountAvailable("test@email.com",
                                              /*consent_level=*/GetParam());

  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();
  auth_manager->ConnectionOpened();
  ASSERT_TRUE(auth_manager->IsSyncPaused());

  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback;
  EXPECT_CALL(callback, Run(Field(&signin::AccessTokenInfo::token, "")));
  auth_manager->FetchAccessToken(callback.Get());
}

TEST_P(SyncAuthManagerTest,
       FetchAccessTokenClearsPendingCallbacksOnConnectionClosed) {
  base::test::ScopedFeatureList feature_list(kSyncUsePropagatedAccessToken);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  identity_env()->MakePrimaryAccountAvailable("test@email.com",
                                              /*consent_level=*/GetParam());

  auth_manager->ConnectionOpened();

  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback1;
  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback2;
  EXPECT_CALL(callback1, Run(Field(&signin::AccessTokenInfo::token, "")));
  EXPECT_CALL(callback2, Run(Field(&signin::AccessTokenInfo::token, "")));
  auth_manager->FetchAccessToken(callback1.Get());
  auth_manager->FetchAccessToken(callback2.Get());

  auth_manager->ConnectionClosed();
}

TEST_P(SyncAuthManagerTest, FetchAccessTokenRetriesOnRequestCanceled) {
  base::test::ScopedFeatureList feature_list(kSyncUsePropagatedAccessToken);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  identity_env()->MakePrimaryAccountAvailable("test@email.com",
                                              /*consent_level=*/GetParam());

  auth_manager->ConnectionOpened();

  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback;
  // Callback should only be called once the retried request succeeds with a
  // valid token.
  EXPECT_CALL(callback,
              Run(Field(&signin::AccessTokenInfo::token, "access_token")));

  auth_manager->FetchAccessToken(callback.Get());

  // Simulate first request getting canceled. SyncAuthManager should immediately
  // retry without running the callback yet.
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::CreateRequestCanceled());

  // Respond to the retried request.
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::Hours(1));
}

TEST_P(SyncAuthManagerTest, FetchAccessTokenReturnsEmptyTokenOnPermanentError) {
  base::test::ScopedFeatureList feature_list(kSyncUsePropagatedAccessToken);

  std::unique_ptr<SyncAuthManager> auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  identity_env()->MakePrimaryAccountAvailable("test@email.com",
                                              /*consent_level=*/GetParam());

  auth_manager->ConnectionOpened();

  base::MockCallback<base::OnceCallback<void(signin::AccessTokenInfo)>>
      callback;
  EXPECT_CALL(callback, Run(Field(&signin::AccessTokenInfo::token, "")));

  auth_manager->FetchAccessToken(callback.Get());

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
          GoogleServiceAuthError::InvalidGaiaCredentialsReason::
              CREDENTIALS_REJECTED_BY_SERVER));
}

}  // namespace

}  // namespace syncer
