// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/file_util.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/values.h" #include "chrome/browser/net/gaia/token_service.h" #include "chrome/browser/sync/glue/bookmark_data_type_controller.h" #include "chrome/browser/sync/glue/data_type_controller.h" #include "chrome/browser/sync/js/js_arg_list.h" #include "chrome/browser/sync/js/js_event_details.h" #include "chrome/browser/sync/js/js_test_util.h" #include "chrome/browser/sync/signin_manager.h" #include "chrome/browser/sync/profile_sync_components_factory_mock.h" #include "chrome/browser/sync/test_profile_sync_service.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/net/gaia/gaia_constants.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_pref_service.h" #include "chrome/test/base/testing_profile.h" #include "content/test/test_browser_thread.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/glue/user_agent.h" #include "webkit/glue/webkit_glue.h" // TODO(akalin): Add tests here that exercise the whole // ProfileSyncService/SyncBackendHost stack while mocking out as // little as possible. namespace browser_sync { namespace { using content::BrowserThread; using testing::_; using testing::AtLeast; using testing::AtMost; using testing::Return; using testing::StrictMock; class ProfileSyncServiceTest : public testing::Test { protected: ProfileSyncServiceTest() : ui_thread_(BrowserThread::UI, &ui_loop_), file_thread_(BrowserThread::FILE), io_thread_(BrowserThread::IO) {} virtual ~ProfileSyncServiceTest() {} virtual void SetUp() { file_thread_.Start(); io_thread_.StartIOThread(); profile_.reset(new TestingProfile()); profile_->CreateRequestContext(); // We need to set the user agent before the backend host can call // webkit_glue::GetUserAgent(). chrome::VersionInfo version_info; std::string product("Chrome/"); product += version_info.is_valid() ? version_info.Version() : "0.0.0.0"; webkit_glue::SetUserAgent(webkit_glue::BuildUserAgentFromProduct(product), false); } virtual void TearDown() { // Kill the service before the profile. service_.reset(); profile_.reset(); // Pump messages posted by the sync core thread (which may end up // posting on the IO thread). ui_loop_.RunAllPending(); io_thread_.Stop(); file_thread_.Stop(); // Ensure that the sync objects destruct to avoid memory leaks. ui_loop_.RunAllPending(); } // TODO(akalin): Refactor the StartSyncService*() functions below. void StartSyncService() { StartSyncServiceAndSetInitialSyncEnded(true, true, false, true, true); } void StartSyncServiceAndSetInitialSyncEnded( bool set_initial_sync_ended, bool issue_auth_token, bool synchronous_sync_configuration, bool sync_setup_completed, bool expect_create_dtm) { if (!service_.get()) { SigninManager* signin = new SigninManager(); signin->SetAuthenticatedUsername("test"); service_.reset(new TestProfileSyncService(&factory_, profile_.get(), signin, ProfileSyncService::AUTO_START, true, base::Closure())); if (!set_initial_sync_ended) service_->dont_set_initial_sync_ended_on_init(); if (synchronous_sync_configuration) service_->set_synchronous_sync_configuration(); if (!sync_setup_completed) profile_->GetPrefs()->SetBoolean(prefs::kSyncHasSetupCompleted, false); if (expect_create_dtm) { // Register the bookmark data type. EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillOnce(ReturnNewDataTypeManager()); } else { EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).Times(0); } if (issue_auth_token) { IssueTestTokens(); } service_->Initialize(); } } void IssueTestTokens() { profile_->GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "token1"); profile_->GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kGaiaOAuth2LoginRefreshToken, "token2"); } MessageLoop ui_loop_; // Needed by |service_|. content::TestBrowserThread ui_thread_; // Needed by DisableAndEnableSyncTemporarily test case. content::TestBrowserThread file_thread_; // Needed by |service| and |profile_|'s request context. content::TestBrowserThread io_thread_; scoped_ptr<TestProfileSyncService> service_; scoped_ptr<TestingProfile> profile_; ProfileSyncComponentsFactoryMock factory_; }; TEST_F(ProfileSyncServiceTest, InitialState) { service_.reset(new TestProfileSyncService(&factory_, profile_.get(), new SigninManager(), ProfileSyncService::MANUAL_START, true, base::Closure())); EXPECT_TRUE( service_->sync_service_url().spec() == ProfileSyncService::kSyncServerUrl || service_->sync_service_url().spec() == ProfileSyncService::kDevServerUrl); } TEST_F(ProfileSyncServiceTest, DisabledByPolicy) { profile_->GetTestingPrefService()->SetManagedPref( prefs::kSyncManaged, Value::CreateBooleanValue(true)); service_.reset(new TestProfileSyncService(&factory_, profile_.get(), new SigninManager(), ProfileSyncService::MANUAL_START, true, base::Closure())); service_->Initialize(); EXPECT_TRUE(service_->IsManaged()); } TEST_F(ProfileSyncServiceTest, AbortedByShutdown) { SigninManager* signin = new SigninManager(); signin->SetAuthenticatedUsername("test"); service_.reset(new TestProfileSyncService(&factory_, profile_.get(), signin, ProfileSyncService::AUTO_START, true, base::Closure())); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).Times(0); EXPECT_CALL(factory_, CreateBookmarkSyncComponents(_, _)).Times(0); service_->RegisterDataTypeController( new BookmarkDataTypeController(&factory_, profile_.get(), service_.get())); service_->Initialize(); service_.reset(); } TEST_F(ProfileSyncServiceTest, DisableAndEnableSyncTemporarily) { SigninManager* signin = new SigninManager(); signin->SetAuthenticatedUsername("test"); service_.reset(new TestProfileSyncService(&factory_, profile_.get(), signin, ProfileSyncService::AUTO_START, true, base::Closure())); // Register the bookmark data type. EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillRepeatedly(ReturnNewDataTypeManager()); IssueTestTokens(); service_->Initialize(); EXPECT_TRUE(service_->sync_initialized()); EXPECT_TRUE(service_->GetBackendForTest() != NULL); EXPECT_FALSE(profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)); service_->StopAndSuppress(); EXPECT_FALSE(service_->sync_initialized()); EXPECT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)); service_->UnsuppressAndStart(); EXPECT_TRUE(service_->sync_initialized()); EXPECT_FALSE(profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)); } TEST_F(ProfileSyncServiceTest, JsControllerHandlersBasic) { StartSyncService(); EXPECT_TRUE(service_->sync_initialized()); EXPECT_TRUE(service_->GetBackendForTest() != NULL); JsController* js_controller = service_->GetJsController(); StrictMock<MockJsEventHandler> event_handler; js_controller->AddJsEventHandler(&event_handler); js_controller->RemoveJsEventHandler(&event_handler); } TEST_F(ProfileSyncServiceTest, JsControllerHandlersDelayedBackendInitialization) { StartSyncServiceAndSetInitialSyncEnded(true, false, false, true, true); StrictMock<MockJsEventHandler> event_handler; EXPECT_CALL(event_handler, HandleJsEvent(_, _)).Times(AtLeast(1)); EXPECT_EQ(NULL, service_->GetBackendForTest()); EXPECT_FALSE(service_->sync_initialized()); JsController* js_controller = service_->GetJsController(); js_controller->AddJsEventHandler(&event_handler); // Since we're doing synchronous initialization, backend should be // initialized by this call. IssueTestTokens(); EXPECT_TRUE(service_->sync_initialized()); js_controller->RemoveJsEventHandler(&event_handler); } TEST_F(ProfileSyncServiceTest, JsControllerProcessJsMessageBasic) { StartSyncService(); StrictMock<MockJsReplyHandler> reply_handler; ListValue arg_list1; arg_list1.Append(Value::CreateBooleanValue(false)); JsArgList args1(&arg_list1); EXPECT_CALL(reply_handler, HandleJsReply("getNotificationState", HasArgs(args1))); { JsController* js_controller = service_->GetJsController(); js_controller->ProcessJsMessage("getNotificationState", args1, reply_handler.AsWeakHandle()); } // This forces the sync thread to process the message and reply. service_.reset(); ui_loop_.RunAllPending(); } TEST_F(ProfileSyncServiceTest, JsControllerProcessJsMessageBasicDelayedBackendInitialization) { StartSyncServiceAndSetInitialSyncEnded(true, false, false, true, true); StrictMock<MockJsReplyHandler> reply_handler; ListValue arg_list1; arg_list1.Append(Value::CreateBooleanValue(false)); JsArgList args1(&arg_list1); EXPECT_CALL(reply_handler, HandleJsReply("getNotificationState", HasArgs(args1))); { JsController* js_controller = service_->GetJsController(); js_controller->ProcessJsMessage("getNotificationState", args1, reply_handler.AsWeakHandle()); } IssueTestTokens(); // This forces the sync thread to process the message and reply. service_.reset(); ui_loop_.RunAllPending(); } // Make sure that things still work if sync is not enabled, but some old sync // databases are lingering in the "Sync Data" folder. TEST_F(ProfileSyncServiceTest, TestStartupWithOldSyncData) { const char* nonsense1 = "reginald"; const char* nonsense2 = "beartato"; const char* nonsense3 = "harrison"; FilePath temp_directory = profile_->GetPath().AppendASCII("Sync Data"); FilePath sync_file1 = temp_directory.AppendASCII("BookmarkSyncSettings.sqlite3"); FilePath sync_file2 = temp_directory.AppendASCII("SyncData.sqlite3"); FilePath sync_file3 = temp_directory.AppendASCII("nonsense_file"); ASSERT_TRUE(file_util::CreateDirectory(temp_directory)); ASSERT_NE(-1, file_util::WriteFile(sync_file1, nonsense1, strlen(nonsense1))); ASSERT_NE(-1, file_util::WriteFile(sync_file2, nonsense2, strlen(nonsense2))); ASSERT_NE(-1, file_util::WriteFile(sync_file3, nonsense3, strlen(nonsense3))); StartSyncServiceAndSetInitialSyncEnded(false, false, true, false, true); EXPECT_FALSE(service_->HasSyncSetupCompleted()); EXPECT_FALSE(service_->sync_initialized()); // Since we're doing synchronous initialization, backend should be // initialized by this call. IssueTestTokens(); // Stop the service so we can read the new Sync Data files that were // created. service_.reset(); // This file should have been deleted when the whole directory was nuked. ASSERT_FALSE(file_util::PathExists(sync_file3)); ASSERT_FALSE(file_util::PathExists(sync_file1)); // This will still exist, but the text should have changed. ASSERT_TRUE(file_util::PathExists(sync_file2)); std::string file2text; ASSERT_TRUE(file_util::ReadFileToString(sync_file2, &file2text)); ASSERT_NE(file2text.compare(nonsense2), 0); } TEST_F(ProfileSyncServiceTest, CorruptDatabase) { const char* nonesense = "not a database"; FilePath temp_directory = profile_->GetPath().AppendASCII("Sync Data"); FilePath sync_db_file = temp_directory.AppendASCII("SyncData.sqlite3"); ASSERT_TRUE(file_util::CreateDirectory(temp_directory)); ASSERT_NE(-1, file_util::WriteFile(sync_db_file, nonesense, strlen(nonesense))); // Initialize with HasSyncSetupCompleted() set to true and InitialSyncEnded // false. This is to model the scenario that would result when opening the // sync database fails. StartSyncServiceAndSetInitialSyncEnded(false, true, true, true, false); // The backend is not ready. Ensure the PSS knows this. EXPECT_FALSE(service_->sync_initialized()); // Ensure we will be prepared to initialize a fresh DB next time. EXPECT_FALSE(service_->HasSyncSetupCompleted()); } } // namespace } // namespace browser_sync