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 <functional>
8
9#include "base/memory/scoped_ptr.h"
10#include "components/signin/core/browser/fake_auth_status_provider.h"
11#include "testing/gtest/include/gtest/gtest.h"
12
13static const char kTestAccountId[] = "testuser@test.com";
14static const char kTestUsername[] = "testuser@test.com";
15static const char kOtherTestAccountId[] = "otheruser@test.com";
16static const char kOtherTestUsername[] = "otheruser@test.com";
17
18class SigninErrorControllerTest : public testing::Test {
19 public:
20  virtual void SetUp() OVERRIDE {
21    error_controller_.reset(new SigninErrorController());
22  }
23
24  scoped_ptr<SigninErrorController> error_controller_;
25};
26
27TEST_F(SigninErrorControllerTest, NoErrorAuthStatusProviders) {
28  scoped_ptr<FakeAuthStatusProvider> provider;
29
30  // No providers.
31  ASSERT_FALSE(error_controller_->HasError());
32
33  // Add a provider.
34  provider.reset(new FakeAuthStatusProvider(error_controller_.get()));
35  ASSERT_FALSE(error_controller_->HasError());
36
37  // Remove the provider.
38  provider.reset();
39  ASSERT_FALSE(error_controller_->HasError());
40}
41
42TEST_F(SigninErrorControllerTest, ErrorAuthStatusProvider) {
43  scoped_ptr<FakeAuthStatusProvider> provider;
44  scoped_ptr<FakeAuthStatusProvider> error_provider;
45
46  provider.reset(new FakeAuthStatusProvider(error_controller_.get()));
47  ASSERT_FALSE(error_controller_->HasError());
48
49  error_provider.reset(new FakeAuthStatusProvider(error_controller_.get()));
50  error_provider->SetAuthError(
51      kTestAccountId,
52      kTestUsername,
53      GoogleServiceAuthError(
54          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
55  ASSERT_TRUE(error_controller_->HasError());
56
57  error_provider.reset();
58  ASSERT_FALSE(error_controller_->HasError());
59
60  provider.reset();
61  // All providers should be removed now.
62  ASSERT_FALSE(error_controller_->HasError());
63}
64
65TEST_F(SigninErrorControllerTest, AuthStatusProviderErrorTransition) {
66  scoped_ptr<FakeAuthStatusProvider> provider0(
67      new FakeAuthStatusProvider(error_controller_.get()));
68  scoped_ptr<FakeAuthStatusProvider> provider1(
69      new FakeAuthStatusProvider(error_controller_.get()));
70
71  ASSERT_FALSE(error_controller_->HasError());
72  provider0->SetAuthError(
73      kTestAccountId,
74      kTestUsername,
75      GoogleServiceAuthError(
76          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
77  ASSERT_TRUE(error_controller_->HasError());
78  provider1->SetAuthError(
79      kTestAccountId,
80      kTestUsername,
81      GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED));
82  ASSERT_TRUE(error_controller_->HasError());
83
84  // Now resolve the auth errors - the menu item should go away.
85  provider0->SetAuthError(kTestAccountId,
86                          kTestUsername,
87                         GoogleServiceAuthError::AuthErrorNone());
88  ASSERT_TRUE(error_controller_->HasError());
89  provider1->SetAuthError(kTestAccountId,
90                          kTestUsername,
91                          GoogleServiceAuthError::AuthErrorNone());
92  ASSERT_FALSE(error_controller_->HasError());
93
94  provider0.reset();
95  provider1.reset();
96  ASSERT_FALSE(error_controller_->HasError());
97}
98
99TEST_F(SigninErrorControllerTest, AuthStatusProviderAccountTransition) {
100  scoped_ptr<FakeAuthStatusProvider> provider0(
101      new FakeAuthStatusProvider(error_controller_.get()));
102  scoped_ptr<FakeAuthStatusProvider> provider1(
103      new FakeAuthStatusProvider(error_controller_.get()));
104
105  ASSERT_FALSE(error_controller_->HasError());
106
107  provider0->SetAuthError(
108      kTestAccountId,
109      kTestUsername,
110      GoogleServiceAuthError(
111          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
112  provider1->SetAuthError(
113      kOtherTestAccountId,
114      kOtherTestUsername,
115      GoogleServiceAuthError(GoogleServiceAuthError::NONE));
116  ASSERT_TRUE(error_controller_->HasError());
117  ASSERT_STREQ(kTestAccountId,
118               error_controller_->error_account_id().c_str());
119
120  // Swap providers reporting errors.
121  provider1->set_error_without_status_change(
122      GoogleServiceAuthError(
123          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
124  provider0->set_error_without_status_change(
125      GoogleServiceAuthError(GoogleServiceAuthError::NONE));
126  error_controller_->AuthStatusChanged();
127  ASSERT_TRUE(error_controller_->HasError());
128  ASSERT_STREQ(kOtherTestAccountId,
129               error_controller_->error_account_id().c_str());
130
131  // Now resolve the auth errors - the menu item should go away.
132  provider0->set_error_without_status_change(
133      GoogleServiceAuthError::AuthErrorNone());
134  provider1->set_error_without_status_change(
135      GoogleServiceAuthError::AuthErrorNone());
136  error_controller_->AuthStatusChanged();
137  ASSERT_FALSE(error_controller_->HasError());
138
139  provider0.reset();
140  provider1.reset();
141  ASSERT_FALSE(error_controller_->HasError());
142}
143
144// Verify that SigninErrorController handles errors properly.
145TEST_F(SigninErrorControllerTest, AuthStatusEnumerateAllErrors) {
146  typedef struct {
147    GoogleServiceAuthError::State error_state;
148    bool is_error;
149  } ErrorTableEntry;
150
151  ErrorTableEntry table[] = {
152    { GoogleServiceAuthError::NONE, false },
153    { GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, true },
154    { GoogleServiceAuthError::USER_NOT_SIGNED_UP, true },
155    { GoogleServiceAuthError::CONNECTION_FAILED, false },
156    { GoogleServiceAuthError::CAPTCHA_REQUIRED, true },
157    { GoogleServiceAuthError::ACCOUNT_DELETED, true },
158    { GoogleServiceAuthError::ACCOUNT_DISABLED, true },
159    { GoogleServiceAuthError::SERVICE_UNAVAILABLE, true },
160    { GoogleServiceAuthError::TWO_FACTOR, true },
161    { GoogleServiceAuthError::REQUEST_CANCELED, true },
162    { GoogleServiceAuthError::HOSTED_NOT_ALLOWED, true },
163    { GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE, true },
164    { GoogleServiceAuthError::SERVICE_ERROR, true },
165  };
166  COMPILE_ASSERT(ARRAYSIZE_UNSAFE(table) == GoogleServiceAuthError::NUM_STATES,
167      kTable_size_does_not_match_number_of_auth_error_types);
168
169  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(table); ++i) {
170    FakeAuthStatusProvider provider(error_controller_.get());
171    provider.SetAuthError(kTestAccountId,
172                          kTestUsername,
173                          GoogleServiceAuthError(table[i].error_state));
174
175    EXPECT_EQ(error_controller_->HasError(), table[i].is_error);
176
177    if (table[i].is_error) {
178      EXPECT_EQ(table[i].error_state,
179                error_controller_->auth_error().state());
180      EXPECT_STREQ(kTestAccountId,
181                   error_controller_->error_account_id().c_str());
182    } else {
183      EXPECT_EQ(GoogleServiceAuthError::NONE,
184                error_controller_->auth_error().state());
185      EXPECT_STREQ("",
186                   error_controller_->error_account_id().c_str());
187    }
188  }
189}
190
191// Verify that existing error is not replaced by new error.
192TEST_F(SigninErrorControllerTest, AuthStatusChange) {
193  scoped_ptr<FakeAuthStatusProvider> fake_provider0(
194      new FakeAuthStatusProvider(error_controller_.get()));
195  scoped_ptr<FakeAuthStatusProvider> fake_provider1(
196      new FakeAuthStatusProvider(error_controller_.get()));
197
198  // If there are multiple providers in the provider set...
199  //
200  // | provider0 |       provider1          | ...
201  // |   NONE    | INVALID_GAIA_CREDENTIALS | ...
202  //
203  // SigninErrorController picks the first error found when iterating through
204  // the set. But if another error crops up...
205  //
206  // |     provider0       |       provider1          | ...
207  // | SERVICE_UNAVAILABLE | INVALID_GAIA_CREDENTIALS | ...
208  //
209  // we want the controller to still use the original error.
210
211  // The provider pointers are stored in a set, which is sorted by std::less.
212  std::less<SigninErrorController::AuthStatusProvider*> compare;
213  FakeAuthStatusProvider* provider0 =
214      compare(fake_provider0.get(), fake_provider1.get()) ?
215          fake_provider0.get() : fake_provider1.get();
216  FakeAuthStatusProvider* provider1 =
217      provider0 == fake_provider0.get() ?
218          fake_provider1.get() : fake_provider0.get();
219
220  provider0->SetAuthError(
221      kTestAccountId,
222      kTestUsername,
223      GoogleServiceAuthError(
224          GoogleServiceAuthError::NONE));
225  provider1->SetAuthError(
226      kOtherTestAccountId,
227      kOtherTestUsername,
228      GoogleServiceAuthError(
229          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
230  ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
231            error_controller_->auth_error().state());
232  ASSERT_STREQ(kOtherTestAccountId,
233               error_controller_->error_account_id().c_str());
234
235  // Change the 1st provider's error.
236  provider1->SetAuthError(
237      kOtherTestAccountId,
238      kOtherTestUsername,
239      GoogleServiceAuthError(
240          GoogleServiceAuthError::SERVICE_UNAVAILABLE));
241  ASSERT_EQ(GoogleServiceAuthError::SERVICE_UNAVAILABLE,
242            error_controller_->auth_error().state());
243  ASSERT_STREQ(kOtherTestAccountId,
244               error_controller_->error_account_id().c_str());
245
246  // Set the 0th provider's error -- nothing should change.
247  provider0->SetAuthError(
248      kTestAccountId,
249      kTestUsername,
250      GoogleServiceAuthError(
251          GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE));
252  ASSERT_EQ(GoogleServiceAuthError::SERVICE_UNAVAILABLE,
253            error_controller_->auth_error().state());
254  ASSERT_STREQ(kOtherTestAccountId,
255               error_controller_->error_account_id().c_str());
256
257  // Clear the 1st provider's error, so the 0th provider's error is used.
258  provider1->SetAuthError(
259      kOtherTestAccountId,
260      kOtherTestUsername,
261      GoogleServiceAuthError(
262          GoogleServiceAuthError::NONE));
263  ASSERT_EQ(GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE,
264            error_controller_->auth_error().state());
265  ASSERT_STREQ(kTestAccountId,
266               error_controller_->error_account_id().c_str());
267
268  fake_provider0.reset();
269  fake_provider1.reset();
270  ASSERT_FALSE(error_controller_->HasError());
271}
272