1// Copyright (c) 2012 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 <set>
6#include "base/basictypes.h"
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/browser/profiles/profile.h"
9#include "chrome/browser/signin/fake_auth_status_provider.h"
10#include "chrome/browser/signin/fake_signin_manager.h"
11#include "chrome/browser/signin/signin_manager.h"
12#include "chrome/browser/sync/profile_sync_service_mock.h"
13#include "chrome/browser/sync/sync_ui_util.h"
14#include "content/public/test/test_browser_thread.h"
15#include "content/public/test/test_browser_thread_bundle.h"
16#include "grit/generated_resources.h"
17#include "testing/gmock/include/gmock/gmock-actions.h"
18#include "testing/gmock/include/gmock/gmock.h"
19#include "testing/gtest/include/gtest/gtest.h"
20#include "ui/base/l10n/l10n_util.h"
21
22using ::testing::AtMost;
23using ::testing::NiceMock;
24using ::testing::Return;
25using ::testing::ReturnRef;
26using ::testing::SetArgPointee;
27using ::testing::_;
28using content::BrowserThread;
29
30// A number of distinct states of the ProfileSyncService can be generated for
31// tests.
32enum DistinctState {
33  STATUS_CASE_SETUP_IN_PROGRESS,
34  STATUS_CASE_SETUP_ERROR,
35  STATUS_CASE_AUTHENTICATING,
36  STATUS_CASE_AUTH_ERROR,
37  STATUS_CASE_PROTOCOL_ERROR,
38  STATUS_CASE_PASSPHRASE_ERROR,
39  STATUS_CASE_SYNCED,
40  STATUS_CASE_SYNC_DISABLED_BY_POLICY,
41  NUMBER_OF_STATUS_CASES
42};
43
44namespace {
45
46// Utility function to test that GetStatusLabelsForSyncGlobalError returns
47// the correct results for the given states.
48void VerifySyncGlobalErrorResult(NiceMock<ProfileSyncServiceMock>* service,
49                                 const SigninManagerBase& signin,
50                                 GoogleServiceAuthError::State error_state,
51                                 bool is_signed_in,
52                                 bool is_error) {
53  EXPECT_CALL(*service, HasSyncSetupCompleted())
54              .WillRepeatedly(Return(is_signed_in));
55
56  GoogleServiceAuthError auth_error(error_state);
57  EXPECT_CALL(*service, GetAuthError()).WillRepeatedly(ReturnRef(auth_error));
58
59  string16 label1, label2, label3;
60  sync_ui_util::GetStatusLabelsForSyncGlobalError(
61      service, signin, &label1, &label2, &label3);
62  EXPECT_EQ(label1.empty(), !is_error);
63  EXPECT_EQ(label2.empty(), !is_error);
64  EXPECT_EQ(label3.empty(), !is_error);
65}
66
67} // namespace
68
69
70class SyncUIUtilTest : public testing::Test {
71 private:
72  content::TestBrowserThreadBundle thread_bundle_;
73};
74
75// Test that GetStatusLabelsForSyncGlobalError returns an error if a
76// passphrase is required.
77TEST_F(SyncUIUtilTest, PassphraseGlobalError) {
78  scoped_ptr<Profile> profile(
79      ProfileSyncServiceMock::MakeSignedInTestingProfile());
80  NiceMock<ProfileSyncServiceMock> service(profile.get());
81  FakeSigninManagerBase signin;
82  browser_sync::SyncBackendHost::Status status;
83  EXPECT_CALL(service, QueryDetailedSyncStatus(_))
84              .WillRepeatedly(Return(false));
85  EXPECT_CALL(service, IsPassphraseRequired())
86              .WillRepeatedly(Return(true));
87  EXPECT_CALL(service, IsPassphraseRequiredForDecryption())
88              .WillRepeatedly(Return(true));
89  VerifySyncGlobalErrorResult(
90      &service, signin, GoogleServiceAuthError::NONE, true, true);
91}
92
93// Test that GetStatusLabelsForSyncGlobalError returns an error if a
94// passphrase is required and not for auth errors.
95TEST_F(SyncUIUtilTest, AuthAndPassphraseGlobalError) {
96  scoped_ptr<Profile> profile(
97      ProfileSyncServiceMock::MakeSignedInTestingProfile());
98  NiceMock<ProfileSyncServiceMock> service(profile.get());
99  FakeSigninManagerBase signin;
100  browser_sync::SyncBackendHost::Status status;
101  EXPECT_CALL(service, QueryDetailedSyncStatus(_))
102              .WillRepeatedly(Return(false));
103
104  EXPECT_CALL(service, IsPassphraseRequired())
105              .WillRepeatedly(Return(true));
106  EXPECT_CALL(service, IsPassphraseRequiredForDecryption())
107              .WillRepeatedly(Return(true));
108  EXPECT_CALL(service, HasSyncSetupCompleted())
109              .WillRepeatedly(Return(true));
110
111  GoogleServiceAuthError auth_error(
112      GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
113  EXPECT_CALL(service, GetAuthError()).WillRepeatedly(ReturnRef(auth_error));
114  string16 menu_label, label2, label3;
115  sync_ui_util::GetStatusLabelsForSyncGlobalError(
116      &service, signin, &menu_label, &label2, &label3);
117  // Make sure we are still displaying the passphrase error badge (don't show
118  // auth errors through SyncUIUtil).
119  EXPECT_EQ(menu_label, l10n_util::GetStringUTF16(
120      IDS_SYNC_PASSPHRASE_ERROR_WRENCH_MENU_ITEM));
121}
122
123// Test that GetStatusLabelsForSyncGlobalError does not indicate errors for
124// auth errors (these are reported through SigninGlobalError).
125TEST_F(SyncUIUtilTest, AuthStateGlobalError) {
126  scoped_ptr<Profile> profile(
127      ProfileSyncServiceMock::MakeSignedInTestingProfile());
128  NiceMock<ProfileSyncServiceMock> service(profile.get());
129
130  browser_sync::SyncBackendHost::Status status;
131  EXPECT_CALL(service, QueryDetailedSyncStatus(_))
132              .WillRepeatedly(Return(false));
133
134  GoogleServiceAuthError::State table[] = {
135    GoogleServiceAuthError::NONE,
136    GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
137    GoogleServiceAuthError::USER_NOT_SIGNED_UP,
138    GoogleServiceAuthError::CONNECTION_FAILED,
139    GoogleServiceAuthError::CAPTCHA_REQUIRED,
140    GoogleServiceAuthError::ACCOUNT_DELETED,
141    GoogleServiceAuthError::ACCOUNT_DISABLED,
142    GoogleServiceAuthError::SERVICE_UNAVAILABLE,
143    GoogleServiceAuthError::TWO_FACTOR,
144    GoogleServiceAuthError::REQUEST_CANCELED,
145    GoogleServiceAuthError::HOSTED_NOT_ALLOWED
146  };
147
148  FakeSigninManagerBase signin;
149  for (size_t i = 0; i < arraysize(table); ++i) {
150    VerifySyncGlobalErrorResult(&service, signin, table[i], true, false);
151    VerifySyncGlobalErrorResult(&service, signin, table[i], false, false);
152  }
153}
154
155// TODO(tim): This shouldn't be required. r194857 removed the
156// AuthInProgress override from FakeSigninManager, which meant this test started
157// using the "real" SigninManager AuthInProgress logic. Without that override,
158// it's no longer possible to test both chrome os + desktop flows as part of the
159// same test, because AuthInProgress is always false on chrome os. Most of the
160// tests are unaffected, but STATUS_CASE_AUTHENTICATING can't exist in both
161// versions, so it we will require two separate tests, one using SigninManager
162// and one using SigninManagerBase (which require different setup procedures.
163class FakeSigninManagerForSyncUIUtilTest : public FakeSigninManagerBase {
164 public:
165  explicit FakeSigninManagerForSyncUIUtilTest(Profile* profile)
166      : auth_in_progress_(false) {
167    Initialize(profile, NULL);
168  }
169
170  virtual ~FakeSigninManagerForSyncUIUtilTest() {
171  }
172
173  virtual bool AuthInProgress() const OVERRIDE {
174    return auth_in_progress_;
175  }
176
177  void set_auth_in_progress() {
178    auth_in_progress_ = true;
179  }
180
181 private:
182  bool auth_in_progress_;
183};
184
185// Loads a ProfileSyncServiceMock to emulate one of a number of distinct cases
186// in order to perform tests on the generated messages.
187void GetDistinctCase(ProfileSyncServiceMock& service,
188                     FakeSigninManagerForSyncUIUtilTest* signin,
189                     FakeAuthStatusProvider* provider,
190                     int caseNumber) {
191  // Auth Error object is returned by reference in mock and needs to stay in
192  // scope throughout test, so it is owned by calling method. However it is
193  // immutable so can only be allocated in this method.
194  switch (caseNumber) {
195    case STATUS_CASE_SETUP_IN_PROGRESS: {
196      EXPECT_CALL(service, HasSyncSetupCompleted())
197                  .WillRepeatedly(Return(false));
198      EXPECT_CALL(service, FirstSetupInProgress())
199                  .WillRepeatedly(Return(true));
200      browser_sync::SyncBackendHost::Status status;
201      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
202                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
203                                  Return(false)));
204      return;
205    }
206   case STATUS_CASE_SETUP_ERROR: {
207      EXPECT_CALL(service, HasSyncSetupCompleted())
208                  .WillRepeatedly(Return(false));
209      EXPECT_CALL(service, FirstSetupInProgress())
210                  .WillRepeatedly(Return(false));
211      EXPECT_CALL(service, HasUnrecoverableError())
212                  .WillRepeatedly(Return(true));
213      browser_sync::SyncBackendHost::Status status;
214      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
215                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
216                                  Return(false)));
217      return;
218    }
219    case STATUS_CASE_AUTHENTICATING: {
220      EXPECT_CALL(service, HasSyncSetupCompleted())
221                  .WillRepeatedly(Return(true));
222      EXPECT_CALL(service, sync_initialized()).WillRepeatedly(Return(true));
223      EXPECT_CALL(service, IsPassphraseRequired())
224                  .WillRepeatedly(Return(false));
225      browser_sync::SyncBackendHost::Status status;
226      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
227                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
228                                  Return(false)));
229      EXPECT_CALL(service, HasUnrecoverableError())
230                  .WillRepeatedly(Return(false));
231      signin->set_auth_in_progress();
232      return;
233    }
234    case STATUS_CASE_AUTH_ERROR: {
235      EXPECT_CALL(service, HasSyncSetupCompleted())
236                  .WillRepeatedly(Return(true));
237      EXPECT_CALL(service, sync_initialized()).WillRepeatedly(Return(true));
238      EXPECT_CALL(service, IsPassphraseRequired())
239                  .WillRepeatedly(Return(false));
240      browser_sync::SyncBackendHost::Status status;
241      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
242                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
243                                  Return(false)));
244      provider->SetAuthError(GoogleServiceAuthError(
245          GoogleServiceAuthError::SERVICE_UNAVAILABLE));
246      EXPECT_CALL(service, HasUnrecoverableError())
247                  .WillRepeatedly(Return(false));
248      return;
249    }
250    case STATUS_CASE_PROTOCOL_ERROR: {
251      EXPECT_CALL(service, HasSyncSetupCompleted())
252                  .WillRepeatedly(Return(true));
253      EXPECT_CALL(service, sync_initialized()).WillRepeatedly(Return(true));
254      EXPECT_CALL(service, IsPassphraseRequired())
255                  .WillRepeatedly(Return(false));
256      syncer::SyncProtocolError protocolError;
257      protocolError.action = syncer::STOP_AND_RESTART_SYNC;
258      browser_sync::SyncBackendHost::Status status;
259      status.sync_protocol_error = protocolError;
260      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
261                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
262                                  Return(false)));
263      EXPECT_CALL(service, HasUnrecoverableError())
264                  .WillRepeatedly(Return(false));
265      return;
266    }
267    case STATUS_CASE_PASSPHRASE_ERROR: {
268      EXPECT_CALL(service, HasSyncSetupCompleted())
269                  .WillRepeatedly(Return(true));
270      EXPECT_CALL(service, sync_initialized()).WillRepeatedly(Return(true));
271      browser_sync::SyncBackendHost::Status status;
272      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
273                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
274                                  Return(false)));
275      EXPECT_CALL(service, HasUnrecoverableError())
276                  .WillRepeatedly(Return(false));
277      EXPECT_CALL(service, IsPassphraseRequired())
278                  .WillRepeatedly(Return(true));
279      EXPECT_CALL(service, IsPassphraseRequiredForDecryption())
280                  .WillRepeatedly(Return(true));
281      return;
282    }
283    case STATUS_CASE_SYNCED: {
284      EXPECT_CALL(service, HasSyncSetupCompleted())
285              .WillRepeatedly(Return(true));
286      EXPECT_CALL(service, sync_initialized()).WillRepeatedly(Return(true));
287      EXPECT_CALL(service, IsPassphraseRequired())
288                  .WillRepeatedly(Return(false));
289      browser_sync::SyncBackendHost::Status status;
290      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
291                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
292                                  Return(false)));
293      EXPECT_CALL(service, HasUnrecoverableError())
294                  .WillRepeatedly(Return(false));
295      EXPECT_CALL(service, IsPassphraseRequired())
296                  .WillRepeatedly(Return(false));
297      return;
298    }
299    case STATUS_CASE_SYNC_DISABLED_BY_POLICY: {
300      EXPECT_CALL(service, IsManaged()).WillRepeatedly(Return(true));
301      EXPECT_CALL(service, HasSyncSetupCompleted())
302          .WillRepeatedly(Return(false));
303      EXPECT_CALL(service, sync_initialized()).WillRepeatedly(Return(false));
304      EXPECT_CALL(service, IsPassphraseRequired())
305                  .WillRepeatedly(Return(false));
306      browser_sync::SyncBackendHost::Status status;
307      EXPECT_CALL(service, QueryDetailedSyncStatus(_))
308                  .WillRepeatedly(DoAll(SetArgPointee<0>(status),
309                                  Return(false)));
310      EXPECT_CALL(service, HasUnrecoverableError())
311                  .WillRepeatedly(Return(false));
312      return;
313    }
314    default:
315      NOTREACHED();
316  }
317}
318
319// This test ensures that a each distinctive ProfileSyncService statuses
320// will return a unique combination of status and link messages from
321// GetStatusLabels().
322TEST_F(SyncUIUtilTest, DistinctCasesReportUniqueMessageSets) {
323  std::set<string16> messages;
324  for (int idx = 0; idx != NUMBER_OF_STATUS_CASES; idx++) {
325    scoped_ptr<Profile> profile(new TestingProfile());
326    ProfileSyncServiceMock service(profile.get());
327    GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone();
328    EXPECT_CALL(service, GetAuthError()).WillRepeatedly(ReturnRef(error));
329    FakeSigninManagerForSyncUIUtilTest signin(profile.get());
330    signin.SetAuthenticatedUsername("test_user@test.com");
331    scoped_ptr<FakeAuthStatusProvider> provider(
332        new FakeAuthStatusProvider(
333            SigninGlobalError::GetForProfile(profile.get())));
334    GetDistinctCase(service, &signin, provider.get(), idx);
335    string16 status_label;
336    string16 link_label;
337    sync_ui_util::GetStatusLabels(&service,
338                                  signin,
339                                  sync_ui_util::WITH_HTML,
340                                  &status_label,
341                                  &link_label);
342    // If the status and link message combination is already present in the set
343    // of messages already seen, this is a duplicate rather than a unique
344    // message, and the test has failed.
345    EXPECT_FALSE(status_label.empty()) <<
346        "Empty status label returned for case #" << idx;
347    string16 combined_label =
348        status_label + string16(ASCIIToUTF16("#")) + link_label;
349    EXPECT_TRUE(messages.find(combined_label) == messages.end()) <<
350        "Duplicate message for case #" << idx << ": " << combined_label;
351    messages.insert(combined_label);
352    testing::Mock::VerifyAndClearExpectations(&service);
353    testing::Mock::VerifyAndClearExpectations(&signin);
354    EXPECT_CALL(service, GetAuthError()).WillRepeatedly(ReturnRef(error));
355    provider.reset();
356    signin.Shutdown();
357  }
358}
359
360// This test ensures that the html_links parameter on GetStatusLabels() is
361// honored.
362TEST_F(SyncUIUtilTest, HtmlNotIncludedInStatusIfNotRequested) {
363  for (int idx = 0; idx != NUMBER_OF_STATUS_CASES; idx++) {
364    scoped_ptr<Profile> profile(
365        ProfileSyncServiceMock::MakeSignedInTestingProfile());
366    ProfileSyncServiceMock service(profile.get());
367    GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone();
368    EXPECT_CALL(service, GetAuthError()).WillRepeatedly(ReturnRef(error));
369    FakeSigninManagerForSyncUIUtilTest signin(profile.get());
370    signin.SetAuthenticatedUsername("test_user@test.com");
371    scoped_ptr<FakeAuthStatusProvider> provider(
372        new FakeAuthStatusProvider(
373            SigninGlobalError::GetForProfile(profile.get())));
374    GetDistinctCase(service, &signin, provider.get(), idx);
375    string16 status_label;
376    string16 link_label;
377    sync_ui_util::GetStatusLabels(&service,
378                                  signin,
379                                  sync_ui_util::PLAIN_TEXT,
380                                  &status_label,
381                                  &link_label);
382
383    // Ensures a search for string 'href' (found in links, not a string to be
384    // found in an English language message) fails when links are excluded from
385    // the status label.
386    EXPECT_FALSE(status_label.empty());
387    EXPECT_EQ(status_label.find(string16(ASCIIToUTF16("href"))),
388              string16::npos);
389    testing::Mock::VerifyAndClearExpectations(&service);
390    testing::Mock::VerifyAndClearExpectations(&signin);
391    EXPECT_CALL(service, GetAuthError()).WillRepeatedly(ReturnRef(error));
392    provider.reset();
393    signin.Shutdown();
394  }
395}
396