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