|  | // 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/functional/callback_helpers.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/test/mock_callback.h" | 
|  | #include "base/test/task_environment.h" | 
|  | #include "build/build_config.h" | 
|  | #include "build/chromeos_buildflags.h" | 
|  | #include "components/signin/public/identity_manager/identity_test_environment.h" | 
|  | #include "components/signin/public/identity_manager/primary_account_mutator.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 { | 
|  |  | 
|  | class SyncAuthManagerTest : public testing::Test { | 
|  | protected: | 
|  | using AccountStateChangedCallback = | 
|  | SyncAuthManager::AccountStateChangedCallback; | 
|  | using CredentialsChangedCallback = | 
|  | SyncAuthManager::CredentialsChangedCallback; | 
|  |  | 
|  | SyncAuthManagerTest() : identity_env_(&test_url_loader_factory_) {} | 
|  |  | 
|  | ~SyncAuthManagerTest() override = default; | 
|  |  | 
|  | 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>(identity_env_.identity_manager(), | 
|  | account_state_changed, | 
|  | credentials_changed); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<SyncAuthManager> CreateAuthManagerForLocalSync() { | 
|  | return std::make_unique<SyncAuthManager>(nullptr, base::DoNothing(), | 
|  | base::DoNothing()); | 
|  | } | 
|  |  | 
|  | signin::IdentityTestEnvironment* identity_env() { return &identity_env_; } | 
|  |  | 
|  | private: | 
|  | base::test::SingleThreadTaskEnvironment task_environment_; | 
|  | network::TestURLLoaderFactory test_url_loader_factory_; | 
|  | signin::IdentityTestEnvironment identity_env_; | 
|  | }; | 
|  |  | 
|  | TEST_F(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.email.empty()); | 
|  | EXPECT_TRUE(credentials.access_token.empty()); | 
|  | EXPECT_TRUE(auth_manager->access_token().empty()); | 
|  | // Note: Calling RegisterForAuthNotifications or any of the Connection*() | 
|  | // methods is illegal in local Sync mode, so we don't test that. | 
|  | } | 
|  |  | 
|  | 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); | 
|  | std::unique_ptr<SyncAuthManager> 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. | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .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()); | 
|  |  | 
|  | // ChromeOS doesn't support sign-out. | 
|  | #if !BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | identity_env()->ClearPrimaryAccount(); | 
|  | EXPECT_TRUE( | 
|  | auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); | 
|  | #endif  // !BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | } | 
|  |  | 
|  | // ChromeOS doesn't support sign-out. | 
|  | #if !BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | TEST_F(SyncAuthManagerTest, ForwardsPrimaryAccountEvents) { | 
|  | // Start out already signed in before the SyncAuthManager is created. | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .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); | 
|  | std::unique_ptr<SyncAuthManager> 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()); | 
|  | CoreAccountId second_account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, | 
|  | second_account_id); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, NotifiesOfSignoutBeforeAccessTokenIsGone) { | 
|  | // Start out already signed in before the SyncAuthManager is created. | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  |  | 
|  | base::MockCallback<AccountStateChangedCallback> account_state_changed; | 
|  | base::MockCallback<CredentialsChangedCallback> credentials_changed; | 
|  | std::unique_ptr<SyncAuthManager> auth_manager = | 
|  | CreateAuthManager(account_state_changed.Get(), base::DoNothing()); | 
|  |  | 
|  | 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, "access_token"); | 
|  |  | 
|  | // Sign out of the account. | 
|  | EXPECT_CALL(account_state_changed, Run()).WillOnce([&]() { | 
|  | // At the time the callback gets run, the access token should still be here. | 
|  | EXPECT_FALSE(auth_manager->GetCredentials().access_token.empty()); | 
|  | }); | 
|  | identity_env()->ClearPrimaryAccount(); | 
|  | // After the signout is complete, the access token should be gone. | 
|  | EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); | 
|  | ASSERT_TRUE( | 
|  | auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); | 
|  | } | 
|  | #endif  // !BUILDFLAG(IS_CHROMEOS_ASH) | 
|  |  | 
|  | // Unconsented primary accounts are only supported on Win/Mac/Linux. | 
|  | #if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
|  | TEST_F(SyncAuthManagerTest, ForwardsUnconsentedAccountEvents) { | 
|  | 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); | 
|  | std::unique_ptr<SyncAuthManager> auth_manager = | 
|  | CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); | 
|  | auth_manager->RegisterForAuthNotifications(); | 
|  |  | 
|  | ASSERT_TRUE( | 
|  | auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); | 
|  |  | 
|  | // Make a primary account available without Sync consent. | 
|  | EXPECT_CALL(account_state_changed, Run()); | 
|  | 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.account_id); | 
|  |  | 
|  | // Make the account Sync-consented. | 
|  | EXPECT_CALL(account_state_changed, Run()); | 
|  | signin::PrimaryAccountMutator* primary_account_mutator = | 
|  | identity_env()->identity_manager()->GetPrimaryAccountMutator(); | 
|  | primary_account_mutator->SetPrimaryAccount(account_info.account_id, | 
|  | signin::ConsentLevel::kSync); | 
|  |  | 
|  | EXPECT_TRUE(auth_manager->GetActiveAccountInfo().is_sync_consented); | 
|  | } | 
|  | #endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID) && | 
|  | // !BUILDFLAG(IS_IOS) | 
|  |  | 
|  | // ChromeOS doesn't support sign-out. | 
|  | #if !BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | TEST_F(SyncAuthManagerTest, ClearsAuthErrorOnSignoutWithRefreshTokenRemoval) { | 
|  | // Start out already signed in before the SyncAuthManager is created. | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  |  | 
|  | 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_F(SyncAuthManagerTest, | 
|  | ClearsAuthErrorOnSignoutWithoutRefreshTokenRemoval) { | 
|  | // Start out already signed in before the SyncAuthManager is created. | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  |  | 
|  | 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_ASH) | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, DoesNotClearAuthErrorOnSyncDisable) { | 
|  | // Start out already signed in before the SyncAuthManager is created. | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  |  | 
|  | 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_F(SyncAuthManagerTest, ForwardsCredentialsEvents) { | 
|  | // Start out already signed in before the SyncAuthManager is created. | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .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); | 
|  | std::unique_ptr<SyncAuthManager> auth_manager = | 
|  | CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); | 
|  |  | 
|  | 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(credentials_changed, Run()); | 
|  | identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
|  | "access_token", base::Time::Now() + base::Hours(1)); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_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().access_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::Hours(1)); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_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(credentials_changed, Run()).Times(testing::AtLeast(1)); | 
|  | identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); | 
|  | EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, RequestsAccessTokenOnSyncStartup) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "access_token"); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, | 
|  | RetriesAccessTokenFetchWithBackoffOnTransientFailure) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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_F(SyncAuthManagerTest, | 
|  | RetriesAccessTokenFetchWithoutBackoffOnceOnFirstCancelTransientFailure) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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(GoogleServiceAuthError::REQUEST_CANCELED)); | 
|  |  | 
|  | // Expect no backoff the first time the request is canceled. | 
|  | EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest()); | 
|  |  | 
|  | // Cancel the retry as well. | 
|  | identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( | 
|  | GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); | 
|  |  | 
|  | // Expect retry with backoff when the first retry was also canceled. | 
|  | EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest()); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, | 
|  | RetriesAccessTokenFetchOnFirstCancelTransientFailure) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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(GoogleServiceAuthError::REQUEST_CANCELED)); | 
|  |  | 
|  | // 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, "access_token"); | 
|  | // Don't expect any backoff when the retry is a success. | 
|  | EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest()); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, AbortsAccessTokenFetchOnPersistentFailure) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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_F(SyncAuthManagerTest, FetchesNewAccessTokenWithBackoffOnServerError) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "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_F(SyncAuthManagerTest, DoesNotExposeServerError) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "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, "access_token"); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, ClearsServerErrorOnSyncDisable) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "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_F(SyncAuthManagerTest, RequestsNewAccessTokenOnExpiry) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "access_token"); | 
|  |  | 
|  | // Now everything is okay for a while. | 
|  | auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_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.empty()); | 
|  |  | 
|  | identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
|  | "access_token_2", base::Time::Now() + base::Hours(1)); | 
|  | EXPECT_EQ(auth_manager->GetCredentials().access_token, "access_token_2"); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, RequestsNewAccessTokenOnRefreshTokenUpdate) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "access_token"); | 
|  |  | 
|  | // Now everything is okay for a while. | 
|  | auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_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.empty()); | 
|  |  | 
|  | identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
|  | "access_token_2", base::Time::Now() + base::Hours(1)); | 
|  | EXPECT_EQ(auth_manager->GetCredentials().access_token, "access_token_2"); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, DoesNotRequestAccessTokenAutonomously) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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.empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, ClearsCredentialsOnRefreshTokenRemoval) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "access_token"); | 
|  |  | 
|  | // Now everything is okay for a while. | 
|  | auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_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.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) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "access_token"); | 
|  |  | 
|  | // Now everything is okay for a while. | 
|  | auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); | 
|  | ASSERT_EQ(auth_manager->GetLastAuthError(), | 
|  | GoogleServiceAuthError::AuthErrorNone()); | 
|  |  | 
|  | // But now an invalid refresh token gets set, i.e. we enter the "Sync 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.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_F(SyncAuthManagerTest, EntersPausedStateOnPersistentAuthError) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "access_token"); | 
|  |  | 
|  | // Now everything is okay for a while. | 
|  | auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_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.empty()); | 
|  | EXPECT_TRUE(auth_manager->GetLastAuthError().IsPersistentError()); | 
|  | EXPECT_TRUE(auth_manager->IsSyncPaused()); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, | 
|  | RequestsAccessTokenWhenInvalidRefreshTokenResolved) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | 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, "access_token"); | 
|  | auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); | 
|  | ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); | 
|  | ASSERT_EQ(auth_manager->GetLastAuthError(), | 
|  | GoogleServiceAuthError::AuthErrorNone()); | 
|  |  | 
|  | // But now an invalid refresh token gets set, i.e. we enter the "Sync paused" | 
|  | // state. | 
|  | identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); | 
|  | ASSERT_TRUE(auth_manager->GetCredentials().access_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, "access_token_2"); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncAuthManagerTest, DoesNotRequestAccessTokenIfSyncInactive) { | 
|  | CoreAccountId account_id = | 
|  | identity_env() | 
|  | ->MakePrimaryAccountAvailable("test@email.com", | 
|  | signin::ConsentLevel::kSync) | 
|  | .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); | 
|  | std::unique_ptr<SyncAuthManager> auth_manager = | 
|  | CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); | 
|  | 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 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(credentials_changed, Run()).Times(testing::AtLeast(1)); | 
|  | identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); | 
|  | ASSERT_TRUE(auth_manager->GetCredentials().access_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(credentials_changed, Run()); | 
|  | 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_F(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.account_id); | 
|  | } | 
|  | #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) | 
|  |  | 
|  | #if !BUILDFLAG(IS_CHROMEOS_ASH) && !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_F(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.account_id); | 
|  |  | 
|  | // 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", | 
|  | signin::ConsentLevel::kSync); | 
|  | EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, | 
|  | primary_account_info.account_id); | 
|  | } | 
|  |  | 
|  | TEST_F(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.account_id); | 
|  |  | 
|  | identity_env()->ClearPrimaryAccount(); | 
|  | EXPECT_TRUE( | 
|  | auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); | 
|  | } | 
|  | #endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID) && | 
|  | // !BUILDFLAG(IS_IOS) | 
|  |  | 
|  | TEST_F(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", | 
|  | signin::ConsentLevel::kSync) | 
|  | .account_id; | 
|  | identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); | 
|  |  | 
|  | // On initialization, SyncAuthManager should pick up the auth error. This | 
|  | // should not result in a notification. | 
|  | 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); | 
|  |  | 
|  | std::unique_ptr<SyncAuthManager> auth_manager = | 
|  | CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); | 
|  | auth_manager->RegisterForAuthNotifications(); | 
|  | ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, | 
|  | account_id); | 
|  |  | 
|  | EXPECT_TRUE(auth_manager->GetLastAuthError().IsPersistentError()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace syncer |