// 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/browser_sync/sync_auth_manager.h"

#include "base/bind_helpers.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "build/build_config.h"
#include "components/sync/base/sync_prefs.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "components/sync/engine/connection_status.h"
#include "components/sync/engine/sync_credentials.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "net/base/net_errors.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace browser_sync {

namespace {

class SyncAuthManagerTest : public testing::Test {
 protected:
  using AccountStateChangedCallback =
      SyncAuthManager::AccountStateChangedCallback;
  using CredentialsChangedCallback =
      SyncAuthManager::CredentialsChangedCallback;

  SyncAuthManagerTest() : identity_env_(&test_url_loader_factory_) {
    syncer::SyncPrefs::RegisterProfilePrefs(pref_service_.registry());
    sync_prefs_ = std::make_unique<syncer::SyncPrefs>(&pref_service_);
  }

  ~SyncAuthManagerTest() override {}

  std::unique_ptr<SyncAuthManager> CreateAuthManager() {
    return CreateAuthManager(base::DoNothing(), base::DoNothing());
  }

  std::unique_ptr<SyncAuthManager> CreateAuthManager(
      const AccountStateChangedCallback& account_state_changed,
      const CredentialsChangedCallback& credentials_changed) {
    return std::make_unique<SyncAuthManager>(
        sync_prefs_.get(), identity_env_.identity_manager(),
        account_state_changed, credentials_changed);
  }

  std::unique_ptr<SyncAuthManager> CreateAuthManagerForLocalSync() {
    return std::make_unique<SyncAuthManager>(
        sync_prefs_.get(), nullptr, base::DoNothing(), base::DoNothing());
  }

  identity::IdentityTestEnvironment* identity_env() { return &identity_env_; }

 private:
  base::test::ScopedTaskEnvironment task_environment_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  identity::IdentityTestEnvironment identity_env_;
  sync_preferences::TestingPrefServiceSyncable pref_service_;
  std::unique_ptr<syncer::SyncPrefs> sync_prefs_;
};

TEST_F(SyncAuthManagerTest, ProvidesNothingInLocalSyncMode) {
  auto auth_manager = CreateAuthManagerForLocalSync();
  EXPECT_TRUE(auth_manager->GetActiveAccountInfo().account_info.IsEmpty());
  syncer::SyncCredentials credentials = auth_manager->GetCredentials();
  EXPECT_TRUE(credentials.account_id.empty());
  EXPECT_TRUE(credentials.email.empty());
  EXPECT_TRUE(credentials.sync_token.empty());
  EXPECT_TRUE(auth_manager->access_token().empty());
  // Note: Calling RegisterForAuthNotifications is illegal in local Sync mode,
  // so we don't test that.
  // Calling Clear() does nothing, but shouldn't crash.
  auth_manager->Clear();
}

// ChromeOS doesn't support sign-in/sign-out.
#if !defined(OS_CHROMEOS)
TEST_F(SyncAuthManagerTest, IgnoresEventsIfNotRegistered) {
  base::MockCallback<AccountStateChangedCallback> account_state_changed;
  base::MockCallback<CredentialsChangedCallback> credentials_changed;
  EXPECT_CALL(account_state_changed, Run()).Times(0);
  EXPECT_CALL(credentials_changed, Run()).Times(0);
  auto auth_manager =
      CreateAuthManager(account_state_changed.Get(), credentials_changed.Get());

  // Fire some auth events. We haven't called RegisterForAuthNotifications, so
  // none of this should result in any callback calls.
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  // 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());
  identity_env()->ClearPrimaryAccount();
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());
}

TEST_F(SyncAuthManagerTest, ForwardsPrimaryAccountEvents) {
  // Start out already signed in before the SyncAuthManager is created.
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;

  base::MockCallback<AccountStateChangedCallback> account_state_changed;
  base::MockCallback<CredentialsChangedCallback> credentials_changed;
  EXPECT_CALL(account_state_changed, Run()).Times(0);
  EXPECT_CALL(credentials_changed, Run()).Times(0);
  auto auth_manager =
      CreateAuthManager(account_state_changed.Get(), credentials_changed.Get());

  auth_manager->RegisterForAuthNotifications();

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

  // Sign out of the account.
  EXPECT_CALL(account_state_changed, Run());
  // 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(credentials_changed, Run()).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(account_state_changed, Run());
  std::string second_account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            second_account_id);
}

TEST_F(SyncAuthManagerTest, ClearsAuthErrorOnSignout) {
  // Start out already signed in before the SyncAuthManager is created.
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;

  auto 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 to force an auth error.
  identity_env()->RemoveRefreshTokenForPrimaryAccount();

  ASSERT_NE(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);

  // Now actually sign out, i.e. remove the primary account. This should clear
  // the auth error, since it's now not meaningful anymore.
  identity_env()->ClearPrimaryAccount();
  EXPECT_EQ(auth_manager->GetLastAuthError().state(),
            GoogleServiceAuthError::NONE);
}
#endif  // !OS_CHROMEOS

TEST_F(SyncAuthManagerTest, ForwardsCredentialsEvents) {
  // Start out already signed in before the SyncAuthManager is created.
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;

  base::MockCallback<AccountStateChangedCallback> account_state_changed;
  base::MockCallback<CredentialsChangedCallback> credentials_changed;
  EXPECT_CALL(account_state_changed, Run()).Times(0);
  EXPECT_CALL(credentials_changed, Run()).Times(0);
  auto auth_manager =
      CreateAuthManager(account_state_changed.Get(), credentials_changed.Get());

  auth_manager->RegisterForAuthNotifications();

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

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);

  // Once an access token is available, the callback should get run.
  EXPECT_CALL(credentials_changed, Run());
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token");

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

  // Once a new token is available, there's another notification.
  EXPECT_CALL(credentials_changed, Run());
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token_2");

  // Revoking the refresh token should also cause the access token to get
  // dropped.
  EXPECT_CALL(credentials_changed, Run());
  identity_env()->RemoveRefreshTokenForPrimaryAccount();
  EXPECT_TRUE(auth_manager->GetCredentials().sync_token.empty());
}

TEST_F(SyncAuthManagerTest, RequestsAccessTokenOnSyncStartup) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);

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

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

TEST_F(SyncAuthManagerTest,
       RetriesAccessTokenFetchWithBackoffOnTransientFailure) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);

  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_F(SyncAuthManagerTest, AbortsAccessTokenFetchOnPersistentFailure) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);

  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_F(SyncAuthManagerTest, FetchesNewAccessTokenWithBackoffOnServerError) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token");

  // But now the server is still returning AUTH_ERROR - maybe something's wrong
  // with the token.
  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_F(SyncAuthManagerTest, ExposesServerError) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token");

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

  // The error should be reported.
  EXPECT_NE(auth_manager->GetLastAuthError(),
            GoogleServiceAuthError::AuthErrorNone());
  // But the access token should still be there - this might just be some
  // non-auth-related problem with the server.
  EXPECT_EQ(auth_manager->GetCredentials().sync_token, "access_token");
}

TEST_F(SyncAuthManagerTest, RequestsNewAccessTokenOnExpiry) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().sync_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().sync_token.empty());

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1));
  EXPECT_EQ(auth_manager->GetCredentials().sync_token, "access_token_2");
}

TEST_F(SyncAuthManagerTest, RequestsNewAccessTokenOnRefreshTokenUpdate) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().sync_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().sync_token.empty());

  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1));
  EXPECT_EQ(auth_manager->GetCredentials().sync_token, "access_token_2");
}

TEST_F(SyncAuthManagerTest, DoesNotRequestAccessTokenAutonomously) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto 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().sync_token.empty());
}

TEST_F(SyncAuthManagerTest, ClearsCredentialsOnRefreshTokenRemoval) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token");

  // Now everything is okay for a while.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
  ASSERT_EQ(auth_manager->GetCredentials().sync_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()->RemoveRefreshTokenForPrimaryAccount();

  // Should immediately drop the access token and expose an auth error.
  EXPECT_TRUE(auth_manager->GetCredentials().sync_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_F(SyncAuthManagerTest, ClearsCredentialsOnInvalidRefreshToken) {
  std::string account_id =
      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_id);

  // During Sync startup, the SyncEngine attempts to connect to the server
  // without an access token, resulting in a call to ConnectionStatusChanged
  // with CONNECTION_AUTH_ERROR. This is what kicks off the initial access token
  // fetch.
  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR);
  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
  ASSERT_EQ(auth_manager->GetCredentials().sync_token, "access_token");

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

  // But now an invalid refresh token gets set. 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().sync_token.empty());
  GoogleServiceAuthError invalid_token_error =
      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
          GoogleServiceAuthError::InvalidGaiaCredentialsReason::
              CREDENTIALS_REJECTED_BY_CLIENT);
  EXPECT_EQ(auth_manager->GetLastAuthError(), invalid_token_error);

  // 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_F(SyncAuthManagerTest, IgnoresCookieJarIfFeatureDisabled) {
  base::test::ScopedFeatureList features;
  features.InitAndDisableFeature(switches::kSyncSupportSecondaryAccount);

  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

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

  // Make a non-primary account available with both a refresh token and cookie.
  AccountInfo account_info =
      identity_env()->MakeAccountAvailable("test@email.com");
  identity_env()->SetCookieAccounts({{account_info.email, account_info.gaia}});

  // Since secondary account support is disabled, this should have no effect.
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());
}

TEST_F(SyncAuthManagerTest, UsesCookieJarIfFeatureEnabled) {
  base::test::ScopedFeatureList features;
  features.InitWithFeatures(
      /*enabled_features=*/{switches::kSyncStandaloneTransport,
                            switches::kSyncSupportSecondaryAccount},
      /*disabled_features=*/{});

  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

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

  // Make a non-primary account available with both a refresh token and cookie.
  AccountInfo account_info =
      identity_env()->MakeAccountAvailable("test@email.com");
  identity_env()->SetCookieAccounts({{account_info.email, account_info.gaia}});

  // Since secondary account support is enabled, SyncAuthManager should have
  // picked up this account
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.account_id);
}

TEST_F(SyncAuthManagerTest, DropsAccountWhenCookieGoesAway) {
  base::test::ScopedFeatureList features;
  features.InitWithFeatures(
      /*enabled_features=*/{switches::kSyncStandaloneTransport,
                            switches::kSyncSupportSecondaryAccount},
      /*disabled_features=*/{});

  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  // Make a non-primary account available with both a refresh token and cookie.
  AccountInfo account_info =
      identity_env()->MakeAccountAvailable("test@email.com");
  identity_env()->SetCookieAccounts({{account_info.email, account_info.gaia}});
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.account_id);

  // If the cookie goes away, we're not using the account anymore, even though
  // we still have a refresh token.
  identity_env()->SetCookieAccounts({});
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());

  // Once the cookie comes back, we can use the account again.
  identity_env()->SetCookieAccounts({{account_info.email, account_info.gaia}});
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.account_id);
}

TEST_F(SyncAuthManagerTest, DropsAccountWhenRefreshTokenGoesAway) {
  base::test::ScopedFeatureList features;
  features.InitWithFeatures(
      /*enabled_features=*/{switches::kSyncStandaloneTransport,
                            switches::kSyncSupportSecondaryAccount},
      /*disabled_features=*/{});

  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

  // Make a non-primary account available with both a refresh token and cookie.
  AccountInfo account_info =
      identity_env()->MakeAccountAvailable("test@email.com");
  identity_env()->SetCookieAccounts({{account_info.email, account_info.gaia}});
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.account_id);

  // If the refresh token goes away, we're not using the account anymore, even
  // though the cookie is still there.
  identity_env()->RemoveRefreshTokenForAccount(account_info.account_id);
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());

  // Once the refresh token comes back, we can use the account again.
  identity_env()->SetRefreshTokenForAccount(account_info.account_id);
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info.account_id);
}

TEST_F(SyncAuthManagerTest, PrefersPrimaryAccountOverCookie) {
  base::test::ScopedFeatureList features;
  features.InitWithFeatures(
      /*enabled_features=*/{switches::kSyncStandaloneTransport,
                            switches::kSyncSupportSecondaryAccount},
      /*disabled_features=*/{});

  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

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

  // Make a non-primary account available with both a refresh token and cookie.
  AccountInfo secondary_account_info =
      identity_env()->MakeAccountAvailable("test@email.com");
  identity_env()->SetCookieAccounts(
      {{secondary_account_info.email, secondary_account_info.gaia}});
  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            secondary_account_info.account_id);

  // Once a primary account becomes available, that one is preferred over the
  // one from the cookie.
  AccountInfo primary_account_info =
      identity_env()->MakePrimaryAccountAvailable("primary@email.com");
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            primary_account_info.account_id);
}

TEST_F(SyncAuthManagerTest, OnlyUsesFirstCookieAccount) {
  base::test::ScopedFeatureList features;
  features.InitWithFeatures(
      /*enabled_features=*/{switches::kSyncStandaloneTransport,
                            switches::kSyncSupportSecondaryAccount},
      /*disabled_features=*/{});

  auto auth_manager = CreateAuthManager();
  auth_manager->RegisterForAuthNotifications();

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

  // Make two non-primary accounts available with both refresh token and cookie.
  AccountInfo account_info1 =
      identity_env()->MakeAccountAvailable("test1@email.com");
  AccountInfo account_info2 =
      identity_env()->MakeAccountAvailable("test2@email.com");
  identity_env()->SetCookieAccounts(
      {{account_info1.email, account_info1.gaia},
       {account_info2.email, account_info2.gaia}});

  // SyncAuthManager should have picked up the first account.
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info1.account_id);

  // If the order of the accounts in the cookie changes, then SyncAuthManager
  // should move to the other (now-first) account.
  identity_env()->SetCookieAccounts(
      {{account_info2.email, account_info2.gaia},
       {account_info1.email, account_info1.gaia}});
  EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
            account_info2.account_id);

  // If the refresh token for this account goes away, there should be no active
  // account anymore - we should *not* fall back to the second cookie account.
  identity_env()->RemoveRefreshTokenForAccount(account_info2.account_id);
  EXPECT_TRUE(
      auth_manager->GetActiveAccountInfo().account_info.account_id.empty());
}

}  // namespace

}  // namespace browser_sync
