Chromium Code Reviews| Index: components/signin/core/browser/access_token_fetcher_unittest.cc |
| diff --git a/components/signin/core/browser/access_token_fetcher_unittest.cc b/components/signin/core/browser/access_token_fetcher_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..331468b0e1520f87623ef2f2661222c632b503b1 |
| --- /dev/null |
| +++ b/components/signin/core/browser/access_token_fetcher_unittest.cc |
| @@ -0,0 +1,390 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "components/signin/core/browser/access_token_fetcher.h" |
| + |
| +#include <memory> |
| +#include <utility> |
| + |
| +#include "base/memory/ptr_util.h" |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/run_loop.h" |
| +#include "base/test/mock_callback.h" |
| +#include "components/prefs/testing_pref_service.h" |
| +#include "components/signin/core/browser/account_tracker_service.h" |
| +#include "components/signin/core/browser/fake_profile_oauth2_token_service.h" |
| +#include "components/signin/core/browser/fake_signin_manager.h" |
| +#include "components/signin/core/browser/test_signin_client.h" |
| +#include "components/sync_preferences/testing_pref_service_syncable.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gmock_mutant.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +using base::MockCallback; |
| +using sync_preferences::TestingPrefServiceSyncable; |
| +using testing::CallbackToFunctor; |
| +using testing::InvokeWithoutArgs; |
| +using testing::StrictMock; |
| + |
| +#if defined(OS_CHROMEOS) |
| +// ChromeOS doesn't have SigninManager. |
| +using SigninManagerForTest = FakeSigninManagerBase; |
| +#else |
| +using SigninManagerForTest = FakeSigninManager; |
| +#endif // OS_CHROMEOS |
| + |
| +class AccessTokenFetcherTest : public testing::Test { |
| + public: |
| + using TestTokenCallback = |
| + StrictMock<MockCallback<AccessTokenFetcher::TokenCallback>>; |
| + |
| + AccessTokenFetcherTest() : signin_client_(&pref_service_) { |
| + AccountTrackerService::RegisterPrefs(pref_service_.registry()); |
| +#if defined(OS_CHROMEOS) |
| + SigninManagerBase::RegisterProfilePrefs(pref_service_.registry()); |
| + SigninManagerBase::RegisterPrefs(pref_service_.registry()); |
| +#else |
| + SigninManager::RegisterProfilePrefs(pref_service_.registry()); |
| + SigninManager::RegisterPrefs(pref_service_.registry()); |
| +#endif // OS_CHROMEOS |
| + |
| + account_tracker_ = base::MakeUnique<AccountTrackerService>(); |
| + account_tracker_->Initialize(&signin_client_); |
| + |
| +#if defined(OS_CHROMEOS) |
| + signin_manager_ = base::MakeUnique<FakeSigninManagerBase>( |
| + &signin_client_, account_tracker_.get()); |
| +#else |
| + signin_manager_ = base::MakeUnique<FakeSigninManager>( |
| + &signin_client_, &token_service_, account_tracker_.get(), |
| + /*cookie_manager_service=*/nullptr); |
| +#endif // OS_CHROMEOS |
| + } |
| + |
| + ~AccessTokenFetcherTest() override {} |
| + |
| + std::unique_ptr<AccessTokenFetcher> CreateFetcher( |
| + AccessTokenFetcher::TokenCallback callback) { |
| + std::set<std::string> scopes{"scope"}; |
| + return base::MakeUnique<AccessTokenFetcher>( |
| + "test_consumer", signin_manager_.get(), &token_service_, scopes, |
| + std::move(callback)); |
| + } |
| + |
| + FakeProfileOAuth2TokenService* token_service() { return &token_service_; } |
| + SigninManagerForTest* signin_manager() { return signin_manager_.get(); } |
| + |
| + void SignIn(const std::string& account) { |
| +#if defined(OS_CHROMEOS) |
| + signin_manager_->SignIn(account); |
| +#else |
| + signin_manager_->SignIn(account, "user", "pass"); |
| +#endif // OS_CHROMEOS |
| + } |
| + |
| + private: |
| + TestingPrefServiceSyncable pref_service_; |
| + TestSigninClient signin_client_; |
| + FakeProfileOAuth2TokenService token_service_; |
| + |
| + std::unique_ptr<AccountTrackerService> account_tracker_; |
| + std::unique_ptr<SigninManagerForTest> signin_manager_; |
| +}; |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldReturnAccessToken) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Signed in and refresh token already exists, so this should result in a |
| + // request for an access token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // Once the access token request is fulfilled, we should get called back with |
| + // the access token. |
| + EXPECT_CALL(callback, |
| + Run(GoogleServiceAuthError::AuthErrorNone(), "access token")); |
| + token_service()->IssueAllTokensForAccount( |
| + "account", "access token", |
| + base::Time::Now() + base::TimeDelta::FromHours(1)); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldNotReplyIfDestroyed) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Signed in and refresh token already exists, so this should result in a |
| + // request for an access token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // Destroy the fetcher before the access token request is fulfilled. |
| + fetcher.reset(); |
| + |
| + // Now fulfilling the access token request should have no effect. |
| + token_service()->IssueAllTokensForAccount( |
| + "account", "access token", |
| + base::Time::Now() + base::TimeDelta::FromHours(1)); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldNotReturnWhenSignedOut) { |
| + TestTokenCallback callback; |
| + |
| + // Signed out -> the fetcher should wait for a sign-in which never happens |
| + // in this test, so we shouldn't get called back. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| +} |
| + |
| +// Tests related to waiting for sign-in don't apply on ChromeOS (it doesn't have |
| +// that concept). |
| +#if !defined(OS_CHROMEOS) |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldWaitForSignIn) { |
| + TestTokenCallback callback; |
| + |
| + // Not signed in, so this should wait for a sign-in to complete. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Once the access token request is fulfilled, we should get called back with |
| + // the access token. |
| + EXPECT_CALL(callback, |
| + Run(GoogleServiceAuthError::AuthErrorNone(), "access token")); |
| + token_service()->IssueAllTokensForAccount( |
| + "account", "access token", |
| + base::Time::Now() + base::TimeDelta::FromHours(1)); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldWaitForSignInInProgress) { |
| + TestTokenCallback callback; |
| + |
| + signin_manager()->set_auth_in_progress("account"); |
| + |
| + // A sign-in is currently in progress, so this should wait for the sign-in to |
| + // complete. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Once the access token request is fulfilled, we should get called back with |
| + // the access token. |
| + EXPECT_CALL(callback, |
| + Run(GoogleServiceAuthError::AuthErrorNone(), "access token")); |
| + token_service()->IssueAllTokensForAccount( |
| + "account", "access token", |
| + base::Time::Now() + base::TimeDelta::FromHours(1)); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldWaitForFailedSignIn) { |
| + TestTokenCallback callback; |
| + |
| + signin_manager()->set_auth_in_progress("account"); |
| + |
| + // A sign-in is currently in progress, so this should wait for the sign-in to |
| + // complete. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // The fetcher should detect the failed sign-in and call us with an empty |
| + // access token. |
| + EXPECT_CALL( |
| + callback, |
| + Run(GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED), |
| + std::string())); |
| + |
| + signin_manager()->FailSignin( |
| + GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED)); |
| +} |
| + |
| +#endif // !OS_CHROMEOS |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldWaitForRefreshToken) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + |
| + // Signed in, but there is no refresh token -> we should not get called back |
| + // (yet). |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // Getting a refresh token should result in a request for an access token. |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Once the access token request is fulfilled, we should get called back with |
| + // the access token. |
| + EXPECT_CALL(callback, |
| + Run(GoogleServiceAuthError::AuthErrorNone(), "access token")); |
| + token_service()->IssueAllTokensForAccount( |
| + "account", "access token", |
| + base::Time::Now() + base::TimeDelta::FromHours(1)); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldIgnoreRefreshTokensForOtherAccounts) { |
| + TestTokenCallback callback; |
| + |
| + // Signed-in to "account", but there's only a refresh token for a different |
| + // account. |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account 2", "refresh"); |
| + |
| + // The fetcher should wait for the correct refresh token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // A refresh token for yet another account shouldn't matter either. |
| + token_service()->GetDelegate()->UpdateCredentials("account 3", "refresh"); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldReturnWhenNoRefreshTokenAvailable) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + |
| + // Signed in, but there is no refresh token -> we should not get called back |
| + // (yet). |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // Getting a refresh token for some other account should have no effect. |
| + token_service()->GetDelegate()->UpdateCredentials("different account", |
| + "refresh token"); |
| + |
| + // The OAuth2TokenService posts a task to the current thread when we try to |
| + // get an access token for an account without a refresh token, so we need a |
| + // MessageLoop in this test to not crash. |
| + base::MessageLoop message_loop; |
|
Marc Treib
2017/01/27 13:16:39
This is unfortunate :(
But I don't see any other s
|
| + |
| + // When all refresh tokens have been loaded by the token service, but the one |
| + // for our account wasn't among them, we should get called back with an empty |
| + // access token. |
| + EXPECT_CALL(callback, Run(testing::_, std::string())); |
| + token_service()->GetDelegate()->LoadCredentials("account doesn't matter"); |
| + |
| + // Wait for the task posted by OAuth2TokenService to run. |
| + base::RunLoop().RunUntilIdle(); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldRetryCanceledAccessTokenRequest) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Signed in and refresh token already exists, so this should result in a |
| + // request for an access token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // A canceled access token request should get retried once. |
| + token_service()->IssueErrorForAllPendingRequestsForAccount( |
| + "account", |
| + GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| + |
| + // Once the access token request is fulfilled, we should get called back with |
| + // the access token. |
| + EXPECT_CALL(callback, |
| + Run(GoogleServiceAuthError::AuthErrorNone(), "access token")); |
| + token_service()->IssueAllTokensForAccount( |
| + "account", "access token", |
| + base::Time::Now() + base::TimeDelta::FromHours(1)); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldRetryCanceledAccessTokenRequestOnlyOnce) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Signed in and refresh token already exists, so this should result in a |
| + // request for an access token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // A canceled access token request should get retried once. |
| + token_service()->IssueErrorForAllPendingRequestsForAccount( |
| + "account", |
| + GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| + |
| + // On the second failure, we should get called back with an empty access |
| + // token. |
| + EXPECT_CALL( |
| + callback, |
| + Run(GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED), |
| + std::string())); |
| + token_service()->IssueErrorForAllPendingRequestsForAccount( |
| + "account", |
| + GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| +} |
| + |
| +#if !defined(OS_CHROMEOS) |
| + |
| +TEST_F(AccessTokenFetcherTest, |
| + ShouldNotRetryCanceledAccessTokenRequestIfSignedOut) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Signed in and refresh token already exists, so this should result in a |
| + // request for an access token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // Simulate the user signing out while the access token request is pending. |
| + // In this case, the pending request gets canceled, and the fetcher should |
| + // *not* retry. |
| + signin_manager()->ForceSignOut(); |
| + EXPECT_CALL( |
| + callback, |
| + Run(GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED), |
| + std::string())); |
| + token_service()->IssueErrorForAllPendingRequestsForAccount( |
| + "account", |
| + GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| +} |
| + |
| +#endif // !OS_CHROMEOS |
| + |
| +TEST_F(AccessTokenFetcherTest, |
| + ShouldNotRetryCanceledAccessTokenRequestIfRefreshTokenRevoked) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Signed in and refresh token already exists, so this should result in a |
| + // request for an access token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // Simulate the refresh token getting invalidated. In this case, pending |
| + // access token requests get canceled, and the fetcher should *not* retry. |
| + token_service()->GetDelegate()->RevokeCredentials("account"); |
| + EXPECT_CALL( |
| + callback, |
| + Run(GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED), |
| + std::string())); |
| + token_service()->IssueErrorForAllPendingRequestsForAccount( |
| + "account", |
| + GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| +} |
| + |
| +TEST_F(AccessTokenFetcherTest, ShouldNotRetryFailedAccessTokenRequest) { |
| + TestTokenCallback callback; |
| + |
| + SignIn("account"); |
| + token_service()->GetDelegate()->UpdateCredentials("account", "refresh token"); |
| + |
| + // Signed in and refresh token already exists, so this should result in a |
| + // request for an access token. |
| + auto fetcher = CreateFetcher(callback.Get()); |
| + |
| + // An access token failure other than "canceled" should not be retried; we |
| + // should immediately get called back with an empty access token. |
| + EXPECT_CALL( |
| + callback, |
| + Run(GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE), |
| + std::string())); |
| + token_service()->IssueErrorForAllPendingRequestsForAccount( |
| + "account", |
| + GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); |
| +} |