// 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_arg_list.h"
#include "chrome/browser/sync/js_test_util.h"
#include "chrome/browser/sync/profile_sync_factory_mock.h"
#include "chrome/browser/sync/test_profile_sync_service.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/testing_pref_service.h"
#include "chrome/test/testing_profile.h"
#include "content/browser/browser_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.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 testing::_;
using testing::AtLeast;
using testing::AtMost;
using testing::Return;
using testing::StrictMock;
class ProfileSyncServiceTest : public testing::Test {
protected:
ProfileSyncServiceTest()
: ui_thread_(BrowserThread::UI, &message_loop_) {
profile_.reset(new TestingProfile());
}
virtual ~ProfileSyncServiceTest() {
// Kill the service before the profile.
service_.reset();
profile_.reset();
// Ensure that the sync objects destruct to avoid memory leaks.
MessageLoop::current()->RunAllPending();
}
virtual void SetUp() {
profile_->CreateRequestContext();
}
virtual void TearDown() {
{
// The request context gets deleted on the I/O thread. To prevent a leak
// supply one here.
BrowserThread io_thread(BrowserThread::IO, MessageLoop::current());
profile_->ResetRequestContext();
}
MessageLoop::current()->RunAllPending();
}
// TODO(akalin): Refactor the StartSyncService*() functions below.
void StartSyncService() {
StartSyncServiceAndSetInitialSyncEnded(true, true, false, true);
}
void StartSyncServiceAndSetInitialSyncEnded(
bool set_initial_sync_ended,
bool issue_auth_token,
bool synchronous_sync_configuration,
bool sync_setup_completed) {
if (!service_.get()) {
// Set bootstrap to true and it will provide a logged in user for test
service_.reset(new TestProfileSyncService(&factory_,
profile_.get(),
"test", true, NULL));
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);
// Register the bookmark data type.
EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).
WillOnce(ReturnNewDataTypeManager());
if (issue_auth_token) {
profile_->GetTokenService()->IssueAuthTokenForTest(
GaiaConstants::kSyncService, "token");
}
service_->Initialize();
}
}
// This serves as the "UI loop" on which the ProfileSyncService lives and
// operates. It is needed because the SyncBackend can post tasks back to
// the service, meaning it can't be null. It doesn't have to be running,
// though -- OnInitializationCompleted is the only example (so far) in this
// test where we need to Run the loop to swallow a task and then quit, to
// avoid leaking the ProfileSyncService (the PostTask will retain the callee
// and caller until the task is run).
MessageLoop message_loop_;
BrowserThread ui_thread_;
scoped_ptr<TestProfileSyncService> service_;
scoped_ptr<TestingProfile> profile_;
ProfileSyncFactoryMock factory_;
};
TEST_F(ProfileSyncServiceTest, InitialState) {
service_.reset(new TestProfileSyncService(&factory_, profile_.get(),
"", true, NULL));
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(),
"", true, NULL));
service_->Initialize();
EXPECT_TRUE(service_->IsManaged());
}
TEST_F(ProfileSyncServiceTest, AbortedByShutdown) {
service_.reset(new TestProfileSyncService(&factory_, profile_.get(),
"test", true, NULL));
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();
}
#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD)
#define MAYBE_JsFrontendHandlersBasic DISABLED_JsFrontendHandlersBasic
#else
#define MAYBE_JsFrontendHandlersBasic JsFrontendHandlersBasic
#endif
TEST_F(ProfileSyncServiceTest, MAYBE_JsFrontendHandlersBasic) {
StartSyncService();
StrictMock<MockJsEventHandler> event_handler;
SyncBackendHostForProfileSyncTest* test_backend =
service_->GetBackendForTest();
EXPECT_TRUE(service_->sync_initialized());
ASSERT_TRUE(test_backend != NULL);
ASSERT_TRUE(test_backend->GetJsBackend() != NULL);
EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter());
JsFrontend* js_backend = service_->GetJsFrontend();
js_backend->AddHandler(&event_handler);
ASSERT_TRUE(test_backend->GetJsBackend() != NULL);
EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL);
js_backend->RemoveHandler(&event_handler);
EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter());
}
TEST_F(ProfileSyncServiceTest,
JsFrontendHandlersDelayedBackendInitialization) {
StartSyncServiceAndSetInitialSyncEnded(true, false, false, true);
StrictMock<MockJsEventHandler> event_handler;
EXPECT_CALL(event_handler,
HandleJsEvent("onSyncServiceStateChanged",
HasArgs(JsArgList()))).Times(AtLeast(3));
// For some reason, these events may or may not fire.
//
// TODO(akalin): Figure out exactly why there's non-determinism
// here, and if possible remove it.
EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _))
.Times(AtMost(1));
EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _))
.Times(AtMost(1));
EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _))
.Times(AtMost(1));
EXPECT_EQ(NULL, service_->GetBackendForTest());
EXPECT_FALSE(service_->sync_initialized());
JsFrontend* js_backend = service_->GetJsFrontend();
js_backend->AddHandler(&event_handler);
// Since we're doing synchronous initialization, backend should be
// initialized by this call.
profile_->GetTokenService()->IssueAuthTokenForTest(
GaiaConstants::kSyncService, "token");
SyncBackendHostForProfileSyncTest* test_backend =
service_->GetBackendForTest();
EXPECT_TRUE(service_->sync_initialized());
ASSERT_TRUE(test_backend != NULL);
ASSERT_TRUE(test_backend->GetJsBackend() != NULL);
EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL);
js_backend->RemoveHandler(&event_handler);
EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter());
}
TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasic) {
StartSyncService();
StrictMock<MockJsEventHandler> event_handler;
// For some reason, these events may or may not fire.
EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _))
.Times(AtMost(1));
EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _))
.Times(AtMost(1));
EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _))
.Times(AtMost(1));
ListValue arg_list1;
arg_list1.Append(Value::CreateBooleanValue(true));
arg_list1.Append(Value::CreateIntegerValue(5));
JsArgList args1(arg_list1);
EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1)));
ListValue arg_list2;
arg_list2.Append(Value::CreateStringValue("test"));
arg_list2.Append(arg_list1.DeepCopy());
JsArgList args2(arg_list2);
EXPECT_CALL(event_handler,
HandleJsEvent("delayTestMessage2", HasArgs(args2)));
ListValue arg_list3;
arg_list3.Append(arg_list1.DeepCopy());
arg_list3.Append(arg_list2.DeepCopy());
JsArgList args3(arg_list3);
JsFrontend* js_backend = service_->GetJsFrontend();
// Never replied to.
js_backend->ProcessMessage("notRepliedTo", args3, &event_handler);
// Replied to later.
js_backend->ProcessMessage("delayTestMessage2", args2, &event_handler);
js_backend->AddHandler(&event_handler);
// Replied to immediately.
js_backend->ProcessMessage("testMessage1", args1, &event_handler);
// Fires off reply for delayTestMessage2.
message_loop_.RunAllPending();
// Never replied to.
js_backend->ProcessMessage("delayNotRepliedTo", args3, &event_handler);
js_backend->RemoveHandler(&event_handler);
message_loop_.RunAllPending();
// Never replied to.
js_backend->ProcessMessage("notRepliedTo", args3, &event_handler);
}
TEST_F(ProfileSyncServiceTest,
JsFrontendProcessMessageBasicDelayedBackendInitialization) {
StartSyncServiceAndSetInitialSyncEnded(true, false, false, true);
StrictMock<MockJsEventHandler> event_handler;
// For some reason, these events may or may not fire.
EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _))
.Times(AtMost(1));
EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _))
.Times(AtMost(1));
EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _))
.Times(AtMost(1));
ListValue arg_list1;
arg_list1.Append(Value::CreateBooleanValue(true));
arg_list1.Append(Value::CreateIntegerValue(5));
JsArgList args1(arg_list1);
EXPECT_CALL(event_handler, HandleJsEvent("testMessage1", HasArgs(args1)));
ListValue arg_list2;
arg_list2.Append(Value::CreateStringValue("test"));
arg_list2.Append(arg_list1.DeepCopy());
JsArgList args2(arg_list2);
EXPECT_CALL(event_handler, HandleJsEvent("testMessage2", HasArgs(args2)));
ListValue arg_list3;
arg_list3.Append(arg_list1.DeepCopy());
arg_list3.Append(arg_list2.DeepCopy());
JsArgList args3(arg_list3);
EXPECT_CALL(event_handler,
HandleJsEvent("delayTestMessage3", HasArgs(args3)));
const JsArgList kNoArgs;
EXPECT_CALL(event_handler, HandleJsEvent("onSyncServiceStateChanged",
HasArgs(kNoArgs))).Times(AtLeast(3));
JsFrontend* js_backend = service_->GetJsFrontend();
// We expect a reply for this message, even though its sent before
// |event_handler| is added as a handler.
js_backend->ProcessMessage("testMessage1", args1, &event_handler);
js_backend->AddHandler(&event_handler);
js_backend->ProcessMessage("testMessage2", args2, &event_handler);
js_backend->ProcessMessage("delayTestMessage3", args3, &event_handler);
// Fires testMessage1 and testMessage2.
profile_->GetTokenService()->IssueAuthTokenForTest(
GaiaConstants::kSyncService, "token");
// Fires delayTestMessage3.
message_loop_.RunAllPending();
js_backend->ProcessMessage("delayNotRepliedTo", kNoArgs, &event_handler);
js_backend->RemoveHandler(&event_handler);
message_loop_.RunAllPending();
js_backend->ProcessMessage("notRepliedTo", kNoArgs, &event_handler);
}
// 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);
EXPECT_FALSE(service_->HasSyncSetupCompleted());
// Since we're doing synchronous initialization, backend should be
// initialized by this call.
profile_->GetTokenService()->IssueAuthTokenForTest(
GaiaConstants::kSyncService, "token");
// 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);
}
} // namespace
} // namespace browser_sync