1 // Copyright 2014 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 "components/signin/core/browser/signin_error_controller.h"
6 
7 #include <stddef.h>
8 
9 #include <functional>
10 #include <memory>
11 
12 #include "base/scoped_observer.h"
13 #include "base/stl_util.h"
14 #include "base/test/task_environment.h"
15 #include "components/signin/public/identity_manager/identity_test_environment.h"
16 #include "components/signin/public/identity_manager/primary_account_mutator.h"
17 #include "testing/gmock/include/gmock/gmock.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 
20 namespace {
21 
22 constexpr char kPrimaryAccountEmail[] = "primary@example.com";
23 constexpr char kTestEmail[] = "me@test.com";
24 constexpr char kOtherTestEmail[] = "you@test.com";
25 
26 class MockSigninErrorControllerObserver
27     : public SigninErrorController::Observer {
28  public:
29   MOCK_METHOD0(OnErrorChanged, void());
30 };
31 
32 }  // namespace
33 
TEST(SigninErrorControllerTest,SingleAccount)34 TEST(SigninErrorControllerTest, SingleAccount) {
35   MockSigninErrorControllerObserver observer;
36   EXPECT_CALL(observer, OnErrorChanged()).Times(0);
37 
38   base::test::TaskEnvironment task_environment;
39   signin::IdentityTestEnvironment identity_test_env;
40   SigninErrorController error_controller(
41       SigninErrorController::AccountMode::ANY_ACCOUNT,
42       identity_test_env.identity_manager());
43   ScopedObserver<SigninErrorController, SigninErrorController::Observer>
44       scoped_observer(&observer);
45   scoped_observer.Add(&error_controller);
46   ASSERT_FALSE(error_controller.HasError());
47   ::testing::Mock::VerifyAndClearExpectations(&observer);
48 
49   // IdentityTestEnvironment does not call OnEndBatchChanges() as part of
50   // MakeAccountAvailable(), and thus the signin error controller is not
51   // updated.
52   EXPECT_CALL(observer, OnErrorChanged()).Times(0);
53 
54   CoreAccountId test_account_id =
55       identity_test_env.MakeAccountAvailable(kTestEmail).account_id;
56   ::testing::Mock::VerifyAndClearExpectations(&observer);
57 
58   GoogleServiceAuthError error1 =
59       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
60   EXPECT_CALL(observer, OnErrorChanged()).Times(1);
61   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
62       test_account_id, error1);
63   EXPECT_TRUE(error_controller.HasError());
64   EXPECT_EQ(error1, error_controller.auth_error());
65   ::testing::Mock::VerifyAndClearExpectations(&observer);
66 
67   GoogleServiceAuthError error2 =
68       GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
69   EXPECT_CALL(observer, OnErrorChanged()).Times(1);
70   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
71       test_account_id, error2);
72   EXPECT_TRUE(error_controller.HasError());
73   EXPECT_EQ(error2, error_controller.auth_error());
74   ::testing::Mock::VerifyAndClearExpectations(&observer);
75 
76   EXPECT_CALL(observer, OnErrorChanged()).Times(1);
77   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
78       test_account_id, GoogleServiceAuthError::AuthErrorNone());
79   EXPECT_FALSE(error_controller.HasError());
80   EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
81             error_controller.auth_error());
82   ::testing::Mock::VerifyAndClearExpectations(&observer);
83 }
84 
TEST(SigninErrorControllerTest,AccountTransitionAnyAccount)85 TEST(SigninErrorControllerTest, AccountTransitionAnyAccount) {
86   base::test::TaskEnvironment task_environment;
87   signin::IdentityTestEnvironment identity_test_env;
88 
89   CoreAccountId test_account_id =
90       identity_test_env.MakeAccountAvailable(kTestEmail).account_id;
91   CoreAccountId other_test_account_id =
92       identity_test_env.MakeAccountAvailable(kOtherTestEmail).account_id;
93   SigninErrorController error_controller(
94       SigninErrorController::AccountMode::ANY_ACCOUNT,
95       identity_test_env.identity_manager());
96   ASSERT_FALSE(error_controller.HasError());
97 
98   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
99       test_account_id,
100       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
101   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
102       other_test_account_id,
103       GoogleServiceAuthError(GoogleServiceAuthError::NONE));
104   ASSERT_TRUE(error_controller.HasError());
105   ASSERT_EQ(test_account_id, error_controller.error_account_id());
106 
107   // Now resolve the auth errors - the menu item should go away.
108   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
109       test_account_id, GoogleServiceAuthError::AuthErrorNone());
110   ASSERT_FALSE(error_controller.HasError());
111 }
112 
113 // Verifies errors are reported in mode ANY_ACCOUNT even if the primary account
114 // has not consented to the browser sync feature.
TEST(SigninErrorControllerTest,UnconsentedPrimaryAccount)115 TEST(SigninErrorControllerTest, UnconsentedPrimaryAccount) {
116   base::test::TaskEnvironment task_environment;
117   signin::IdentityTestEnvironment identity_test_env;
118 
119   CoreAccountId test_account_id =
120       identity_test_env.MakeUnconsentedPrimaryAccountAvailable(kTestEmail)
121           .account_id;
122   SigninErrorController error_controller(
123       SigninErrorController::AccountMode::ANY_ACCOUNT,
124       identity_test_env.identity_manager());
125   ASSERT_FALSE(error_controller.HasError());
126 
127   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
128       test_account_id,
129       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
130   EXPECT_TRUE(error_controller.HasError());
131   EXPECT_EQ(test_account_id, error_controller.error_account_id());
132 
133   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
134       test_account_id, GoogleServiceAuthError::AuthErrorNone());
135   EXPECT_FALSE(error_controller.HasError());
136 }
137 
138 // This test exercises behavior on signin/signout, which is not relevant on
139 // ChromeOS.
140 #if !defined(OS_CHROMEOS)
TEST(SigninErrorControllerTest,AccountTransitionPrimaryAccount)141 TEST(SigninErrorControllerTest, AccountTransitionPrimaryAccount) {
142   base::test::TaskEnvironment task_environment;
143   signin::IdentityTestEnvironment identity_test_env;
144   signin::PrimaryAccountMutator* primary_account_mutator =
145       identity_test_env.identity_manager()->GetPrimaryAccountMutator();
146 
147   CoreAccountId test_account_id =
148       identity_test_env.MakeAccountAvailable(kTestEmail).account_id;
149   CoreAccountId other_test_account_id =
150       identity_test_env.MakeAccountAvailable(kOtherTestEmail).account_id;
151   SigninErrorController error_controller(
152       SigninErrorController::AccountMode::PRIMARY_ACCOUNT,
153       identity_test_env.identity_manager());
154   ASSERT_FALSE(error_controller.HasError());
155 
156   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
157       test_account_id,
158       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
159   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
160       other_test_account_id,
161       GoogleServiceAuthError(GoogleServiceAuthError::NONE));
162   ASSERT_FALSE(error_controller.HasError());  // No primary account.
163 
164   // Set the primary account.
165   identity_test_env.SetPrimaryAccount(kOtherTestEmail);
166 
167   ASSERT_FALSE(error_controller.HasError());  // Error is on secondary.
168 
169   // Change the primary account to the account with an error and check that the
170   // error controller updates its error status accordingly.
171   primary_account_mutator->ClearPrimaryAccount(
172       signin::PrimaryAccountMutator::ClearAccountsAction::kKeepAll,
173       signin_metrics::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST,
174       signin_metrics::SignoutDelete::IGNORE_METRIC);
175   identity_test_env.SetPrimaryAccount(kTestEmail);
176   ASSERT_TRUE(error_controller.HasError());
177   ASSERT_EQ(test_account_id, error_controller.error_account_id());
178 
179   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
180       other_test_account_id,
181       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
182   ASSERT_TRUE(error_controller.HasError());
183   ASSERT_EQ(test_account_id, error_controller.error_account_id());
184 
185   // Change the primary account again and check that the error controller
186   // updates its error status accordingly.
187   primary_account_mutator->ClearPrimaryAccount(
188       signin::PrimaryAccountMutator::ClearAccountsAction::kKeepAll,
189       signin_metrics::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST,
190       signin_metrics::SignoutDelete::IGNORE_METRIC);
191   identity_test_env.SetPrimaryAccount(kOtherTestEmail);
192   ASSERT_TRUE(error_controller.HasError());
193   ASSERT_EQ(other_test_account_id, error_controller.error_account_id());
194 
195   // Sign out and check that that the error controller updates its error status
196   // accordingly.
197   primary_account_mutator->ClearPrimaryAccount(
198       signin::PrimaryAccountMutator::ClearAccountsAction::kKeepAll,
199       signin_metrics::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST,
200       signin_metrics::SignoutDelete::IGNORE_METRIC);
201   ASSERT_FALSE(error_controller.HasError());
202 }
203 #endif
204 
205 // Verify that SigninErrorController handles errors properly.
TEST(SigninErrorControllerTest,AuthStatusEnumerateAllErrors)206 TEST(SigninErrorControllerTest, AuthStatusEnumerateAllErrors) {
207   base::test::TaskEnvironment task_environment;
208   signin::IdentityTestEnvironment identity_test_env;
209 
210   CoreAccountId test_account_id =
211       identity_test_env.MakeAccountAvailable(kTestEmail).account_id;
212   SigninErrorController error_controller(
213       SigninErrorController::AccountMode::ANY_ACCOUNT,
214       identity_test_env.identity_manager());
215 
216   GoogleServiceAuthError::State table[] = {
217       GoogleServiceAuthError::NONE,
218       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
219       GoogleServiceAuthError::USER_NOT_SIGNED_UP,
220       GoogleServiceAuthError::CONNECTION_FAILED,
221       GoogleServiceAuthError::SERVICE_UNAVAILABLE,
222       GoogleServiceAuthError::REQUEST_CANCELED,
223       GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE,
224       GoogleServiceAuthError::SERVICE_ERROR};
225   static_assert(
226       base::size(table) == GoogleServiceAuthError::NUM_STATES -
227                                GoogleServiceAuthError::kDeprecatedStateCount,
228       "table array does not match the number of auth error types");
229 
230   for (GoogleServiceAuthError::State state : table) {
231     GoogleServiceAuthError error(state);
232 
233     if (error.IsTransientError())
234       continue;  // Only persistent errors or non-errors are reported.
235 
236     identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
237         test_account_id, error);
238 
239     EXPECT_EQ(error_controller.HasError(), error.IsPersistentError());
240 
241     if (error.IsPersistentError()) {
242       EXPECT_EQ(state, error_controller.auth_error().state());
243       EXPECT_EQ(test_account_id, error_controller.error_account_id());
244     } else {
245       EXPECT_EQ(GoogleServiceAuthError::NONE,
246                 error_controller.auth_error().state());
247       EXPECT_EQ(CoreAccountId(), error_controller.error_account_id());
248     }
249   }
250 }
251 
252 // Verify that existing error is not replaced by new error.
TEST(SigninErrorControllerTest,AuthStatusChange)253 TEST(SigninErrorControllerTest, AuthStatusChange) {
254   base::test::TaskEnvironment task_environment;
255   signin::IdentityTestEnvironment identity_test_env;
256 
257   CoreAccountId test_account_id =
258       identity_test_env.MakeAccountAvailable(kTestEmail).account_id;
259   CoreAccountId other_test_account_id =
260       identity_test_env.MakeAccountAvailable(kOtherTestEmail).account_id;
261   SigninErrorController error_controller(
262       SigninErrorController::AccountMode::ANY_ACCOUNT,
263       identity_test_env.identity_manager());
264   ASSERT_FALSE(error_controller.HasError());
265 
266   // Set an error for other_test_account_id.
267   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
268       test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::NONE));
269   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
270       other_test_account_id,
271       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
272   ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
273             error_controller.auth_error().state());
274   ASSERT_EQ(other_test_account_id, error_controller.error_account_id());
275 
276   // Change the error for other_test_account_id.
277   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
278       other_test_account_id,
279       GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));
280   ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR,
281             error_controller.auth_error().state());
282   ASSERT_EQ(other_test_account_id, error_controller.error_account_id());
283 
284   // Set the error for test_account_id -- nothing should change.
285   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
286       test_account_id,
287       GoogleServiceAuthError(
288           GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE));
289   ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR,
290             error_controller.auth_error().state());
291   ASSERT_EQ(other_test_account_id, error_controller.error_account_id());
292 
293   // Clear the error for other_test_account_id, so the test_account_id's error
294   // is used.
295   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
296       other_test_account_id,
297       GoogleServiceAuthError(GoogleServiceAuthError::NONE));
298   ASSERT_EQ(GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE,
299             error_controller.auth_error().state());
300   ASSERT_EQ(test_account_id, error_controller.error_account_id());
301 
302   // Clear the remaining error.
303   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
304       test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::NONE));
305   ASSERT_FALSE(error_controller.HasError());
306 }
307 
TEST(SigninErrorControllerTest,PrimaryAccountErrorsArePreferredToSecondaryAccountErrors)308 TEST(SigninErrorControllerTest,
309      PrimaryAccountErrorsArePreferredToSecondaryAccountErrors) {
310   base::test::TaskEnvironment task_environment;
311   signin::IdentityTestEnvironment identity_test_env;
312 
313   AccountInfo primary_account_info =
314       identity_test_env.MakePrimaryAccountAvailable(kPrimaryAccountEmail);
315   CoreAccountId secondary_account_id =
316       identity_test_env.MakeAccountAvailable(kTestEmail).account_id;
317   SigninErrorController error_controller(
318       SigninErrorController::AccountMode::ANY_ACCOUNT,
319       identity_test_env.identity_manager());
320   ASSERT_FALSE(error_controller.HasError());
321 
322   // Set an error for the Secondary Account.
323   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
324       secondary_account_id,
325       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
326   ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
327             error_controller.auth_error().state());
328   ASSERT_EQ(secondary_account_id, error_controller.error_account_id());
329 
330   // Set an error for the Primary Account. This should override the previous
331   // error.
332   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
333       primary_account_info.account_id,
334       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
335   ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
336             error_controller.auth_error().state());
337   ASSERT_EQ(primary_account_info.account_id,
338             error_controller.error_account_id());
339 
340   // Clear the Primary Account error. This should cause the Secondary Account
341   // error to be returned again.
342   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
343       primary_account_info.account_id,
344       GoogleServiceAuthError(GoogleServiceAuthError::NONE));
345   ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
346             error_controller.auth_error().state());
347   ASSERT_EQ(secondary_account_id, error_controller.error_account_id());
348 
349   // Clear the Secondary Account error too. All errors should be gone now.
350   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
351       secondary_account_id,
352       GoogleServiceAuthError(GoogleServiceAuthError::NONE));
353   ASSERT_FALSE(error_controller.HasError());
354 }
355 
TEST(SigninErrorControllerTest,PrimaryAccountErrorsAreSticky)356 TEST(SigninErrorControllerTest, PrimaryAccountErrorsAreSticky) {
357   base::test::TaskEnvironment task_environment;
358   signin::IdentityTestEnvironment identity_test_env;
359 
360   AccountInfo primary_account_info =
361       identity_test_env.MakePrimaryAccountAvailable(kPrimaryAccountEmail);
362   CoreAccountId secondary_account_id =
363       identity_test_env.MakeAccountAvailable(kTestEmail).account_id;
364   SigninErrorController error_controller(
365       SigninErrorController::AccountMode::ANY_ACCOUNT,
366       identity_test_env.identity_manager());
367   ASSERT_FALSE(error_controller.HasError());
368 
369   // Set an error for the Primary Account.
370   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
371       primary_account_info.account_id,
372       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
373   ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
374             error_controller.auth_error().state());
375   ASSERT_EQ(primary_account_info.account_id,
376             error_controller.error_account_id());
377 
378   // Set an error for the Secondary Account. The Primary Account error should
379   // stick.
380   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
381       secondary_account_id,
382       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
383   ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
384             error_controller.auth_error().state());
385   ASSERT_EQ(primary_account_info.account_id,
386             error_controller.error_account_id());
387 
388   // Clear the Primary Account error. This should cause the Secondary Account
389   // error to be returned again.
390   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
391       primary_account_info.account_id,
392       GoogleServiceAuthError(GoogleServiceAuthError::NONE));
393   ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
394             error_controller.auth_error().state());
395   ASSERT_EQ(secondary_account_id, error_controller.error_account_id());
396 
397   // Clear the Secondary Account error too. All errors should be gone now.
398   identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount(
399       secondary_account_id,
400       GoogleServiceAuthError(GoogleServiceAuthError::NONE));
401   ASSERT_FALSE(error_controller.HasError());
402 }
403