1// Copyright (c) 2011 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 "base/file_util.h" 6#include "base/memory/scoped_ptr.h" 7#include "base/message_loop.h" 8#include "base/values.h" 9#include "chrome/browser/net/gaia/token_service.h" 10#include "chrome/browser/sync/glue/bookmark_data_type_controller.h" 11#include "chrome/browser/sync/glue/data_type_controller.h" 12#include "chrome/browser/sync/js_arg_list.h" 13#include "chrome/browser/sync/js_test_util.h" 14#include "chrome/browser/sync/profile_sync_factory_mock.h" 15#include "chrome/browser/sync/test_profile_sync_service.h" 16#include "chrome/common/net/gaia/gaia_constants.h" 17#include "chrome/common/pref_names.h" 18#include "chrome/test/testing_pref_service.h" 19#include "chrome/test/testing_profile.h" 20#include "content/browser/browser_thread.h" 21#include "testing/gmock/include/gmock/gmock.h" 22#include "testing/gtest/include/gtest/gtest.h" 23 24// TODO(akalin): Add tests here that exercise the whole 25// ProfileSyncService/SyncBackendHost stack while mocking out as 26// little as possible. 27 28namespace browser_sync { 29 30namespace { 31 32using testing::_; 33using testing::AtLeast; 34using testing::AtMost; 35using testing::Return; 36using testing::StrictMock; 37 38class ProfileSyncServiceTest : public testing::Test { 39 protected: 40 ProfileSyncServiceTest() 41 : ui_thread_(BrowserThread::UI, &message_loop_) { 42 profile_.reset(new TestingProfile()); 43 } 44 virtual ~ProfileSyncServiceTest() { 45 // Kill the service before the profile. 46 service_.reset(); 47 profile_.reset(); 48 49 // Ensure that the sync objects destruct to avoid memory leaks. 50 MessageLoop::current()->RunAllPending(); 51 } 52 virtual void SetUp() { 53 profile_->CreateRequestContext(); 54 } 55 virtual void TearDown() { 56 { 57 // The request context gets deleted on the I/O thread. To prevent a leak 58 // supply one here. 59 BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); 60 profile_->ResetRequestContext(); 61 } 62 MessageLoop::current()->RunAllPending(); 63 } 64 65 // TODO(akalin): Refactor the StartSyncService*() functions below. 66 67 void StartSyncService() { 68 StartSyncServiceAndSetInitialSyncEnded(true, true, false, true); 69 } 70 71 void StartSyncServiceAndSetInitialSyncEnded( 72 bool set_initial_sync_ended, 73 bool issue_auth_token, 74 bool synchronous_sync_configuration, 75 bool sync_setup_completed) { 76 if (!service_.get()) { 77 // Set bootstrap to true and it will provide a logged in user for test 78 service_.reset(new TestProfileSyncService(&factory_, 79 profile_.get(), 80 "test", true, NULL)); 81 if (!set_initial_sync_ended) 82 service_->dont_set_initial_sync_ended_on_init(); 83 if (synchronous_sync_configuration) 84 service_->set_synchronous_sync_configuration(); 85 if (!sync_setup_completed) 86 profile_->GetPrefs()->SetBoolean(prefs::kSyncHasSetupCompleted, false); 87 88 // Register the bookmark data type. 89 EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). 90 WillOnce(ReturnNewDataTypeManager()); 91 92 if (issue_auth_token) { 93 profile_->GetTokenService()->IssueAuthTokenForTest( 94 GaiaConstants::kSyncService, "token"); 95 } 96 service_->Initialize(); 97 } 98 } 99 100 // This serves as the "UI loop" on which the ProfileSyncService lives and 101 // operates. It is needed because the SyncBackend can post tasks back to 102 // the service, meaning it can't be null. It doesn't have to be running, 103 // though -- OnInitializationCompleted is the only example (so far) in this 104 // test where we need to Run the loop to swallow a task and then quit, to 105 // avoid leaking the ProfileSyncService (the PostTask will retain the callee 106 // and caller until the task is run). 107 MessageLoop message_loop_; 108 BrowserThread ui_thread_; 109 110 scoped_ptr<TestProfileSyncService> service_; 111 scoped_ptr<TestingProfile> profile_; 112 ProfileSyncFactoryMock factory_; 113}; 114 115TEST_F(ProfileSyncServiceTest, InitialState) { 116 service_.reset(new TestProfileSyncService(&factory_, profile_.get(), 117 "", true, NULL)); 118 EXPECT_TRUE( 119 service_->sync_service_url().spec() == 120 ProfileSyncService::kSyncServerUrl || 121 service_->sync_service_url().spec() == 122 ProfileSyncService::kDevServerUrl); 123} 124 125TEST_F(ProfileSyncServiceTest, DisabledByPolicy) { 126 profile_->GetTestingPrefService()->SetManagedPref( 127 prefs::kSyncManaged, 128 Value::CreateBooleanValue(true)); 129 service_.reset(new TestProfileSyncService(&factory_, profile_.get(), 130 "", true, NULL)); 131 service_->Initialize(); 132 EXPECT_TRUE(service_->IsManaged()); 133} 134 135TEST_F(ProfileSyncServiceTest, AbortedByShutdown) { 136 service_.reset(new TestProfileSyncService(&factory_, profile_.get(), 137 "test", true, NULL)); 138 EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).Times(0); 139 EXPECT_CALL(factory_, CreateBookmarkSyncComponents(_, _)).Times(0); 140 service_->RegisterDataTypeController( 141 new BookmarkDataTypeController(&factory_, 142 profile_.get(), 143 service_.get())); 144 145 service_->Initialize(); 146 service_.reset(); 147} 148#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD) 149#define MAYBE_JsFrontendHandlersBasic DISABLED_JsFrontendHandlersBasic 150#else 151#define MAYBE_JsFrontendHandlersBasic JsFrontendHandlersBasic 152#endif 153TEST_F(ProfileSyncServiceTest, MAYBE_JsFrontendHandlersBasic) { 154 StartSyncService(); 155 156 StrictMock<MockJsEventHandler> event_handler; 157 158 SyncBackendHostForProfileSyncTest* test_backend = 159 service_->GetBackendForTest(); 160 161 EXPECT_TRUE(service_->sync_initialized()); 162 ASSERT_TRUE(test_backend != NULL); 163 ASSERT_TRUE(test_backend->GetJsBackend() != NULL); 164 EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter()); 165 166 JsFrontend* js_backend = service_->GetJsFrontend(); 167 js_backend->AddHandler(&event_handler); 168 ASSERT_TRUE(test_backend->GetJsBackend() != NULL); 169 EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL); 170 171 js_backend->RemoveHandler(&event_handler); 172 EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter()); 173} 174 175TEST_F(ProfileSyncServiceTest, 176 JsFrontendHandlersDelayedBackendInitialization) { 177 StartSyncServiceAndSetInitialSyncEnded(true, false, false, true); 178 179 StrictMock<MockJsEventHandler> event_handler; 180 EXPECT_CALL(event_handler, 181 HandleJsEvent("onSyncServiceStateChanged", 182 HasArgs(JsArgList()))).Times(AtLeast(3)); 183 // For some reason, these events may or may not fire. 184 // 185 // TODO(akalin): Figure out exactly why there's non-determinism 186 // here, and if possible remove it. 187 EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _)) 188 .Times(AtMost(1)); 189 EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _)) 190 .Times(AtMost(1)); 191 EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _)) 192 .Times(AtMost(1)); 193 194 EXPECT_EQ(NULL, service_->GetBackendForTest()); 195 EXPECT_FALSE(service_->sync_initialized()); 196 197 JsFrontend* js_backend = service_->GetJsFrontend(); 198 js_backend->AddHandler(&event_handler); 199 // Since we're doing synchronous initialization, backend should be 200 // initialized by this call. 201 profile_->GetTokenService()->IssueAuthTokenForTest( 202 GaiaConstants::kSyncService, "token"); 203 204 SyncBackendHostForProfileSyncTest* test_backend = 205 service_->GetBackendForTest(); 206 207 EXPECT_TRUE(service_->sync_initialized()); 208 ASSERT_TRUE(test_backend != NULL); 209 ASSERT_TRUE(test_backend->GetJsBackend() != NULL); 210 EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL); 211 212 js_backend->RemoveHandler(&event_handler); 213 EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter()); 214} 215 216TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasic) { 217 StartSyncService(); 218 219 StrictMock<MockJsEventHandler> event_handler; 220 // For some reason, these events may or may not fire. 221 EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _)) 222 .Times(AtMost(1)); 223 EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _)) 224 .Times(AtMost(1)); 225 EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _)) 226 .Times(AtMost(1)); 227 228 ListValue arg_list1; 229 arg_list1.Append(Value::CreateBooleanValue(true)); 230 arg_list1.Append(Value::CreateIntegerValue(5)); 231 JsArgList args1(arg_list1); 232 EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1))); 233 234 ListValue arg_list2; 235 arg_list2.Append(Value::CreateStringValue("test")); 236 arg_list2.Append(arg_list1.DeepCopy()); 237 JsArgList args2(arg_list2); 238 EXPECT_CALL(event_handler, 239 HandleJsEvent("delayTestMessage2", HasArgs(args2))); 240 241 ListValue arg_list3; 242 arg_list3.Append(arg_list1.DeepCopy()); 243 arg_list3.Append(arg_list2.DeepCopy()); 244 JsArgList args3(arg_list3); 245 246 JsFrontend* js_backend = service_->GetJsFrontend(); 247 248 // Never replied to. 249 js_backend->ProcessMessage("notRepliedTo", args3, &event_handler); 250 251 // Replied to later. 252 js_backend->ProcessMessage("delayTestMessage2", args2, &event_handler); 253 254 js_backend->AddHandler(&event_handler); 255 256 // Replied to immediately. 257 js_backend->ProcessMessage("testMessage1", args1, &event_handler); 258 259 // Fires off reply for delayTestMessage2. 260 message_loop_.RunAllPending(); 261 262 // Never replied to. 263 js_backend->ProcessMessage("delayNotRepliedTo", args3, &event_handler); 264 265 js_backend->RemoveHandler(&event_handler); 266 267 message_loop_.RunAllPending(); 268 269 // Never replied to. 270 js_backend->ProcessMessage("notRepliedTo", args3, &event_handler); 271} 272 273TEST_F(ProfileSyncServiceTest, 274 JsFrontendProcessMessageBasicDelayedBackendInitialization) { 275 StartSyncServiceAndSetInitialSyncEnded(true, false, false, true); 276 277 StrictMock<MockJsEventHandler> event_handler; 278 // For some reason, these events may or may not fire. 279 EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _)) 280 .Times(AtMost(1)); 281 EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _)) 282 .Times(AtMost(1)); 283 EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _)) 284 .Times(AtMost(1)); 285 286 ListValue arg_list1; 287 arg_list1.Append(Value::CreateBooleanValue(true)); 288 arg_list1.Append(Value::CreateIntegerValue(5)); 289 JsArgList args1(arg_list1); 290 EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1))); 291 292 ListValue arg_list2; 293 arg_list2.Append(Value::CreateStringValue("test")); 294 arg_list2.Append(arg_list1.DeepCopy()); 295 JsArgList args2(arg_list2); 296 EXPECT_CALL(event_handler, HandleJsEvent("testMessage2", HasArgs(args2))); 297 298 ListValue arg_list3; 299 arg_list3.Append(arg_list1.DeepCopy()); 300 arg_list3.Append(arg_list2.DeepCopy()); 301 JsArgList args3(arg_list3); 302 EXPECT_CALL(event_handler, 303 HandleJsEvent("delayTestMessage3", HasArgs(args3))); 304 305 const JsArgList kNoArgs; 306 307 EXPECT_CALL(event_handler, HandleJsEvent("onSyncServiceStateChanged", 308 HasArgs(kNoArgs))).Times(AtLeast(3)); 309 310 JsFrontend* js_backend = service_->GetJsFrontend(); 311 312 // We expect a reply for this message, even though its sent before 313 // |event_handler| is added as a handler. 314 js_backend->ProcessMessage("testMessage1", args1, &event_handler); 315 316 js_backend->AddHandler(&event_handler); 317 318 js_backend->ProcessMessage("testMessage2", args2, &event_handler); 319 js_backend->ProcessMessage("delayTestMessage3", args3, &event_handler); 320 321 // Fires testMessage1 and testMessage2. 322 profile_->GetTokenService()->IssueAuthTokenForTest( 323 GaiaConstants::kSyncService, "token"); 324 325 // Fires delayTestMessage3. 326 message_loop_.RunAllPending(); 327 328 js_backend->ProcessMessage("delayNotRepliedTo", kNoArgs, &event_handler); 329 330 js_backend->RemoveHandler(&event_handler); 331 332 message_loop_.RunAllPending(); 333 334 js_backend->ProcessMessage("notRepliedTo", kNoArgs, &event_handler); 335} 336 337// Make sure that things still work if sync is not enabled, but some old sync 338// databases are lingering in the "Sync Data" folder. 339TEST_F(ProfileSyncServiceTest, TestStartupWithOldSyncData) { 340 const char* nonsense1 = "reginald"; 341 const char* nonsense2 = "beartato"; 342 const char* nonsense3 = "harrison"; 343 FilePath temp_directory = profile_->GetPath().AppendASCII("Sync Data"); 344 FilePath sync_file1 = 345 temp_directory.AppendASCII("BookmarkSyncSettings.sqlite3"); 346 FilePath sync_file2 = temp_directory.AppendASCII("SyncData.sqlite3"); 347 FilePath sync_file3 = temp_directory.AppendASCII("nonsense_file"); 348 ASSERT_TRUE(file_util::CreateDirectory(temp_directory)); 349 ASSERT_NE(-1, 350 file_util::WriteFile(sync_file1, nonsense1, strlen(nonsense1))); 351 ASSERT_NE(-1, 352 file_util::WriteFile(sync_file2, nonsense2, strlen(nonsense2))); 353 ASSERT_NE(-1, 354 file_util::WriteFile(sync_file3, nonsense3, strlen(nonsense3))); 355 356 StartSyncServiceAndSetInitialSyncEnded(false, false, true, false); 357 EXPECT_FALSE(service_->HasSyncSetupCompleted()); 358 359 // Since we're doing synchronous initialization, backend should be 360 // initialized by this call. 361 profile_->GetTokenService()->IssueAuthTokenForTest( 362 GaiaConstants::kSyncService, "token"); 363 364 // Stop the service so we can read the new Sync Data files that were 365 // created. 366 service_.reset(); 367 368 // This file should have been deleted when the whole directory was nuked. 369 ASSERT_FALSE(file_util::PathExists(sync_file3)); 370 ASSERT_FALSE(file_util::PathExists(sync_file1)); 371 372 // This will still exist, but the text should have changed. 373 ASSERT_TRUE(file_util::PathExists(sync_file2)); 374 std::string file2text; 375 ASSERT_TRUE(file_util::ReadFileToString(sync_file2, &file2text)); 376 ASSERT_NE(file2text.compare(nonsense2), 0); 377} 378 379} // namespace 380 381} // namespace browser_sync 382