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 // Utility function to test that GetStatusLabelsForSyncGlobalError returns 47 // the correct results for the given states. 48 void 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 70 class 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. 77 TEST_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. 95 TEST_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). 125 TEST_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. 163 class 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. 187 void 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(). 322 TEST_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 (at) 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. 362 TEST_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 (at) 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