| // 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 { |
| |
| 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() { |
| return std::make_unique<SyncAuthManager>(identity_env_.identity_manager(), |
| &delegate_); |
| } |
| |
| std::unique_ptr<SyncAuthManager> CreateAuthManagerForLocalSync() { |
| return std::make_unique<SyncAuthManager>(nullptr, &delegate_); |
| } |
| |
| 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.email.empty()); |
| 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()) |
| .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) |
| 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()) |
| .account_id; |
| |
| 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()) |
| .account_id; |
| 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()) |
| .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(); |
| |
| // 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.account_id); |
| |
| // 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.account_id, 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()) |
| .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_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()) |
| .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) |
| |
| 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()) |
| .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_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()) |
| .account_id; |
| |
| 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()) |
| .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_info.token, |
| "access_token"); |
| } |
| |
| TEST_P(SyncAuthManagerTest, |
| RetriesAccessTokenFetchWithBackoffOnTransientFailure) { |
| CoreAccountId account_id = |
| identity_env() |
| ->MakePrimaryAccountAvailable("test@email.com", |
| /*consent_level=*/GetParam()) |
| .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_P(SyncAuthManagerTest, |
| RetriesAccessTokenFetchWithoutBackoffOnceOnFirstCancelTransientFailure) { |
| CoreAccountId account_id = |
| identity_env() |
| ->MakePrimaryAccountAvailable("test@email.com", |
| /*consent_level=*/GetParam()) |
| .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::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()) |
| .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::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()) |
| .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_P(SyncAuthManagerTest, FetchesNewAccessTokenWithBackoffOnServerError) { |
| CoreAccountId account_id = |
| identity_env() |
| ->MakePrimaryAccountAvailable("test@email.com", |
| /*consent_level=*/GetParam()) |
| .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_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()) |
| .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_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()) |
| .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_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()) |
| .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_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()) |
| .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_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()) |
| .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_info.token.empty()); |
| } |
| |
| TEST_P(SyncAuthManagerTest, ClearsCredentialsOnRefreshTokenRemoval) { |
| CoreAccountId account_id = |
| identity_env() |
| ->MakePrimaryAccountAvailable("test@email.com", |
| /*consent_level=*/GetParam()) |
| .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_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()) |
| .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_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()) |
| .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_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()) |
| .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_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()) |
| .account_id; |
| |
| 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.account_id); |
| } |
| #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.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", |
| /*consent_level=*/GetParam()); |
| EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| primary_account_info.account_id); |
| } |
| |
| 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.account_id); |
| |
| 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()) |
| .account_id; |
| 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()); |
| } |
| |
| class SyncAuthManagerWithDetermineAccountTypeTest : public SyncAuthManagerTest { |
| public: |
| SyncAuthManagerWithDetermineAccountTypeTest() { |
| scoped_feature_list_.InitAndEnableFeature( |
| kSyncDetermineAccountManagedStatus); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(SyncAuthManagerWithDetermineAccountTypeTest, |
| 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.account_id); |
| |
| // 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(SyncAuthManagerWithDetermineAccountTypeTest, |
| 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.account_id); |
| |
| // 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(SyncAuthManagerWithDetermineAccountTypeTest, |
| 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.account_id); |
| |
| // 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) |
| |
| } // namespace |
| |
| } // namespace syncer |