// 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 <map>
#include <string>
#include "base/json/json_reader.h"
#include "base/stl_util-inl.h"
#include "base/string_piece.h"
#include "base/task.h"
#include "chrome/browser/prefs/scoped_user_pref_update.h"
#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/preference_change_processor.h"
#include "chrome/browser/sync/glue/preference_data_type_controller.h"
#include "chrome/browser/sync/glue/preference_model_associator.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/profile_sync_test_util.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
#include "chrome/browser/sync/syncable/model_type.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/common/json_value_serializer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::JSONReader;
using browser_sync::PreferenceChangeProcessor;
using browser_sync::PreferenceDataTypeController;
using browser_sync::PreferenceModelAssociator;
using browser_sync::SyncBackendHost;
using sync_api::SyncManager;
using testing::_;
using testing::Return;
typedef std::map<const std::string, const Value*> PreferenceValues;
class ProfileSyncServicePreferenceTest
: public AbstractProfileSyncServiceTest {
protected:
ProfileSyncServicePreferenceTest()
: example_url0_("http://example.com/0"),
example_url1_("http://example.com/1"),
example_url2_("http://example.com/2"),
not_synced_preference_name_("nonsense_pref_name"),
not_synced_preference_default_value_("default"),
non_default_charset_value_("foo") {}
virtual void SetUp() {
profile_.reset(new TestingProfile());
profile_->CreateRequestContext();
prefs_ = profile_->GetTestingPrefService();
prefs_->RegisterStringPref(not_synced_preference_name_.c_str(),
not_synced_preference_default_value_);
}
virtual void TearDown() {
service_.reset();
{
// 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_.reset();
}
MessageLoop::current()->RunAllPending();
}
bool StartSyncService(Task* task, bool will_fail_association) {
if (service_.get())
return false;
service_.reset(new TestProfileSyncService(
&factory_, profile_.get(), "test", false, task));
// Register the preference data type.
model_associator_ =
new PreferenceModelAssociator(service_.get());
change_processor_ = new PreferenceChangeProcessor(model_associator_,
service_.get());
EXPECT_CALL(factory_, CreatePreferenceSyncComponents(_, _)).
WillOnce(Return(ProfileSyncFactory::SyncComponents(
model_associator_, change_processor_)));
EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).
WillOnce(ReturnNewDataTypeManager());
service_->RegisterDataTypeController(
new PreferenceDataTypeController(&factory_,
profile_.get(),
service_.get()));
profile_->GetTokenService()->IssueAuthTokenForTest(
GaiaConstants::kSyncService, "token");
service_->Initialize();
MessageLoop::current()->Run();
return true;
}
const Value& GetPreferenceValue(const std::string& name) {
const PrefService::Preference* preference =
prefs_->FindPreference(name.c_str());
return *preference->GetValue();
}
// Caller gets ownership of the returned value.
const Value* GetSyncedValue(const std::string& name) {
sync_api::ReadTransaction trans(service_->GetUserShare());
sync_api::ReadNode node(&trans);
int64 node_id = model_associator_->GetSyncIdFromChromeId(name);
if (node_id == sync_api::kInvalidId)
return NULL;
if (!node.InitByIdLookup(node_id))
return NULL;
const sync_pb::PreferenceSpecifics& specifics(
node.GetPreferenceSpecifics());
JSONReader reader;
return reader.JsonToValue(specifics.value(), false, false);
}
int64 WriteSyncedValue(const std::string& name,
const Value& value,
sync_api::WriteNode* node) {
if (!PreferenceModelAssociator::WritePreferenceToNode(name, value, node))
return sync_api::kInvalidId;
return node->GetId();
}
int64 SetSyncedValue(const std::string& name, const Value& value) {
sync_api::WriteTransaction trans(service_->GetUserShare());
sync_api::ReadNode root(&trans);
if (!root.InitByTagLookup(browser_sync::kPreferencesTag))
return sync_api::kInvalidId;
sync_api::WriteNode tag_node(&trans);
sync_api::WriteNode node(&trans);
int64 node_id = model_associator_->GetSyncIdFromChromeId(name);
if (node_id == sync_api::kInvalidId) {
if (tag_node.InitByClientTagLookup(syncable::PREFERENCES, name))
return WriteSyncedValue(name, value, &tag_node);
if (node.InitUniqueByCreation(syncable::PREFERENCES, root, name))
return WriteSyncedValue(name, value, &node);
} else if (node.InitByIdLookup(node_id)) {
return WriteSyncedValue(name, value, &node);
}
return sync_api::kInvalidId;
}
SyncManager::ChangeRecord* MakeChangeRecord(const std::string& name,
SyncManager::ChangeRecord) {
int64 node_id = model_associator_->GetSyncIdFromChromeId(name);
SyncManager::ChangeRecord* record = new SyncManager::ChangeRecord();
record->action = SyncManager::ChangeRecord::ACTION_UPDATE;
record->id = node_id;
return record;
}
bool IsSynced(const std::string& pref_name) {
return model_associator_->synced_preferences().count(pref_name) > 0;
}
std::string ValueString(const Value& value) {
std::string serialized;
JSONStringValueSerializer json(&serialized);
json.Serialize(value);
return serialized;
}
friend class AddPreferenceEntriesTask;
scoped_ptr<TestingProfile> profile_;
TestingPrefService* prefs_;
PreferenceModelAssociator* model_associator_;
PreferenceChangeProcessor* change_processor_;
std::string example_url0_;
std::string example_url1_;
std::string example_url2_;
std::string not_synced_preference_name_;
std::string not_synced_preference_default_value_;
std::string non_default_charset_value_;
};
class AddPreferenceEntriesTask : public Task {
public:
AddPreferenceEntriesTask(ProfileSyncServicePreferenceTest* test,
const PreferenceValues& entries)
: test_(test), entries_(entries), success_(false) {
}
virtual void Run() {
if (!test_->CreateRoot(syncable::PREFERENCES))
return;
for (PreferenceValues::const_iterator i = entries_.begin();
i != entries_.end(); ++i) {
if (test_->SetSyncedValue(i->first, *i->second) == sync_api::kInvalidId)
return;
}
success_ = true;
}
bool success() { return success_; }
private:
ProfileSyncServicePreferenceTest* test_;
const PreferenceValues& entries_;
bool success_;
};
TEST_F(ProfileSyncServicePreferenceTest, WritePreferenceToNode) {
prefs_->SetString(prefs::kHomePage, example_url0_);
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
const PrefService::Preference* pref =
prefs_->FindPreference(prefs::kHomePage);
sync_api::WriteTransaction trans(service_->GetUserShare());
sync_api::WriteNode node(&trans);
EXPECT_TRUE(node.InitByClientTagLookup(syncable::PREFERENCES,
prefs::kHomePage));
EXPECT_TRUE(PreferenceModelAssociator::WritePreferenceToNode(
pref->name(), *pref->GetValue(), &node));
EXPECT_EQ(UTF8ToWide(prefs::kHomePage), node.GetTitle());
const sync_pb::PreferenceSpecifics& specifics(node.GetPreferenceSpecifics());
EXPECT_EQ(std::string(prefs::kHomePage), specifics.name());
base::JSONReader reader;
scoped_ptr<Value> value(reader.JsonToValue(specifics.value(), false, false));
EXPECT_TRUE(pref->GetValue()->Equals(value.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationDoNotSyncDefaults) {
const PrefService::Preference* pref =
prefs_->FindPreference(prefs::kHomePage);
EXPECT_TRUE(pref->IsDefaultValue());
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
EXPECT_TRUE(IsSynced(prefs::kHomePage));
EXPECT_TRUE(pref->IsDefaultValue());
EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL);
EXPECT_TRUE(GetSyncedValue(not_synced_preference_name_) == NULL);
}
TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationEmptyCloud) {
prefs_->SetString(prefs::kHomePage, example_url0_);
{
ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup);
ListValue* url_list = update.Get();
url_list->Append(Value::CreateStringValue(example_url0_));
url_list->Append(Value::CreateStringValue(example_url1_));
}
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<const Value> value(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(value.get());
EXPECT_TRUE(GetPreferenceValue(prefs::kHomePage).Equals(value.get()));
value.reset(GetSyncedValue(prefs::kURLsToRestoreOnStartup));
ASSERT_TRUE(value.get());
EXPECT_TRUE(
GetPreferenceValue(prefs::kURLsToRestoreOnStartup).Equals(value.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationCloudHasData) {
prefs_->SetString(prefs::kHomePage, example_url0_);
{
ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup);
ListValue* url_list = update.Get();
url_list->Append(Value::CreateStringValue(example_url0_));
url_list->Append(Value::CreateStringValue(example_url1_));
}
PreferenceValues cloud_data;
cloud_data[prefs::kHomePage] = Value::CreateStringValue(example_url1_);
ListValue* urls_to_restore = new ListValue;
urls_to_restore->Append(Value::CreateStringValue(example_url1_));
urls_to_restore->Append(Value::CreateStringValue(example_url2_));
cloud_data[prefs::kURLsToRestoreOnStartup] = urls_to_restore;
cloud_data[prefs::kDefaultCharset] =
Value::CreateStringValue(non_default_charset_value_);
AddPreferenceEntriesTask task(this, cloud_data);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<const Value> value(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(value.get());
std::string string_value;
EXPECT_TRUE(static_cast<const StringValue*>(value.get())->
GetAsString(&string_value));
EXPECT_EQ(example_url1_, string_value);
EXPECT_EQ(example_url1_, prefs_->GetString(prefs::kHomePage));
scoped_ptr<ListValue> expected_urls(new ListValue);
expected_urls->Append(Value::CreateStringValue(example_url1_));
expected_urls->Append(Value::CreateStringValue(example_url2_));
expected_urls->Append(Value::CreateStringValue(example_url0_));
value.reset(GetSyncedValue(prefs::kURLsToRestoreOnStartup));
ASSERT_TRUE(value.get());
EXPECT_TRUE(value->Equals(expected_urls.get()));
EXPECT_TRUE(GetPreferenceValue(prefs::kURLsToRestoreOnStartup).
Equals(expected_urls.get()));
value.reset(GetSyncedValue(prefs::kDefaultCharset));
ASSERT_TRUE(value.get());
EXPECT_TRUE(static_cast<const StringValue*>(value.get())->
GetAsString(&string_value));
EXPECT_EQ(non_default_charset_value_, string_value);
EXPECT_EQ(non_default_charset_value_,
prefs_->GetString(prefs::kDefaultCharset));
STLDeleteValues(&cloud_data);
}
TEST_F(ProfileSyncServicePreferenceTest, FailModelAssociation) {
ASSERT_TRUE(StartSyncService(NULL, true));
EXPECT_TRUE(service_->unrecoverable_error_detected());
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedPreferenceWithDefaultValue) {
const PrefService::Preference* pref =
prefs_->FindPreference(prefs::kHomePage);
EXPECT_TRUE(pref->IsDefaultValue());
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
profile_->GetPrefs()->Set(prefs::kHomePage, *expected);
scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(actual.get());
EXPECT_TRUE(expected->Equals(actual.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedPreferenceWithValue) {
profile_->GetPrefs()->SetString(prefs::kHomePage, example_url0_);
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url1_));
profile_->GetPrefs()->Set(prefs::kHomePage, *expected);
scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
ASSERT_TRUE(actual.get());
EXPECT_TRUE(expected->Equals(actual.get()));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeActionUpdate) {
profile_->GetPrefs()->SetString(prefs::kHomePage, example_url0_);
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url1_));
ASSERT_NE(SetSyncedValue(prefs::kHomePage, *expected), sync_api::kInvalidId);
int64 node_id = model_associator_->GetSyncIdFromChromeId(prefs::kHomePage);
scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
record->action = SyncManager::ChangeRecord::ACTION_UPDATE;
record->id = node_id;
{
sync_api::WriteTransaction trans(service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
}
const Value& actual = GetPreferenceValue(prefs::kHomePage);
EXPECT_TRUE(expected->Equals(&actual));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeActionAdd) {
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
int64 node_id = SetSyncedValue(prefs::kHomePage, *expected);
ASSERT_NE(node_id, sync_api::kInvalidId);
scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
record->action = SyncManager::ChangeRecord::ACTION_ADD;
record->id = node_id;
{
sync_api::WriteTransaction trans(service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
}
const Value& actual = GetPreferenceValue(prefs::kHomePage);
EXPECT_TRUE(expected->Equals(&actual));
EXPECT_EQ(node_id,
model_associator_->GetSyncIdFromChromeId(prefs::kHomePage));
}
TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeUnknownPreference) {
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<Value> expected(Value::CreateStringValue(example_url0_));
int64 node_id = SetSyncedValue("unknown preference", *expected);
ASSERT_NE(node_id, sync_api::kInvalidId);
scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
record->action = SyncManager::ChangeRecord::ACTION_ADD;
record->id = node_id;
{
sync_api::WriteTransaction trans(service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
}
// Nothing interesting happens on the client when it gets an update
// of an unknown preference. We just should not crash.
}
TEST_F(ProfileSyncServicePreferenceTest, ManagedPreferences) {
// Make the homepage preference managed.
scoped_ptr<Value> managed_value(
Value::CreateStringValue("http://example.com"));
prefs_->SetManagedPref(prefs::kHomePage, managed_value->DeepCopy());
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
// Changing the homepage preference should not sync anything.
scoped_ptr<Value> user_value(
Value::CreateStringValue("http://chromium..com"));
prefs_->SetUserPref(prefs::kHomePage, user_value->DeepCopy());
EXPECT_EQ(NULL, GetSyncedValue(prefs::kHomePage));
// An incoming sync transaction shouldn't change the user value.
scoped_ptr<Value> sync_value(
Value::CreateStringValue("http://crbug.com"));
int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value);
ASSERT_NE(node_id, sync_api::kInvalidId);
scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
record->action = SyncManager::ChangeRecord::ACTION_UPDATE;
record->id = node_id;
{
sync_api::WriteTransaction trans(service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
}
EXPECT_TRUE(managed_value->Equals(
prefs_->GetManagedPref(prefs::kHomePage)));
EXPECT_TRUE(user_value->Equals(
prefs_->GetUserPref(prefs::kHomePage)));
}
TEST_F(ProfileSyncServicePreferenceTest, DynamicManagedPreferences) {
CreateRootTask task(this, syncable::PREFERENCES);
ASSERT_TRUE(StartSyncService(&task, false));
ASSERT_TRUE(task.success());
scoped_ptr<Value> initial_value(
Value::CreateStringValue("http://example.com/initial"));
profile_->GetPrefs()->Set(prefs::kHomePage, *initial_value);
scoped_ptr<const Value> actual(GetSyncedValue(prefs::kHomePage));
EXPECT_TRUE(initial_value->Equals(actual.get()));
// Switch kHomePage to managed and set a different value.
scoped_ptr<Value> managed_value(
Value::CreateStringValue("http://example.com/managed"));
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kHomePage, managed_value->DeepCopy());
// Sync node should be gone.
EXPECT_EQ(sync_api::kInvalidId,
model_associator_->GetSyncIdFromChromeId(prefs::kHomePage));
// Change the sync value.
scoped_ptr<Value> sync_value(
Value::CreateStringValue("http://example.com/sync"));
int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value);
ASSERT_NE(node_id, sync_api::kInvalidId);
scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord);
record->action = SyncManager::ChangeRecord::ACTION_ADD;
record->id = node_id;
{
sync_api::WriteTransaction trans(service_->GetUserShare());
change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1);
}
// The pref value should still be the one dictated by policy.
EXPECT_TRUE(managed_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
// Switch kHomePage back to unmanaged.
profile_->GetTestingPrefService()->RemoveManagedPref(prefs::kHomePage);
// Sync value should be picked up.
EXPECT_TRUE(sync_value->Equals(&GetPreferenceValue(prefs::kHomePage)));
}