1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <algorithm>
6 #include <cstring>
7 #include <map>
8 #include <memory>
9 #include <string>
10 #include <utility>
11
12 #include "base/macros.h"
13 #include "base/run_loop.h"
14 #include "base/scoped_observer.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/test/metrics/histogram_tester.h"
17 #include "base/test/scoped_feature_list.h"
18 #include "base/test/task_environment.h"
19 #include "base/time/time.h"
20 #include "base/timer/mock_timer.h"
21 #include "build/build_config.h"
22 #include "components/prefs/pref_service.h"
23 #include "components/signin/core/browser/account_reconcilor.h"
24 #include "components/signin/core/browser/mirror_account_reconcilor_delegate.h"
25 #include "components/signin/public/base/account_consistency_method.h"
26 #include "components/signin/public/base/list_accounts_test_utils.h"
27 #include "components/signin/public/base/signin_buildflags.h"
28 #include "components/signin/public/base/signin_metrics.h"
29 #include "components/signin/public/base/signin_pref_names.h"
30 #include "components/signin/public/base/test_signin_client.h"
31 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
32 #include "components/signin/public/identity_manager/identity_test_environment.h"
33 #include "components/signin/public/identity_manager/identity_test_utils.h"
34 #include "components/signin/public/identity_manager/primary_account_mutator.h"
35 #include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h"
36 #include "components/sync_preferences/testing_pref_service_syncable.h"
37 #include "google_apis/gaia/gaia_constants.h"
38 #include "google_apis/gaia/gaia_urls.h"
39 #include "google_apis/gaia/google_service_auth_error.h"
40 #include "testing/gmock/include/gmock/gmock.h"
41 #include "testing/gtest/include/gtest/gtest.h"
42
43 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
44 #include "components/signin/core/browser/dice_account_reconcilor_delegate.h"
45 #endif
46
47 #if defined(OS_CHROMEOS)
48 #include "chromeos/tpm/stub_install_attributes.h"
49 #include "components/signin/core/browser/active_directory_account_reconcilor_delegate.h"
50 #endif
51
52 using signin::RevokeTokenAction;
53 using signin_metrics::AccountReconcilorState;
54
55 namespace {
56
57 // An AccountReconcilorDelegate that records all calls (Spy pattern).
58 class SpyReconcilorDelegate : public signin::AccountReconcilorDelegate {
59 public:
60 int num_reconcile_finished_calls_{0};
61 int num_reconcile_timeout_calls_{0};
62
IsReconcileEnabled() const63 bool IsReconcileEnabled() const override { return true; }
64
IsAccountConsistencyEnforced() const65 bool IsAccountConsistencyEnforced() const override { return true; }
66
GetGaiaApiSource() const67 gaia::GaiaSource GetGaiaApiSource() const override {
68 return gaia::GaiaSource::kChrome;
69 }
70
ShouldAbortReconcileIfPrimaryHasError() const71 bool ShouldAbortReconcileIfPrimaryHasError() const override { return true; }
72
GetFirstGaiaAccountForReconcile(const std::vector<CoreAccountId> & chrome_accounts,const std::vector<gaia::ListedAccount> & gaia_accounts,const CoreAccountId & primary_account,bool first_execution,bool will_logout) const73 CoreAccountId GetFirstGaiaAccountForReconcile(
74 const std::vector<CoreAccountId>& chrome_accounts,
75 const std::vector<gaia::ListedAccount>& gaia_accounts,
76 const CoreAccountId& primary_account,
77 bool first_execution,
78 bool will_logout) const override {
79 return primary_account;
80 }
81
GetChromeAccountsForReconcile(const std::vector<CoreAccountId> & chrome_accounts,const CoreAccountId & primary_account,const std::vector<gaia::ListedAccount> & gaia_accounts,const gaia::MultiloginMode mode) const82 std::vector<CoreAccountId> GetChromeAccountsForReconcile(
83 const std::vector<CoreAccountId>& chrome_accounts,
84 const CoreAccountId& primary_account,
85 const std::vector<gaia::ListedAccount>& gaia_accounts,
86 const gaia::MultiloginMode mode) const override {
87 return chrome_accounts;
88 }
89
OnReconcileFinished(const CoreAccountId & first_account)90 void OnReconcileFinished(const CoreAccountId& first_account) override {
91 ++num_reconcile_finished_calls_;
92 }
93
GetReconcileTimeout() const94 base::TimeDelta GetReconcileTimeout() const override {
95 // Does not matter as long as it is different from base::TimeDelta::Max().
96 return base::TimeDelta::FromMinutes(100);
97 }
98
OnReconcileError(const GoogleServiceAuthError & error)99 void OnReconcileError(const GoogleServiceAuthError& error) override {
100 ++num_reconcile_timeout_calls_;
101 }
102 };
103
104 // gmock does not allow mocking classes with move-only parameters, preventing
105 // from mocking the AccountReconcilor class directly (because of the
106 // unique_ptr<AccountReconcilorDelegate> parameter).
107 // Introduce a dummy class creating the delegate internally, to avoid the move.
108 class DummyAccountReconcilorWithDelegate : public AccountReconcilor {
109 public:
DummyAccountReconcilorWithDelegate(signin::IdentityManager * identity_manager,SigninClient * client,signin::AccountConsistencyMethod account_consistency,bool dice_migration_completed)110 DummyAccountReconcilorWithDelegate(
111 signin::IdentityManager* identity_manager,
112 SigninClient* client,
113 signin::AccountConsistencyMethod account_consistency,
114 bool dice_migration_completed)
115 : AccountReconcilor(
116 identity_manager,
117 client,
118 CreateAccountReconcilorDelegate(client,
119 identity_manager,
120 account_consistency,
121 dice_migration_completed)) {
122 Initialize(false /* start_reconcile_if_tokens_available */);
123 }
124
125 // Takes ownership of |delegate|.
126 // gmock can't work with move only parameters.
DummyAccountReconcilorWithDelegate(signin::IdentityManager * identity_manager,SigninClient * client,signin::AccountReconcilorDelegate * delegate)127 DummyAccountReconcilorWithDelegate(
128 signin::IdentityManager* identity_manager,
129 SigninClient* client,
130 signin::AccountReconcilorDelegate* delegate)
131 : AccountReconcilor(
132 identity_manager,
133 client,
134 std::unique_ptr<signin::AccountReconcilorDelegate>(delegate)) {
135 Initialize(false /* start_reconcile_if_tokens_available */);
136 }
137
138 static std::unique_ptr<signin::AccountReconcilorDelegate>
CreateAccountReconcilorDelegate(SigninClient * signin_client,signin::IdentityManager * identity_manager,signin::AccountConsistencyMethod account_consistency,bool dice_migration_completed)139 CreateAccountReconcilorDelegate(
140 SigninClient* signin_client,
141 signin::IdentityManager* identity_manager,
142 signin::AccountConsistencyMethod account_consistency,
143 bool dice_migration_completed) {
144 switch (account_consistency) {
145 case signin::AccountConsistencyMethod::kMirror:
146 return std::make_unique<signin::MirrorAccountReconcilorDelegate>(
147 identity_manager);
148 case signin::AccountConsistencyMethod::kDisabled:
149 return std::make_unique<signin::AccountReconcilorDelegate>();
150 case signin::AccountConsistencyMethod::kDice:
151 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
152 return std::make_unique<signin::DiceAccountReconcilorDelegate>(
153 signin_client, dice_migration_completed);
154 #else
155 NOTREACHED();
156 return nullptr;
157 #endif
158 }
159 NOTREACHED();
160 return nullptr;
161 }
162 };
163
164 class MockAccountReconcilor
165 : public testing::StrictMock<DummyAccountReconcilorWithDelegate> {
166 public:
167 MockAccountReconcilor(signin::IdentityManager* identity_manager,
168 SigninClient* client,
169 signin::AccountConsistencyMethod account_consistency,
170 bool dice_migration_completed);
171
172 MockAccountReconcilor(
173 signin::IdentityManager* identity_manager,
174 SigninClient* client,
175 std::unique_ptr<signin::AccountReconcilorDelegate> delegate);
176
177 MOCK_METHOD1(PerformMergeAction, void(const CoreAccountId& account_id));
178 MOCK_METHOD0(PerformLogoutAllAccountsAction, void());
179 MOCK_METHOD1(PerformSetCookiesAction,
180 void(const signin::MultiloginParameters& parameters));
181 };
182
MockAccountReconcilor(signin::IdentityManager * identity_manager,SigninClient * client,signin::AccountConsistencyMethod account_consistency,bool dice_migration_completed)183 MockAccountReconcilor::MockAccountReconcilor(
184 signin::IdentityManager* identity_manager,
185 SigninClient* client,
186 signin::AccountConsistencyMethod account_consistency,
187 bool dice_migration_completed)
188 : testing::StrictMock<DummyAccountReconcilorWithDelegate>(
189 identity_manager,
190 client,
191 account_consistency,
192 dice_migration_completed) {}
193
MockAccountReconcilor(signin::IdentityManager * identity_manager,SigninClient * client,std::unique_ptr<signin::AccountReconcilorDelegate> delegate)194 MockAccountReconcilor::MockAccountReconcilor(
195 signin::IdentityManager* identity_manager,
196 SigninClient* client,
197 std::unique_ptr<signin::AccountReconcilorDelegate> delegate)
198 : testing::StrictMock<DummyAccountReconcilorWithDelegate>(
199 identity_manager,
200 client,
201 delegate.release()) {}
202
203 struct Cookie {
204 std::string gaia_id;
205 bool is_valid;
206
operator ==__anonacf8a3c60111::Cookie207 bool operator==(const Cookie& other) const {
208 return gaia_id == other.gaia_id && is_valid == other.is_valid;
209 }
210 };
211
212 // Converts CookieParams to ListedAccounts.
ListedAccountFromCookieParams(const signin::CookieParams & params,const CoreAccountId & account_id)213 gaia::ListedAccount ListedAccountFromCookieParams(
214 const signin::CookieParams& params,
215 const CoreAccountId& account_id) {
216 gaia::ListedAccount listed_account;
217 listed_account.id = account_id;
218 listed_account.email = params.email;
219 listed_account.gaia_id = params.gaia_id;
220 listed_account.raw_email = params.email;
221 listed_account.valid = params.valid;
222 listed_account.signed_out = params.signed_out;
223 listed_account.verified = params.verified;
224 return listed_account;
225 }
226
227 } // namespace
228
229 class AccountReconcilorTest : public ::testing::Test {
230 protected:
231 AccountReconcilorTest();
232 ~AccountReconcilorTest() override;
233
identity_test_env()234 signin::IdentityTestEnvironment* identity_test_env() {
235 return &identity_test_env_;
236 }
237
task_environment()238 base::test::SingleThreadTaskEnvironment* task_environment() {
239 return &task_environment_;
240 }
241
test_signin_client()242 TestSigninClient* test_signin_client() { return &test_signin_client_; }
histogram_tester()243 base::HistogramTester* histogram_tester() { return &histogram_tester_; }
244
245 MockAccountReconcilor* GetMockReconcilor();
246 MockAccountReconcilor* GetMockReconcilor(
247 std::unique_ptr<signin::AccountReconcilorDelegate> delegate);
248
249 AccountInfo ConnectProfileToAccount(const std::string& email);
250
251 CoreAccountId PickAccountIdForAccount(const std::string& gaia_id,
252 const std::string& username);
253
254 void SimulateAddAccountToCookieCompleted(AccountReconcilor* reconcilor,
255 const CoreAccountId& account_id,
256 const GoogleServiceAuthError& error);
257
258 void SimulateSetAccountsInCookieCompleted(
259 AccountReconcilor* reconcilor,
260 signin::SetAccountsInCookieResult result);
261
262 void SimulateLogOutFromCookieCompleted(AccountReconcilor* reconcilor,
263 const GoogleServiceAuthError& error);
264
265 void SimulateCookieContentSettingsChanged(
266 content_settings::Observer* observer,
267 const ContentSettingsPattern& primary_pattern);
268
269 void SetAccountConsistency(signin::AccountConsistencyMethod method);
270
271 // Should never be called before |SetAccountConsistency|.
272 void SetDiceMigrationCompleted(bool dice_migration_completed);
273
pref_service()274 PrefService* pref_service() { return &pref_service_; }
275
DeleteReconcilor()276 void DeleteReconcilor() { mock_reconcilor_.reset(); }
277
278 network::TestURLLoaderFactory test_url_loader_factory_;
279
280 private:
281 base::test::SingleThreadTaskEnvironment task_environment_;
282 signin::AccountConsistencyMethod account_consistency_;
283 bool dice_migration_completed_ = false;
284 sync_preferences::TestingPrefServiceSyncable pref_service_;
285 TestSigninClient test_signin_client_;
286 signin::IdentityTestEnvironment identity_test_env_;
287 std::unique_ptr<MockAccountReconcilor> mock_reconcilor_;
288 base::HistogramTester histogram_tester_;
289
290 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTest);
291 };
292
293 class AccountReconcilorMirrorTest : public AccountReconcilorTest {
294 public:
AccountReconcilorMirrorTest()295 AccountReconcilorMirrorTest() {
296 SetAccountConsistency(signin::AccountConsistencyMethod::kMirror);
297 }
298
299 private:
300 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorMirrorTest);
301 };
302
303 // For tests that must be run with multiple account consistency methods.
304 class AccountReconcilorMethodParamTest
305 : public AccountReconcilorTest,
306 public ::testing::WithParamInterface<signin::AccountConsistencyMethod> {
307 public:
308 AccountReconcilorMethodParamTest() = default;
309
310 private:
311 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorMethodParamTest);
312 };
313
314 INSTANTIATE_TEST_SUITE_P(Dice_Mirror,
315 AccountReconcilorMethodParamTest,
316 ::testing::Values(
317 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
318 signin::AccountConsistencyMethod::kDice,
319 #endif
320 signin::AccountConsistencyMethod::kMirror));
321
AccountReconcilorTest()322 AccountReconcilorTest::AccountReconcilorTest()
323 : task_environment_(
324 base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME),
325 account_consistency_(signin::AccountConsistencyMethod::kDisabled),
326 test_signin_client_(&pref_service_, &test_url_loader_factory_),
327 identity_test_env_(/*test_url_loader_factory=*/nullptr,
328 &pref_service_,
329 account_consistency_,
330 &test_signin_client_) {
331 signin::SetListAccountsResponseHttpNotFound(&test_url_loader_factory_);
332
333 // The reconcilor should not be built before the test can set the account
334 // consistency method.
335 EXPECT_FALSE(mock_reconcilor_);
336 }
337
GetMockReconcilor()338 MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor() {
339 if (!mock_reconcilor_) {
340 mock_reconcilor_ = std::make_unique<MockAccountReconcilor>(
341 identity_test_env_.identity_manager(), &test_signin_client_,
342 account_consistency_, dice_migration_completed_);
343 }
344
345 return mock_reconcilor_.get();
346 }
347
GetMockReconcilor(std::unique_ptr<signin::AccountReconcilorDelegate> delegate)348 MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor(
349 std::unique_ptr<signin::AccountReconcilorDelegate> delegate) {
350 mock_reconcilor_ = std::make_unique<MockAccountReconcilor>(
351 identity_test_env_.identity_manager(), &test_signin_client_,
352 std::move(delegate));
353
354 return mock_reconcilor_.get();
355 }
356
~AccountReconcilorTest()357 AccountReconcilorTest::~AccountReconcilorTest() {
358 if (mock_reconcilor_)
359 mock_reconcilor_->Shutdown();
360 test_signin_client_.Shutdown();
361 }
362
ConnectProfileToAccount(const std::string & email)363 AccountInfo AccountReconcilorTest::ConnectProfileToAccount(
364 const std::string& email) {
365 AccountInfo account_info =
366 identity_test_env()->MakePrimaryAccountAvailable(email);
367 return account_info;
368 }
369
PickAccountIdForAccount(const std::string & gaia_id,const std::string & username)370 CoreAccountId AccountReconcilorTest::PickAccountIdForAccount(
371 const std::string& gaia_id,
372 const std::string& username) {
373 return identity_test_env()->identity_manager()->PickAccountIdForAccount(
374 gaia_id, username);
375 }
376
SimulateAddAccountToCookieCompleted(AccountReconcilor * reconcilor,const CoreAccountId & account_id,const GoogleServiceAuthError & error)377 void AccountReconcilorTest::SimulateAddAccountToCookieCompleted(
378 AccountReconcilor* reconcilor,
379 const CoreAccountId& account_id,
380 const GoogleServiceAuthError& error) {
381 reconcilor->OnAddAccountToCookieCompleted(account_id, error);
382 }
383
SimulateSetAccountsInCookieCompleted(AccountReconcilor * reconcilor,signin::SetAccountsInCookieResult result)384 void AccountReconcilorTest::SimulateSetAccountsInCookieCompleted(
385 AccountReconcilor* reconcilor,
386 signin::SetAccountsInCookieResult result) {
387 reconcilor->OnSetAccountsInCookieCompleted(result);
388 }
389
SimulateLogOutFromCookieCompleted(AccountReconcilor * reconcilor,const GoogleServiceAuthError & error)390 void AccountReconcilorTest::SimulateLogOutFromCookieCompleted(
391 AccountReconcilor* reconcilor,
392 const GoogleServiceAuthError& error) {
393 reconcilor->OnLogOutFromCookieCompleted(error);
394 }
395
SimulateCookieContentSettingsChanged(content_settings::Observer * observer,const ContentSettingsPattern & primary_pattern)396 void AccountReconcilorTest::SimulateCookieContentSettingsChanged(
397 content_settings::Observer* observer,
398 const ContentSettingsPattern& primary_pattern) {
399 observer->OnContentSettingChanged(
400 primary_pattern, ContentSettingsPattern::Wildcard(),
401 ContentSettingsType::COOKIES, std::string());
402 }
403
SetAccountConsistency(signin::AccountConsistencyMethod method)404 void AccountReconcilorTest::SetAccountConsistency(
405 signin::AccountConsistencyMethod method) {
406 account_consistency_ = method;
407 dice_migration_completed_ =
408 account_consistency_ == signin::AccountConsistencyMethod::kDice;
409 }
410
SetDiceMigrationCompleted(bool dice_migration_completed)411 void AccountReconcilorTest::SetDiceMigrationCompleted(
412 bool dice_migration_completed) {
413 DCHECK_EQ(signin::AccountConsistencyMethod::kDice, account_consistency_);
414 dice_migration_completed_ = dice_migration_completed;
415 }
416
TEST_F(AccountReconcilorTest,Basic)417 TEST_F(AccountReconcilorTest, Basic) {
418 AccountReconcilor* reconcilor = GetMockReconcilor();
419 ASSERT_TRUE(reconcilor);
420 }
421
422 enum class IsFirstReconcile {
423 kBoth = 0,
424 kFirst,
425 kNotFirst,
426 };
427
428 struct AccountReconcilorTestTableParam {
429 const char* tokens;
430 const char* cookies;
431 IsFirstReconcile is_first_reconcile;
432 const char* gaia_api_calls;
433 const char* tokens_after_reconcile;
434 const char* cookies_after_reconcile;
435 const char* gaia_api_calls_multilogin;
436 const char* tokens_after_reconcile_multilogin;
437 const char* cookies_after_reconcile_multilogin;
438 // Int represents AccountReconcilorDelegate::InconsistencyReason.
439 const int inconsistency_reason;
440 };
441
GenerateTestCasesFromParams(const std::vector<AccountReconcilorTestTableParam> & params)442 std::vector<AccountReconcilorTestTableParam> GenerateTestCasesFromParams(
443 const std::vector<AccountReconcilorTestTableParam>& params) {
444 std::vector<AccountReconcilorTestTableParam> return_params;
445 for (const AccountReconcilorTestTableParam& param : params) {
446 if (param.is_first_reconcile == IsFirstReconcile::kBoth) {
447 AccountReconcilorTestTableParam param_true = param;
448 param_true.is_first_reconcile = IsFirstReconcile::kFirst;
449 AccountReconcilorTestTableParam param_false = param;
450 param_false.is_first_reconcile = IsFirstReconcile::kNotFirst;
451 return_params.push_back(param_true);
452 return_params.push_back(param_false);
453 } else {
454 return_params.push_back(param);
455 }
456 }
457 return return_params;
458 }
459
460 struct ForceDiceMigrationTestTableParam {
461 const char* tokens;
462 const char* cookies;
463 const char* gaia_api_calls;
464 const char* tokens_after_reconcile;
465 const char* cookies_after_reconcile;
466 RevokeTokenAction revoke_token_action;
467 };
468
469 // Pretty prints a AccountReconcilorTestTableParam. Used by gtest.
PrintTo(const AccountReconcilorTestTableParam & param,::std::ostream * os)470 void PrintTo(const AccountReconcilorTestTableParam& param, ::std::ostream* os) {
471 *os << "Tokens: " << param.tokens << ". Cookies: " << param.cookies
472 << ". First reconcile: "
473 << (param.is_first_reconcile == IsFirstReconcile::kFirst ? "true"
474 : "false");
475 }
476
477 class BaseAccountReconcilorTestTable : public AccountReconcilorTest {
478 protected:
BaseAccountReconcilorTestTable(const AccountReconcilorTestTableParam & param)479 BaseAccountReconcilorTestTable(const AccountReconcilorTestTableParam& param)
480 : BaseAccountReconcilorTestTable(param.tokens,
481 param.cookies,
482 param.is_first_reconcile,
483 param.gaia_api_calls,
484 param.tokens_after_reconcile,
485 param.cookies_after_reconcile) {}
486
BaseAccountReconcilorTestTable(const char * tokens,const char * cookies,IsFirstReconcile is_first_reconcile,const char * gaia_api_calls,const char * tokens_after_reconcile,const char * cookies_after_reconcile)487 BaseAccountReconcilorTestTable(const char* tokens,
488 const char* cookies,
489 IsFirstReconcile is_first_reconcile,
490 const char* gaia_api_calls,
491 const char* tokens_after_reconcile,
492 const char* cookies_after_reconcile)
493 : tokens_(tokens),
494 cookies_(cookies),
495 is_first_reconcile_(is_first_reconcile),
496 gaia_api_calls_(gaia_api_calls),
497 tokens_after_reconcile_(tokens_after_reconcile),
498 cookies_after_reconcile_(cookies_after_reconcile) {
499 accounts_['A'] = {"a@gmail.com",
500 signin::GetTestGaiaIdForEmail("a@gmail.com")};
501 accounts_['B'] = {"b@gmail.com",
502 signin::GetTestGaiaIdForEmail("b@gmail.com")};
503 accounts_['C'] = {"c@gmail.com",
504 signin::GetTestGaiaIdForEmail("c@gmail.com")};
505 }
506
507 struct Account {
508 std::string email;
509 std::string gaia_id;
510 };
511
512 struct Token {
513 std::string gaia_id;
514 std::string email;
515 bool is_authenticated;
516 bool has_error;
517 };
518
519 // Build Tokens from string.
ParseTokenString(const char * token_string)520 std::vector<Token> ParseTokenString(const char* token_string) {
521 std::vector<Token> parsed_tokens;
522 bool is_authenticated = false;
523 bool has_error = false;
524 for (int i = 0; token_string[i] != '\0'; ++i) {
525 char token_code = token_string[i];
526 if (token_code == '*') {
527 is_authenticated = true;
528 continue;
529 }
530 if (token_code == 'x') {
531 has_error = true;
532 continue;
533 }
534 parsed_tokens.push_back({accounts_[token_code].gaia_id,
535 accounts_[token_code].email, is_authenticated,
536 has_error});
537 is_authenticated = false;
538 has_error = false;
539 }
540 return parsed_tokens;
541 }
542
543 // Build Cookies from string.
ParseCookieString(const char * cookie_string)544 std::vector<Cookie> ParseCookieString(const char* cookie_string) {
545 std::vector<Cookie> parsed_cookies;
546 bool valid = true;
547 for (int i = 0; cookie_string[i] != '\0'; ++i) {
548 char cookie_code = cookie_string[i];
549 if (cookie_code == 'x') {
550 valid = false;
551 continue;
552 }
553 parsed_cookies.push_back({accounts_[cookie_code].gaia_id, valid});
554 valid = true;
555 }
556 return parsed_cookies;
557 }
558
559 // Checks that the tokens in the TokenService match the tokens.
VerifyCurrentTokens(const std::vector<Token> & tokens)560 void VerifyCurrentTokens(const std::vector<Token>& tokens) {
561 auto* identity_manager = identity_test_env()->identity_manager();
562 EXPECT_EQ(identity_manager->GetAccountsWithRefreshTokens().size(),
563 tokens.size());
564
565 bool authenticated_account_found = false;
566 for (const Token& token : tokens) {
567 CoreAccountId account_id =
568 PickAccountIdForAccount(token.gaia_id, token.email);
569 EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id));
570 EXPECT_EQ(
571 token.has_error,
572 identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
573 account_id));
574 if (token.is_authenticated) {
575 EXPECT_EQ(account_id, identity_manager->GetPrimaryAccountId());
576 authenticated_account_found = true;
577 }
578 }
579 if (!authenticated_account_found)
580 EXPECT_EQ(CoreAccountId(), identity_manager->GetPrimaryAccountId());
581 }
582
SetupTokens(const char * tokens_string)583 void SetupTokens(const char* tokens_string) {
584 std::vector<Token> tokens = ParseTokenString(tokens_string);
585 Token primary_account;
586 for (const Token& token : tokens) {
587 CoreAccountId account_id;
588 if (token.is_authenticated) {
589 account_id = ConnectProfileToAccount(token.email).account_id;
590 } else {
591 account_id =
592 identity_test_env()->MakeAccountAvailable(token.email).account_id;
593 }
594 if (token.has_error) {
595 signin::UpdatePersistentErrorOfRefreshTokenForAccount(
596 identity_test_env()->identity_manager(), account_id,
597 GoogleServiceAuthError(
598 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
599 }
600 }
601 VerifyCurrentTokens(tokens);
602 }
603
ConfigureCookieManagerService(const std::vector<Cookie> & cookies)604 void ConfigureCookieManagerService(const std::vector<Cookie>& cookies) {
605 std::vector<signin::CookieParams> cookie_params;
606 for (const auto& cookie : cookies) {
607 std::string gaia_id = cookie.gaia_id;
608
609 // Figure the account token of this specific account id,
610 // ie 'A', 'B', or 'C'.
611 char account_key = '\0';
612 for (const auto& account : accounts_) {
613 if (account.second.gaia_id == gaia_id) {
614 account_key = account.first;
615 break;
616 }
617 }
618 ASSERT_NE(account_key, '\0');
619
620 cookie_params.push_back({accounts_[account_key].email, gaia_id,
621 cookie.is_valid, false /* signed_out */,
622 true /* verified */});
623 }
624 signin::SetListAccountsResponseWithParams(cookie_params,
625 &test_url_loader_factory_);
626 identity_test_env()->SetFreshnessOfAccountsInGaiaCookie(false);
627 }
628
629 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
RunReconcile()630 void RunReconcile() {
631 // Setup cookies.
632 std::vector<Cookie> cookies = ParseCookieString(cookies_);
633 ConfigureCookieManagerService(cookies);
634
635 // Setup tokens. This triggers listing cookies so we need to setup cookies
636 // before that.
637 SetupTokens(tokens_);
638
639 // Call list accounts now so that the next call completes synchronously.
640 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
641 base::RunLoop().RunUntilIdle();
642
643 // Setup expectations.
644 testing::InSequence mock_sequence;
645 bool logout_action = false;
646 for (int i = 0; gaia_api_calls_[i] != '\0'; ++i) {
647 if (gaia_api_calls_[i] == 'X') {
648 logout_action = true;
649 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
650 .Times(1);
651 cookies.clear();
652 continue;
653 }
654 std::string cookie(1, gaia_api_calls_[i]);
655 CoreAccountId account_id_for_cookie = PickAccountIdForAccount(
656 accounts_[cookie[0]].gaia_id, accounts_[cookie[0]].email);
657 EXPECT_CALL(*GetMockReconcilor(),
658 PerformMergeAction(account_id_for_cookie))
659 .Times(1);
660 // MergeSession fixes an existing cookie or appends it at the end.
661 auto it =
662 std::find(cookies.begin(), cookies.end(),
663 Cookie{accounts_[cookie[0]].gaia_id, false /* is_valid */});
664 if (it == cookies.end())
665 cookies.push_back({accounts_[cookie[0]].gaia_id, true});
666 else
667 it->is_valid = true;
668 }
669 if (!logout_action) {
670 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
671 .Times(0);
672 }
673
674 // Check the expected cookies after reconcile.
675 std::vector<Cookie> expected_cookies =
676 ParseCookieString(cookies_after_reconcile_);
677 ASSERT_EQ(expected_cookies, cookies);
678
679 // Reconcile.
680 AccountReconcilor* reconcilor = GetMockReconcilor();
681 ASSERT_TRUE(reconcilor->first_execution_);
682 reconcilor->first_execution_ =
683 is_first_reconcile_ == IsFirstReconcile::kFirst;
684 ASSERT_TRUE(reconcilor->delegate_->IsAccountConsistencyEnforced());
685 reconcilor->StartReconcile();
686 for (int i = 0; gaia_api_calls_[i] != '\0'; ++i) {
687 if (gaia_api_calls_[i] == 'X') {
688 SimulateLogOutFromCookieCompleted(
689 reconcilor, GoogleServiceAuthError::AuthErrorNone());
690 continue;
691 }
692 CoreAccountId account_id =
693 PickAccountIdForAccount(accounts_[gaia_api_calls_[i]].gaia_id,
694 accounts_[gaia_api_calls_[i]].email);
695 SimulateAddAccountToCookieCompleted(
696 reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone());
697 }
698 ASSERT_FALSE(reconcilor->is_reconcile_started_);
699
700 if (tokens_ == tokens_after_reconcile_) {
701 EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
702 } else {
703 // If the tokens were changed by the reconcile, a new reconcile should be
704 // scheduled.
705 EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED,
706 reconcilor->GetState());
707 }
708
709 VerifyCurrentTokens(ParseTokenString(tokens_after_reconcile_));
710
711 testing::Mock::VerifyAndClearExpectations(GetMockReconcilor());
712
713 // Another reconcile is sometimes triggered if Chrome accounts have changed.
714 // Allow it to finish.
715 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_))
716 .WillRepeatedly(testing::Return());
717 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
718 .WillRepeatedly(testing::Return());
719 ConfigureCookieManagerService({});
720 base::RunLoop().RunUntilIdle();
721 }
722 #endif // BUILDFLAG(ENABLE_DICE_SUPPORT)
723
GaiaIdForAccountKey(char account_key)724 std::string GaiaIdForAccountKey(char account_key) {
725 return accounts_[account_key].gaia_id;
726 }
727
728 std::map<char, Account> accounts_;
729 const char* tokens_;
730 const char* cookies_;
731 IsFirstReconcile is_first_reconcile_;
732 const char* gaia_api_calls_;
733 const char* tokens_after_reconcile_;
734 const char* cookies_after_reconcile_;
735 };
736
737 // Parameterized version of AccountReconcilorTest.
738 class AccountReconcilorTestTable
739 : public BaseAccountReconcilorTestTable,
740 public ::testing::WithParamInterface<AccountReconcilorTestTableParam> {
741 protected:
AccountReconcilorTestTable()742 AccountReconcilorTestTable() : BaseAccountReconcilorTestTable(GetParam()) {}
743
744 // Checks that reconcile is idempotent.
CheckReconcileIdempotent(const std::vector<AccountReconcilorTestTableParam> & params,const AccountReconcilorTestTableParam & param,bool multilogin)745 void CheckReconcileIdempotent(
746 const std::vector<AccountReconcilorTestTableParam>& params,
747 const AccountReconcilorTestTableParam& param,
748 bool multilogin) {
749 // Simulate another reconcile based on the results of this one: find the
750 // corresponding row in the table and check that it does nothing.
751 for (const AccountReconcilorTestTableParam& row : params) {
752 if (row.is_first_reconcile == IsFirstReconcile::kFirst)
753 continue;
754
755 if (!((strcmp(row.tokens, param.tokens_after_reconcile) == 0 &&
756 strcmp(row.cookies, param.cookies_after_reconcile) == 0 &&
757 !multilogin) ||
758 (strcmp(row.tokens, param.tokens_after_reconcile_multilogin) == 0 &&
759 strcmp(row.cookies, param.cookies_after_reconcile_multilogin) ==
760 0 &&
761 multilogin))) {
762 continue;
763 }
764 if (multilogin) {
765 EXPECT_STREQ(row.tokens, row.tokens_after_reconcile_multilogin);
766 EXPECT_STREQ(row.cookies, row.cookies_after_reconcile_multilogin);
767 } else {
768 EXPECT_STREQ(row.tokens, row.tokens_after_reconcile);
769 EXPECT_STREQ(row.cookies, row.cookies_after_reconcile);
770 }
771 return;
772 }
773
774 ADD_FAILURE() << "Could not check that reconcile is idempotent.";
775 }
776 };
777
778 #if !defined(OS_CHROMEOS)
779
TEST_F(AccountReconcilorMirrorTest,IdentityManagerRegistration)780 TEST_F(AccountReconcilorMirrorTest, IdentityManagerRegistration) {
781 AccountReconcilor* reconcilor = GetMockReconcilor();
782 ASSERT_TRUE(reconcilor);
783 ASSERT_FALSE(reconcilor->IsRegisteredWithIdentityManager());
784
785 identity_test_env()->MakePrimaryAccountAvailable("user@gmail.com");
786 ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager());
787
788 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction());
789
790 identity_test_env()->ClearPrimaryAccount();
791 ASSERT_FALSE(reconcilor->IsRegisteredWithIdentityManager());
792 }
793
TEST_F(AccountReconcilorMirrorTest,Reauth)794 TEST_F(AccountReconcilorMirrorTest, Reauth) {
795 const std::string email = "user@gmail.com";
796 AccountInfo account_info = ConnectProfileToAccount(email);
797
798 AccountReconcilor* reconcilor = GetMockReconcilor();
799 ASSERT_TRUE(reconcilor);
800 ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager());
801
802 // Simulate reauth. The state of the reconcilor should not change.
803 auto* account_mutator =
804 identity_test_env()->identity_manager()->GetPrimaryAccountMutator();
805 DCHECK(account_mutator);
806 account_mutator->SetPrimaryAccount(account_info.account_id);
807
808 ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager());
809 }
810
811 #endif // !defined(OS_CHROMEOS)
812
TEST_F(AccountReconcilorMirrorTest,ProfileAlreadyConnected)813 TEST_F(AccountReconcilorMirrorTest, ProfileAlreadyConnected) {
814 ConnectProfileToAccount("user@gmail.com");
815
816 AccountReconcilor* reconcilor = GetMockReconcilor();
817 ASSERT_TRUE(reconcilor);
818 ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager());
819 }
820
821 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
822
823 namespace {
FakeSetAccountsInCookie(const signin::MultiloginParameters & parameters,const std::vector<Cookie> & cookies_before_reconcile)824 std::vector<Cookie> FakeSetAccountsInCookie(
825 const signin::MultiloginParameters& parameters,
826 const std::vector<Cookie>& cookies_before_reconcile) {
827 std::vector<Cookie> cookies_after_reconcile;
828 if (parameters.mode ==
829 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER) {
830 for (const CoreAccountId& account : parameters.accounts_to_send) {
831 cookies_after_reconcile.push_back({account.ToString(), true});
832 }
833 } else {
834 std::set<CoreAccountId> accounts_set(parameters.accounts_to_send.begin(),
835 parameters.accounts_to_send.end());
836 cookies_after_reconcile = cookies_before_reconcile;
837 for (Cookie& param : cookies_after_reconcile) {
838 if (accounts_set.find(CoreAccountId(param.gaia_id)) !=
839 accounts_set.end()) {
840 param.is_valid = true;
841 accounts_set.erase(CoreAccountId(param.gaia_id));
842 } else {
843 param.is_valid = false;
844 }
845 }
846 for (const CoreAccountId& account : accounts_set) {
847 cookies_after_reconcile.push_back({account.ToString(), true});
848 }
849 }
850 return cookies_after_reconcile;
851 }
852 } // namespace
853
854 // clang-format off
855 const std::vector<AccountReconcilorTestTableParam> kDiceParams = {
856 // This table encodes the initial state and expectations of a reconcile.
857 // The syntax is:
858 // - Tokens:
859 // A, B, C: Accounts for which we have a token in Chrome.
860 // *: The next account is the main Chrome account (i.e. in
861 // IdentityManager).
862 // x: The next account has a token error.
863 // - API calls:
864 // U: Multilogin with mode UPDATE
865 // P: Multilogin with mode PRESERVE
866 // X: Logout all accounts.
867 // A, B, C: Merge account.
868
869 // - Cookies:
870 // A, B, C: Accounts in the Gaia cookie (returned by ListAccounts).
871 // x: The next cookie is marked "invalid".
872 // - First Run: true if this is the first reconcile (i.e. Chrome startup).
873 // -------------------------------------------------------------------------
874 // Tokens|Cookies|First Run|Gaia calls|Tokens aft.|Cookies aft.|M.calls| M.Tokens aft.| M.Cookies aft.| AccountReconcilorDelegate::InconsistencyReason |
875 // -------------------------------------------------------------------------
876
877 // First reconcile (Chrome restart): Rebuild the Gaia cookie to match the
878 // tokens. Make the Sync account the default account in the Gaia cookie.
879 // Sync enabled.
880 { "", "A", IsFirstReconcile::kBoth, "X", "", "", "P", "", "xA", 3},
881 { "*AB", "AB", IsFirstReconcile::kBoth, "", "*AB", "AB", "", "*AB", "AB", 0},
882 { "*A", "A", IsFirstReconcile::kBoth, "", "*A", "A", "", "*A" , "A", 0},
883
884 { "*A", "", IsFirstReconcile::kFirst, "A", "*A", "A", "UA", "*A" , "A", 1},
885 { "*A", "", IsFirstReconcile::kNotFirst, "A", "*A", "A", "PA", "*A" , "A", 1},
886
887 { "*A", "B", IsFirstReconcile::kFirst, "XA", "*A", "A", "UA", "*A" , "A", 1},
888 { "*A", "B", IsFirstReconcile::kNotFirst, "XA", "*A", "A", "PA", "*A" , "xBA", 1},
889
890 { "*A", "AB", IsFirstReconcile::kBoth, "XA", "*A", "A", "PA", "*A" , "AxB", 5},
891
892 { "*AB", "BA", IsFirstReconcile::kFirst, "XAB", "*AB", "AB", "UAB", "*AB", "AB", 7},
893 { "*AB", "BA", IsFirstReconcile::kNotFirst, "", "*AB", "BA", "", "*AB", "BA", 0},
894
895 { "*AB", "A", IsFirstReconcile::kBoth, "B", "*AB", "AB", "PAB", "*AB", "AB", 4},
896
897 { "*AB", "B", IsFirstReconcile::kFirst, "XAB", "*AB", "AB", "UAB", "*AB", "AB", 1},
898 { "*AB", "B", IsFirstReconcile::kNotFirst, "A", "*AB", "BA", "PAB", "*AB", "BA", 1},
899
900 { "*AB", "", IsFirstReconcile::kFirst, "AB", "*AB", "AB", "UAB", "*AB", "AB", 1},
901 { "*AB", "", IsFirstReconcile::kNotFirst, "AB", "*AB", "AB", "PAB", "*AB", "AB", 1},
902 // Sync enabled, token error on primary.
903 { "*xAB", "AB", IsFirstReconcile::kBoth, "X", "*xA", "" , "PB", "*xAB", "xAB", 2},
904 { "*xAB", "BA", IsFirstReconcile::kBoth, "XB", "*xAB", "B", "PB", "*xAB", "BxA", 2},
905 { "*xAB", "A", IsFirstReconcile::kBoth, "X", "*xA", "" , "PB", "*xAB", "xAB", 2},
906 { "*xAB", "B", IsFirstReconcile::kBoth, "", "*xAB", "B", "", "*xAB", "B" , 0},
907 { "*xAB", "", IsFirstReconcile::kBoth, "B", "*xAB", "B", "PB", "*xAB", "B" , 0},
908 // Sync enabled, token error on secondary.
909 { "*AxB", "AB", IsFirstReconcile::kBoth, "XA", "*A", "A", "PA", "*A", "AxB", 5},
910
911 { "*AxB", "BA", IsFirstReconcile::kFirst, "XA", "*A", "A", "UA", "*A", "A" , 5},
912 { "*AxB", "BA", IsFirstReconcile::kNotFirst, "XA", "*A", "A", "PA", "*A", "xBA", 5},
913
914 { "*AxB", "A", IsFirstReconcile::kBoth, "", "*A", "A", "", "*A", "A" , 0 },
915
916 { "*AxB", "B", IsFirstReconcile::kFirst, "XA", "*A", "A", "UA", "*A", "A" , 1},
917 { "*AxB", "B", IsFirstReconcile::kNotFirst, "XA", "*A", "A", "PA", "*A", "xBA", 1 },
918
919 { "*AxB", "", IsFirstReconcile::kFirst, "A", "*A", "A", "UA", "*A", "A", 1},
920 { "*AxB", "", IsFirstReconcile::kNotFirst, "A", "*A", "A", "PA", "*A", "A", 1},
921 // Sync enabled, token error on both accounts.
922 { "*xAxB", "AB", IsFirstReconcile::kBoth, "X", "*xA", "", "P", "*xA", "xAxB", 2},
923 { "*xAxB", "BA", IsFirstReconcile::kBoth, "X", "*xA", "", "P", "*xA", "xBxA", 2},
924 { "*xAxB", "A", IsFirstReconcile::kBoth, "X", "*xA", "", "P", "*xA", "xA", 2},
925 { "*xAxB", "B", IsFirstReconcile::kBoth, "X", "*xA", "", "P", "*xA", "xB", 5},
926 { "*xAxB", "", IsFirstReconcile::kBoth, "", "*xA", "", "", "*xA", "", 0},
927 // Sync disabled.
928 { "AB", "AB", IsFirstReconcile::kBoth, "", "AB", "AB", "", "AB", "AB", 0},
929 { "AB", "BA", IsFirstReconcile::kBoth, "", "AB", "BA", "", "AB", "BA", 0},
930 { "AB", "A", IsFirstReconcile::kBoth, "B", "AB", "AB", "PAB", "AB", "AB", 4},
931 { "AB", "B", IsFirstReconcile::kBoth, "A", "AB", "BA", "PAB", "AB", "BA", 4},
932 { "AB", "", IsFirstReconcile::kBoth, "AB", "AB", "AB", "PAB", "AB", "AB", 0},
933 // Sync disabled, token error on first account.
934 { "xAB", "AB", IsFirstReconcile::kFirst, "XB", "B", "B", "PB", "B", "xAB", 3},
935 { "xAB", "AB", IsFirstReconcile::kNotFirst, "X", "", "" , "PB", "B", "xAB", 3},
936
937 { "xAB", "BA", IsFirstReconcile::kBoth, "XB", "B", "B", "PB", "B", "BxA", 5},
938
939 { "xAB", "A", IsFirstReconcile::kFirst, "XB", "B", "B", "PB", "B", "xAB", 3},
940 { "xAB", "A", IsFirstReconcile::kNotFirst, "X", "", "" , "PB", "B", "xAB", 3},
941
942 { "xAB", "B", IsFirstReconcile::kBoth, "", "B", "B", "", "B", "B", 0},
943
944 { "xAB", "", IsFirstReconcile::kBoth, "B", "B", "B", "PB", "B", "B", 0},
945 // Sync disabled, token error on second account .
946 { "AxB", "AB", IsFirstReconcile::kBoth, "XA", "A", "A", "PA", "A", "AxB", 5},
947
948 { "AxB", "BA", IsFirstReconcile::kFirst, "XA", "A", "A", "PA", "A", "xBA", 3},
949 { "AxB", "BA", IsFirstReconcile::kNotFirst, "X", "", "" , "PA", "A", "xBA", 3},
950
951 { "AxB", "A", IsFirstReconcile::kBoth, "", "A", "A", "", "A", "A", 0},
952
953 { "AxB", "B", IsFirstReconcile::kFirst, "XA", "A", "A", "PA", "A", "xBA", 3},
954 { "AxB", "B", IsFirstReconcile::kNotFirst, "X", "", "" , "PA", "A", "xBA", 3},
955
956 { "AxB", "", IsFirstReconcile::kBoth, "A", "A", "A", "PA", "A", "A", 0},
957 // Sync disabled, token error on both accounts.
958 { "xAxB", "AB", IsFirstReconcile::kBoth, "X", "", "", "P", "", "xAxB", 3},
959 { "xAxB", "BA", IsFirstReconcile::kBoth, "X", "", "", "P", "", "xBxA", 3},
960 { "xAxB", "A", IsFirstReconcile::kBoth, "X", "", "", "P", "", "xA", 3},
961 { "xAxB", "B", IsFirstReconcile::kBoth, "X", "", "", "P", "", "xB", 3},
962 { "xAxB", "", IsFirstReconcile::kBoth, "", "", "", "", "", "", 0},
963 // Account marked as invalid in cookies.
964 // No difference between cookies and tokens, do not do do anything.
965 // Do not logout. Regression tests for http://crbug.com/854799
966 { "", "xA", IsFirstReconcile::kBoth, "", "", "xA", "", "", "xA", 0},
967 { "", "xAxB", IsFirstReconcile::kBoth, "", "", "xAxB", "", "", "xAxB", 0},
968 { "xA", "xA", IsFirstReconcile::kBoth, "", "", "xA", "", "", "xA", 0},
969 { "xAB", "xAB", IsFirstReconcile::kBoth, "", "B", "xAB", "", "B", "xAB", 0},
970 { "AxB", "AxC", IsFirstReconcile::kBoth, "", "A", "AxC", "", "A", "AxC", 0},
971 { "B", "xAB", IsFirstReconcile::kBoth, "", "B", "xAB", "", "B", "xAB", 0},
972 { "*xA", "xA", IsFirstReconcile::kBoth, "", "*xA", "xA", "", "*xA", "xA", 0},
973 { "*xA", "xB", IsFirstReconcile::kBoth, "", "*xA", "xB", "", "*xA", "xB", 0},
974 { "*xAB", "xAB", IsFirstReconcile::kBoth, "", "*xAB", "xAB", "", "*xAB", "xAB", 0},
975 { "*AxB", "xBA", IsFirstReconcile::kNotFirst, "", "*A", "xBA", "", "*A", "xBA", 0},
976 // Appending a new cookie after the invalid one.
977 { "B", "xA", IsFirstReconcile::kBoth, "B", "B", "xAB", "PB", "B", "xAB", 4},
978 { "xAB", "xA", IsFirstReconcile::kBoth, "B", "B", "xAB", "PB", "B", "xAB", 4},
979 // Refresh existing cookies.
980 { "AB", "xAB", IsFirstReconcile::kBoth, "A", "AB", "AB", "PAB","AB", "AB", 4},
981 { "*AB", "xBxA", IsFirstReconcile::kNotFirst, "BA", "*AB", "BA", "PAB","*AB", "BA", 1},
982 // Appending and invalidating cookies at the same time.
983 // Difference should disappear after migrating to Multilogin.
984 { "xAB", "xAC", IsFirstReconcile::kFirst, "XB", "B", "B", "PB", "B", "xAxCB", 6},
985 { "xAB", "xAC", IsFirstReconcile::kNotFirst, "X", "", "", "PB", "B", "xAxCB", 6},
986
987 { "xAB", "AxC", IsFirstReconcile::kFirst, "XB", "B", "B", "PB", "B", "xAxCB", 3},
988 { "xAB", "AxC", IsFirstReconcile::kNotFirst, "X", "", "", "PB", "B", "xAxCB", 3},
989
990 { "*xAB", "xABC", IsFirstReconcile::kFirst, "XB", "*xAB", "B", "PB", "*xAB", "xABxC", 5},
991 { "*xAB", "xABC", IsFirstReconcile::kNotFirst, "X", "*xA", "", "PB", "*xAB", "xABxC", 5},
992
993 { "xAB", "xABC", IsFirstReconcile::kFirst, "XB", "B", "B", "PB", "B", "xABxC", 5},
994 { "xAB", "xABC", IsFirstReconcile::kNotFirst, "X", "", "", "PB", "B", "xABxC", 5},
995
996 // Miscellaneous cases.
997 // Check that unknown Gaia accounts are signed out.
998 { "*A", "AB", IsFirstReconcile::kBoth, "XA", "*A", "A", "PA", "*A", "AxB", 5},
999 // Check that Gaia default account is kept in first position.
1000 { "AB", "BC", IsFirstReconcile::kBoth, "XBA","AB", "BA", "PAB","AB", "BxCA", 6},
1001 // Check that Gaia cookie order is preserved for B.
1002 { "*ABC", "CB", IsFirstReconcile::kFirst, "XABC","*ABC", "ABC", "UABC","*ABC","ABC", 1},
1003 // Required for idempotency check.
1004 { "", "", IsFirstReconcile::kNotFirst, "", "", "", "", "", "", 0},
1005 { "", "xA", IsFirstReconcile::kNotFirst, "", "", "xA", "", "", "xA", 0},
1006 { "", "xB", IsFirstReconcile::kNotFirst, "", "", "xB", "", "", "xB", 0},
1007 { "", "xAxB", IsFirstReconcile::kNotFirst, "", "", "xAxB", "", "", "xAxB", 0},
1008 { "", "xBxA", IsFirstReconcile::kNotFirst, "", "", "xBxA", "", "", "xBxA", 0},
1009 { "*A", "A", IsFirstReconcile::kNotFirst, "", "*A", "A", "", "*A", "A", 0},
1010 { "*A", "xBA", IsFirstReconcile::kNotFirst, "", "*A", "xBA", "", "*A", "xBA", 0},
1011 { "*A", "AxB", IsFirstReconcile::kNotFirst, "", "*A", "AxB", "", "*A", "AxB", 0},
1012 { "A", "A", IsFirstReconcile::kNotFirst, "", "A", "A", "", "A", "A", 0},
1013 { "A", "xBA", IsFirstReconcile::kNotFirst, "", "A", "xBA", "", "A", "xBA", 0},
1014 { "A", "AxB", IsFirstReconcile::kNotFirst, "", "A", "AxB", "", "A", "AxB", 0},
1015 { "B", "B", IsFirstReconcile::kNotFirst, "", "B", "B", "", "B", "B", 0},
1016 { "B", "xAB", IsFirstReconcile::kNotFirst, "", "B", "xAB", "", "B", "xAB", 0},
1017 { "B", "BxA", IsFirstReconcile::kNotFirst, "", "B", "BxA", "", "B", "BxA", 0},
1018 { "*xA", "", IsFirstReconcile::kNotFirst, "", "*xA", "", "", "*xA", "", 0},
1019 { "*xA", "xAxB", IsFirstReconcile::kNotFirst, "", "*xA", "xAxB", "", "*xA", "xAxB", 0},
1020 { "*xA", "xBxA", IsFirstReconcile::kNotFirst, "", "*xA", "xBxA", "", "*xA", "xBxA", 0},
1021 { "*xA", "xA", IsFirstReconcile::kNotFirst, "", "*xA", "xA", "", "*xA", "xA", 0},
1022 { "*xA", "xB", IsFirstReconcile::kNotFirst, "", "*xA", "xB", "", "*xA", "xB", 0},
1023 { "*xAB", "B", IsFirstReconcile::kNotFirst, "", "*xAB", "B", "", "*xAB", "B", 0},
1024 { "*xAB", "BxA", IsFirstReconcile::kNotFirst, "", "*xAB", "BxA", "", "*xAB", "BxA", 0},
1025 { "*xAB", "xAB", IsFirstReconcile::kNotFirst, "", "*xAB", "xAB", "", "*xAB", "xAB", 0},
1026 { "*xAB", "xABxC",IsFirstReconcile::kNotFirst, "", "*xAB", "xABxC","", "*xAB", "xABxC", 0},
1027 { "A", "AxC", IsFirstReconcile::kNotFirst, "", "A", "AxC", "", "A", "AxC", 0},
1028 { "AB", "BxCA", IsFirstReconcile::kNotFirst, "", "AB", "BxCA", "", "AB", "BxCA", 0},
1029 { "B", "xABxC",IsFirstReconcile::kNotFirst, "", "B", "xABxC","", "B", "xABxC", 0},
1030 { "B", "xAxCB",IsFirstReconcile::kNotFirst, "", "B", "xAxCB","", "B", "xAxCB", 0},
1031 { "*ABC", "ACB", IsFirstReconcile::kNotFirst, "", "*ABC", "ACB", "", "*ABC", "ACB", 0},
1032 { "*ABC", "ABC", IsFirstReconcile::kNotFirst, "", "*ABC", "ABC", "", "*ABC", "ABC", 0}
1033 };
1034 // clang-format on
1035
1036 // Checks one row of the kDiceParams table above.
TEST_P(AccountReconcilorTestTable,TableRowTest)1037 TEST_P(AccountReconcilorTestTable, TableRowTest) {
1038 // Enable Dice.
1039 SetAccountConsistency(signin::AccountConsistencyMethod::kDice);
1040
1041 // Check that reconcile is idempotent: when called twice in a row it should do
1042 // nothing on the second call.
1043 CheckReconcileIdempotent(kDiceParams, GetParam(), /*multilogin=*/false);
1044 RunReconcile();
1045 histogram_tester()->ExpectTotalCount("ForceDiceMigration.RevokeTokenAction",
1046 0);
1047 }
1048
1049 INSTANTIATE_TEST_SUITE_P(
1050 DiceTable,
1051 AccountReconcilorTestTable,
1052 ::testing::ValuesIn(GenerateTestCasesFromParams(kDiceParams)));
1053
1054 class AccountReconcilorTestForceDiceMigration
1055 : public BaseAccountReconcilorTestTable,
1056 public ::testing::WithParamInterface<ForceDiceMigrationTestTableParam> {
1057 public:
AccountReconcilorTestForceDiceMigration()1058 AccountReconcilorTestForceDiceMigration()
1059 : BaseAccountReconcilorTestTable(GetParam().tokens,
1060 GetParam().cookies,
1061 IsFirstReconcile::kFirst,
1062 GetParam().gaia_api_calls,
1063 GetParam().tokens_after_reconcile,
1064 GetParam().cookies_after_reconcile) {}
1065
1066 private:
1067 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestForceDiceMigration);
1068 };
1069
1070 // clang-format off
1071 const std::vector<ForceDiceMigrationTestTableParam> kForceDiceParams = {
1072 {"*A", "AB", "XA", "*A", "A" , RevokeTokenAction::kNone},
1073 {"*AxB", "AB", "XA", "*A", "A" , RevokeTokenAction::kNone},
1074 {"AxB", "AB", "XA", "A", "A" , RevokeTokenAction::kNone},
1075 {"xAxB", "AB", "X", "", "" , RevokeTokenAction::kNone},
1076 {"*A", "", "", "*xA", "" , RevokeTokenAction::kInvalidatePrimaryAccountToken},
1077 {"*A", "B", "X", "*xA", "" , RevokeTokenAction::kInvalidatePrimaryAccountToken},
1078 {"*AB", "B", "", "*xAB", "B" , RevokeTokenAction::kInvalidatePrimaryAccountToken},
1079 {"*AxB", "B", "X", "*xA", "" , RevokeTokenAction::kInvalidatePrimaryAccountToken},
1080 {"*ABC", "CB", "", "*xABC", "CB" , RevokeTokenAction::kInvalidatePrimaryAccountToken},
1081 {"*AB", "A", "", "*A", "A" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1082 {"AB", "A", "", "A", "A" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1083 {"AB", "", "", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1084 {"xAB", "", "", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1085 {"xAB", "A", "X", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1086 {"xAB", "xA", "", "", "xA" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1087 {"xAB", "B", "", "B", "B" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1088 {"AxB", "B", "X", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1089 {"AxB", "", "", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1090 {"xAxB", "", "", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1091 {"B", "xA", "", "", "xA" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1092 {"AB", "xAB", "", "B", "xAB" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1093 {"xAB", "xAC", "X", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1094 {"xAB", "AxC", "X", "", "" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1095 {"AB", "BC", "XB", "B", "B" , RevokeTokenAction::kRevokeSecondaryAccountsTokens},
1096 {"*AB", "", "", "*xA", "" , RevokeTokenAction::kRevokeTokensForPrimaryAndSecondaryAccounts},
1097 {"*xAB", "", "", "*xA", "" , RevokeTokenAction::kRevokeTokensForPrimaryAndSecondaryAccounts},
1098 {"*AxB", "", "", "*xA", "" , RevokeTokenAction::kRevokeTokensForPrimaryAndSecondaryAccounts},
1099 {"*AB", "xBxA", "", "*xA", "xBxA", RevokeTokenAction::kRevokeTokensForPrimaryAndSecondaryAccounts}
1100 };
1101 // clang-format on
1102
1103 // Checks one row of the kForceDiceParams table above.
TEST_P(AccountReconcilorTestForceDiceMigration,TableRowTest)1104 TEST_P(AccountReconcilorTestForceDiceMigration, TableRowTest) {
1105 SetAccountConsistency(signin::AccountConsistencyMethod::kDice);
1106 EXPECT_FALSE(test_signin_client()->is_dice_migration_completed());
1107 SetDiceMigrationCompleted(false);
1108 RunReconcile();
1109 EXPECT_TRUE(test_signin_client()->is_dice_migration_completed());
1110 EXPECT_FALSE(
1111 GetMockReconcilor()->delegate_->ShouldRevokeTokensNotInCookies());
1112 histogram_tester()->ExpectUniqueSample("ForceDiceMigration.RevokeTokenAction",
1113 GetParam().revoke_token_action, 1);
1114 }
1115
1116 // Check that the result state of the reconcile is in a final state (reconcile
1117 // started from this state is a no-op).
TEST_P(AccountReconcilorTestForceDiceMigration,TableRowTestCheckNoOp)1118 TEST_P(AccountReconcilorTestForceDiceMigration, TableRowTestCheckNoOp) {
1119 SetAccountConsistency(signin::AccountConsistencyMethod::kDice);
1120 // Setup cookies.
1121 std::vector<Cookie> cookies = ParseCookieString(cookies_after_reconcile_);
1122 ConfigureCookieManagerService(cookies);
1123
1124 // Setup tokens. This triggers listing cookies so we need to setup cookies
1125 // before that.
1126 SetupTokens(tokens_after_reconcile_);
1127
1128 // Call list accounts now so that the next call completes synchronously.
1129 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
1130 base::RunLoop().RunUntilIdle();
1131
1132 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0);
1133 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0);
1134 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_))
1135 .Times(0);
1136
1137 AccountReconcilor* reconcilor = GetMockReconcilor();
1138 EXPECT_FALSE(reconcilor->delegate_->ShouldRevokeTokensNotInCookies());
1139 reconcilor->StartReconcile();
1140 base::RunLoop().RunUntilIdle();
1141 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1142 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1143 }
1144
1145 INSTANTIATE_TEST_SUITE_P(DiceMigrationTable,
1146 AccountReconcilorTestForceDiceMigration,
1147 ::testing::ValuesIn(kForceDiceParams));
1148
1149 // Parameterized version of AccountReconcilorTest that tests Dice
1150 // implementation with Multilogin endpoint.
1151 class AccountReconcilorTestDiceMultilogin : public AccountReconcilorTestTable {
1152 public:
1153 AccountReconcilorTestDiceMultilogin() = default;
1154
1155 protected:
1156 base::test::ScopedFeatureList scoped_feature_list_;
1157
1158 private:
1159 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestDiceMultilogin);
1160 };
1161
1162 // Checks one row of the kDiceParams table above.
TEST_P(AccountReconcilorTestDiceMultilogin,TableRowTest)1163 TEST_P(AccountReconcilorTestDiceMultilogin, TableRowTest) {
1164 SetAccountConsistency(signin::AccountConsistencyMethod::kDice);
1165 scoped_feature_list_.InitAndEnableFeature(kUseMultiloginEndpoint);
1166
1167 CheckReconcileIdempotent(kDiceParams, GetParam(), /*multilogin=*/true);
1168
1169 // Setup cookies.
1170 std::vector<Cookie> cookies = ParseCookieString(GetParam().cookies);
1171 ConfigureCookieManagerService(cookies);
1172 std::vector<Cookie> cookies_after_reconcile = cookies;
1173
1174 // Setup tokens. This triggers listing cookies so we need to setup cookies
1175 // before that.
1176 SetupTokens(GetParam().tokens);
1177
1178 // Call list accounts now so that the next call completes synchronously.
1179 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
1180 base::RunLoop().RunUntilIdle();
1181
1182 // Setup expectations.
1183 testing::InSequence mock_sequence;
1184 if (GetParam().gaia_api_calls_multilogin[0] != '\0') {
1185 gaia::MultiloginMode mode =
1186 GetParam().gaia_api_calls_multilogin[0] == 'U'
1187 ? gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER
1188 : gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER;
1189 // Generate expected array of accounts in cookies and set fake gaia
1190 // response.
1191 std::vector<CoreAccountId> accounts_to_send;
1192 for (int i = 1; GetParam().gaia_api_calls_multilogin[i] != '\0'; ++i) {
1193 accounts_to_send.push_back(CoreAccountId(
1194 accounts_[GetParam().gaia_api_calls_multilogin[i]].gaia_id));
1195 }
1196 const signin::MultiloginParameters params(mode, accounts_to_send);
1197 cookies_after_reconcile = FakeSetAccountsInCookie(params, cookies);
1198 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)).Times(1);
1199 }
1200 // Reconcile.
1201 AccountReconcilor* reconcilor = GetMockReconcilor();
1202 ASSERT_TRUE(reconcilor);
1203 ASSERT_TRUE(reconcilor->first_execution_);
1204 reconcilor->first_execution_ =
1205 GetParam().is_first_reconcile == IsFirstReconcile::kFirst ? true : false;
1206 reconcilor->StartReconcile();
1207
1208 SimulateSetAccountsInCookieCompleted(
1209 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1210
1211 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1212 if (GetParam().tokens == GetParam().tokens_after_reconcile_multilogin) {
1213 EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1214 } else {
1215 // If the tokens were changed by the reconcile, a new reconcile should be
1216 // scheduled.
1217 EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED,
1218 reconcilor->GetState());
1219 }
1220 VerifyCurrentTokens(
1221 ParseTokenString(GetParam().tokens_after_reconcile_multilogin));
1222
1223 std::vector<Cookie> cookies_after =
1224 ParseCookieString(GetParam().cookies_after_reconcile_multilogin);
1225 EXPECT_EQ(cookies_after, cookies_after_reconcile);
1226
1227 testing::Mock::VerifyAndClearExpectations(GetMockReconcilor());
1228
1229 // Another reconcile is sometimes triggered if Chrome accounts have
1230 // changed. Allow it to finish.
1231 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_))
1232 .WillRepeatedly(testing::Return());
1233 ConfigureCookieManagerService({});
1234 base::RunLoop().RunUntilIdle();
1235 }
1236
1237 INSTANTIATE_TEST_SUITE_P(
1238 DiceTableMultilogin,
1239 AccountReconcilorTestDiceMultilogin,
1240 ::testing::ValuesIn(GenerateTestCasesFromParams(kDiceParams)));
1241
1242 class AccountReconcilorDiceEndpointParamTest
1243 : public AccountReconcilorTest,
1244 public ::testing::WithParamInterface<bool> {
1245 public:
AccountReconcilorDiceEndpointParamTest()1246 AccountReconcilorDiceEndpointParamTest() {
1247 SetAccountConsistency(signin::AccountConsistencyMethod::kDice);
1248 if (IsMultiloginEnabled())
1249 scoped_feature_list_.InitAndEnableFeature(kUseMultiloginEndpoint);
1250 }
IsMultiloginEnabled()1251 bool IsMultiloginEnabled() { return GetParam(); }
1252
1253 protected:
1254 base::test::ScopedFeatureList scoped_feature_list_;
1255
1256 private:
1257 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorDiceEndpointParamTest);
1258 };
1259
1260 // Tests that the AccountReconcilor is always registered.
TEST_P(AccountReconcilorDiceEndpointParamTest,DiceTokenServiceRegistration)1261 TEST_P(AccountReconcilorDiceEndpointParamTest, DiceTokenServiceRegistration) {
1262 AccountReconcilor* reconcilor = GetMockReconcilor();
1263 ASSERT_TRUE(reconcilor);
1264 ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager());
1265
1266 identity_test_env()->MakePrimaryAccountAvailable("user@gmail.com");
1267 ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager());
1268
1269 // Reconcilor should not logout all accounts from the cookies when
1270 // the primary account is cleared in IdentityManager.
1271 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0);
1272 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(::testing::_))
1273 .Times(0);
1274
1275 identity_test_env()->ClearPrimaryAccount();
1276 ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager());
1277 }
1278
1279 // Tests that reconcile starts even when Sync is not enabled.
TEST_P(AccountReconcilorDiceEndpointParamTest,DiceReconcileWithoutSignin)1280 TEST_P(AccountReconcilorDiceEndpointParamTest, DiceReconcileWithoutSignin) {
1281 // Add a token in Chrome but do not sign in. Making account available (setting
1282 // a refresh token) triggers listing cookies so we need to setup cookies
1283 // before that.
1284 signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_);
1285 const CoreAccountId account_id =
1286 identity_test_env()->MakeAccountAvailable("user@gmail.com").account_id;
1287
1288 if (!IsMultiloginEnabled()) {
1289 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id));
1290 } else {
1291 std::vector<CoreAccountId> accounts_to_send = {account_id};
1292 const signin::MultiloginParameters params(
1293 gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER,
1294 accounts_to_send);
1295 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
1296 }
1297
1298 AccountReconcilor* reconcilor = GetMockReconcilor();
1299 reconcilor->StartReconcile();
1300
1301 base::RunLoop().RunUntilIdle();
1302 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1303 if (!IsMultiloginEnabled()) {
1304 SimulateAddAccountToCookieCompleted(
1305 reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone());
1306 } else {
1307 SimulateSetAccountsInCookieCompleted(
1308 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1309 }
1310 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1311 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1312 }
1313
1314 // Checks that nothing happens when there is no Chrome account and no Gaia
1315 // cookie.
TEST_P(AccountReconcilorDiceEndpointParamTest,DiceReconcileNoop)1316 TEST_P(AccountReconcilorDiceEndpointParamTest, DiceReconcileNoop) {
1317 // No Chrome account and no cookie.
1318 signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_);
1319 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0);
1320 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0);
1321 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_))
1322 .Times(0);
1323
1324 AccountReconcilor* reconcilor = GetMockReconcilor();
1325 reconcilor->StartReconcile();
1326 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1327 base::RunLoop().RunUntilIdle();
1328 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1329 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1330 }
1331
1332 // Tests that the first Gaia account is re-used when possible.
TEST_P(AccountReconcilorDiceEndpointParamTest,DiceReconcileReuseGaiaFirstAccount)1333 TEST_P(AccountReconcilorDiceEndpointParamTest,
1334 DiceReconcileReuseGaiaFirstAccount) {
1335 // Add account "other" to the Gaia cookie.
1336 signin::SetListAccountsResponseTwoAccounts(
1337 "other@gmail.com", signin::GetTestGaiaIdForEmail("other@gmail.com"),
1338 "foo@gmail.com", "9999", &test_url_loader_factory_);
1339
1340 // Add accounts "user" and "other" to the token service.
1341 const AccountInfo account_info_1 =
1342 identity_test_env()->MakeAccountAvailable("user@gmail.com");
1343 const CoreAccountId account_id_1 = account_info_1.account_id;
1344 const AccountInfo account_info_2 =
1345 identity_test_env()->MakeAccountAvailable("other@gmail.com");
1346 const CoreAccountId account_id_2 = account_info_2.account_id;
1347
1348 auto* identity_manager = identity_test_env()->identity_manager();
1349 std::vector<CoreAccountInfo> accounts =
1350 identity_manager->GetAccountsWithRefreshTokens();
1351 ASSERT_EQ(2u, accounts.size());
1352 ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1));
1353 ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2));
1354
1355 if (!IsMultiloginEnabled()) {
1356 testing::InSequence mock_sequence;
1357 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction());
1358 // Account 2 is added first.
1359 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_2));
1360 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1));
1361 } else {
1362 std::vector<CoreAccountId> accounts_to_send = {account_id_2, account_id_1};
1363 // Send accounts to Gaia in any order, it will determine the order itself in
1364 // PRESERVE order.
1365 const signin::MultiloginParameters params(
1366 gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER,
1367 accounts_to_send);
1368 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
1369 }
1370
1371 AccountReconcilor* reconcilor = GetMockReconcilor();
1372 reconcilor->StartReconcile();
1373 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1374 base::RunLoop().RunUntilIdle();
1375 if (!IsMultiloginEnabled()) {
1376 SimulateLogOutFromCookieCompleted(reconcilor,
1377 GoogleServiceAuthError::AuthErrorNone());
1378 SimulateAddAccountToCookieCompleted(
1379 reconcilor, account_id_1, GoogleServiceAuthError::AuthErrorNone());
1380 SimulateAddAccountToCookieCompleted(
1381 reconcilor, account_id_2, GoogleServiceAuthError::AuthErrorNone());
1382 } else {
1383 SimulateSetAccountsInCookieCompleted(
1384 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1385 }
1386 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1387 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1388 }
1389
1390 // Tests that the first account is kept in cache and reused when cookies are
1391 // lost.
TEST_P(AccountReconcilorDiceEndpointParamTest,DiceLastKnownFirstAccount)1392 TEST_P(AccountReconcilorDiceEndpointParamTest, DiceLastKnownFirstAccount) {
1393 // Add accounts to the token service and the Gaia cookie in a different order.
1394 // Making account available (setting a refresh token) triggers listing cookies
1395 // so we need to setup cookies before that.
1396 signin::SetListAccountsResponseTwoAccounts(
1397 "other@gmail.com", signin::GetTestGaiaIdForEmail("other@gmail.com"),
1398 "user@gmail.com", signin::GetTestGaiaIdForEmail("user@gmail.com"),
1399 &test_url_loader_factory_);
1400
1401 AccountInfo account_info_1 =
1402 identity_test_env()->MakeAccountAvailable("user@gmail.com");
1403 const CoreAccountId account_id_1 = account_info_1.account_id;
1404 AccountInfo account_info_2 =
1405 identity_test_env()->MakeAccountAvailable("other@gmail.com");
1406 const CoreAccountId account_id_2 = account_info_2.account_id;
1407
1408 auto* identity_manager = identity_test_env()->identity_manager();
1409 std::vector<CoreAccountInfo> accounts =
1410 identity_manager->GetAccountsWithRefreshTokens();
1411 ASSERT_EQ(2u, accounts.size());
1412
1413 ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1));
1414 ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2));
1415
1416 // Do one reconcile. It should do nothing but to populating the last known
1417 // account.
1418 {
1419 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0);
1420 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
1421 .Times(0);
1422 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_))
1423 .Times(0);
1424
1425 AccountReconcilor* reconcilor = GetMockReconcilor();
1426 reconcilor->StartReconcile();
1427 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1428 base::RunLoop().RunUntilIdle();
1429 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1430 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1431 }
1432
1433 // Delete the cookies.
1434 signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_);
1435 identity_test_env()->SetFreshnessOfAccountsInGaiaCookie(false);
1436
1437 if (!IsMultiloginEnabled()) {
1438 // Reconcile again and check that account_id_2 is added first.
1439 testing::InSequence mock_sequence;
1440
1441 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_2))
1442 .Times(1);
1443 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1))
1444 .Times(1);
1445 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
1446 .Times(0);
1447 } else {
1448 // Since Gaia can't know about cached account, make sure that we reorder
1449 // chrome accounts accordingly even in PRESERVE mode.
1450 std::vector<CoreAccountId> accounts_to_send = {account_id_2, account_id_1};
1451 const signin::MultiloginParameters params(
1452 gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER,
1453 accounts_to_send);
1454 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
1455 }
1456
1457 AccountReconcilor* reconcilor = GetMockReconcilor();
1458 reconcilor->StartReconcile();
1459 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1460 base::RunLoop().RunUntilIdle();
1461 if (!IsMultiloginEnabled()) {
1462 SimulateAddAccountToCookieCompleted(
1463 reconcilor, account_id_2, GoogleServiceAuthError::AuthErrorNone());
1464 SimulateAddAccountToCookieCompleted(
1465 reconcilor, account_id_1, GoogleServiceAuthError::AuthErrorNone());
1466 } else {
1467 SimulateSetAccountsInCookieCompleted(
1468 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1469 }
1470 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1471 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1472 }
1473
1474 // Checks that the reconcilor does not log out unverified accounts.
TEST_P(AccountReconcilorDiceEndpointParamTest,UnverifiedAccountNoop)1475 TEST_P(AccountReconcilorDiceEndpointParamTest, UnverifiedAccountNoop) {
1476 // Add a unverified account to the Gaia cookie.
1477 signin::SetListAccountsResponseOneAccountWithParams(
1478 {"user@gmail.com", "12345", true /* valid */, false /* signed_out */,
1479 false /* verified */},
1480 &test_url_loader_factory_);
1481
1482 // Check that nothing happens.
1483 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0);
1484 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0);
1485 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_))
1486 .Times(0);
1487
1488 AccountReconcilor* reconcilor = GetMockReconcilor();
1489 reconcilor->StartReconcile();
1490 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1491 base::RunLoop().RunUntilIdle();
1492 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1493 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1494 }
1495
1496 // Checks that the reconcilor does not log out unverified accounts when adding
1497 // a new account to the Gaia cookie.
TEST_P(AccountReconcilorDiceEndpointParamTest,UnverifiedAccountMerge)1498 TEST_P(AccountReconcilorDiceEndpointParamTest, UnverifiedAccountMerge) {
1499 // Add a unverified account to the Gaia cookie.
1500 signin::SetListAccountsResponseOneAccountWithParams(
1501 {"user@gmail.com", "12345", true /* valid */, false /* signed_out */,
1502 false /* verified */},
1503 &test_url_loader_factory_);
1504
1505 // Add a token to Chrome.
1506 const CoreAccountId chrome_account_id =
1507 identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id;
1508
1509 if (!IsMultiloginEnabled()) {
1510 // Check that the Chrome account is merged and the unverified account is not
1511 // logged out.
1512 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(chrome_account_id))
1513 .Times(1);
1514 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
1515 .Times(0);
1516 } else {
1517 // In PRESERVE mode it is up to Gaia to not delete existing accounts in
1518 // cookies and not sign out unveridied accounts.
1519 std::vector<CoreAccountId> accounts_to_send = {chrome_account_id};
1520 const signin::MultiloginParameters params(
1521 gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER,
1522 accounts_to_send);
1523 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
1524 }
1525
1526 AccountReconcilor* reconcilor = GetMockReconcilor();
1527 reconcilor->StartReconcile();
1528 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1529 base::RunLoop().RunUntilIdle();
1530 if (!IsMultiloginEnabled()) {
1531 SimulateAddAccountToCookieCompleted(
1532 reconcilor, chrome_account_id, GoogleServiceAuthError::AuthErrorNone());
1533 } else {
1534 SimulateSetAccountsInCookieCompleted(
1535 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1536 }
1537 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1538 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1539 }
1540
1541 INSTANTIATE_TEST_SUITE_P(TestDiceEndpoint,
1542 AccountReconcilorDiceEndpointParamTest,
1543 ::testing::ValuesIn({false, true}));
1544
TEST_F(AccountReconcilorTest,DiceDeleteCookie)1545 TEST_F(AccountReconcilorTest, DiceDeleteCookie) {
1546 SetAccountConsistency(signin::AccountConsistencyMethod::kDice);
1547
1548 const CoreAccountId primary_account_id =
1549 identity_test_env()
1550 ->MakePrimaryAccountAvailable("user@gmail.com")
1551 .account_id;
1552 const CoreAccountId secondary_account_id =
1553 identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id;
1554
1555 auto* identity_manager = identity_test_env()->identity_manager();
1556 ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(primary_account_id));
1557 ASSERT_FALSE(
1558 identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
1559 primary_account_id));
1560 ASSERT_TRUE(
1561 identity_manager->HasAccountWithRefreshToken(secondary_account_id));
1562 ASSERT_FALSE(
1563 identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
1564 secondary_account_id));
1565
1566 AccountReconcilor* reconcilor = GetMockReconcilor();
1567
1568 // With scoped deletion, only secondary tokens are revoked.
1569 {
1570 std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> deletion =
1571 reconcilor->GetScopedSyncDataDeletion();
1572 reconcilor->OnAccountsCookieDeletedByUserAction();
1573 EXPECT_TRUE(
1574 identity_manager->HasAccountWithRefreshToken(primary_account_id));
1575 EXPECT_FALSE(
1576 identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
1577 primary_account_id));
1578 EXPECT_FALSE(
1579 identity_manager->HasAccountWithRefreshToken(secondary_account_id));
1580 }
1581
1582 identity_test_env()->SetRefreshTokenForAccount(secondary_account_id);
1583 reconcilor->OnAccountsCookieDeletedByUserAction();
1584
1585 // Without scoped deletion, the primary token is also invalidated.
1586 EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(primary_account_id));
1587 EXPECT_TRUE(
1588 identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
1589 primary_account_id));
1590 EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason::
1591 CREDENTIALS_REJECTED_BY_CLIENT,
1592 identity_manager
1593 ->GetErrorStateOfRefreshTokenForAccount(primary_account_id)
1594 .GetInvalidGaiaCredentialsReason());
1595 EXPECT_FALSE(
1596 identity_manager->HasAccountWithRefreshToken(secondary_account_id));
1597
1598 // If the primary account has an error, always revoke it.
1599 identity_test_env()->SetRefreshTokenForAccount(primary_account_id);
1600 EXPECT_FALSE(
1601 identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
1602 primary_account_id));
1603 signin::UpdatePersistentErrorOfRefreshTokenForAccount(
1604 identity_test_env()->identity_manager(), primary_account_id,
1605 GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
1606 GoogleServiceAuthError::InvalidGaiaCredentialsReason::
1607 CREDENTIALS_REJECTED_BY_SERVER));
1608 {
1609 std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> deletion =
1610 reconcilor->GetScopedSyncDataDeletion();
1611 reconcilor->OnAccountsCookieDeletedByUserAction();
1612 EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason::
1613 CREDENTIALS_REJECTED_BY_CLIENT,
1614 identity_manager
1615 ->GetErrorStateOfRefreshTokenForAccount(primary_account_id)
1616 .GetInvalidGaiaCredentialsReason());
1617 }
1618 }
1619
1620 #endif // BUILDFLAG(ENABLE_DICE_SUPPORT)
1621
1622 // clang-format off
1623 const std::vector<AccountReconcilorTestTableParam> kMirrorParams = {
1624 // This table encodes the initial state and expectations of a reconcile.
1625 // See kDiceParams for documentation of the syntax.
1626 // -------------------------------------------------------------------------
1627 // Tokens | Cookies | First Run | Gaia calls | Tokens after | Cookies after
1628 // -------------------------------------------------------------------------
1629
1630 // First reconcile (Chrome restart): Rebuild the Gaia cookie to match the
1631 // tokens. Make the Sync account the default account in the Gaia cookie.
1632 // Sync enabled.
1633 { "*AB", "AB", IsFirstReconcile::kBoth, "", "*AB", "AB"},
1634 { "*AB", "BA", IsFirstReconcile::kBoth, "U", "*AB", "AB"},
1635 { "*AB", "A", IsFirstReconcile::kBoth, "U", "*AB", "AB"},
1636 { "*AB", "B", IsFirstReconcile::kBoth, "U", "*AB", "AB"},
1637 { "*AB", "", IsFirstReconcile::kBoth, "U", "*AB", "AB"},
1638 // Sync enabled, token error on primary.
1639 // Sync enabled, token error on secondary.
1640 { "*AxB", "AB", IsFirstReconcile::kBoth, "U", "*AxB", "A"},
1641 { "*AxB", "BA", IsFirstReconcile::kBoth, "U", "*AxB", "A"},
1642 { "*AxB", "A", IsFirstReconcile::kBoth, "", "*AxB", ""},
1643 { "*AxB", "B", IsFirstReconcile::kBoth, "U", "*AxB", "A"},
1644 { "*AxB", "", IsFirstReconcile::kBoth, "U", "*AxB", "A"},
1645
1646 // Cookies can be refreshed in pace, without logout.
1647 { "*AB", "xBxA", IsFirstReconcile::kBoth, "U", "*AB", "AB"},
1648
1649 // Check that unknown Gaia accounts are signed out.
1650 { "*A", "AB", IsFirstReconcile::kBoth, "U", "*A", "A"},
1651 // Check that the previous case is idempotent.
1652 { "*A", "A", IsFirstReconcile::kBoth, "", "*A", ""},
1653 };
1654 // clang-format on
1655
1656 // Parameterized version of AccountReconcilorTest that tests Mirror
1657 // implementation with Multilogin endpoint.
1658 class AccountReconcilorTestMirrorMultilogin
1659 : public AccountReconcilorTestTable {
1660 public:
1661 AccountReconcilorTestMirrorMultilogin() = default;
1662
1663 private:
1664 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestMirrorMultilogin);
1665 };
1666
1667 // Checks one row of the kDiceParams table above.
TEST_P(AccountReconcilorTestMirrorMultilogin,TableRowTest)1668 TEST_P(AccountReconcilorTestMirrorMultilogin, TableRowTest) {
1669 // Enable Mirror.
1670 SetAccountConsistency(signin::AccountConsistencyMethod::kMirror);
1671
1672 // Setup tokens.
1673 SetupTokens(GetParam().tokens);
1674
1675 // Setup cookies.
1676 std::vector<Cookie> cookies = ParseCookieString(GetParam().cookies);
1677 ConfigureCookieManagerService(cookies);
1678
1679 // Call list accounts now so that the next call completes synchronously.
1680 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
1681 base::RunLoop().RunUntilIdle();
1682
1683 // Setup expectations.
1684 testing::InSequence mock_sequence;
1685 bool logout_action = false;
1686 for (int i = 0; GetParam().gaia_api_calls[i] != '\0'; ++i) {
1687 if (GetParam().gaia_api_calls[i] == 'X') {
1688 logout_action = true;
1689 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
1690 .Times(1);
1691 cookies.clear();
1692 continue;
1693 }
1694 if (GetParam().gaia_api_calls[i] == 'U') {
1695 std::vector<CoreAccountId> accounts_to_send;
1696 for (int i = 0; GetParam().cookies_after_reconcile[i] != '\0'; ++i) {
1697 char cookie = GetParam().cookies_after_reconcile[i];
1698 std::string account_to_send = GaiaIdForAccountKey(cookie);
1699 accounts_to_send.push_back(PickAccountIdForAccount(
1700 account_to_send,
1701 accounts_[GetParam().cookies_after_reconcile[i]].email));
1702 }
1703 const signin::MultiloginParameters ml_params(
1704 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
1705 accounts_to_send);
1706 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(ml_params))
1707 .Times(1);
1708 }
1709 }
1710 if (!logout_action) {
1711 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
1712 .Times(0);
1713 }
1714
1715 // Reconcile.
1716 AccountReconcilor* reconcilor = GetMockReconcilor();
1717 ASSERT_TRUE(reconcilor);
1718 ASSERT_TRUE(reconcilor->first_execution_);
1719 reconcilor->first_execution_ =
1720 GetParam().is_first_reconcile == IsFirstReconcile::kFirst ? true : false;
1721 reconcilor->StartReconcile();
1722
1723 SimulateSetAccountsInCookieCompleted(
1724 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1725
1726 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1727 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1728 VerifyCurrentTokens(ParseTokenString(GetParam().tokens_after_reconcile));
1729
1730 testing::Mock::VerifyAndClearExpectations(GetMockReconcilor());
1731
1732 // Another reconcile is sometimes triggered if Chrome accounts have
1733 // changed. Allow it to finish.
1734 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_))
1735 .WillRepeatedly(testing::Return());
1736 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
1737 .WillRepeatedly(testing::Return());
1738 ConfigureCookieManagerService({});
1739 base::RunLoop().RunUntilIdle();
1740 }
1741
1742 INSTANTIATE_TEST_SUITE_P(
1743 MirrorTableMultilogin,
1744 AccountReconcilorTestMirrorMultilogin,
1745 ::testing::ValuesIn(GenerateTestCasesFromParams(kMirrorParams)));
1746
1747 #if defined(OS_CHROMEOS)
1748 class AccountReconcilorTestActiveDirectory : public AccountReconcilorTestTable {
1749 public:
1750 AccountReconcilorTestActiveDirectory() = default;
1751
SetUp()1752 void SetUp() override {
1753 SetAccountConsistency(signin::AccountConsistencyMethod::kMirror);
1754 }
1755
1756 private:
1757 chromeos::ScopedStubInstallAttributes install_attributes_{
1758 chromeos::StubInstallAttributes::CreateActiveDirectoryManaged(
1759 "realm.com",
1760 "device_id")};
1761
1762 DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestActiveDirectory);
1763 };
1764
1765 // clang-format off
1766 const std::vector<AccountReconcilorTestTableParam> kActiveDirectoryParams = {
1767 // This table encodes the initial state and expectations of a reconcile.
1768 // See kDiceParams for documentation of the syntax.
1769 // -------------------------------------------------------------------------
1770 // Tokens |Cookies |First Run |Gaia calls|Tokens aft.|Cookies aft |
1771 // -------------------------------------------------------------------------
1772 { "ABC", "ABC", IsFirstReconcile::kBoth, "" , "ABC", "ABC" },
1773 { "ABC", "", IsFirstReconcile::kBoth, "U", "ABC", "ABC" },
1774 { "", "ABC", IsFirstReconcile::kBoth, "X", "", "", },
1775 // Order of Gaia accounts can be different from chrome accounts.
1776 { "ABC", "CBA", IsFirstReconcile::kBoth, "" , "ABC", "CBA" },
1777 { "ABC", "CB", IsFirstReconcile::kBoth, "U", "ABC", "CBA" },
1778 // Gaia accounts which are not present in chrome accounts should be removed. In
1779 // this case Gaia accounts are going to be in the same order as chrome accounts.
1780 // this case Gaia accounts are going to be in thcousame order as chromcnts.
1781 { "A", "AB", IsFirstReconcile::kBoth, "U", "A", "A" },
1782 { "AB", "CBA", IsFirstReconcile::kBoth, "U", "AB", "AB" },
1783 { "AB", "C", IsFirstReconcile::kBoth, "U", "AB", "AB" },
1784 // Cookies can be refreshed in pace, without logout.
1785 { "AB", "xAxB", IsFirstReconcile::kBoth, "U", "AB", "AB" },
1786 // Token error on the account - remove it from cookies
1787 { "AxB", "AB", IsFirstReconcile::kBoth, "U", "AxB", "A" },
1788 { "xAxB", "AB", IsFirstReconcile::kBoth, "X", "xAxB", "" },
1789 };
1790 // clang-format on
1791
TEST_P(AccountReconcilorTestActiveDirectory,TableRowTestMultilogin)1792 TEST_P(AccountReconcilorTestActiveDirectory, TableRowTestMultilogin) {
1793 // Setup tokens.
1794 std::vector<Token> tokens = ParseTokenString(GetParam().tokens);
1795 SetupTokens(GetParam().tokens);
1796
1797 // Setup cookies.
1798 std::vector<Cookie> cookies = ParseCookieString(GetParam().cookies);
1799 ConfigureCookieManagerService(cookies);
1800
1801 // Call list accounts now so that the next call completes synchronously.
1802 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
1803 base::RunLoop().RunUntilIdle();
1804
1805 testing::InSequence mock_sequence;
1806 MockAccountReconcilor* reconcilor = GetMockReconcilor(
1807 std::make_unique<signin::ActiveDirectoryAccountReconcilorDelegate>());
1808
1809 // Setup expectations.
1810 bool logout_action = false;
1811 for (int i = 0; GetParam().gaia_api_calls[i] != '\0'; ++i) {
1812 if (GetParam().gaia_api_calls[i] == 'X') {
1813 logout_action = true;
1814 EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction()).Times(1);
1815 cookies.clear();
1816 continue;
1817 }
1818 if (GetParam().gaia_api_calls[i] == 'U') {
1819 std::vector<CoreAccountId> accounts_to_send;
1820 for (int i = 0; GetParam().cookies_after_reconcile[i] != '\0'; ++i) {
1821 char cookie = GetParam().cookies_after_reconcile[i];
1822 std::string account_to_send = GaiaIdForAccountKey(cookie);
1823 accounts_to_send.push_back(PickAccountIdForAccount(
1824 account_to_send,
1825 accounts_[GetParam().cookies_after_reconcile[i]].email));
1826 }
1827 const signin::MultiloginParameters params(
1828 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
1829 accounts_to_send);
1830 EXPECT_CALL(*reconcilor, PerformSetCookiesAction(params)).Times(1);
1831 }
1832 }
1833 if (!logout_action) {
1834 EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction()).Times(0);
1835 }
1836
1837 // Reconcile.
1838 ASSERT_TRUE(reconcilor);
1839 ASSERT_TRUE(reconcilor->first_execution_);
1840 reconcilor->first_execution_ =
1841 GetParam().is_first_reconcile == IsFirstReconcile::kFirst ? true : false;
1842 reconcilor->StartReconcile();
1843
1844 SimulateSetAccountsInCookieCompleted(
1845 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1846
1847 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1848 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1849 VerifyCurrentTokens(ParseTokenString(GetParam().tokens_after_reconcile));
1850
1851 testing::Mock::VerifyAndClearExpectations(reconcilor);
1852
1853 // Another reconcile is sometimes triggered if Chrome accounts have
1854 // changed. Allow it to finish.
1855 EXPECT_CALL(*reconcilor, PerformSetCookiesAction(testing::_))
1856 .WillRepeatedly(testing::Return());
1857 EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction())
1858 .WillRepeatedly(testing::Return());
1859 ConfigureCookieManagerService({});
1860 base::RunLoop().RunUntilIdle();
1861 }
1862
1863 INSTANTIATE_TEST_SUITE_P(
1864 ActiveDirectoryTable,
1865 AccountReconcilorTestActiveDirectory,
1866 ::testing::ValuesIn(GenerateTestCasesFromParams(kActiveDirectoryParams)));
1867 #endif // defined(OS_CHROMEOS)
1868
1869 // Tests that reconcile cannot start before the tokens are loaded, and is
1870 // automatically started when tokens are loaded.
TEST_F(AccountReconcilorMirrorTest,TokensNotLoaded)1871 TEST_F(AccountReconcilorMirrorTest, TokensNotLoaded) {
1872 const CoreAccountId account_id =
1873 ConnectProfileToAccount("user@gmail.com").account_id;
1874 signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_);
1875 identity_test_env()->ResetToAccountsNotYetLoadedFromDiskState();
1876
1877 AccountReconcilor* reconcilor = GetMockReconcilor();
1878 reconcilor->StartReconcile();
1879
1880 // No reconcile when tokens are not loaded.
1881 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1882 // When tokens are loaded, reconcile starts automatically.
1883 identity_test_env()->ReloadAccountsFromDisk();
1884
1885 std::vector<CoreAccountId> accounts_to_send = {account_id};
1886 const signin::MultiloginParameters params(
1887 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
1888 accounts_to_send);
1889 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
1890
1891 ASSERT_TRUE(reconcilor->is_reconcile_started_);
1892 base::RunLoop().RunUntilIdle();
1893
1894 SimulateSetAccountsInCookieCompleted(
1895 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
1896
1897 ASSERT_FALSE(reconcilor->is_reconcile_started_);
1898 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
1899 }
1900
TEST_F(AccountReconcilorMirrorTest,GetAccountsFromCookieSuccess)1901 TEST_F(AccountReconcilorMirrorTest, GetAccountsFromCookieSuccess) {
1902 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
1903 const CoreAccountId account_id = account_info.account_id;
1904 signin::SetListAccountsResponseOneAccountWithParams(
1905 {account_info.email, account_info.gaia, false /* valid */,
1906 false /* signed_out */, true /* verified */},
1907 &test_url_loader_factory_);
1908
1909 std::vector<CoreAccountId> accounts_to_send = {account_id};
1910 const signin::MultiloginParameters params(
1911 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
1912 accounts_to_send);
1913 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
1914
1915 AccountReconcilor* reconcilor = GetMockReconcilor();
1916 ASSERT_TRUE(reconcilor);
1917
1918 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED,
1919 reconcilor->GetState());
1920 reconcilor->StartReconcile();
1921 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState());
1922 base::RunLoop().RunUntilIdle();
1923 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState());
1924
1925 signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info =
1926 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
1927 ASSERT_TRUE(accounts_in_cookie_jar_info.accounts_are_fresh);
1928 ASSERT_EQ(1u, accounts_in_cookie_jar_info.signed_in_accounts.size());
1929 ASSERT_EQ(account_id, accounts_in_cookie_jar_info.signed_in_accounts[0].id);
1930 ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_out_accounts.size());
1931 }
1932
1933 // Checks that calling EnableReconcile() while the reconcilor is already running
1934 // doesn't have any effect. Regression test for https://crbug.com/1043651
TEST_F(AccountReconcilorMirrorTest,EnableReconcileWhileAlreadyRunning)1935 TEST_F(AccountReconcilorMirrorTest, EnableReconcileWhileAlreadyRunning) {
1936 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
1937 const CoreAccountId account_id = account_info.account_id;
1938 signin::SetListAccountsResponseOneAccountWithParams(
1939 {account_info.email, account_info.gaia, false /* valid */,
1940 false /* signed_out */, true /* verified */},
1941 &test_url_loader_factory_);
1942
1943 std::vector<CoreAccountId> accounts_to_send = {account_id};
1944 const signin::MultiloginParameters params(
1945 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
1946 accounts_to_send);
1947 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
1948
1949 AccountReconcilor* reconcilor = GetMockReconcilor();
1950 ASSERT_TRUE(reconcilor);
1951
1952 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED,
1953 reconcilor->GetState());
1954 reconcilor->StartReconcile();
1955 EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState());
1956 reconcilor->EnableReconcile();
1957 EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState());
1958 base::RunLoop().RunUntilIdle();
1959 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState());
1960
1961 signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info =
1962 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
1963 ASSERT_TRUE(accounts_in_cookie_jar_info.accounts_are_fresh);
1964 ASSERT_EQ(1u, accounts_in_cookie_jar_info.signed_in_accounts.size());
1965 ASSERT_EQ(account_id, accounts_in_cookie_jar_info.signed_in_accounts[0].id);
1966 ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_out_accounts.size());
1967 }
1968
TEST_F(AccountReconcilorMirrorTest,GetAccountsFromCookieFailure)1969 TEST_F(AccountReconcilorMirrorTest, GetAccountsFromCookieFailure) {
1970 ConnectProfileToAccount("user@gmail.com");
1971 signin::SetListAccountsResponseWithUnexpectedServiceResponse(
1972 &test_url_loader_factory_);
1973
1974 AccountReconcilor* reconcilor = GetMockReconcilor();
1975 ASSERT_TRUE(reconcilor);
1976
1977 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED,
1978 reconcilor->GetState());
1979 reconcilor->StartReconcile();
1980 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState());
1981 base::RunLoop().RunUntilIdle();
1982
1983 signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info =
1984 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
1985 ASSERT_FALSE(accounts_in_cookie_jar_info.accounts_are_fresh);
1986 ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_in_accounts.size());
1987 ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_out_accounts.size());
1988 // List accounts retries once on |UNEXPECTED_SERVICE_RESPONSE| errors with
1989 // backoff protection.
1990 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
1991 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_ERROR, reconcilor->GetState());
1992 }
1993
1994 // Regression test for https://crbug.com/923716
TEST_F(AccountReconcilorMirrorTest,ExtraCookieChangeNotification)1995 TEST_F(AccountReconcilorMirrorTest, ExtraCookieChangeNotification) {
1996 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
1997 const CoreAccountId account_id = account_info.account_id;
1998 signin::CookieParams cookie_params = {
1999 account_info.email, account_info.gaia, false /* valid */,
2000 false /* signed_out */, true /* verified */};
2001
2002 signin::SetListAccountsResponseOneAccountWithParams(
2003 cookie_params, &test_url_loader_factory_);
2004
2005 std::vector<CoreAccountId> accounts_to_send = {account_id};
2006 const signin::MultiloginParameters params(
2007 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2008 accounts_to_send);
2009 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2010
2011 AccountReconcilor* reconcilor = GetMockReconcilor();
2012 ASSERT_TRUE(reconcilor);
2013
2014 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED,
2015 reconcilor->GetState());
2016 reconcilor->StartReconcile();
2017 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState());
2018
2019 // Add extra cookie change notification. Reconcilor should ignore it.
2020 gaia::ListedAccount listed_account =
2021 ListedAccountFromCookieParams(cookie_params, account_id);
2022 signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info = {
2023 /*accounts_are_fresh=*/true, {listed_account}, {}};
2024 reconcilor->OnAccountsInCookieUpdated(
2025 accounts_in_cookie_jar_info, GoogleServiceAuthError::AuthErrorNone());
2026
2027 base::RunLoop().RunUntilIdle();
2028
2029 SimulateSetAccountsInCookieCompleted(
2030 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2031
2032 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2033 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
2034 }
2035
TEST_F(AccountReconcilorMirrorTest,StartReconcileNoop)2036 TEST_F(AccountReconcilorMirrorTest, StartReconcileNoop) {
2037 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2038
2039 AccountReconcilor* reconcilor = GetMockReconcilor();
2040 ASSERT_TRUE(reconcilor);
2041
2042 signin::SetListAccountsResponseOneAccount(
2043 account_info.email, account_info.gaia, &test_url_loader_factory_);
2044
2045 reconcilor->StartReconcile();
2046 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2047
2048 base::RunLoop().RunUntilIdle();
2049 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2050 }
2051
TEST_F(AccountReconcilorMirrorTest,StartReconcileCookiesDisabled)2052 TEST_F(AccountReconcilorMirrorTest, StartReconcileCookiesDisabled) {
2053 const CoreAccountId account_id =
2054 ConnectProfileToAccount("user@gmail.com").account_id;
2055 identity_test_env()->SetRefreshTokenForAccount(account_id);
2056 test_signin_client()->set_are_signin_cookies_allowed(false);
2057
2058 AccountReconcilor* reconcilor = GetMockReconcilor();
2059 ASSERT_TRUE(reconcilor);
2060
2061 reconcilor->StartReconcile();
2062 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2063
2064 base::RunLoop().RunUntilIdle();
2065 std::vector<gaia::ListedAccount> accounts;
2066 // This will be the first call to ListAccounts.
2067 signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info =
2068 identity_test_env()->identity_manager()->GetAccountsInCookieJar();
2069 ASSERT_FALSE(accounts_in_cookie_jar_info.accounts_are_fresh);
2070 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2071 }
2072
TEST_F(AccountReconcilorMirrorTest,StartReconcileContentSettings)2073 TEST_F(AccountReconcilorMirrorTest, StartReconcileContentSettings) {
2074 const CoreAccountId account_id =
2075 ConnectProfileToAccount("user@gmail.com").account_id;
2076 identity_test_env()->SetRefreshTokenForAccount(account_id);
2077
2078 AccountReconcilor* reconcilor = GetMockReconcilor();
2079 ASSERT_TRUE(reconcilor);
2080
2081 test_signin_client()->set_are_signin_cookies_allowed(false);
2082 SimulateCookieContentSettingsChanged(reconcilor,
2083 ContentSettingsPattern::Wildcard());
2084 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2085
2086 test_signin_client()->set_are_signin_cookies_allowed(true);
2087 SimulateCookieContentSettingsChanged(reconcilor,
2088 ContentSettingsPattern::Wildcard());
2089 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2090 }
2091
TEST_F(AccountReconcilorMirrorTest,StartReconcileContentSettingsGaiaUrl)2092 TEST_F(AccountReconcilorMirrorTest, StartReconcileContentSettingsGaiaUrl) {
2093 const CoreAccountId account_id =
2094 ConnectProfileToAccount("user@gmail.com").account_id;
2095 identity_test_env()->SetRefreshTokenForAccount(account_id);
2096
2097 AccountReconcilor* reconcilor = GetMockReconcilor();
2098 ASSERT_TRUE(reconcilor);
2099
2100 SimulateCookieContentSettingsChanged(
2101 reconcilor,
2102 ContentSettingsPattern::FromURL(GaiaUrls::GetInstance()->gaia_url()));
2103 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2104 }
2105
TEST_F(AccountReconcilorMirrorTest,StartReconcileContentSettingsNonGaiaUrl)2106 TEST_F(AccountReconcilorMirrorTest, StartReconcileContentSettingsNonGaiaUrl) {
2107 const CoreAccountId account_id =
2108 ConnectProfileToAccount("user@gmail.com").account_id;
2109 identity_test_env()->SetRefreshTokenForAccount(account_id);
2110
2111 AccountReconcilor* reconcilor = GetMockReconcilor();
2112 ASSERT_TRUE(reconcilor);
2113
2114 SimulateCookieContentSettingsChanged(
2115 reconcilor,
2116 ContentSettingsPattern::FromURL(GURL("http://www.example.com")));
2117 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2118 }
2119
TEST_F(AccountReconcilorMirrorTest,StartReconcileContentSettingsInvalidPattern)2120 TEST_F(AccountReconcilorMirrorTest,
2121 StartReconcileContentSettingsInvalidPattern) {
2122 const CoreAccountId account_id =
2123 ConnectProfileToAccount("user@gmail.com").account_id;
2124 identity_test_env()->SetRefreshTokenForAccount(account_id);
2125
2126 AccountReconcilor* reconcilor = GetMockReconcilor();
2127 ASSERT_TRUE(reconcilor);
2128
2129 std::unique_ptr<ContentSettingsPattern::BuilderInterface> builder =
2130 ContentSettingsPattern::CreateBuilder();
2131 builder->Invalid();
2132
2133 SimulateCookieContentSettingsChanged(reconcilor, builder->Build());
2134 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2135 }
2136
2137 // This test is needed until chrome changes to use gaia obfuscated id.
2138 // The primary account manager and token service use the gaia "email" property,
2139 // which preserves dots in usernames and preserves case.
2140 // gaia::ParseListAccountsData() however uses gaia "displayEmail" which does not
2141 // preserve case, and then passes the string through gaia::CanonicalizeEmail()
2142 // which removes dots. This tests makes sure that an email like
2143 // "Dot.S@hmail.com", as seen by the token service, will be considered the same
2144 // as "dots@gmail.com" as returned by gaia::ParseListAccountsData().
TEST_F(AccountReconcilorMirrorTest,StartReconcileNoopWithDots)2145 TEST_F(AccountReconcilorMirrorTest, StartReconcileNoopWithDots) {
2146 if (identity_test_env()->identity_manager()->GetAccountIdMigrationState() !=
2147 signin::IdentityManager::AccountIdMigrationState::MIGRATION_NOT_STARTED) {
2148 return;
2149 }
2150
2151 AccountInfo account_info = ConnectProfileToAccount("Dot.S@gmail.com");
2152 signin::SetListAccountsResponseOneAccount(
2153 account_info.email, account_info.gaia, &test_url_loader_factory_);
2154 AccountReconcilor* reconcilor = GetMockReconcilor();
2155 ASSERT_TRUE(reconcilor);
2156
2157 reconcilor->StartReconcile();
2158 base::RunLoop().RunUntilIdle();
2159 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2160 }
2161
TEST_F(AccountReconcilorMirrorTest,StartReconcileNoopMultiple)2162 TEST_F(AccountReconcilorMirrorTest, StartReconcileNoopMultiple) {
2163 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2164 AccountInfo account_info_2 =
2165 identity_test_env()->MakeAccountAvailable("other@gmail.com");
2166 signin::SetListAccountsResponseTwoAccounts(
2167 account_info.email, account_info.gaia, account_info_2.email,
2168 account_info_2.gaia, &test_url_loader_factory_);
2169
2170 AccountReconcilor* reconcilor = GetMockReconcilor();
2171 ASSERT_TRUE(reconcilor);
2172
2173 reconcilor->StartReconcile();
2174 base::RunLoop().RunUntilIdle();
2175 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2176 }
2177
TEST_F(AccountReconcilorMirrorTest,StartReconcileAddToCookie)2178 TEST_F(AccountReconcilorMirrorTest, StartReconcileAddToCookie) {
2179 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2180 const CoreAccountId account_id = account_info.account_id;
2181 identity_test_env()->SetRefreshTokenForAccount(account_id);
2182 signin::SetListAccountsResponseOneAccount(
2183 account_info.email, account_info.gaia, &test_url_loader_factory_);
2184
2185 const CoreAccountId account_id2 =
2186 identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id;
2187
2188 std::vector<CoreAccountId> accounts_to_send = {account_id, account_id2};
2189 const signin::MultiloginParameters params(
2190 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2191 accounts_to_send);
2192 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2193
2194 AccountReconcilor* reconcilor = GetMockReconcilor();
2195 reconcilor->StartReconcile();
2196
2197 base::RunLoop().RunUntilIdle();
2198 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2199
2200 SimulateSetAccountsInCookieCompleted(
2201 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2202
2203 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2204
2205 base::HistogramTester::CountsMap expected_counts;
2206 expected_counts["Signin.Reconciler.Duration.UpTo3mins.Success"] = 1;
2207 EXPECT_THAT(histogram_tester()->GetTotalCountsForPrefix(
2208 "Signin.Reconciler.Duration.UpTo3mins.Success"),
2209 testing::ContainerEq(expected_counts));
2210 }
2211
TEST_F(AccountReconcilorTest,AuthErrorTriggersListAccount)2212 TEST_F(AccountReconcilorTest, AuthErrorTriggersListAccount) {
2213 class TestGaiaCookieObserver : public signin::IdentityManager::Observer {
2214 public:
2215 void OnAccountsInCookieUpdated(
2216 const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
2217 const GoogleServiceAuthError& error) override {
2218 cookies_updated_ = true;
2219 }
2220
2221 bool cookies_updated_ = false;
2222 };
2223
2224 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
2225 signin::AccountConsistencyMethod account_consistency =
2226 signin::AccountConsistencyMethod::kDice;
2227 SetAccountConsistency(account_consistency);
2228 #else
2229 signin::AccountConsistencyMethod account_consistency =
2230 signin::AccountConsistencyMethod::kMirror;
2231 SetAccountConsistency(account_consistency);
2232 #endif
2233
2234 // Add one account to Chrome and instantiate the reconcilor.
2235 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2236 const CoreAccountId account_id = account_info.account_id;
2237 identity_test_env()->SetRefreshTokenForAccount(account_id);
2238 TestGaiaCookieObserver observer;
2239 identity_test_env()->identity_manager()->AddObserver(&observer);
2240 AccountReconcilor* reconcilor = GetMockReconcilor();
2241 base::RunLoop().RunUntilIdle();
2242 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2243 signin::SetListAccountsResponseOneAccount(
2244 account_info.email, account_info.gaia, &test_url_loader_factory_);
2245 if (account_consistency == signin::AccountConsistencyMethod::kDice) {
2246 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction())
2247 .Times(1);
2248 }
2249
2250 // Set an authentication error.
2251 ASSERT_FALSE(observer.cookies_updated_);
2252 signin::UpdatePersistentErrorOfRefreshTokenForAccount(
2253 identity_test_env()->identity_manager(), account_id,
2254 GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
2255 GoogleServiceAuthError::InvalidGaiaCredentialsReason::
2256 CREDENTIALS_REJECTED_BY_SERVER));
2257 base::RunLoop().RunUntilIdle();
2258
2259 // Check that a call to ListAccount was triggered.
2260 EXPECT_TRUE(observer.cookies_updated_);
2261 testing::Mock::VerifyAndClearExpectations(GetMockReconcilor());
2262
2263 identity_test_env()->identity_manager()->RemoveObserver(&observer);
2264 }
2265
2266 #if !defined(OS_CHROMEOS)
2267 // This test does not run on ChromeOS because it clears the primary account,
2268 // which is not a flow that exists on ChromeOS.
2269
TEST_F(AccountReconcilorMirrorTest,SignoutAfterErrorDoesNotRecordUma)2270 TEST_F(AccountReconcilorMirrorTest, SignoutAfterErrorDoesNotRecordUma) {
2271 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2272 const CoreAccountId account_id = account_info.account_id;
2273 identity_test_env()->SetRefreshTokenForAccount(account_id);
2274 signin::SetListAccountsResponseOneAccount(
2275 account_info.email, account_info.gaia, &test_url_loader_factory_);
2276
2277 const CoreAccountId account_id2 =
2278 identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id;
2279
2280 std::vector<CoreAccountId> accounts_to_send = {account_id, account_id2};
2281 const signin::MultiloginParameters params(
2282 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2283 accounts_to_send);
2284 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2285
2286 AccountReconcilor* reconcilor = GetMockReconcilor();
2287 reconcilor->StartReconcile();
2288
2289 base::RunLoop().RunUntilIdle();
2290 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2291
2292 SimulateSetAccountsInCookieCompleted(
2293 reconcilor, signin::SetAccountsInCookieResult::kPersistentError);
2294
2295 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2296
2297 EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction());
2298 identity_test_env()->ClearPrimaryAccount();
2299
2300 base::HistogramTester::CountsMap expected_counts;
2301 expected_counts["Signin.Reconciler.Duration.UpTo3mins.Failure"] = 1;
2302 }
2303
2304 #endif // !defined(OS_CHROMEOS)
2305
TEST_F(AccountReconcilorMirrorTest,StartReconcileRemoveFromCookie)2306 TEST_F(AccountReconcilorMirrorTest, StartReconcileRemoveFromCookie) {
2307 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2308 const CoreAccountId account_id = account_info.account_id;
2309 identity_test_env()->SetRefreshTokenForAccount(account_id);
2310 signin::SetListAccountsResponseTwoAccounts(
2311 account_info.email, account_info.gaia, "other@gmail.com", "12345",
2312 &test_url_loader_factory_);
2313
2314 std::vector<CoreAccountId> accounts_to_send = {account_id};
2315 const signin::MultiloginParameters params(
2316 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2317 accounts_to_send);
2318 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2319
2320 AccountReconcilor* reconcilor = GetMockReconcilor();
2321 reconcilor->StartReconcile();
2322 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2323
2324 base::RunLoop().RunUntilIdle();
2325
2326 SimulateSetAccountsInCookieCompleted(
2327 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2328
2329 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2330 }
2331
2332 // Check that reconcile is aborted if there is token error on primary account.
TEST_F(AccountReconcilorMirrorTest,TokenErrorOnPrimary)2333 TEST_F(AccountReconcilorMirrorTest, TokenErrorOnPrimary) {
2334 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2335 signin::UpdatePersistentErrorOfRefreshTokenForAccount(
2336 identity_test_env()->identity_manager(), account_info.account_id,
2337 GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
2338
2339 signin::SetListAccountsResponseTwoAccounts(
2340 account_info.email, account_info.gaia, "other@gmail.com", "67890",
2341 &test_url_loader_factory_);
2342
2343 AccountReconcilor* reconcilor = GetMockReconcilor();
2344 reconcilor->StartReconcile();
2345
2346 base::RunLoop().RunUntilIdle();
2347 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2348 }
2349
TEST_F(AccountReconcilorMirrorTest,StartReconcileAddToCookieTwice)2350 TEST_F(AccountReconcilorMirrorTest, StartReconcileAddToCookieTwice) {
2351 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2352 const CoreAccountId account_id = account_info.account_id;
2353 AccountInfo account_info2 =
2354 identity_test_env()->MakeAccountAvailable("other@gmail.com");
2355 const CoreAccountId account_id2 = account_info2.account_id;
2356
2357 const std::string email3 = "third@gmail.com";
2358 const std::string gaia_id3 = signin::GetTestGaiaIdForEmail(email3);
2359 const CoreAccountId account_id3 = PickAccountIdForAccount(gaia_id3, email3);
2360
2361 signin::SetListAccountsResponseOneAccount(
2362 account_info.email, account_info.gaia, &test_url_loader_factory_);
2363
2364 std::vector<CoreAccountId> accounts_to_send_1 = {account_id, account_id2};
2365 const signin::MultiloginParameters ml_params_1(
2366 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2367 accounts_to_send_1);
2368 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(ml_params_1));
2369
2370 AccountReconcilor* reconcilor = GetMockReconcilor();
2371 reconcilor->StartReconcile();
2372
2373 base::RunLoop().RunUntilIdle();
2374 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2375
2376 SimulateSetAccountsInCookieCompleted(
2377 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2378 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2379
2380
2381 // Do another pass after I've added a third account to the token service
2382 signin::SetListAccountsResponseTwoAccounts(
2383 account_info.email, account_info.gaia, account_info2.email,
2384 account_info2.gaia, &test_url_loader_factory_);
2385 identity_test_env()->SetFreshnessOfAccountsInGaiaCookie(false);
2386
2387 // This will cause the reconcilor to fire.
2388 identity_test_env()->MakeAccountAvailable(email3);
2389 std::vector<CoreAccountId> accounts_to_send_2 = {account_id, account_id2,
2390 account_id3};
2391 const signin::MultiloginParameters ml_params_2(
2392 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2393 accounts_to_send_2);
2394 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(ml_params_2));
2395 base::RunLoop().RunUntilIdle();
2396
2397 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2398
2399 SimulateSetAccountsInCookieCompleted(
2400 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2401
2402 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2403 }
2404
TEST_F(AccountReconcilorMirrorTest,StartReconcileBadPrimary)2405 TEST_F(AccountReconcilorMirrorTest, StartReconcileBadPrimary) {
2406 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2407 const CoreAccountId account_id = account_info.account_id;
2408
2409 AccountInfo account_info2 =
2410 identity_test_env()->MakeAccountAvailable("other@gmail.com");
2411 const CoreAccountId account_id2 = account_info2.account_id;
2412 signin::SetListAccountsResponseTwoAccounts(
2413 account_info2.email, account_info2.gaia, account_info.email,
2414 account_info.gaia, &test_url_loader_factory_);
2415
2416 std::vector<CoreAccountId> accounts_to_send = {account_id, account_id2};
2417 const signin::MultiloginParameters params(
2418 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2419 accounts_to_send);
2420 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2421
2422 AccountReconcilor* reconcilor = GetMockReconcilor();
2423 reconcilor->StartReconcile();
2424
2425 base::RunLoop().RunUntilIdle();
2426 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2427
2428 SimulateSetAccountsInCookieCompleted(
2429 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2430
2431 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2432 }
2433
TEST_F(AccountReconcilorMirrorTest,StartReconcileOnlyOnce)2434 TEST_F(AccountReconcilorMirrorTest, StartReconcileOnlyOnce) {
2435 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2436 signin::SetListAccountsResponseOneAccount(
2437 account_info.email, account_info.gaia, &test_url_loader_factory_);
2438
2439 AccountReconcilor* reconcilor = GetMockReconcilor();
2440 ASSERT_TRUE(reconcilor);
2441
2442 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2443 reconcilor->StartReconcile();
2444 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2445
2446 base::RunLoop().RunUntilIdle();
2447 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2448 }
2449
TEST_F(AccountReconcilorMirrorTest,Lock)2450 TEST_F(AccountReconcilorMirrorTest, Lock) {
2451 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2452 signin::SetListAccountsResponseOneAccount(
2453 account_info.email, account_info.gaia, &test_url_loader_factory_);
2454
2455 AccountReconcilor* reconcilor = GetMockReconcilor();
2456 ASSERT_TRUE(reconcilor);
2457 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2458 EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_);
2459
2460 class TestAccountReconcilorObserver : public AccountReconcilor::Observer {
2461 public:
2462 void OnStateChanged(AccountReconcilorState state) override {
2463 if (state == AccountReconcilorState::ACCOUNT_RECONCILOR_RUNNING) {
2464 ++started_count_;
2465 }
2466 }
2467 void OnBlockReconcile() override { ++blocked_count_; }
2468 void OnUnblockReconcile() override { ++unblocked_count_; }
2469
2470 int started_count_ = 0;
2471 int blocked_count_ = 0;
2472 int unblocked_count_ = 0;
2473 };
2474
2475 TestAccountReconcilorObserver observer;
2476 ScopedObserver<AccountReconcilor, AccountReconcilor::Observer>
2477 scoped_observer(&observer);
2478 scoped_observer.Add(reconcilor);
2479
2480 // Lock prevents reconcile from starting, as long as one instance is alive.
2481 std::unique_ptr<AccountReconcilor::Lock> lock_1 =
2482 std::make_unique<AccountReconcilor::Lock>(reconcilor);
2483 EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_);
2484 reconcilor->StartReconcile();
2485 // lock_1 is blocking the reconcile.
2486 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2487 {
2488 AccountReconcilor::Lock lock_2(reconcilor);
2489 EXPECT_EQ(2, reconcilor->account_reconcilor_lock_count_);
2490 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2491 lock_1.reset();
2492 // lock_1 is no longer blocking, but lock_2 is still alive.
2493 EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_);
2494 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2495 EXPECT_EQ(0, observer.started_count_);
2496 EXPECT_EQ(0, observer.unblocked_count_);
2497 EXPECT_EQ(1, observer.blocked_count_);
2498 }
2499
2500 // All locks are deleted, reconcile starts.
2501 EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_);
2502 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2503 EXPECT_EQ(1, observer.started_count_);
2504 EXPECT_EQ(1, observer.unblocked_count_);
2505 EXPECT_EQ(1, observer.blocked_count_);
2506
2507 // Lock aborts current reconcile, and restarts it later.
2508 {
2509 AccountReconcilor::Lock lock(reconcilor);
2510 EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_);
2511 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2512 }
2513 EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_);
2514 EXPECT_TRUE(reconcilor->is_reconcile_started_);
2515 EXPECT_EQ(2, observer.started_count_);
2516 EXPECT_EQ(2, observer.unblocked_count_);
2517 EXPECT_EQ(2, observer.blocked_count_);
2518
2519 // Reconcile can complete successfully after being restarted.
2520 base::RunLoop().RunUntilIdle();
2521 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2522 }
2523
2524 // Checks that an "invalid" Gaia account can be refreshed in place, without
2525 // performing a full logout.
TEST_P(AccountReconcilorMethodParamTest,StartReconcileWithSessionInfoExpiredDefault)2526 TEST_P(AccountReconcilorMethodParamTest,
2527 StartReconcileWithSessionInfoExpiredDefault) {
2528 signin::AccountConsistencyMethod account_consistency = GetParam();
2529 SetAccountConsistency(account_consistency);
2530 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2531 const CoreAccountId account_id = account_info.account_id;
2532 AccountInfo account_info2 =
2533 identity_test_env()->MakeAccountAvailable("other@gmail.com");
2534 const CoreAccountId account_id2 = account_info2.account_id;
2535 signin::SetListAccountsResponseWithParams(
2536 {{account_info.email, account_info.gaia, false /* valid */,
2537 false /* signed_out */, true /* verified */},
2538 {account_info2.email, account_info2.gaia, true /* valid */,
2539 false /* signed_out */, true /* verified */}},
2540 &test_url_loader_factory_);
2541
2542 AccountReconcilor* reconcilor = GetMockReconcilor();
2543 ASSERT_TRUE(reconcilor);
2544
2545 if (!reconcilor->IsMultiloginEndpointEnabled()) {
2546 EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id));
2547 } else {
2548 switch (account_consistency) {
2549 case signin::AccountConsistencyMethod::kMirror: {
2550 signin::MultiloginParameters params(
2551 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2552 {account_id, account_id2});
2553 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2554 break;
2555 }
2556 case signin::AccountConsistencyMethod::kDice: {
2557 signin::MultiloginParameters params(
2558 gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER,
2559 {account_id2, account_id});
2560 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2561 break;
2562 }
2563 case signin::AccountConsistencyMethod::kDisabled:
2564 NOTREACHED();
2565 break;
2566 }
2567 }
2568
2569 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2570 reconcilor->StartReconcile();
2571 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2572
2573 base::RunLoop().RunUntilIdle();
2574 if (!reconcilor->IsMultiloginEndpointEnabled()) {
2575 SimulateAddAccountToCookieCompleted(
2576 reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone());
2577 } else {
2578 SimulateSetAccountsInCookieCompleted(
2579 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2580 }
2581 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2582 }
2583
TEST_F(AccountReconcilorMirrorTest,AddAccountToCookieCompletedWithBogusAccount)2584 TEST_F(AccountReconcilorMirrorTest,
2585 AddAccountToCookieCompletedWithBogusAccount) {
2586 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2587 const CoreAccountId account_id = account_info.account_id;
2588 signin::SetListAccountsResponseOneAccountWithParams(
2589 {account_info.email, account_info.gaia, false /* valid */,
2590 false /* signed_out */, true /* verified */},
2591 &test_url_loader_factory_);
2592
2593 std::vector<CoreAccountId> accounts_to_send = {account_id};
2594 const signin::MultiloginParameters params(
2595 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2596 accounts_to_send);
2597 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2598
2599 AccountReconcilor* reconcilor = GetMockReconcilor();
2600 ASSERT_TRUE(reconcilor);
2601
2602 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2603 reconcilor->StartReconcile();
2604 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2605
2606 base::RunLoop().RunUntilIdle();
2607
2608 // If an unknown account id is sent, it should not upset the state.
2609 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2610
2611 SimulateSetAccountsInCookieCompleted(
2612 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2613
2614 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2615 }
2616
TEST_F(AccountReconcilorMirrorTest,NoLoopWithBadPrimary)2617 TEST_F(AccountReconcilorMirrorTest, NoLoopWithBadPrimary) {
2618 // Connect profile to a primary account and then add a secondary account.
2619 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2620 const CoreAccountId account_id1 = account_info.account_id;
2621 AccountInfo account_info2 =
2622 identity_test_env()->MakeAccountAvailable("other@gmail.com");
2623 const CoreAccountId account_id2 = account_info2.account_id;
2624
2625 std::vector<CoreAccountId> accounts_to_send = {account_id1, account_id2};
2626 const signin::MultiloginParameters params(
2627 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2628 accounts_to_send);
2629 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2630
2631 // The primary account is in auth error, so it is not in the cookie.
2632 signin::SetListAccountsResponseOneAccountWithParams(
2633 {account_info2.email, account_info2.gaia, false /* valid */,
2634 false /* signed_out */, true /* verified */},
2635 &test_url_loader_factory_);
2636
2637 AccountReconcilor* reconcilor = GetMockReconcilor();
2638 ASSERT_TRUE(reconcilor);
2639
2640 reconcilor->StartReconcile();
2641 base::RunLoop().RunUntilIdle();
2642 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2643
2644 GoogleServiceAuthError error(
2645 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
2646
2647 // The primary cannot be added to cookie, so it fails.
2648 SimulateSetAccountsInCookieCompleted(
2649 reconcilor, signin::SetAccountsInCookieResult::kPersistentError);
2650 base::RunLoop().RunUntilIdle();
2651 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2652 ASSERT_NE(GoogleServiceAuthError::State::NONE,
2653 reconcilor->error_during_last_reconcile_.state());
2654 testing::Mock::VerifyAndClearExpectations(GetMockReconcilor());
2655
2656 // Now that we've tried once, the token service knows that the primary
2657 // account has an auth error.
2658 signin::UpdatePersistentErrorOfRefreshTokenForAccount(
2659 identity_test_env()->identity_manager(), account_id1, error);
2660
2661 // A second attempt to reconcile should be a noop.
2662 reconcilor->StartReconcile();
2663 base::RunLoop().RunUntilIdle();
2664 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2665 testing::Mock::VerifyAndClearExpectations(GetMockReconcilor());
2666 }
2667
TEST_F(AccountReconcilorMirrorTest,WontMergeAccountsWithError)2668 TEST_F(AccountReconcilorMirrorTest, WontMergeAccountsWithError) {
2669 // Connect profile to a primary account and then add a secondary account.
2670 const CoreAccountId account_id1 =
2671 ConnectProfileToAccount("user@gmail.com").account_id;
2672 const CoreAccountId account_id2 =
2673 identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id;
2674
2675 // Mark the secondary account in auth error state.
2676 signin::UpdatePersistentErrorOfRefreshTokenForAccount(
2677 identity_test_env()->identity_manager(), account_id2,
2678 GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
2679
2680 // The cookie starts empty.
2681 signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_);
2682
2683 // Since the cookie jar starts empty, the reconcilor should attempt to merge
2684 // accounts into it. However, it should only try accounts not in auth
2685 // error state.
2686 std::vector<CoreAccountId> accounts_to_send = {account_id1};
2687 const signin::MultiloginParameters params(
2688 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER,
2689 accounts_to_send);
2690 EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params));
2691
2692 AccountReconcilor* reconcilor = GetMockReconcilor();
2693 ASSERT_TRUE(reconcilor);
2694
2695 reconcilor->StartReconcile();
2696 base::RunLoop().RunUntilIdle();
2697 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2698
2699 SimulateSetAccountsInCookieCompleted(
2700 reconcilor, signin::SetAccountsInCookieResult::kSuccess);
2701 base::RunLoop().RunUntilIdle();
2702 ASSERT_FALSE(reconcilor->is_reconcile_started_);
2703 ASSERT_EQ(GoogleServiceAuthError::State::NONE,
2704 reconcilor->error_during_last_reconcile_.state());
2705 }
2706
2707 // Test that delegate timeout is called when the delegate offers a valid
2708 // timeout.
TEST_F(AccountReconcilorTest,DelegateTimeoutIsCalled)2709 TEST_F(AccountReconcilorTest, DelegateTimeoutIsCalled) {
2710 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2711 auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>();
2712 SpyReconcilorDelegate* spy_delegate = spy_delegate0.get();
2713 AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0));
2714 ASSERT_TRUE(reconcilor);
2715 auto timer0 = std::make_unique<base::MockOneShotTimer>();
2716 base::MockOneShotTimer* timer = timer0.get();
2717 reconcilor->set_timer_for_testing(std::move(timer0));
2718
2719 reconcilor->StartReconcile();
2720 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2721 ASSERT_TRUE(timer->IsRunning());
2722
2723 // Simulate a timeout
2724 timer->Fire();
2725 EXPECT_EQ(1, spy_delegate->num_reconcile_timeout_calls_);
2726 EXPECT_EQ(0, spy_delegate->num_reconcile_finished_calls_);
2727 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2728 }
2729
2730 // Test that delegate timeout is not called when the delegate does not offer a
2731 // valid timeout.
TEST_F(AccountReconcilorMirrorTest,DelegateTimeoutIsNotCalled)2732 TEST_F(AccountReconcilorMirrorTest, DelegateTimeoutIsNotCalled) {
2733 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2734 signin::SetListAccountsResponseOneAccount(
2735 account_info.email, account_info.gaia, &test_url_loader_factory_);
2736 AccountReconcilor* reconcilor = GetMockReconcilor();
2737 ASSERT_TRUE(reconcilor);
2738 auto timer0 = std::make_unique<base::MockOneShotTimer>();
2739 base::MockOneShotTimer* timer = timer0.get();
2740 reconcilor->set_timer_for_testing(std::move(timer0));
2741
2742 reconcilor->StartReconcile();
2743 EXPECT_TRUE(reconcilor->is_reconcile_started_);
2744 EXPECT_FALSE(timer->IsRunning());
2745 }
2746
TEST_F(AccountReconcilorTest,DelegateTimeoutIsNotCalledIfTimeoutIsNotReached)2747 TEST_F(AccountReconcilorTest, DelegateTimeoutIsNotCalledIfTimeoutIsNotReached) {
2748 AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
2749 signin::SetListAccountsResponseOneAccount(
2750 account_info.email, account_info.gaia, &test_url_loader_factory_);
2751 auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>();
2752 SpyReconcilorDelegate* spy_delegate = spy_delegate0.get();
2753 AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0));
2754 ASSERT_TRUE(reconcilor);
2755 auto timer0 = std::make_unique<base::MockOneShotTimer>();
2756 base::MockOneShotTimer* timer = timer0.get();
2757 reconcilor->set_timer_for_testing(std::move(timer0));
2758
2759 reconcilor->StartReconcile();
2760 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2761 ASSERT_TRUE(timer->IsRunning());
2762
2763 base::RunLoop().RunUntilIdle();
2764 EXPECT_FALSE(timer->IsRunning());
2765 EXPECT_EQ(0, spy_delegate->num_reconcile_timeout_calls_);
2766 EXPECT_EQ(1, spy_delegate->num_reconcile_finished_calls_);
2767 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2768 }
2769
TEST_F(AccountReconcilorTest,ScopedSyncedDataDeletionDestructionOrder)2770 TEST_F(AccountReconcilorTest, ScopedSyncedDataDeletionDestructionOrder) {
2771 AccountReconcilor* reconcilor = GetMockReconcilor();
2772 std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> data_deletion =
2773 reconcilor->GetScopedSyncDataDeletion();
2774 DeleteReconcilor();
2775 // data_deletion is destroyed after the reconcilor, this should not crash.
2776 }
2777
TEST_F(AccountReconcilorTest,LockDestructionOrder)2778 TEST_F(AccountReconcilorTest, LockDestructionOrder) {
2779 AccountReconcilor* reconcilor = GetMockReconcilor();
2780 AccountReconcilor::Lock lock(reconcilor);
2781 DeleteReconcilor();
2782 // |lock| is destroyed after the reconcilor, this should not crash.
2783 }
2784
2785 // Checks that multilogin with empty list of accounts in UPDATE mode is changed
2786 // into a Logout call.
TEST_F(AccountReconcilorTest,MultiloginLogout)2787 TEST_F(AccountReconcilorTest, MultiloginLogout) {
2788 // Delegate implementation always returning UPDATE mode with no accounts.
2789 class MultiloginLogoutDelegate : public signin::AccountReconcilorDelegate {
2790 bool IsReconcileEnabled() const override { return true; }
2791 bool IsAccountConsistencyEnforced() const override { return true; }
2792 std::vector<CoreAccountId> GetChromeAccountsForReconcile(
2793 const std::vector<CoreAccountId>& chrome_accounts,
2794 const CoreAccountId& primary_account,
2795 const std::vector<gaia::ListedAccount>& gaia_accounts,
2796 const gaia::MultiloginMode mode) const override {
2797 return {};
2798 }
2799 gaia::MultiloginMode CalculateModeForReconcile(
2800 const std::vector<gaia::ListedAccount>& gaia_accounts,
2801 const CoreAccountId& primary_account,
2802 bool first_execution,
2803 bool primary_has_error) const override {
2804 return gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER;
2805 }
2806 };
2807
2808 MockAccountReconcilor* reconcilor =
2809 GetMockReconcilor(std::make_unique<MultiloginLogoutDelegate>());
2810 signin::SetListAccountsResponseOneAccount("user@gmail.com", "123456",
2811 &test_url_loader_factory_);
2812
2813 // Logout call to Gaia.
2814 EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction());
2815 // No multilogin call.
2816 EXPECT_CALL(*reconcilor, PerformSetCookiesAction(testing::_)).Times(0);
2817
2818 reconcilor->StartReconcile();
2819 ASSERT_TRUE(reconcilor->is_reconcile_started_);
2820 base::RunLoop().RunUntilIdle();
2821 EXPECT_FALSE(reconcilor->is_reconcile_started_);
2822 ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
2823 }
2824