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