// Copyright (c) 2012 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 #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/json/json_reader.h" #include "base/json/json_string_value_serializer.h" #include "base/location.h" #include "base/stl_util.h" #include "base/string_piece.h" #include "chrome/browser/prefs/pref_model_associator.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/signin/signin_manager.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/signin/token_service_factory.h" #include "chrome/browser/sync/abstract_profile_sync_service_test.h" #include "chrome/browser/sync/glue/generic_change_processor.h" #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/glue/ui_data_type_controller.h" #include "chrome/browser/sync/profile_sync_test_util.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/base/testing_pref_service.h" #include "chrome/test/base/testing_profile.h" #include "sync/api/sync_data.h" #include "sync/internal_api/change_record.h" #include "sync/internal_api/read_node.h" #include "sync/internal_api/read_transaction.h" #include "sync/internal_api/write_node.h" #include "sync/internal_api/write_transaction.h" #include "sync/protocol/preference_specifics.pb.h" #include "sync/syncable/model_type.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using base::JSONReader; using browser_sync::GenericChangeProcessor; using browser_sync::SharedChangeProcessor; using browser_sync::UIDataTypeController; using sync_api::ChangeRecord; using testing::_; using testing::Invoke; using testing::Return; typedef std::map PreferenceValues; ACTION_P(CreateAndSaveChangeProcessor, change_processor) { sync_api::UserShare* user_share = arg0->GetUserShare(); *change_processor = new GenericChangeProcessor(arg1, arg2, user_share); return *change_processor; } // TODO(zea): Refactor to remove the ProfileSyncService usage. class ProfileSyncServicePreferenceTest : public AbstractProfileSyncServiceTest { public: int64 SetSyncedValue(const std::string& name, const Value& value) { sync_api::WriteTransaction trans(FROM_HERE, service_->GetUserShare()); sync_api::ReadNode root(&trans); if (root.InitByTagLookup(syncable::ModelTypeToRootTag( syncable::PREFERENCES)) != sync_api::BaseNode::INIT_OK) { return sync_api::kInvalidId; } sync_api::WriteNode tag_node(&trans); sync_api::WriteNode node(&trans); if (tag_node.InitByClientTagLookup(syncable::PREFERENCES, name) == sync_api::BaseNode::INIT_OK) { return WriteSyncedValue(name, value, &tag_node); } if (node.InitUniqueByCreation(syncable::PREFERENCES, root, name)) return WriteSyncedValue(name, value, &node); return sync_api::kInvalidId; } 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() { AbstractProfileSyncServiceTest::SetUp(); profile_.reset(new TestingProfile()); profile_->CreateRequestContext(); prefs_ = profile_->GetTestingPrefService(); prefs_->RegisterStringPref(not_synced_preference_name_.c_str(), not_synced_preference_default_value_, PrefService::UNSYNCABLE_PREF); } virtual void TearDown() { service_.reset(); profile_.reset(); AbstractProfileSyncServiceTest::TearDown(); } bool StartSyncService(const base::Closure& callback, bool will_fail_association) { if (service_.get()) return false; SigninManager* signin = SigninManagerFactory::GetForProfile(profile_.get()); signin->SetAuthenticatedUsername("test"); ProfileSyncComponentsFactoryMock* factory = new ProfileSyncComponentsFactoryMock(); service_.reset(new TestProfileSyncService( factory, profile_.get(), signin, ProfileSyncService::AUTO_START, false, callback)); pref_sync_service_ = reinterpret_cast( prefs_->GetSyncableService()); if (!pref_sync_service_) return false; EXPECT_CALL(*factory, GetSyncableServiceForType(syncable::PREFERENCES)). WillOnce(Return(pref_sync_service_->AsWeakPtr())); EXPECT_CALL(*factory, CreateDataTypeManager(_, _)). WillOnce(ReturnNewDataTypeManager()); dtc_ = new UIDataTypeController(syncable::PREFERENCES, factory, profile_.get(), service_.get()); EXPECT_CALL(*factory, CreateSharedChangeProcessor()). WillOnce(Return(new SharedChangeProcessor())); EXPECT_CALL(*factory, CreateGenericChangeProcessor(_, _, _)). WillOnce(CreateAndSaveChangeProcessor(&change_processor_)); service_->RegisterDataTypeController(dtc_); TokenServiceFactory::GetForProfile(profile_.get())->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(FROM_HERE, service_->GetUserShare()); sync_api::ReadNode node(&trans); if (node.InitByClientTagLookup(syncable::PREFERENCES, name) != sync_api::BaseNode::INIT_OK) { return NULL; } const sync_pb::PreferenceSpecifics& specifics( node.GetEntitySpecifics().preference()); return base::JSONReader::Read(specifics.value()); } int64 WriteSyncedValue(const std::string& name, const Value& value, sync_api::WriteNode* node) { SyncData sync_data; if (!PrefModelAssociator::CreatePrefSyncData(name, value, &sync_data)) { return sync_api::kInvalidId; } node->SetEntitySpecifics(sync_data.GetSpecifics()); return node->GetId(); } bool IsSynced(const std::string& pref_name) { return pref_sync_service_->registered_preferences().count(pref_name) > 0; } std::string ValueString(const Value& value) { std::string serialized; JSONStringValueSerializer json(&serialized); json.Serialize(value); return serialized; } scoped_ptr profile_; TestingPrefService* prefs_; UIDataTypeController* dtc_; PrefModelAssociator* pref_sync_service_; GenericChangeProcessor* 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 AddPreferenceEntriesHelper { public: AddPreferenceEntriesHelper(ProfileSyncServicePreferenceTest* test, const PreferenceValues& entries) : ALLOW_THIS_IN_INITIALIZER_LIST(callback_( base::Bind( &AddPreferenceEntriesHelper::AddPreferenceEntriesCallback, base::Unretained(this), test, entries))), success_(false) { } const base::Closure& callback() const { return callback_; } bool success() { return success_; } private: void AddPreferenceEntriesCallback(ProfileSyncServicePreferenceTest* test, const PreferenceValues& entries) { 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; } base::Closure callback_; bool success_; }; TEST_F(ProfileSyncServicePreferenceTest, CreatePrefSyncData) { prefs_->SetString(prefs::kHomePage, example_url0_); CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); const PrefService::Preference* pref = prefs_->FindPreference(prefs::kHomePage); SyncData sync_data; EXPECT_TRUE(PrefModelAssociator::CreatePrefSyncData(pref->name(), *pref->GetValue(), &sync_data)); EXPECT_EQ(std::string(prefs::kHomePage), sync_data.GetTag()); const sync_pb::PreferenceSpecifics& specifics(sync_data.GetSpecifics(). preference()); EXPECT_EQ(std::string(prefs::kHomePage), specifics.name()); scoped_ptr value(base::JSONReader::Read(specifics.value())); EXPECT_TRUE(pref->GetValue()->Equals(value.get())); } TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationDoNotSyncDefaults) { const PrefService::Preference* pref = prefs_->FindPreference(prefs::kHomePage); EXPECT_TRUE(pref->IsDefaultValue()); CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.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_)); } CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr 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::kGlobalDefaultCharset] = Value::CreateStringValue(non_default_charset_value_); AddPreferenceEntriesHelper helper(this, cloud_data); ASSERT_TRUE(StartSyncService(helper.callback(), false)); ASSERT_TRUE(helper.success()); scoped_ptr value(GetSyncedValue(prefs::kHomePage)); ASSERT_TRUE(value.get()); std::string string_value; EXPECT_TRUE(static_cast(value.get())-> GetAsString(&string_value)); EXPECT_EQ(example_url1_, string_value); EXPECT_EQ(example_url1_, prefs_->GetString(prefs::kHomePage)); scoped_ptr 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::kGlobalDefaultCharset)); ASSERT_TRUE(value.get()); EXPECT_TRUE(static_cast(value.get())-> GetAsString(&string_value)); EXPECT_EQ(non_default_charset_value_, string_value); EXPECT_EQ(non_default_charset_value_, prefs_->GetString(prefs::kGlobalDefaultCharset)); STLDeleteValues(&cloud_data); } TEST_F(ProfileSyncServicePreferenceTest, FailModelAssociation) { ASSERT_TRUE(StartSyncService(base::Closure(), true)); EXPECT_TRUE(service_->unrecoverable_error_detected()); } TEST_F(ProfileSyncServicePreferenceTest, UpdatedPreferenceWithDefaultValue) { const PrefService::Preference* pref = prefs_->FindPreference(prefs::kHomePage); EXPECT_TRUE(pref->IsDefaultValue()); CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr expected(Value::CreateStringValue(example_url0_)); profile_->GetPrefs()->Set(prefs::kHomePage, *expected); scoped_ptr 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_); CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr expected(Value::CreateStringValue(example_url1_)); profile_->GetPrefs()->Set(prefs::kHomePage, *expected); scoped_ptr 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_); CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr expected(Value::CreateStringValue(example_url1_)); int64 node_id = SetSyncedValue(prefs::kHomePage, *expected); ASSERT_NE(node_id, sync_api::kInvalidId); { sync_api::WriteTransaction trans(FROM_HERE, service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList( node_id, ChangeRecord::ACTION_UPDATE)); } change_processor_->CommitChangesFromSyncModel(); const Value& actual = GetPreferenceValue(prefs::kHomePage); EXPECT_TRUE(expected->Equals(&actual)); } TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeActionAdd) { CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr expected(Value::CreateStringValue(example_url0_)); int64 node_id = SetSyncedValue(prefs::kHomePage, *expected); ASSERT_NE(node_id, sync_api::kInvalidId); { sync_api::WriteTransaction trans(FROM_HERE, service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList( node_id, ChangeRecord::ACTION_ADD)); } change_processor_->CommitChangesFromSyncModel(); const Value& actual = GetPreferenceValue(prefs::kHomePage); EXPECT_TRUE(expected->Equals(&actual)); EXPECT_EQ(1U, pref_sync_service_->registered_preferences().count(prefs::kHomePage)); } TEST_F(ProfileSyncServicePreferenceTest, UpdatedSyncNodeUnknownPreference) { CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr expected(Value::CreateStringValue(example_url0_)); int64 node_id = SetSyncedValue("unknown preference", *expected); ASSERT_NE(node_id, sync_api::kInvalidId); { sync_api::WriteTransaction trans(FROM_HERE, service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList( node_id, ChangeRecord::ACTION_UPDATE)); } change_processor_->CommitChangesFromSyncModel(); // 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 managed_value( Value::CreateStringValue("http://example.com")); prefs_->SetManagedPref(prefs::kHomePage, managed_value->DeepCopy()); CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); // Changing the homepage preference should not sync anything. scoped_ptr user_value( Value::CreateStringValue("http://chromium..com")); prefs_->SetUserPref(prefs::kHomePage, user_value->DeepCopy()); EXPECT_EQ(NULL, GetSyncedValue(prefs::kHomePage)); // An incoming sync transaction should change the user value, not the managed // value. scoped_ptr sync_value( Value::CreateStringValue("http://crbug.com")); int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value); ASSERT_NE(node_id, sync_api::kInvalidId); { sync_api::WriteTransaction trans(FROM_HERE, service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList( node_id, ChangeRecord::ACTION_UPDATE)); } change_processor_->CommitChangesFromSyncModel(); EXPECT_TRUE(managed_value->Equals(prefs_->GetManagedPref(prefs::kHomePage))); EXPECT_TRUE(sync_value->Equals(prefs_->GetUserPref(prefs::kHomePage))); } TEST_F(ProfileSyncServicePreferenceTest, DynamicManagedPreferences) { CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr initial_value( Value::CreateStringValue("http://example.com/initial")); profile_->GetPrefs()->Set(prefs::kHomePage, *initial_value); scoped_ptr actual(GetSyncedValue(prefs::kHomePage)); EXPECT_TRUE(initial_value->Equals(actual.get())); // Switch kHomePage to managed and set a different value. scoped_ptr managed_value( Value::CreateStringValue("http://example.com/managed")); profile_->GetTestingPrefService()->SetManagedPref( prefs::kHomePage, managed_value->DeepCopy()); // The pref value should be the one dictated by policy. EXPECT_TRUE(managed_value->Equals(&GetPreferenceValue(prefs::kHomePage))); // Switch kHomePage back to unmanaged. profile_->GetTestingPrefService()->RemoveManagedPref(prefs::kHomePage); // The original value should be picked up. EXPECT_TRUE(initial_value->Equals(&GetPreferenceValue(prefs::kHomePage))); } TEST_F(ProfileSyncServicePreferenceTest, DynamicManagedPreferencesWithSyncChange) { CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); scoped_ptr initial_value( Value::CreateStringValue("http://example.com/initial")); profile_->GetPrefs()->Set(prefs::kHomePage, *initial_value); scoped_ptr actual(GetSyncedValue(prefs::kHomePage)); EXPECT_TRUE(initial_value->Equals(actual.get())); // Switch kHomePage to managed and set a different value. scoped_ptr managed_value( Value::CreateStringValue("http://example.com/managed")); profile_->GetTestingPrefService()->SetManagedPref( prefs::kHomePage, managed_value->DeepCopy()); // Change the sync value. scoped_ptr sync_value( Value::CreateStringValue("http://example.com/sync")); int64 node_id = SetSyncedValue(prefs::kHomePage, *sync_value); ASSERT_NE(node_id, sync_api::kInvalidId); { sync_api::WriteTransaction trans(FROM_HERE, service_->GetUserShare()); change_processor_->ApplyChangesFromSyncModel( &trans, ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList( node_id, ChangeRecord::ACTION_ADD)); } change_processor_->CommitChangesFromSyncModel(); // 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))); } TEST_F(ProfileSyncServicePreferenceTest, DynamicManagedDefaultPreferences) { const PrefService::Preference* pref = prefs_->FindPreference(prefs::kHomePage); EXPECT_TRUE(pref->IsDefaultValue()); CreateRootHelper create_root(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(create_root.callback(), false)); ASSERT_TRUE(create_root.success()); EXPECT_TRUE(IsSynced(prefs::kHomePage)); EXPECT_TRUE(pref->IsDefaultValue()); EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL); // Switch kHomePage to managed and set a different value. scoped_ptr managed_value( Value::CreateStringValue("http://example.com/managed")); profile_->GetTestingPrefService()->SetManagedPref( prefs::kHomePage, managed_value->DeepCopy()); // The pref value should be the one dictated by policy. EXPECT_TRUE(managed_value->Equals(&GetPreferenceValue(prefs::kHomePage))); EXPECT_FALSE(pref->IsDefaultValue()); // There should be no synced value. EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL); // Switch kHomePage back to unmanaged. profile_->GetTestingPrefService()->RemoveManagedPref(prefs::kHomePage); // The original value should be picked up. EXPECT_TRUE(pref->IsDefaultValue()); // There should still be no synced value. EXPECT_TRUE(GetSyncedValue(prefs::kHomePage) == NULL); }