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