| // Copyright 2013 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 <algorithm> |
| #include <cstring> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/scoped_observer.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/time/time.h" |
| #include "base/timer/mock_timer.h" |
| #include "build/build_config.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/signin/core/browser/account_reconcilor.h" |
| #include "components/signin/core/browser/account_tracker_service.h" |
| #include "components/signin/core/browser/fake_gaia_cookie_manager_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/mirror_account_reconcilor_delegate.h" |
| #include "components/signin/core/browser/profile_management_switches.h" |
| #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| #include "components/signin/core/browser/signin_buildflags.h" |
| #include "components/signin/core/browser/signin_manager.h" |
| #include "components/signin/core/browser/signin_metrics.h" |
| #include "components/signin/core/browser/signin_pref_names.h" |
| #include "components/signin/core/browser/test_signin_client.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "google_apis/gaia/google_service_auth_error.h" |
| #include "net/url_request/test_url_fetcher_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| #include "components/signin/core/browser/dice_account_reconcilor_delegate.h" |
| #endif |
| |
| namespace { |
| |
| #if defined(OS_CHROMEOS) |
| using FakeSigninManagerForTesting = FakeSigninManagerBase; |
| #else |
| using FakeSigninManagerForTesting = FakeSigninManager; |
| #endif |
| |
| // TestSigninClient keeping track of the dice migration. |
| class DiceTestSigninClient : public TestSigninClient { |
| public: |
| explicit DiceTestSigninClient(PrefService* prefs) : TestSigninClient(prefs) {} |
| |
| void SetReadyForDiceMigration(bool ready) override { |
| is_ready_for_dice_migration_ = ready; |
| } |
| |
| bool is_ready_for_dice_migration() { return is_ready_for_dice_migration_; } |
| |
| private: |
| bool is_ready_for_dice_migration_ = false; |
| DISALLOW_COPY_AND_ASSIGN(DiceTestSigninClient); |
| }; |
| |
| // An AccountReconcilorDelegate that records all calls (Spy pattern). |
| class SpyReconcilorDelegate : public signin::AccountReconcilorDelegate { |
| public: |
| int num_reconcile_finished_calls_{0}; |
| int num_reconcile_timeout_calls_{0}; |
| |
| bool IsReconcileEnabled() const override { return true; } |
| |
| bool IsAccountConsistencyEnforced() const override { return true; } |
| |
| std::string GetGaiaApiSource() const override { return "TestSource"; } |
| |
| bool ShouldAbortReconcileIfPrimaryHasError() const override { return true; } |
| |
| std::string GetFirstGaiaAccountForReconcile( |
| const std::vector<std::string>& chrome_accounts, |
| const std::vector<gaia::ListedAccount>& gaia_accounts, |
| const std::string& primary_account, |
| bool first_execution, |
| bool will_logout) const override { |
| return primary_account; |
| } |
| |
| void OnReconcileFinished(const std::string& first_account, |
| bool is_reconcile_noop) override { |
| ++num_reconcile_finished_calls_; |
| } |
| |
| base::TimeDelta GetReconcileTimeout() const override { |
| // Does not matter as long as it is different from base::TimeDelta::Max(). |
| return base::TimeDelta::FromMinutes(100); |
| } |
| |
| void OnReconcileError(const GoogleServiceAuthError& error) override { |
| ++num_reconcile_timeout_calls_; |
| } |
| }; |
| |
| // gmock does not allow mocking classes with move-only parameters, preventing |
| // from mocking the AccountReconcilor class directly (because of the |
| // unique_ptr<AccountReconcilorDelegate> parameter). |
| // Introduce a dummy class creating the delegate internally, to avoid the move. |
| class DummyAccountReconcilorWithDelegate : public AccountReconcilor { |
| public: |
| explicit DummyAccountReconcilorWithDelegate( |
| ProfileOAuth2TokenService* token_service, |
| SigninManagerBase* signin_manager, |
| SigninClient* client, |
| GaiaCookieManagerService* cookie_manager_service, |
| signin::AccountConsistencyMethod account_consistency) |
| : AccountReconcilor( |
| token_service, |
| signin_manager, |
| client, |
| cookie_manager_service, |
| CreateAccountReconcilorDelegate(client, |
| signin_manager, |
| account_consistency)) { |
| Initialize(false /* start_reconcile_if_tokens_available */); |
| } |
| |
| // Takes ownership of |delegate|. |
| // gmock can't work with move only parameters. |
| explicit DummyAccountReconcilorWithDelegate( |
| ProfileOAuth2TokenService* token_service, |
| SigninManagerBase* signin_manager, |
| SigninClient* client, |
| GaiaCookieManagerService* cookie_manager_service, |
| signin::AccountReconcilorDelegate* delegate) |
| : AccountReconcilor( |
| token_service, |
| signin_manager, |
| client, |
| cookie_manager_service, |
| std::unique_ptr<signin::AccountReconcilorDelegate>(delegate)) { |
| Initialize(false /* start_reconcile_if_tokens_available */); |
| } |
| |
| static std::unique_ptr<signin::AccountReconcilorDelegate> |
| CreateAccountReconcilorDelegate( |
| SigninClient* signin_client, |
| SigninManagerBase* signin_manager, |
| signin::AccountConsistencyMethod account_consistency) { |
| switch (account_consistency) { |
| case signin::AccountConsistencyMethod::kMirror: |
| return std::make_unique<signin::MirrorAccountReconcilorDelegate>( |
| signin_manager); |
| case signin::AccountConsistencyMethod::kDisabled: |
| case signin::AccountConsistencyMethod::kDiceFixAuthErrors: |
| return std::make_unique<signin::AccountReconcilorDelegate>(); |
| case signin::AccountConsistencyMethod::kDicePrepareMigration: |
| case signin::AccountConsistencyMethod::kDiceMigration: |
| case signin::AccountConsistencyMethod::kDice: |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| return std::make_unique<signin::DiceAccountReconcilorDelegate>( |
| signin_client, account_consistency); |
| #else |
| NOTREACHED(); |
| return nullptr; |
| #endif |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| }; |
| |
| class MockAccountReconcilor |
| : public testing::StrictMock<DummyAccountReconcilorWithDelegate> { |
| public: |
| explicit MockAccountReconcilor( |
| ProfileOAuth2TokenService* token_service, |
| SigninManagerBase* signin_manager, |
| SigninClient* client, |
| GaiaCookieManagerService* cookie_manager_service, |
| signin::AccountConsistencyMethod account_consistency); |
| |
| explicit MockAccountReconcilor( |
| ProfileOAuth2TokenService* token_service, |
| SigninManagerBase* signin_manager, |
| SigninClient* client, |
| GaiaCookieManagerService* cookie_manager_service, |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate); |
| |
| MOCK_METHOD1(PerformMergeAction, void(const std::string& account_id)); |
| MOCK_METHOD0(PerformLogoutAllAccountsAction, void()); |
| }; |
| |
| MockAccountReconcilor::MockAccountReconcilor( |
| ProfileOAuth2TokenService* token_service, |
| SigninManagerBase* signin_manager, |
| SigninClient* client, |
| GaiaCookieManagerService* cookie_manager_service, |
| signin::AccountConsistencyMethod account_consistency) |
| : testing::StrictMock<DummyAccountReconcilorWithDelegate>( |
| token_service, |
| signin_manager, |
| client, |
| cookie_manager_service, |
| account_consistency) {} |
| |
| MockAccountReconcilor::MockAccountReconcilor( |
| ProfileOAuth2TokenService* token_service, |
| SigninManagerBase* signin_manager, |
| SigninClient* client, |
| GaiaCookieManagerService* cookie_manager_service, |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate) |
| : testing::StrictMock<DummyAccountReconcilorWithDelegate>( |
| token_service, |
| signin_manager, |
| client, |
| cookie_manager_service, |
| delegate.release()) {} |
| |
| } // namespace |
| |
| class AccountReconcilorTest : public ::testing::Test { |
| public: |
| AccountReconcilorTest(); |
| ~AccountReconcilorTest() override; |
| |
| FakeSigninManagerForTesting* signin_manager() { return &signin_manager_; } |
| FakeProfileOAuth2TokenService* token_service() { return &token_service_; } |
| DiceTestSigninClient* test_signin_client() { return &test_signin_client_; } |
| AccountTrackerService* account_tracker() { return &account_tracker_; } |
| FakeGaiaCookieManagerService* cookie_manager_service() { |
| return &cookie_manager_service_; |
| } |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| |
| MockAccountReconcilor* GetMockReconcilor(); |
| MockAccountReconcilor* GetMockReconcilor( |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate); |
| |
| std::string ConnectProfileToAccount(const std::string& gaia_id, |
| const std::string& username); |
| |
| std::string PickAccountIdForAccount(const std::string& gaia_id, |
| const std::string& username); |
| |
| void SimulateAddAccountToCookieCompleted( |
| GaiaCookieManagerService::Observer* observer, |
| const std::string& account_id, |
| const GoogleServiceAuthError& error); |
| |
| void SimulateCookieContentSettingsChanged( |
| content_settings::Observer* observer, |
| const ContentSettingsPattern& primary_pattern); |
| |
| GURL get_check_connection_info_url() { |
| return get_check_connection_info_url_; |
| } |
| |
| void SetAccountConsistency(signin::AccountConsistencyMethod method); |
| |
| PrefService* pref_service() { return &pref_service_; } |
| |
| private: |
| base::MessageLoop loop; |
| signin::AccountConsistencyMethod account_consistency_; |
| sync_preferences::TestingPrefServiceSyncable pref_service_; |
| FakeProfileOAuth2TokenService token_service_; |
| DiceTestSigninClient test_signin_client_; |
| AccountTrackerService account_tracker_; |
| FakeGaiaCookieManagerService cookie_manager_service_; |
| FakeSigninManagerForTesting signin_manager_; |
| std::unique_ptr<MockAccountReconcilor> mock_reconcilor_; |
| base::HistogramTester histogram_tester_; |
| GURL get_check_connection_info_url_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTest); |
| }; |
| |
| // For tests that must be run with multiple account consistency methods. |
| class AccountReconcilorMethodParamTest |
| : public AccountReconcilorTest, |
| public ::testing::WithParamInterface<signin::AccountConsistencyMethod> { |
| public: |
| AccountReconcilorMethodParamTest() = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorMethodParamTest); |
| }; |
| |
| INSTANTIATE_TEST_CASE_P(Dice_Mirror, |
| AccountReconcilorMethodParamTest, |
| ::testing::Values( |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| signin::AccountConsistencyMethod::kDice, |
| #endif |
| signin::AccountConsistencyMethod::kMirror)); |
| |
| AccountReconcilorTest::AccountReconcilorTest() |
| : account_consistency_(signin::AccountConsistencyMethod::kDisabled), |
| test_signin_client_(&pref_service_), |
| cookie_manager_service_(&token_service_, |
| GaiaConstants::kChromeSource, |
| &test_signin_client_), |
| #if defined(OS_CHROMEOS) |
| signin_manager_(&test_signin_client_, &account_tracker_) { |
| #else |
| signin_manager_(&test_signin_client_, |
| &token_service_, |
| &account_tracker_, |
| &cookie_manager_service_) { |
| #endif |
| AccountTrackerService::RegisterPrefs(pref_service_.registry()); |
| SigninManagerBase::RegisterProfilePrefs(pref_service_.registry()); |
| SigninManagerBase::RegisterPrefs(pref_service_.registry()); |
| pref_service_.registry()->RegisterBooleanPref( |
| prefs::kTokenServiceDiceCompatible, false); |
| get_check_connection_info_url_ = |
| GaiaUrls::GetInstance()->GetCheckConnectionInfoURLWithSource( |
| GaiaConstants::kChromeSource); |
| |
| account_tracker_.Initialize(&test_signin_client_); |
| cookie_manager_service_.SetListAccountsResponseHttpNotFound(); |
| signin_manager_.Initialize(nullptr); |
| |
| // The reconcilor should not be built before the test can set the account |
| // consistency method. |
| EXPECT_FALSE(mock_reconcilor_); |
| } |
| |
| MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor() { |
| if (!mock_reconcilor_) { |
| mock_reconcilor_ = std::make_unique<MockAccountReconcilor>( |
| &token_service_, &signin_manager_, &test_signin_client_, |
| &cookie_manager_service_, account_consistency_); |
| } |
| |
| return mock_reconcilor_.get(); |
| } |
| |
| MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor( |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate) { |
| mock_reconcilor_ = std::make_unique<MockAccountReconcilor>( |
| &token_service_, &signin_manager_, &test_signin_client_, |
| &cookie_manager_service_, std::move(delegate)); |
| |
| return mock_reconcilor_.get(); |
| } |
| |
| AccountReconcilorTest::~AccountReconcilorTest() { |
| if (mock_reconcilor_) |
| mock_reconcilor_->Shutdown(); |
| signin_manager_.Shutdown(); |
| cookie_manager_service_.Shutdown(); |
| account_tracker_.Shutdown(); |
| test_signin_client_.Shutdown(); |
| token_service_.Shutdown(); |
| } |
| |
| std::string AccountReconcilorTest::ConnectProfileToAccount( |
| const std::string& gaia_id, |
| const std::string& username) { |
| const std::string account_id = PickAccountIdForAccount(gaia_id, username); |
| #if !defined(OS_CHROMEOS) |
| signin_manager()->set_password("password"); |
| #endif |
| signin_manager()->SetAuthenticatedAccountInfo(gaia_id, username); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| return account_id; |
| } |
| |
| std::string AccountReconcilorTest::PickAccountIdForAccount( |
| const std::string& gaia_id, |
| const std::string& username) { |
| return account_tracker()->PickAccountIdForAccount(gaia_id, username); |
| } |
| |
| void AccountReconcilorTest::SimulateAddAccountToCookieCompleted( |
| GaiaCookieManagerService::Observer* observer, |
| const std::string& account_id, |
| const GoogleServiceAuthError& error) { |
| observer->OnAddAccountToCookieCompleted(account_id, error); |
| } |
| |
| void AccountReconcilorTest::SimulateCookieContentSettingsChanged( |
| content_settings::Observer* observer, |
| const ContentSettingsPattern& primary_pattern) { |
| observer->OnContentSettingChanged( |
| primary_pattern, ContentSettingsPattern::Wildcard(), |
| CONTENT_SETTINGS_TYPE_COOKIES, std::string()); |
| } |
| |
| void AccountReconcilorTest::SetAccountConsistency( |
| signin::AccountConsistencyMethod method) { |
| account_consistency_ = method; |
| } |
| |
| TEST_F(AccountReconcilorTest, Basic) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| } |
| |
| #if !defined(OS_CHROMEOS) |
| |
| // This method requires the use of the |TestSigninClient| to be created from the |
| // |ChromeSigninClientFactory| because it overrides the |GoogleSigninSucceeded| |
| // method with an empty implementation. On MacOS, the normal implementation |
| // causes the try_bots to time out. |
| TEST_F(AccountReconcilorTest, SigninManagerRegistration) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_FALSE(reconcilor->IsRegisteredWithTokenService()); |
| |
| account_tracker()->SeedAccountInfo("12345", "user@gmail.com"); |
| signin_manager()->SignIn("12345", "user@gmail.com", "password"); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| |
| signin_manager()->SignOut(signin_metrics::SIGNOUT_TEST, |
| signin_metrics::SignoutDelete::IGNORE_METRIC); |
| ASSERT_FALSE(reconcilor->IsRegisteredWithTokenService()); |
| } |
| |
| // This method requires the use of the |TestSigninClient| to be created from the |
| // |ChromeSigninClientFactory| because it overrides the |GoogleSigninSucceeded| |
| // method with an empty implementation. On MacOS, the normal implementation |
| // causes the try_bots to time out. |
| TEST_F(AccountReconcilorTest, Reauth) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string email = "user@gmail.com"; |
| const std::string account_id = ConnectProfileToAccount("12345", email); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); |
| |
| // Simulate reauth. The state of the reconcilor should not change. |
| signin_manager()->OnExternalSigninCompleted(email); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); |
| } |
| |
| #endif // !defined(OS_CHROMEOS) |
| |
| TEST_F(AccountReconcilorTest, ProfileAlreadyConnected) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); |
| } |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| |
| struct AccountReconcilorTestDiceParam { |
| const char* tokens; |
| const char* cookies; |
| bool is_first_reconcile; |
| const char* gaia_api_calls; |
| const char* tokens_after_reconcile; |
| const char* cookies_after_reconcile; |
| }; |
| |
| // Pretty prints a AccountReconcilorTestDiceParam. Used by gtest. |
| void PrintTo(const AccountReconcilorTestDiceParam& param, ::std::ostream* os) { |
| *os << "Tokens: " << param.tokens << ". Cookies: " << param.cookies |
| << ". First reconcile: " << param.is_first_reconcile; |
| } |
| |
| // clang-format off |
| const AccountReconcilorTestDiceParam kDiceParams[] = { |
| // This table encodes the initial state and expectations of a reconcile. |
| // The syntax is: |
| // - Tokens: |
| // A, B, C: Accounts for which we have a token in Chrome. |
| // *: The next account is the main Chrome account (i.e. in SigninManager). |
| // x: The next account has a token error. |
| // - Cookies: |
| // A, B, C: Accounts in the Gaia cookie (returned by ListAccounts). |
| // x: The next cookie is marked "invalid". |
| // - First Run: true if this is the first reconcile (i.e. Chrome startup). |
| // - API calls: |
| // X: Logout all accounts. |
| // A, B, C: Merge account. |
| // ------------------------------------------------------------------------- |
| // Tokens | Cookies | First Run | Gaia calls | Tokens after | Cookies after |
| // ------------------------------------------------------------------------- |
| |
| // First reconcile (Chrome restart): Rebuild the Gaia cookie to match the |
| // tokens. Make the Sync account the default account in the Gaia cookie. |
| // Sync enabled. |
| { "*AB", "AB", true, "", "*AB", "AB"}, |
| { "*AB", "BA", true, "XAB", "*AB", "AB"}, |
| { "*AB", "A", true, "B", "*AB", "AB"}, |
| { "*AB", "B", true, "XAB", "*AB", "AB"}, |
| { "*AB", "", true, "AB", "*AB", "AB"}, |
| // Sync enabled, token error on primary. |
| { "*xAB", "AB", true, "X", "*xA", ""}, |
| { "*xAB", "BA", true, "XB", "*xAB", "B"}, |
| { "*xAB", "A", true, "X", "*xA", ""}, |
| { "*xAB", "B", true, "", "*xAB", "B"}, |
| { "*xAB", "", true, "B", "*xAB", "B"}, |
| // Sync enabled, token error on secondary. |
| { "*AxB", "AB", true, "XA", "*A", "A"}, |
| { "*AxB", "BA", true, "XA", "*A", "A"}, |
| { "*AxB", "A", true, "", "*A", "A"}, |
| { "*AxB", "B", true, "XA", "*A", "A"}, |
| { "*AxB", "", true, "A", "*A", "A"}, |
| // Sync enabled, token error on both accounts. |
| { "*xAxB", "AB", true, "X", "*xA", ""}, |
| { "*xAxB", "BA", true, "X", "*xA", ""}, |
| { "*xAxB", "A", true, "X", "*xA", ""}, |
| { "*xAxB", "B", true, "X", "*xA", ""}, |
| { "*xAxB", "", true, "", "*xA", ""}, |
| // Sync disabled. |
| { "AB", "AB", true, "", "AB", "AB"}, |
| { "AB", "BA", true, "", "AB", "BA"}, |
| { "AB", "A", true, "B", "AB", "AB"}, |
| { "AB", "B", true, "A", "AB", "BA"}, |
| { "AB", "", true, "AB", "AB", "AB"}, |
| // Sync disabled, token error on first account. |
| { "xAB", "AB", true, "XB", "B", "B"}, |
| { "xAB", "BA", true, "XB", "B", "B"}, |
| { "xAB", "A", true, "XB", "B", "B"}, |
| { "xAB", "B", true, "", "B", "B"}, |
| { "xAB", "", true, "B", "B", "B"}, |
| // Sync disabled, token error on second account . |
| { "AxB", "AB", true, "XA", "A", "A"}, |
| { "AxB", "BA", true, "XA", "A", "A"}, |
| { "AxB", "A", true, "", "A", "A"}, |
| { "AxB", "B", true, "XA", "A", "A"}, |
| { "AxB", "", true, "A", "A", "A"}, |
| // Sync disabled, token error on both accounts. |
| { "xAxB", "AB", true, "X", "", ""}, |
| { "xAxB", "BA", true, "X", "", ""}, |
| { "xAxB", "A", true, "X", "", ""}, |
| { "xAxB", "B", true, "X", "", ""}, |
| { "xAxB", "", true, "", "", ""}, |
| |
| // Chrome is running: Do not change the order of accounts already present in |
| // the Gaia cookies. |
| // Sync enabled. |
| { "*AB", "AB", false, "", "*AB", "AB"}, |
| { "*AB", "BA", false, "", "*AB", "BA"}, |
| { "*AB", "A", false, "B", "*AB", "AB"}, |
| { "*AB", "B", false, "A", "*AB", "BA"}, |
| { "*AB", "", false, "AB", "*AB", "AB"}, |
| // Sync enabled, token error on primary. |
| { "*xAB", "AB", false, "X", "*xA", ""}, |
| { "*xAB", "BA", false, "XB", "*xAB", "B"}, |
| { "*xAB", "A", false, "X", "*xA", ""}, |
| { "*xAB", "B", false, "", "*xAB", "B"}, |
| { "*xAB", "", false, "B", "*xAB", "B"}, |
| // Sync enabled, token error on secondary. |
| { "*AxB", "AB", false, "XA", "*A", "A"}, |
| { "*AxB", "BA", false, "XA", "*A", "A"}, |
| { "*AxB", "A", false, "", "*A", "A"}, |
| { "*AxB", "B", false, "XA", "*A", "A"}, |
| { "*AxB", "", false, "A", "*A", "A"}, |
| // Sync enabled, token error on both accounts. |
| { "*xAxB", "AB", false, "X", "*xA", ""}, |
| { "*xAxB", "BA", false, "X", "*xA", ""}, |
| { "*xAxB", "A", false, "X", "*xA", ""}, |
| { "*xAxB", "B", false, "X", "*xA", ""}, |
| { "*xAxB", "", false, "", "*xA", ""}, |
| // Sync disabled. |
| { "AB", "AB", false, "", "AB", "AB"}, |
| { "AB", "BA", false, "", "AB", "BA"}, |
| { "AB", "A", false, "B", "AB", "AB"}, |
| { "AB", "B", false, "A", "AB", "BA"}, |
| { "AB", "", false, "AB", "AB", "AB"}, |
| // Sync disabled, token error on first account. |
| { "xAB", "AB", false, "X", "", ""}, |
| { "xAB", "BA", false, "XB", "B", "B"}, |
| { "xAB", "A", false, "X", "", ""}, |
| { "xAB", "B", false, "", "B", "B"}, |
| { "xAB", "", false, "B", "B", "B"}, |
| // Sync disabled, token error on second account. |
| { "AxB", "AB", false, "XA", "A", "A"}, |
| { "AxB", "BA", false, "X", "", ""}, |
| { "AxB", "A", false, "", "A", "A"}, |
| { "AxB", "B", false, "X", "", ""}, |
| { "AxB", "", false, "A", "A", "A"}, |
| // Sync disabled, token error on both accounts. |
| { "xAxB", "AB", false, "X", "", ""}, |
| { "xAxB", "BA", false, "X", "", ""}, |
| { "xAxB", "A", false, "X", "", ""}, |
| { "xAxB", "B", false, "X", "", ""}, |
| { "xAxB", "", false, "", "", ""}, |
| |
| // Account marked as invalid in cookies. |
| // Do not logout. Regression tests for http://crbug.com/854799 |
| { "", "xA", false, "", "", "xA"}, |
| { "", "xAxB", false, "", "", "xAxB"}, |
| { "xA", "xA", false, "", "", "xA"}, |
| { "xAB", "xAB", false, "", "B", "xAB"}, |
| { "AxB", "AxC", false, "", "A", "AxC"}, |
| { "B", "xAB", false, "", "B", "xAB"}, |
| { "*xA", "xA", false, "", "*xA", "xA"}, |
| { "*xA", "xB", false, "", "*xA", "xB"}, |
| { "*xAB", "xAB", false, "", "*xAB", "xAB"}, |
| // Appending a new cookie after the invalid one. |
| { "B", "xA", false, "B", "B", "xAB"}, |
| { "xAB", "xA", false, "B", "B", "xAB"}, |
| // Cookies can be refreshed in pace, without logout. |
| { "AB", "xAB", false, "A", "AB", "AB"}, |
| { "*AB", "xBxA", false, "BA", "*AB", "BA"}, |
| // Logout when necessary. |
| { "", "xAB", false, "X", "", ""}, |
| { "xAB", "xAC", false, "X", "", ""}, |
| { "xAB", "AxC", false, "X", "", ""}, |
| { "*xAB", "xABC", false, "X", "*xA", ""}, |
| { "xAB", "xABC", false, "X", "", ""}, |
| |
| // Miscellaneous cases. |
| // Check that unknown Gaia accounts are signed out. |
| { "", "A", true, "X", "", ""}, |
| { "", "A", false, "X", "", ""}, |
| { "*A", "AB", true, "XA", "*A", "A"}, |
| { "*A", "AB", false, "XA", "*A", "A"}, |
| // Check that Gaia default account is kept in first position. |
| { "AB", "BC", true, "XBA", "AB", "BA"}, |
| { "AB", "BC", false, "XBA", "AB", "BA"}, |
| // Required for idempotency check. |
| { "", "", false, "", "", ""}, |
| { "*A", "A", false, "", "*A", "A"}, |
| { "A", "A", false, "", "A", "A"}, |
| { "B", "B", false, "", "B", "B"}, |
| { "*xA", "", false, "", "*xA", ""}, |
| { "*xAB", "B", false, "", "*xAB", "B"}, |
| { "A", "AxC", false, "", "A", "AxC"}, |
| }; |
| // clang-format on |
| |
| // Parameterized version of AccountReconcilorTest. |
| class AccountReconcilorTestDice |
| : public AccountReconcilorTest, |
| public ::testing::WithParamInterface<AccountReconcilorTestDiceParam> { |
| protected: |
| struct Account { |
| std::string email; |
| std::string gaia_id; |
| }; |
| |
| struct Token { |
| std::string gaia_id; |
| std::string email; |
| bool is_authenticated; |
| bool has_error; |
| }; |
| |
| struct Cookie { |
| std::string gaia_id; |
| bool is_valid; |
| |
| bool operator==(const Cookie& other) const { |
| return gaia_id == other.gaia_id && is_valid == other.is_valid; |
| } |
| }; |
| |
| AccountReconcilorTestDice() { |
| accounts_['A'] = {"a@gmail.com", "A"}; |
| accounts_['B'] = {"b@gmail.com", "B"}; |
| accounts_['C'] = {"c@gmail.com", "C"}; |
| } |
| |
| // Build Tokens from string. |
| std::vector<Token> ParseTokenString(const char* token_string) { |
| std::vector<Token> parsed_tokens; |
| bool is_authenticated = false; |
| bool has_error = false; |
| for (int i = 0; token_string[i] != '\0'; ++i) { |
| char token_code = token_string[i]; |
| if (token_code == '*') { |
| is_authenticated = true; |
| continue; |
| } |
| if (token_code == 'x') { |
| has_error = true; |
| continue; |
| } |
| parsed_tokens.push_back({accounts_[token_code].gaia_id, |
| accounts_[token_code].email, is_authenticated, |
| has_error}); |
| is_authenticated = false; |
| has_error = false; |
| } |
| return parsed_tokens; |
| } |
| |
| // Build Cookies from string. |
| std::vector<Cookie> ParseCookieString(const char* cookie_string) { |
| std::vector<Cookie> parsed_cookies; |
| bool valid = true; |
| for (int i = 0; cookie_string[i] != '\0'; ++i) { |
| char cookie_code = cookie_string[i]; |
| if (cookie_code == 'x') { |
| valid = false; |
| continue; |
| } |
| parsed_cookies.push_back({accounts_[cookie_code].gaia_id, valid}); |
| valid = true; |
| } |
| return parsed_cookies; |
| } |
| |
| // Checks that the tokens in the TokenService match the tokens. |
| void VerifyCurrentTokens(const std::vector<Token>& tokens) { |
| EXPECT_EQ(token_service()->GetAccounts().size(), tokens.size()); |
| bool authenticated_account_found = false; |
| for (const Token& token : tokens) { |
| std::string account_id = |
| PickAccountIdForAccount(token.gaia_id, token.email); |
| EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id)); |
| EXPECT_EQ(token.has_error, |
| token_service()->RefreshTokenHasError(account_id)); |
| if (token.is_authenticated) { |
| EXPECT_EQ(account_id, signin_manager()->GetAuthenticatedAccountId()); |
| authenticated_account_found = true; |
| } |
| } |
| if (!authenticated_account_found) |
| EXPECT_EQ("", signin_manager()->GetAuthenticatedAccountId()); |
| } |
| |
| // Checks that reconcile is idempotent. |
| void CheckReconcileIdempotent(const AccountReconcilorTestDiceParam& param) { |
| // Simulate another reconcile based on the results of this one: find the |
| // corresponding row in the table and check that it does nothing. |
| for (const AccountReconcilorTestDiceParam& row : kDiceParams) { |
| if (row.is_first_reconcile) |
| continue; |
| if (strcmp(row.tokens, param.tokens_after_reconcile) != 0) |
| continue; |
| if (strcmp(row.cookies, param.cookies_after_reconcile) != 0) |
| continue; |
| EXPECT_STREQ(row.tokens, row.tokens_after_reconcile); |
| EXPECT_STREQ(row.cookies, row.cookies_after_reconcile); |
| return; |
| } |
| |
| ADD_FAILURE() << "Could not check that reconcile is idempotent."; |
| } |
| |
| void ConfigureCookieManagerService(const std::vector<Cookie>& cookies) { |
| std::vector<FakeGaiaCookieManagerService::CookieParams> cookie_params; |
| for (const auto& cookie : cookies) { |
| std::string gaia_id = cookie.gaia_id; |
| cookie_params.push_back({accounts_[gaia_id[0]].email, gaia_id, |
| cookie.is_valid, false /* signed_out */, |
| true /* verified */}); |
| } |
| cookie_manager_service()->SetListAccountsResponseWithParams(cookie_params); |
| cookie_manager_service()->set_list_accounts_stale_for_testing(true); |
| } |
| |
| std::map<char, Account> accounts_; |
| }; |
| |
| // Checks one row of the kDiceParams table above. |
| TEST_P(AccountReconcilorTestDice, TableRowTest) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // Check that reconcile is idempotent: when called twice in a row it should do |
| // nothing on the second call. |
| CheckReconcileIdempotent(GetParam()); |
| |
| // Setup tokens. |
| std::vector<Token> tokens_before_reconcile = |
| ParseTokenString(GetParam().tokens); |
| for (const Token& token : tokens_before_reconcile) { |
| std::string account_id = |
| PickAccountIdForAccount(token.gaia_id, token.email); |
| if (token.is_authenticated) |
| ConnectProfileToAccount(token.gaia_id, token.email); |
| else |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| if (token.has_error) { |
| token_service()->UpdateAuthErrorForTesting( |
| account_id, GoogleServiceAuthError( |
| GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); |
| } |
| } |
| VerifyCurrentTokens(tokens_before_reconcile); |
| |
| // Setup cookies. |
| std::vector<Cookie> cookies = ParseCookieString(GetParam().cookies); |
| ConfigureCookieManagerService(cookies); |
| |
| // Call list accounts now so that the next call completes synchronously. |
| cookie_manager_service()->ListAccounts(nullptr, nullptr, "foo"); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Setup expectations. |
| testing::InSequence mock_sequence; |
| bool logout_action = false; |
| for (int i = 0; GetParam().gaia_api_calls[i] != '\0'; ++i) { |
| if (GetParam().gaia_api_calls[i] == 'X') { |
| logout_action = true; |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(1); |
| cookies.clear(); |
| continue; |
| } |
| std::string cookie(1, GetParam().gaia_api_calls[i]); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(cookie)).Times(1); |
| // MergeSession fixes an existing cookie or appends it at the end. |
| std::vector<Cookie>::iterator it = std::find( |
| cookies.begin(), cookies.end(), Cookie{cookie, false /* is_valid */}); |
| if (it == cookies.end()) |
| cookies.push_back({cookie, true}); |
| else |
| it->is_valid = true; |
| } |
| if (!logout_action) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| } |
| |
| // Check the expected cookies after reconcile. |
| std::vector<Cookie> expected_cookies = |
| ParseCookieString(GetParam().cookies_after_reconcile); |
| ASSERT_EQ(expected_cookies, cookies); |
| |
| // Reconcile. |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor->first_execution_); |
| reconcilor->first_execution_ = GetParam().is_first_reconcile; |
| reconcilor->StartReconcile(); |
| for (int i = 0; GetParam().gaia_api_calls[i] != '\0'; ++i) { |
| if (GetParam().gaia_api_calls[i] == 'X') |
| continue; |
| std::string account_id = |
| PickAccountIdForAccount(accounts_[GetParam().gaia_api_calls[i]].gaia_id, |
| accounts_[GetParam().gaia_api_calls[i]].email); |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone()); |
| } |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| VerifyCurrentTokens(ParseTokenString(GetParam().tokens_after_reconcile)); |
| |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| // Another reconcile is sometimes triggered if Chrome accounts have changed. |
| // Allow it to finish. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)) |
| .WillRepeatedly(testing::Return()); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .WillRepeatedly(testing::Return()); |
| ConfigureCookieManagerService({}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(DiceTable, |
| AccountReconcilorTestDice, |
| ::testing::ValuesIn(kDiceParams)); |
| |
| // Tests that the AccountReconcilor is always registered. |
| TEST_F(AccountReconcilorTest, DiceTokenServiceRegistration) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); |
| |
| account_tracker()->SeedAccountInfo("12345", "user@gmail.com"); |
| signin_manager()->SignIn("12345", "user@gmail.com", "password"); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); |
| |
| // Reconcilor should not logout all accounts from the cookies when |
| // SigninManager signs out. |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| |
| signin_manager()->SignOut(signin_metrics::SIGNOUT_TEST, |
| signin_metrics::SignoutDelete::IGNORE_METRIC); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); |
| } |
| |
| // Tests that reconcile starts even when Sync is not enabled. |
| TEST_F(AccountReconcilorTest, DiceReconcileWhithoutSignin) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // Add a token in Chrome but do not sign in. |
| const std::string account_id = |
| PickAccountIdForAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Checks that nothing happens when there is no Chrome account and no Gaia |
| // cookie. |
| TEST_F(AccountReconcilorTest, DiceReconcileNoop) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // No Chrome account and no cookie. |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Tests that the first Gaia account is re-used when possible. |
| TEST_F(AccountReconcilorTest, DiceReconcileReuseGaiaFirstAccount) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // Add accounts 1 and 2 to the token service. |
| const std::string account_id_1 = |
| PickAccountIdForAccount("12345", "user@gmail.com"); |
| const std::string account_id_2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id_1, "refresh_token"); |
| token_service()->UpdateCredentials(account_id_2, "refresh_token"); |
| |
| ASSERT_EQ(2u, token_service()->GetAccounts().size()); |
| ASSERT_EQ(account_id_1, token_service()->GetAccounts()[0]); |
| ASSERT_EQ(account_id_2, token_service()->GetAccounts()[1]); |
| |
| // Add accounts 2 and 3 to the Gaia cookie. |
| const std::string account_id_3 = |
| PickAccountIdForAccount("9999", "foo@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseTwoAccounts( |
| "other@gmail.com", "67890", "foo@gmail.com", "9999"); |
| |
| testing::InSequence mock_sequence; |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| // Account 2 is added first. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_2)); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id_1, |
| GoogleServiceAuthError::AuthErrorNone()); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id_2, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Tests that the first account is kept in cache and reused when cookies are |
| // lost. |
| TEST_F(AccountReconcilorTest, DiceLastKnownFirstAccount) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // Add accounts to the token service and the Gaia cookie in a different order. |
| const std::string account_id_1 = |
| PickAccountIdForAccount("12345", "user@gmail.com"); |
| const std::string account_id_2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseTwoAccounts( |
| "other@gmail.com", "67890", "user@gmail.com", "12345"); |
| token_service()->UpdateCredentials(account_id_1, "refresh_token"); |
| token_service()->UpdateCredentials(account_id_2, "refresh_token"); |
| |
| ASSERT_EQ(2u, token_service()->GetAccounts().size()); |
| ASSERT_EQ(account_id_1, token_service()->GetAccounts()[0]); |
| ASSERT_EQ(account_id_2, token_service()->GetAccounts()[1]); |
| |
| // Do one reconcile. It should do nothing but to populating the last known |
| // account. |
| { |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Delete the cookies. |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| cookie_manager_service()->set_list_accounts_stale_for_testing(true); |
| |
| // Reconcile again and check that account_id_2 is added first. |
| { |
| testing::InSequence mock_sequence; |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_2)) |
| .Times(1); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1)) |
| .Times(1); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id_2, GoogleServiceAuthError::AuthErrorNone()); |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id_1, GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| } |
| |
| // Checks that the reconcilor does not log out unverified accounts. |
| TEST_F(AccountReconcilorTest, UnverifiedAccountNoop) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // Add a unverified account to the Gaia cookie. |
| cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( |
| {"user@gmail.com", "12345", true /* valid */, false /* signed_out */, |
| false /* verified */}); |
| |
| // Check that nothing happens. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Checks that the reconcilor does not log out unverified accounts when adding |
| // a new account to the Gaia cookie. |
| TEST_F(AccountReconcilorTest, UnverifiedAccountMerge) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // Add a unverified account to the Gaia cookie. |
| cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( |
| {"user@gmail.com", "12345", true /* valid */, false /* signed_out */, |
| false /* verified */}); |
| |
| // Add a token to Chrome. |
| const std::string chrome_account_id = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(chrome_account_id, "refresh_token"); |
| |
| // Check that the Chrome account is merged and the unverified account is not |
| // logged out. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(chrome_account_id)) |
| .Times(1); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted(reconcilor, chrome_account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Regression test for https://crbug.com/825143 |
| // Checks that the primary account is not signed out when it changes during the |
| // reconcile. |
| TEST_F(AccountReconcilorTest, HandleSigninDuringReconcile) { |
| SetAccountConsistency( |
| signin::AccountConsistencyMethod::kDicePrepareMigration); |
| |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_EQ(signin::AccountReconcilorDelegate::RevokeTokenOption::kRevoke, |
| reconcilor->delegate_->ShouldRevokeSecondaryTokensBeforeReconcile( |
| std::vector<gaia::ListedAccount>())); |
| |
| // Signin during reconcile. |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)).Times(1); |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| // The account has not been deleted. |
| EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id)); |
| } |
| |
| // Tests that the Dice migration happens after a no-op reconcile. |
| TEST_F(AccountReconcilorTest, DiceMigrationAfterNoop) { |
| // Enable Dice migration. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDiceMigration); |
| pref_service()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); |
| |
| // Chrome account is consistent with the cookie. |
| const std::string account_id = |
| PickAccountIdForAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| // Dice is not enabled by default. |
| EXPECT_FALSE(reconcilor->delegate_->IsAccountConsistencyEnforced()); |
| |
| // No-op reconcile. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| |
| // Migration will happen on next startup. |
| EXPECT_TRUE(test_signin_client()->is_ready_for_dice_migration()); |
| } |
| |
| // Tests that the Dice no migration happens if the token service is not ready. |
| TEST_F(AccountReconcilorTest, DiceNoMigrationWhenTokensNotReady) { |
| // Enable Dice migration. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDiceMigration); |
| |
| // Chrome account is consistent with the cookie. |
| const std::string account_id = |
| PickAccountIdForAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| // Dice is not enabled by default. |
| EXPECT_FALSE(reconcilor->delegate_->IsAccountConsistencyEnforced()); |
| |
| // No-op reconcile. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| |
| // Migration did not happen. |
| EXPECT_FALSE(test_signin_client()->is_ready_for_dice_migration()); |
| } |
| |
| // Tests that the Dice migration does not happen after a busy reconcile. |
| TEST_F(AccountReconcilorTest, DiceNoMigrationAfterReconcile) { |
| // Enable Dice migration. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDiceMigration); |
| pref_service()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); |
| |
| // Add a token in Chrome. |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| |
| // Dice is not enabled by default. |
| EXPECT_FALSE(reconcilor->delegate_->IsAccountConsistencyEnforced()); |
| |
| // Busy reconcile. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| |
| // Migration did not happen. |
| EXPECT_FALSE(test_signin_client()->is_ready_for_dice_migration()); |
| } |
| |
| // Tests that secondary refresh tokens are cleared when cookie is empty during |
| // Dice migration. |
| TEST_F(AccountReconcilorTest, MigrationClearSecondaryTokens) { |
| // Enable Dice migration. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDiceMigration); |
| pref_service()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); |
| |
| // Add a tokens in Chrome, signin to Sync, but no Gaia cookies. |
| const std::string account_id_1 = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| const std::string account_id_2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id_2, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_1)); |
| ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_2)); |
| |
| // Reconcile should revoke the secondary account. |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1)); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id_1, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| |
| EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_1)); |
| EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id_2)); |
| |
| // Profile was not migrated. |
| EXPECT_FALSE(test_signin_client()->is_ready_for_dice_migration()); |
| } |
| |
| // Tests that all refresh tokens are cleared when cookie is empty during |
| // Dice migration, if Sync is not enabled. |
| TEST_F(AccountReconcilorTest, MigrationClearAllTokens) { |
| // Enable Dice migration. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDiceMigration); |
| pref_service()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); |
| |
| // Add a tokens in Chrome but no Gaia cookies. |
| const std::string account_id_1 = |
| PickAccountIdForAccount("12345", "user@gmail.com"); |
| const std::string account_id_2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id_1, "refresh_token"); |
| token_service()->UpdateCredentials(account_id_2, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_1)); |
| ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_2)); |
| |
| // Reconcile should revoke the secondary account. |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| |
| EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id_1)); |
| EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id_2)); |
| |
| // Profile is now ready for migration on next startup. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(test_signin_client()->is_ready_for_dice_migration()); |
| } |
| |
| #endif // BUILDFLAG(ENABLE_DICE_SUPPORT) |
| |
| // Tests that reconcile cannot start before the tokens are loaded, and is |
| // automatically started when tokens are loaded. |
| TEST_F(AccountReconcilorTest, TokensNotLoaded) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| token_service()->set_all_credentials_loaded_for_testing(false); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| #if !defined(OS_CHROMEOS) |
| // No reconcile when tokens are not loaded, except on ChromeOS where reconcile |
| // can start as long as the token service is not empty. |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| // When tokens are loaded, reconcile starts automatically. |
| token_service()->LoadCredentials(account_id); |
| #endif |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| TEST_F(AccountReconcilorTest, GetAccountsFromCookieSuccess) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( |
| {"user@gmail.com", "12345", false /* valid */, false /* signed_out */, |
| true /* verified */}); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| reconcilor->StartReconcile(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| |
| std::vector<gaia::ListedAccount> accounts; |
| std::vector<gaia::ListedAccount> signed_out_accounts; |
| ASSERT_TRUE(cookie_manager_service()->ListAccounts( |
| &accounts, &signed_out_accounts, GaiaConstants::kChromeSource)); |
| ASSERT_EQ(1u, accounts.size()); |
| ASSERT_EQ(account_id, accounts[0].id); |
| ASSERT_EQ(0u, signed_out_accounts.size()); |
| } |
| |
| TEST_F(AccountReconcilorTest, GetAccountsFromCookieFailure) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseWebLoginRequired(); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| reconcilor->StartReconcile(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| base::RunLoop().RunUntilIdle(); |
| |
| std::vector<gaia::ListedAccount> accounts; |
| std::vector<gaia::ListedAccount> signed_out_accounts; |
| ASSERT_FALSE(cookie_manager_service()->ListAccounts( |
| &accounts, &signed_out_accounts, GaiaConstants::kChromeSource)); |
| ASSERT_EQ(0u, accounts.size()); |
| ASSERT_EQ(0u, signed_out_accounts.size()); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_ERROR, reconcilor->GetState()); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileNoop) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectTotalCount( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileCookiesDisabled) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| test_signin_client()->set_are_signin_cookies_allowed(false); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| std::vector<gaia::ListedAccount> accounts; |
| // This will be the first call to ListAccounts. |
| ASSERT_FALSE(cookie_manager_service()->ListAccounts( |
| &accounts, nullptr, GaiaConstants::kChromeSource)); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileContentSettings) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| test_signin_client()->set_are_signin_cookies_allowed(false); |
| SimulateCookieContentSettingsChanged(reconcilor, |
| ContentSettingsPattern::Wildcard()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| test_signin_client()->set_are_signin_cookies_allowed(true); |
| SimulateCookieContentSettingsChanged(reconcilor, |
| ContentSettingsPattern::Wildcard()); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileContentSettingsGaiaUrl) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| SimulateCookieContentSettingsChanged( |
| reconcilor, |
| ContentSettingsPattern::FromURL(GaiaUrls::GetInstance()->gaia_url())); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileContentSettingsNonGaiaUrl) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| SimulateCookieContentSettingsChanged( |
| reconcilor, |
| ContentSettingsPattern::FromURL(GURL("http://www.example.com"))); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileContentSettingsInvalidPattern) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| std::unique_ptr<ContentSettingsPattern::BuilderInterface> builder = |
| ContentSettingsPattern::CreateBuilder(); |
| builder->Invalid(); |
| |
| SimulateCookieContentSettingsChanged(reconcilor, builder->Build()); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| } |
| |
| // This test is needed until chrome changes to use gaia obfuscated id. |
| // The signin manager and token service use the gaia "email" property, which |
| // preserves dots in usernames and preserves case. gaia::ParseListAccountsData() |
| // however uses gaia "displayEmail" which does not preserve case, and then |
| // passes the string through gaia::CanonicalizeEmail() which removes dots. This |
| // tests makes sure that an email like "Dot.S@hmail.com", as seen by the |
| // token service, will be considered the same as "dots@gmail.com" as returned |
| // by gaia::ParseListAccountsData(). |
| TEST_F(AccountReconcilorTest, StartReconcileNoopWithDots) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| if (account_tracker()->GetMigrationState() != |
| AccountTrackerService::MIGRATION_NOT_STARTED) { |
| return; |
| } |
| |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "Dot.S@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("dot.s@gmail.com", |
| "12345"); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileNoopMultiple) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseTwoAccounts( |
| "user@gmail.com", "12345", "other@gmail.com", "67890"); |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectTotalCount( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileAddToCookie) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id2, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.AddedToCookieJar.FirstRun", 1, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.RemovedFromCookieJar.FirstRun", 0, 1); |
| |
| base::HistogramTester::CountsMap expected_counts; |
| expected_counts["Signin.Reconciler.Duration.Success"] = 1; |
| EXPECT_THAT(histogram_tester()->GetTotalCountsForPrefix( |
| "Signin.Reconciler.Duration.Success"), |
| testing::ContainerEq(expected_counts)); |
| } |
| |
| TEST_F(AccountReconcilorTest, AuthErrorTriggersListAccount) { |
| class TestGaiaCookieObserver : public GaiaCookieManagerService::Observer { |
| public: |
| void OnGaiaAccountsInCookieUpdated( |
| const std::vector<gaia::ListedAccount>& accounts, |
| const std::vector<gaia::ListedAccount>& signed_out_accounts, |
| const GoogleServiceAuthError& error) override { |
| cookies_updated_ = true; |
| } |
| |
| bool cookies_updated_ = false; |
| }; |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| signin::AccountConsistencyMethod account_consistency = |
| signin::AccountConsistencyMethod::kDice; |
| SetAccountConsistency(account_consistency); |
| #else |
| signin::AccountConsistencyMethod account_consistency = |
| signin::AccountConsistencyMethod::kMirror; |
| SetAccountConsistency(account_consistency); |
| #endif |
| |
| // Add one account to Chrome and instantiate the reconcilor. |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| TestGaiaCookieObserver observer; |
| cookie_manager_service()->AddObserver(&observer); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| if (account_consistency == signin::AccountConsistencyMethod::kDice) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(1); |
| } |
| |
| // Set an authentication error. |
| ASSERT_FALSE(observer.cookies_updated_); |
| token_service()->UpdateAuthErrorForTesting( |
| account_id, GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_SERVER)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that a call to ListAccount was triggered. |
| EXPECT_TRUE(observer.cookies_updated_); |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| cookie_manager_service()->RemoveObserver(&observer); |
| } |
| |
| #if !defined(OS_CHROMEOS) |
| // This test does not run on ChromeOS because it calls |
| // FakeSigninManagerForTesting::SignOut() which doesn't exist for ChromeOS. |
| |
| TEST_F(AccountReconcilorTest, SignoutAfterErrorDoesNotRecordUma) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| GoogleServiceAuthError error( |
| GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id2, error); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| signin_manager()->SignOut(signin_metrics::SIGNOUT_TEST, |
| signin_metrics::SignoutDelete::IGNORE_METRIC); |
| |
| base::HistogramTester::CountsMap expected_counts; |
| expected_counts["Signin.Reconciler.Duration.Failure"] = 1; |
| EXPECT_THAT(histogram_tester()->GetTotalCountsForPrefix( |
| "Signin.Reconciler.Duration.Failure"), |
| testing::ContainerEq(expected_counts)); |
| } |
| |
| #endif // !defined(OS_CHROMEOS) |
| |
| TEST_F(AccountReconcilorTest, StartReconcileRemoveFromCookie) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| token_service()->UpdateCredentials(account_id, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseTwoAccounts( |
| "user@gmail.com", "12345", "other@gmail.com", "67890"); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.AddedToCookieJar.FirstRun", 0, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.RemovedFromCookieJar.FirstRun", 1, 1); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileAddToCookieTwice) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| const std::string account_id3 = |
| PickAccountIdForAccount("34567", "third@gmail.com"); |
| |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id3)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id2, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.AddedToCookieJar.FirstRun", 1, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.RemovedFromCookieJar.FirstRun", 0, 1); |
| |
| // Do another pass after I've added a third account to the token service |
| cookie_manager_service()->SetListAccountsResponseTwoAccounts( |
| "user@gmail.com", "12345", "other@gmail.com", "67890"); |
| cookie_manager_service()->set_list_accounts_stale_for_testing(true); |
| |
| // This will cause the reconcilor to fire. |
| token_service()->UpdateCredentials(account_id3, "refresh_token"); |
| base::RunLoop().RunUntilIdle(); |
| |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id3, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.AddedToCookieJar.FirstRun", 1, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.RemovedFromCookieJar.FirstRun", 0, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.SubsequentRun", |
| signin_metrics::ACCOUNTS_SAME, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.AddedToCookieJar.SubsequentRun", 1, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.RemovedFromCookieJar.SubsequentRun", 0, 1); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileBadPrimary) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseTwoAccounts( |
| "other@gmail.com", "67890", "user@gmail.com", "12345"); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id2, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.DifferentPrimaryAccounts.FirstRun", |
| signin_metrics::COOKIE_AND_TOKEN_PRIMARIES_DIFFERENT, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.AddedToCookieJar.FirstRun", 0, 1); |
| histogram_tester()->ExpectUniqueSample( |
| "Signin.Reconciler.RemovedFromCookieJar.FirstRun", 0, 1); |
| } |
| |
| TEST_F(AccountReconcilorTest, StartReconcileOnlyOnce) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, Lock) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_); |
| |
| class TestAccountReconcilorObserver : public AccountReconcilor::Observer { |
| public: |
| void OnStartReconcile() override { ++started_count_; } |
| void OnBlockReconcile() override { ++blocked_count_; } |
| void OnUnblockReconcile() override { ++unblocked_count_; } |
| |
| int started_count_ = 0; |
| int blocked_count_ = 0; |
| int unblocked_count_ = 0; |
| }; |
| |
| TestAccountReconcilorObserver observer; |
| ScopedObserver<AccountReconcilor, AccountReconcilor::Observer> |
| scoped_observer(&observer); |
| scoped_observer.Add(reconcilor); |
| |
| // Lock prevents reconcile from starting, as long as one instance is alive. |
| std::unique_ptr<AccountReconcilor::Lock> lock_1 = |
| std::make_unique<AccountReconcilor::Lock>(reconcilor); |
| EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_); |
| reconcilor->StartReconcile(); |
| // lock_1 is blocking the reconcile. |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| { |
| AccountReconcilor::Lock lock_2(reconcilor); |
| EXPECT_EQ(2, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| lock_1.reset(); |
| // lock_1 is no longer blocking, but lock_2 is still alive. |
| EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(0, observer.started_count_); |
| EXPECT_EQ(0, observer.unblocked_count_); |
| EXPECT_EQ(1, observer.blocked_count_); |
| } |
| |
| // All locks are deleted, reconcile starts. |
| EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(1, observer.started_count_); |
| EXPECT_EQ(1, observer.unblocked_count_); |
| EXPECT_EQ(1, observer.blocked_count_); |
| |
| // Lock aborts current reconcile, and restarts it later. |
| { |
| AccountReconcilor::Lock lock(reconcilor); |
| EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_TRUE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(2, observer.started_count_); |
| EXPECT_EQ(2, observer.unblocked_count_); |
| EXPECT_EQ(2, observer.blocked_count_); |
| |
| // Reconcile can complete successfully after being restarted. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| // Checks that an "invalid" Gaia account can be refreshed in place, without |
| // performing a full logout. |
| TEST_P(AccountReconcilorMethodParamTest, |
| StartReconcileWithSessionInfoExpiredDefault) { |
| SetAccountConsistency(GetParam()); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| cookie_manager_service()->SetListAccountsResponseWithParams( |
| {{"user@gmail.com", "12345", false /* valid */, false /* signed_out */, |
| true /* verified */}, |
| {"other@gmail.com", "67890", true /* valid */, false /* signed_out */, |
| true /* verified */}}); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, AddAccountToCookieCompletedWithBogusAccount) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( |
| {"user@gmail.com", "12345", false /* valid */, false /* signed_out */, |
| true /* verified */}); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // If an unknown account id is sent, it should not upset the state. |
| SimulateAddAccountToCookieCompleted(reconcilor, "bogus_account_id", |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id, |
| GoogleServiceAuthError::AuthErrorNone()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, NoLoopWithBadPrimary) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| // Connect profile to a primary account and then add a secondary account. |
| const std::string account_id1 = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id1)); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); |
| |
| // The primary account is in auth error, so it is not in the cookie. |
| cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( |
| {"other@gmail.com", "67890", false /* valid */, false /* signed_out */, |
| true /* verified */}); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| GoogleServiceAuthError error( |
| GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
| |
| // The primary cannot be added to cookie, so it fails. |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id1, error); |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id2, |
| GoogleServiceAuthError::AuthErrorNone()); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_NE(GoogleServiceAuthError::State::NONE, |
| reconcilor->error_during_last_reconcile_.state()); |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| // Now that we've tried once, the token service knows that the primary |
| // account has an auth error. |
| token_service()->UpdateAuthErrorForTesting(account_id1, error); |
| |
| // A second attempt to reconcile should be a noop. |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| } |
| |
| TEST_F(AccountReconcilorTest, WontMergeAccountsWithError) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| // Connect profile to a primary account and then add a secondary account. |
| const std::string account_id1 = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| const std::string account_id2 = |
| PickAccountIdForAccount("67890", "other@gmail.com"); |
| token_service()->UpdateCredentials(account_id2, "refresh_token"); |
| |
| // Mark the secondary account in auth error state. |
| token_service()->UpdateAuthErrorForTesting( |
| account_id2, |
| GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); |
| |
| // The cookie starts empty. |
| cookie_manager_service()->SetListAccountsResponseNoAccounts(); |
| |
| // Since the cookie jar starts empty, the reconcilor should attempt to merge |
| // accounts into it. However, it should only try accounts not in auth |
| // error state. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id1)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateAddAccountToCookieCompleted(reconcilor, account_id1, |
| GoogleServiceAuthError::AuthErrorNone()); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(GoogleServiceAuthError::State::NONE, |
| reconcilor->error_during_last_reconcile_.state()); |
| } |
| |
| // Test that delegate timeout is called when the delegate offers a valid |
| // timeout. |
| TEST_F(AccountReconcilorTest, DelegateTimeoutIsCalled) { |
| const std::string account_id = |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>(); |
| SpyReconcilorDelegate* spy_delegate = spy_delegate0.get(); |
| AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0)); |
| ASSERT_TRUE(reconcilor); |
| auto timer0 = std::make_unique<base::MockOneShotTimer>(); |
| base::MockOneShotTimer* timer = timer0.get(); |
| reconcilor->set_timer_for_testing(std::move(timer0)); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| ASSERT_TRUE(timer->IsRunning()); |
| |
| // Simulate a timeout |
| timer->Fire(); |
| EXPECT_EQ(1, spy_delegate->num_reconcile_timeout_calls_); |
| EXPECT_EQ(0, spy_delegate->num_reconcile_finished_calls_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| // Test that delegate timeout is not called when the delegate does not offer a |
| // valid timeout. |
| TEST_F(AccountReconcilorTest, DelegateTimeoutIsNotCalled) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| auto timer0 = std::make_unique<base::MockOneShotTimer>(); |
| base::MockOneShotTimer* timer = timer0.get(); |
| reconcilor->set_timer_for_testing(std::move(timer0)); |
| |
| reconcilor->StartReconcile(); |
| EXPECT_TRUE(reconcilor->is_reconcile_started_); |
| EXPECT_FALSE(timer->IsRunning()); |
| } |
| |
| TEST_F(AccountReconcilorTest, DelegateTimeoutIsNotCalledIfTimeoutIsNotReached) { |
| ConnectProfileToAccount("12345", "user@gmail.com"); |
| cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", |
| "12345"); |
| auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>(); |
| SpyReconcilorDelegate* spy_delegate = spy_delegate0.get(); |
| AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0)); |
| ASSERT_TRUE(reconcilor); |
| auto timer0 = std::make_unique<base::MockOneShotTimer>(); |
| base::MockOneShotTimer* timer = timer0.get(); |
| reconcilor->set_timer_for_testing(std::move(timer0)); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| ASSERT_TRUE(timer->IsRunning()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(timer->IsRunning()); |
| EXPECT_EQ(0, spy_delegate->num_reconcile_timeout_calls_); |
| EXPECT_EQ(1, spy_delegate->num_reconcile_finished_calls_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |