| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/sync_preferences/pref_service_syncable.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_string_value_serializer.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_notifier_impl.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/prefs/testing_pref_store.h" |
| #include "components/signin/public/base/signin_switches.h" |
| #include "components/sync/base/client_tag_hash.h" |
| #include "components/sync/base/data_type.h" |
| #include "components/sync/base/features.h" |
| #include "components/sync/model/sync_change.h" |
| #include "components/sync/model/sync_change_processor.h" |
| #include "components/sync/model/sync_data.h" |
| #include "components/sync/model/syncable_service.h" |
| #include "components/sync/protocol/entity_specifics.pb.h" |
| #include "components/sync/protocol/preference_specifics.pb.h" |
| #include "components/sync/test/test_sync_service.h" |
| #include "components/sync_preferences/pref_model_associator.h" |
| #include "components/sync_preferences/pref_model_associator_client.h" |
| #include "components/sync_preferences/pref_service_syncable_factory.h" |
| #include "components/sync_preferences/pref_service_syncable_observer.h" |
| #include "components/sync_preferences/syncable_prefs_database.h" |
| #include "components/sync_preferences/synced_pref_observer.h" |
| #include "components/sync_preferences/test_syncable_prefs_database.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #endif |
| |
| using syncer::DataType; |
| using syncer::DataTypeSet; |
| using syncer::SyncChange; |
| using syncer::SyncData; |
| using testing::Eq; |
| using testing::IsEmpty; |
| using testing::Matches; |
| using testing::NotNull; |
| using testing::UnorderedElementsAre; |
| using user_prefs::PrefRegistrySyncable; |
| |
| namespace sync_preferences { |
| |
| namespace { |
| |
| const char kExampleUrl0[] = "http://example.com/0"; |
| const char kExampleUrl1[] = "http://example.com/1"; |
| const char kExampleUrl2[] = "http://example.com/2"; |
| const char kStringPrefName[] = "string_pref_name"; |
| const char kListPrefName[] = "list_pref_name"; |
| const char kMergeableListPrefName[] = "mergeable.list_pref_name"; |
| const char kMergeableDictPrefName[] = "mergeable.dict_pref_name"; |
| const char kUnsyncedPreferenceName[] = "nonsense_pref_name"; |
| const char kUnsyncedPreferenceDefaultValue[] = "default"; |
| const char kDefaultCharsetPrefName[] = "default_charset"; |
| const char kNonDefaultCharsetValue[] = "foo"; |
| const char kDefaultCharsetValue[] = "utf-8"; |
| const char kBrowserPrefName[] = "browser_pref"; |
| const char kBrowserPriorityPrefName[] = "browser_priority_pref"; |
| const char kAlwaysSyncingPriorityPrefName[] = "always_syncing_priority_pref"; |
| #if BUILDFLAG(IS_CHROMEOS) |
| const char kOsPrefName[] = "os_pref"; |
| const char kOsPriorityPrefName[] = "os_priority_pref"; |
| #endif |
| |
| // Assigning an id of 0 to all the test prefs. |
| const TestSyncablePrefsDatabase::PrefsMap kSyncablePrefsDatabase = { |
| {kStringPrefName, |
| {1, syncer::PREFERENCES, PrefSensitivity::kNone, MergeBehavior::kNone}}, |
| {kListPrefName, |
| {2, syncer::PREFERENCES, PrefSensitivity::kNone, MergeBehavior::kNone}}, |
| {kMergeableListPrefName, |
| {3, syncer::PREFERENCES, PrefSensitivity::kNone, |
| MergeBehavior::kMergeableListWithRewriteOnUpdate}}, |
| {kMergeableDictPrefName, |
| {4, syncer::PREFERENCES, PrefSensitivity::kNone, |
| MergeBehavior::kMergeableDict}}, |
| {kDefaultCharsetPrefName, |
| {5, syncer::PREFERENCES, PrefSensitivity::kNone, MergeBehavior::kNone}}, |
| {kBrowserPriorityPrefName, |
| {6, syncer::PRIORITY_PREFERENCES, PrefSensitivity::kNone, |
| MergeBehavior::kNone}}, |
| {kBrowserPrefName, |
| {7, syncer::PREFERENCES, PrefSensitivity::kNone, MergeBehavior::kNone}}, |
| {kBrowserPriorityPrefName, |
| {8, syncer::PRIORITY_PREFERENCES, PrefSensitivity::kNone, |
| MergeBehavior::kNone}}, |
| #if BUILDFLAG(IS_CHROMEOS) |
| {kOsPrefName, |
| {9, syncer::OS_PREFERENCES, PrefSensitivity::kNone, MergeBehavior::kNone}}, |
| {kOsPriorityPrefName, |
| {10, syncer::OS_PRIORITY_PREFERENCES, PrefSensitivity::kNone, |
| MergeBehavior::kNone}}, |
| #endif |
| {kAlwaysSyncingPriorityPrefName, |
| {11, syncer::PRIORITY_PREFERENCES, |
| PrefSensitivity::kExemptFromUserControlWhileSignedIn, |
| MergeBehavior::kNone}}, |
| }; |
| |
| // Searches for a preference matching `name` and, if specified,`change_type`, |
| // within `list`. Returns the value of the first matching pref, or nullopt if |
| // none is found. |
| std::optional<base::Value> FindValue( |
| const std::string& name, |
| const syncer::SyncChangeList& list, |
| std::optional<syncer::SyncChange::SyncChangeType> change_type = |
| std::nullopt) { |
| for (const SyncChange& change : list) { |
| if ((!change_type || change.change_type() == *change_type) && |
| change.sync_data().GetClientTagHash() == |
| syncer::ClientTagHash::FromUnhashed(syncer::PREFERENCES, name)) { |
| return base::JSONReader::Read( |
| change.sync_data().GetSpecifics().preference().value(), |
| base::JSON_PARSE_CHROMIUM_EXTENSIONS); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| constexpr DataTypeSet kAllPreferenceDataTypes = { |
| syncer::PREFERENCES, syncer::PRIORITY_PREFERENCES, syncer::OS_PREFERENCES, |
| syncer::OS_PRIORITY_PREFERENCES}; |
| |
| MATCHER_P(MatchesDataType, data_type, "") { |
| const syncer::SyncChange& sync_change = arg; |
| return Matches(data_type)(sync_change.sync_data().GetDataType()); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| class TestSyncProcessorStub : public syncer::SyncChangeProcessor { |
| public: |
| explicit TestSyncProcessorStub(syncer::SyncChangeList* output) |
| : output_(output) {} |
| |
| std::optional<syncer::ModelError> ProcessSyncChanges( |
| const base::Location& from_here, |
| const syncer::SyncChangeList& change_list) override { |
| if (output_) { |
| output_->insert(output_->end(), change_list.begin(), change_list.end()); |
| } |
| if (fail_next_) { |
| fail_next_ = false; |
| return syncer::ModelError(FROM_HERE, |
| syncer::ModelError::Type::kGenericTestError); |
| } |
| return std::nullopt; |
| } |
| |
| void FailNextProcessSyncChanges() { fail_next_ = true; } |
| |
| private: |
| raw_ptr<syncer::SyncChangeList> output_; |
| bool fail_next_ = false; |
| }; |
| |
| class TestSyncedPrefObserver : public SyncedPrefObserver { |
| public: |
| TestSyncedPrefObserver() = default; |
| ~TestSyncedPrefObserver() = default; |
| |
| void OnSyncedPrefChanged(std::string_view path, bool from_sync) override { |
| last_pref_ = std::string(path); |
| changed_count_++; |
| } |
| |
| void OnStartedSyncing(std::string_view path) override { |
| synced_pref_ = std::string(path); |
| sync_started_count_++; |
| } |
| |
| std::string last_pref_; |
| int changed_count_ = 0; |
| |
| std::string synced_pref_; |
| int sync_started_count_ = 0; |
| }; |
| |
| class TestPrefServiceSyncableObserver : public PrefServiceSyncableObserver { |
| public: |
| TestPrefServiceSyncableObserver() = default; |
| ~TestPrefServiceSyncableObserver() override = default; |
| |
| void OnIsSyncingChanged() override { |
| if (sync_pref_observer_ && sync_pref_observer_->sync_started_count_ > 0) { |
| is_syncing_changed_ = true; |
| } |
| } |
| |
| void SetSyncedPrefObserver(const TestSyncedPrefObserver* sync_pref_observer) { |
| sync_pref_observer_ = sync_pref_observer; |
| } |
| |
| bool is_syncing_changed() { return is_syncing_changed_; } |
| |
| private: |
| bool is_syncing_changed_ = false; |
| raw_ptr<const TestSyncedPrefObserver> sync_pref_observer_ = nullptr; |
| }; |
| |
| syncer::SyncChange MakeRemoteChange(const std::string& name, |
| base::ValueView value, |
| SyncChange::SyncChangeType change_type, |
| syncer::DataType data_type) { |
| std::string serialized; |
| JSONStringValueSerializer json(&serialized); |
| bool success = json.Serialize(value); |
| DCHECK(success); |
| sync_pb::EntitySpecifics entity; |
| sync_pb::PreferenceSpecifics* pref = |
| PrefModelAssociator::GetMutableSpecifics(data_type, &entity); |
| pref->set_name(name); |
| pref->set_value(serialized); |
| return syncer::SyncChange( |
| FROM_HERE, change_type, |
| syncer::SyncData::CreateRemoteData( |
| entity, syncer::ClientTagHash::FromUnhashed(data_type, name))); |
| } |
| |
| // Creates a SyncChange for data type |PREFERENCES|. |
| syncer::SyncChange MakeRemoteChange(const std::string& name, |
| base::ValueView value, |
| SyncChange::SyncChangeType type) { |
| return MakeRemoteChange(name, value, type, syncer::DataType::PREFERENCES); |
| } |
| |
| // Creates SyncData for a remote pref change. |
| SyncData CreateRemoteSyncData(const std::string& name, base::ValueView value) { |
| std::string serialized; |
| JSONStringValueSerializer json(&serialized); |
| EXPECT_TRUE(json.Serialize(value)); |
| sync_pb::EntitySpecifics one; |
| sync_pb::PreferenceSpecifics* pref_one = one.mutable_preference(); |
| pref_one->set_name(name); |
| pref_one->set_value(serialized); |
| return SyncData::CreateRemoteData( |
| one, |
| syncer::ClientTagHash::FromUnhashed(syncer::DataType::PREFERENCES, name)); |
| } |
| |
| class PrefServiceSyncableTest : public testing::Test { |
| public: |
| PrefServiceSyncableTest() = default; |
| |
| void SetUp() override { |
| prefs_.registry()->RegisterStringPref(kUnsyncedPreferenceName, |
| kUnsyncedPreferenceDefaultValue); |
| prefs_.registry()->RegisterStringPref( |
| kStringPrefName, std::string(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| prefs_.registry()->RegisterListPref( |
| kListPrefName, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| prefs_.registry()->RegisterStringPref( |
| kDefaultCharsetPrefName, kDefaultCharsetValue, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| |
| pref_sync_service_ = static_cast<PrefModelAssociator*>( |
| prefs_.GetSyncableService(syncer::PREFERENCES)); |
| ASSERT_TRUE(pref_sync_service_); |
| } |
| |
| void AddToRemoteDataList(const std::string& name, |
| base::ValueView value, |
| syncer::SyncDataList* out) { |
| out->push_back(CreateRemoteSyncData(name, value)); |
| } |
| |
| void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data, |
| syncer::SyncChangeList* output) { |
| std::optional<syncer::ModelError> error = |
| pref_sync_service_->MergeDataAndStartSyncing( |
| syncer::PREFERENCES, initial_data, |
| std::make_unique<TestSyncProcessorStub>(output)); |
| EXPECT_FALSE(error.has_value()); |
| } |
| |
| void InitWithNoSyncData() { |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), nullptr); |
| } |
| |
| const base::Value& GetPreferenceValue(const std::string& name) { |
| const PrefService::Preference* preference = |
| prefs_.FindPreference(name.c_str()); |
| return *preference->GetValue(); |
| } |
| |
| bool IsRegistered(const std::string& pref_name) { |
| return pref_sync_service_->IsPrefRegistered(pref_name.c_str()); |
| } |
| |
| PrefService* GetPrefs() { return &prefs_; } |
| TestingPrefServiceSyncable* GetTestingPrefService() { return &prefs_; } |
| |
| protected: |
| TestingPrefServiceSyncable prefs_; |
| |
| raw_ptr<PrefModelAssociator> pref_sync_service_ = nullptr; |
| }; |
| |
| TEST_F(PrefServiceSyncableTest, CreatePrefSyncData) { |
| prefs_.SetString(kStringPrefName, kExampleUrl0); |
| |
| const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); |
| syncer::SyncData sync_data; |
| EXPECT_TRUE(pref_sync_service_->CreatePrefSyncData( |
| pref->name(), *pref->GetValue(), &sync_data)); |
| EXPECT_EQ( |
| syncer::ClientTagHash::FromUnhashed(syncer::PREFERENCES, kStringPrefName), |
| sync_data.GetClientTagHash()); |
| const sync_pb::PreferenceSpecifics& specifics( |
| sync_data.GetSpecifics().preference()); |
| EXPECT_EQ(std::string(kStringPrefName), specifics.name()); |
| |
| std::optional<base::Value> value = base::JSONReader::Read( |
| specifics.value(), base::JSON_PARSE_CHROMIUM_EXTENSIONS); |
| EXPECT_EQ(*pref->GetValue(), *value); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, ModelAssociationDoNotSyncDefaults) { |
| const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); |
| EXPECT_TRUE(pref->IsDefaultValue()); |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| |
| EXPECT_TRUE(IsRegistered(kStringPrefName)); |
| EXPECT_TRUE(pref->IsDefaultValue()); |
| EXPECT_FALSE(FindValue(kStringPrefName, out)); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, ModelAssociationEmptyCloud) { |
| prefs_.SetString(kStringPrefName, kExampleUrl0); |
| { |
| ScopedListPrefUpdate update(GetPrefs(), kListPrefName); |
| update->Append(kExampleUrl0); |
| update->Append(kExampleUrl1); |
| } |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| |
| std::optional<base::Value> value(FindValue(kStringPrefName, out)); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(GetPreferenceValue(kStringPrefName), *value); |
| value = FindValue(kListPrefName, out); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(GetPreferenceValue(kListPrefName), *value); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, ModelAssociationCloudHasData) { |
| prefs_.SetString(kStringPrefName, kExampleUrl0); |
| { |
| ScopedListPrefUpdate update(GetPrefs(), kListPrefName); |
| update->Append(kExampleUrl0); |
| } |
| |
| syncer::SyncDataList in; |
| syncer::SyncChangeList out; |
| AddToRemoteDataList(kStringPrefName, base::Value(kExampleUrl1), &in); |
| auto urls_to_restore = base::Value::List().Append(kExampleUrl1); |
| AddToRemoteDataList(kListPrefName, urls_to_restore, &in); |
| AddToRemoteDataList(kDefaultCharsetPrefName, |
| base::Value(kNonDefaultCharsetValue), &in); |
| InitWithSyncDataTakeOutput(in, &out); |
| |
| ASSERT_FALSE(FindValue(kStringPrefName, out)); |
| ASSERT_FALSE(FindValue(kDefaultCharsetPrefName, out)); |
| |
| EXPECT_EQ(kExampleUrl1, prefs_.GetString(kStringPrefName)); |
| |
| // No associator client is registered, so lists and dictionaries should not |
| // get merged (remote write wins). |
| auto expected_urls = base::Value::List().Append(kExampleUrl1); |
| EXPECT_FALSE(FindValue(kListPrefName, out)); |
| EXPECT_EQ(GetPreferenceValue(kListPrefName), expected_urls); |
| EXPECT_EQ(kNonDefaultCharsetValue, prefs_.GetString(kDefaultCharsetPrefName)); |
| } |
| |
| // Verifies that the implementation gracefully handles an initial remote sync |
| // data of wrong type. The local version should not get modified in these cases. |
| TEST_F(PrefServiceSyncableTest, ModelAssociationWithDataTypeMismatch) { |
| prefs_.SetString(kStringPrefName, kExampleUrl0); |
| |
| syncer::SyncDataList in; |
| base::Value remote_int_value(123); |
| AddToRemoteDataList(kStringPrefName, remote_int_value, &in); |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(in, &out); |
| EXPECT_THAT(out, IsEmpty()); |
| EXPECT_THAT(prefs_.GetString(kStringPrefName), Eq(kExampleUrl0)); |
| } |
| |
| class TestPrefModelAssociatorClient : public PrefModelAssociatorClient { |
| public: |
| TestPrefModelAssociatorClient() |
| : syncable_prefs_database_(kSyncablePrefsDatabase) {} |
| |
| TestPrefModelAssociatorClient(const TestPrefModelAssociatorClient&) = delete; |
| TestPrefModelAssociatorClient& operator=( |
| const TestPrefModelAssociatorClient&) = delete; |
| |
| // PrefModelAssociatorClient implementation. |
| base::Value MaybeMergePreferenceValues( |
| std::string_view pref_name, |
| const base::Value& local_value, |
| const base::Value& server_value) const override { |
| return base::Value(); |
| } |
| |
| private: |
| ~TestPrefModelAssociatorClient() override = default; |
| |
| const SyncablePrefsDatabase& GetSyncablePrefsDatabase() const override { |
| return syncable_prefs_database_; |
| } |
| |
| TestSyncablePrefsDatabase syncable_prefs_database_; |
| }; |
| |
| class PrefServiceSyncableMergeTest : public testing::Test { |
| public: |
| PrefServiceSyncableMergeTest() |
| : prefs_( |
| std::unique_ptr<PrefNotifierImpl>(pref_notifier_), |
| std::make_unique<PrefValueStore>(managed_prefs_.get(), |
| new TestingPrefStore, |
| new TestingPrefStore, |
| new TestingPrefStore, |
| new TestingPrefStore, |
| user_prefs_.get(), |
| pref_registry_->defaults().get(), |
| pref_notifier_), |
| user_prefs_, |
| pref_registry_, |
| client_, |
| /*read_error_callback=*/base::DoNothing(), |
| /*async=*/false) {} |
| |
| void SetUp() override { |
| pref_registry_->RegisterStringPref(kUnsyncedPreferenceName, |
| kUnsyncedPreferenceDefaultValue); |
| pref_registry_->RegisterStringPref( |
| kStringPrefName, std::string(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| pref_registry_->RegisterListPref( |
| kMergeableListPrefName, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| pref_registry_->RegisterDictionaryPref( |
| kMergeableDictPrefName, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| pref_registry_->RegisterStringPref( |
| kDefaultCharsetPrefName, kDefaultCharsetValue, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| |
| pref_sync_service_ = static_cast<PrefModelAssociator*>( |
| prefs_.GetSyncableService(syncer::PREFERENCES)); |
| ASSERT_THAT(pref_sync_service_, NotNull()); |
| } |
| |
| syncer::SyncChange MakeRemoteChange(const std::string& name, |
| base::ValueView value, |
| SyncChange::SyncChangeType type) { |
| std::string serialized; |
| JSONStringValueSerializer json(&serialized); |
| CHECK(json.Serialize(value)); |
| sync_pb::EntitySpecifics entity; |
| sync_pb::PreferenceSpecifics* pref_one = entity.mutable_preference(); |
| pref_one->set_name(name); |
| pref_one->set_value(serialized); |
| return syncer::SyncChange(FROM_HERE, type, |
| syncer::SyncData::CreateRemoteData( |
| entity, syncer::ClientTagHash::FromUnhashed( |
| syncer::PREFERENCES, name))); |
| } |
| |
| void AddToRemoteDataList(const std::string& name, |
| base::ValueView value, |
| syncer::SyncDataList* out) { |
| std::string serialized; |
| JSONStringValueSerializer json(&serialized); |
| ASSERT_TRUE(json.Serialize(value)); |
| sync_pb::EntitySpecifics one; |
| sync_pb::PreferenceSpecifics* pref_one = one.mutable_preference(); |
| pref_one->set_name(name); |
| pref_one->set_value(serialized); |
| out->push_back(SyncData::CreateRemoteData( |
| one, syncer::ClientTagHash::FromUnhashed(syncer::PREFERENCES, name))); |
| } |
| |
| void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data, |
| syncer::SyncChangeList* output) { |
| std::optional<syncer::ModelError> error = |
| pref_sync_service_->MergeDataAndStartSyncing( |
| syncer::PREFERENCES, initial_data, |
| std::make_unique<TestSyncProcessorStub>(output)); |
| EXPECT_FALSE(error.has_value()); |
| } |
| |
| const base::Value& GetPreferenceValue(const std::string& name) { |
| const PrefService::Preference* preference = |
| prefs_.FindPreference(name.c_str()); |
| return *preference->GetValue(); |
| } |
| |
| protected: |
| scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry_ = |
| base::MakeRefCounted<user_prefs::PrefRegistrySyncable>(); |
| // Owned by prefs_; |
| const raw_ptr<PrefNotifierImpl, DanglingUntriaged> pref_notifier_ = |
| new PrefNotifierImpl; |
| scoped_refptr<TestingPrefStore> managed_prefs_ = |
| base::MakeRefCounted<TestingPrefStore>(); |
| scoped_refptr<TestingPrefStore> user_prefs_ = |
| base::MakeRefCounted<TestingPrefStore>(); |
| scoped_refptr<TestPrefModelAssociatorClient> client_ = |
| base::MakeRefCounted<TestPrefModelAssociatorClient>(); |
| PrefServiceSyncable prefs_; |
| raw_ptr<PrefModelAssociator> pref_sync_service_ = nullptr; |
| }; |
| |
| TEST_F(PrefServiceSyncableMergeTest, ShouldMergeSelectedListValues) { |
| { |
| ScopedListPrefUpdate update(&prefs_, kMergeableListPrefName); |
| update->Append(kExampleUrl0); |
| update->Append(kExampleUrl1); |
| } |
| |
| auto urls_to_restore = |
| base::Value::List().Append(kExampleUrl1).Append(kExampleUrl2); |
| syncer::SyncDataList in; |
| AddToRemoteDataList(kMergeableListPrefName, urls_to_restore, &in); |
| |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(in, &out); |
| |
| auto expected_urls = base::Value::List() |
| .Append(kExampleUrl1) |
| .Append(kExampleUrl2) |
| .Append(kExampleUrl0); |
| std::optional<base::Value> value(FindValue(kMergeableListPrefName, out)); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, expected_urls) << *value; |
| EXPECT_EQ(GetPreferenceValue(kMergeableListPrefName), expected_urls); |
| } |
| |
| // List preferences have special handling at association time due to our ability |
| // to merge the local and sync value. Make sure the merge logic doesn't merge |
| // managed preferences. |
| TEST_F(PrefServiceSyncableMergeTest, ManagedListPreferences) { |
| // Make the list of urls to restore on startup managed. |
| auto managed_value = |
| base::Value::List().Append(kExampleUrl0).Append(kExampleUrl1); |
| managed_prefs_->SetValue(kMergeableListPrefName, |
| base::Value(managed_value.Clone()), |
| WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| |
| // Set a cloud version. |
| syncer::SyncDataList in; |
| auto urls_to_restore = |
| base::Value::List().Append(kExampleUrl1).Append(kExampleUrl2); |
| AddToRemoteDataList(kMergeableListPrefName, urls_to_restore, &in); |
| |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(in, &out); |
| |
| // Start sync and verify the synced value didn't get merged. |
| EXPECT_FALSE(FindValue(kMergeableListPrefName, out)); |
| |
| // Changing the user-controlled value should sync as usual. |
| auto user_value = base::Value::List().Append("http://chromium.org"); |
| prefs_.SetList(kMergeableListPrefName, user_value.Clone()); |
| std::optional<base::Value> actual = FindValue(kMergeableListPrefName, out); |
| ASSERT_TRUE(actual); |
| // The user-controlled value should be synced, not the managed one! |
| EXPECT_EQ(*actual, user_value); |
| |
| // An incoming sync transaction should change the user value, not the managed |
| // value. |
| auto sync_value = base::Value::List().Append("http://crbug.com"); |
| syncer::SyncChangeList list; |
| list.push_back(MakeRemoteChange(kMergeableListPrefName, sync_value, |
| SyncChange::ACTION_UPDATE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); |
| |
| const base::Value* managed_prefs_result; |
| ASSERT_TRUE( |
| managed_prefs_->GetValue(kMergeableListPrefName, &managed_prefs_result)); |
| EXPECT_EQ(managed_value, *managed_prefs_result); |
| // Get should return the managed value, too. |
| EXPECT_EQ(managed_value, prefs_.GetValue(kMergeableListPrefName)); |
| // Verify the user pref value has the change. |
| EXPECT_EQ(sync_value, *prefs_.GetUserPrefValue(kMergeableListPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableMergeTest, ShouldMergeSelectedDictionaryValues) { |
| { |
| ScopedDictPrefUpdate update(&prefs_, kMergeableDictPrefName); |
| update->Set("my_key1", "my_value1"); |
| update->Set("my_key3", "my_value3"); |
| } |
| |
| auto remote_update = |
| base::Value::Dict().Set("my_key2", base::Value("my_value2")); |
| syncer::SyncDataList in; |
| AddToRemoteDataList(kMergeableDictPrefName, remote_update, &in); |
| |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(in, &out); |
| |
| auto expected_dict = base::Value::Dict() |
| .Set("my_key1", base::Value("my_value1")) |
| .Set("my_key2", base::Value("my_value2")) |
| .Set("my_key3", base::Value("my_value3")); |
| std::optional<base::Value> value(FindValue(kMergeableDictPrefName, out)); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, expected_dict); |
| EXPECT_EQ(GetPreferenceValue(kMergeableDictPrefName), expected_dict); |
| } |
| |
| // TODO(jamescook): In production all prefs are registered before the |
| // PrefServiceSyncable is created. This test should do the same. |
| TEST_F(PrefServiceSyncableMergeTest, KeepPriorityPreferencesSeparately) { |
| pref_registry_->RegisterStringPref( |
| kBrowserPriorityPrefName, "priority-default", |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); |
| |
| syncer::SyncDataList in; |
| // AddToRemoteDataList() produces sync data for non-priority prefs. |
| AddToRemoteDataList(kBrowserPriorityPrefName, |
| base::Value("non-priority-value"), &in); |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(in, &out); |
| EXPECT_THAT(GetPreferenceValue(kBrowserPriorityPrefName).GetString(), |
| Eq("priority-default")); |
| } |
| |
| TEST_F(PrefServiceSyncableMergeTest, ShouldIgnoreUpdatesToNotSyncablePrefs) { |
| syncer::SyncDataList in; |
| AddToRemoteDataList(kUnsyncedPreferenceName, base::Value("remote_value"), |
| &in); |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(in, &out); |
| EXPECT_THAT(GetPreferenceValue(kUnsyncedPreferenceName).GetString(), |
| Eq(kUnsyncedPreferenceDefaultValue)); |
| |
| syncer::SyncChangeList remote_changes; |
| remote_changes.push_back(MakeRemoteChange(kUnsyncedPreferenceName, |
| base::Value("remote_value2"), |
| SyncChange::ACTION_UPDATE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, remote_changes); |
| // The pref isn't synced. |
| EXPECT_FALSE( |
| pref_sync_service_->IsPrefSyncedForTesting(kUnsyncedPreferenceName)); |
| EXPECT_THAT(GetPreferenceValue(kUnsyncedPreferenceName).GetString(), |
| Eq(kUnsyncedPreferenceDefaultValue)); |
| } |
| |
| using PrefServiceSyncableMetricsTest = PrefServiceSyncableMergeTest; |
| |
| TEST_F(PrefServiceSyncableMetricsTest, RecordRemoteIncrementalChange) { |
| constexpr std::string_view kHistogramName = |
| "Sync.SyncablePrefIncomingIncrementalUpdate"; |
| |
| InitWithSyncDataTakeOutput({}, nullptr); |
| |
| base::HistogramTester tester; |
| |
| // Remote incremental updates. |
| syncer::SyncChangeList update; |
| update.push_back(MakeRemoteChange(kStringPrefName, |
| base::Value(base::Value::Type::STRING), |
| SyncChange::ACTION_DELETE)); |
| update.push_back(MakeRemoteChange(kMergeableListPrefName, |
| base::Value(base::Value::Type::LIST), |
| SyncChange::ACTION_ADD)); |
| update.push_back(MakeRemoteChange(kMergeableDictPrefName, |
| base::Value(base::Value::Type::DICT), |
| SyncChange::ACTION_UPDATE)); |
| |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, update); |
| |
| // Updates for the three syncable prefs were recorded. |
| tester.ExpectTotalCount(kHistogramName, /*expected_count=*/3); |
| tester.ExpectBucketCount(kHistogramName, |
| /*sample=*/1, /*expected_count=*/1); |
| tester.ExpectBucketCount(kHistogramName, |
| /*sample=*/3, |
| /*expected_count=*/1); |
| tester.ExpectBucketCount(kHistogramName, |
| /*sample=*/4, /*expected_count=*/1); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, FailModelAssociation) { |
| syncer::SyncChangeList output; |
| TestSyncProcessorStub* stub = new TestSyncProcessorStub(&output); |
| stub->FailNextProcessSyncChanges(); |
| std::optional<syncer::ModelError> error = |
| pref_sync_service_->MergeDataAndStartSyncing( |
| syncer::PREFERENCES, syncer::SyncDataList(), base::WrapUnique(stub)); |
| EXPECT_TRUE(error.has_value()); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, UpdatedPreferenceWithDefaultValue) { |
| const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); |
| EXPECT_TRUE(pref->IsDefaultValue()); |
| |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| out.clear(); |
| |
| base::Value expected(kExampleUrl0); |
| GetPrefs()->Set(kStringPrefName, expected); |
| |
| std::optional<base::Value> actual(FindValue(kStringPrefName, out)); |
| ASSERT_TRUE(actual); |
| EXPECT_EQ(expected, *actual); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, UpdatedPreferenceWithValue) { |
| GetPrefs()->SetString(kStringPrefName, kExampleUrl0); |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| out.clear(); |
| |
| base::Value expected(kExampleUrl1); |
| GetPrefs()->Set(kStringPrefName, expected); |
| |
| std::optional<base::Value> actual(FindValue(kStringPrefName, out)); |
| ASSERT_TRUE(actual); |
| EXPECT_EQ(expected, *actual); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, AddAndUpdatePreference) { |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| |
| base::Value expected_add(kExampleUrl1); |
| GetPrefs()->Set(kStringPrefName, expected_add); |
| |
| // This should have resulted in an ACTION_ADD. |
| std::optional<base::Value> actual_add( |
| FindValue(kStringPrefName, out, syncer::SyncChange::ACTION_ADD)); |
| ASSERT_TRUE(actual_add); |
| EXPECT_EQ(expected_add, *actual_add); |
| |
| base::Value expected_update(kExampleUrl2); |
| GetPrefs()->Set(kStringPrefName, expected_update); |
| |
| // Since a synced value already existed, this time it should have resulted in |
| // an ACTION_UPDATE. |
| std::optional<base::Value> actual_update( |
| FindValue(kStringPrefName, out, syncer::SyncChange::ACTION_UPDATE)); |
| ASSERT_TRUE(actual_update); |
| EXPECT_EQ(expected_update, *actual_update); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, StopAndRestartSync) { |
| { |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| ASSERT_TRUE(out.empty()); |
| |
| base::Value expected_add1(kExampleUrl1); |
| GetPrefs()->Set(kStringPrefName, expected_add1); |
| |
| // This should have resulted in an ACTION_ADD. |
| ASSERT_TRUE(pref_sync_service_->IsPrefSyncedForTesting(kStringPrefName)); |
| std::optional<base::Value> actual_add1( |
| FindValue(kStringPrefName, out, syncer::SyncChange::ACTION_ADD)); |
| ASSERT_TRUE(actual_add1); |
| EXPECT_EQ(expected_add1, *actual_add1); |
| } |
| |
| // Stop sync, clear the pref, then restart sync. |
| pref_sync_service_->StopSyncing(syncer::PREFERENCES); |
| GetPrefs()->ClearPref(kStringPrefName); |
| |
| { |
| syncer::SyncChangeList out2; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out2); |
| ASSERT_TRUE(out2.empty()); |
| |
| // The pref should not be considered synced anymore. |
| EXPECT_FALSE(pref_sync_service_->IsPrefSyncedForTesting(kStringPrefName)); |
| |
| // Set a new value. |
| base::Value expected_add2(kExampleUrl2); |
| GetPrefs()->Set(kStringPrefName, expected_add2); |
| |
| // This should have resulted in an ACTION_ADD again. |
| std::optional<base::Value> actual_add2( |
| FindValue(kStringPrefName, out2, syncer::SyncChange::ACTION_ADD)); |
| ASSERT_TRUE(actual_add2); |
| EXPECT_EQ(expected_add2, *actual_add2); |
| } |
| } |
| |
| TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeActionUpdate) { |
| GetPrefs()->SetString(kStringPrefName, kExampleUrl0); |
| InitWithNoSyncData(); |
| |
| base::Value expected(kExampleUrl1); |
| syncer::SyncChangeList list; |
| list.push_back( |
| MakeRemoteChange(kStringPrefName, expected, SyncChange::ACTION_UPDATE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); |
| |
| const base::Value& actual = GetPreferenceValue(kStringPrefName); |
| EXPECT_EQ(expected, actual); |
| } |
| |
| // Verifies that the implementation gracefully handles a remote update with the |
| // wrong type. The local version should not get modified in these cases. |
| TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeActionUpdateTypeMismatch) { |
| GetPrefs()->SetString(kStringPrefName, kExampleUrl0); |
| InitWithNoSyncData(); |
| |
| base::Value remote_int_value(123); |
| syncer::SyncChangeList remote_changes; |
| remote_changes.push_back(MakeRemoteChange(kStringPrefName, remote_int_value, |
| SyncChange::ACTION_UPDATE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, remote_changes); |
| |
| EXPECT_THAT(prefs_.GetString(kStringPrefName), Eq(kExampleUrl0)); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeActionAdd) { |
| InitWithNoSyncData(); |
| |
| base::Value expected(kExampleUrl0); |
| syncer::SyncChangeList list; |
| list.push_back( |
| MakeRemoteChange(kStringPrefName, expected, SyncChange::ACTION_ADD)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); |
| |
| const base::Value& actual = GetPreferenceValue(kStringPrefName); |
| EXPECT_EQ(expected, actual); |
| EXPECT_TRUE(pref_sync_service_->IsPrefSyncedForTesting(kStringPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeUnknownPreference) { |
| InitWithNoSyncData(); |
| syncer::SyncChangeList list; |
| base::Value expected(kExampleUrl0); |
| list.push_back(MakeRemoteChange("unknown preference", expected, |
| SyncChange::ACTION_UPDATE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); |
| // Nothing interesting happens on the client when it gets an update |
| // of an unknown preference. We just should not crash. |
| } |
| |
| TEST_F(PrefServiceSyncableTest, ManagedPreferences) { |
| // Make the homepage preference managed. |
| base::Value managed_value("http://example.com"); |
| prefs_.SetManagedPref(kStringPrefName, managed_value.Clone()); |
| |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| out.clear(); |
| |
| // Changing the user-controlled value of the preference should still sync as |
| // usual. |
| base::Value user_value("http://chromium.org"); |
| prefs_.SetUserPref(kStringPrefName, user_value.Clone()); |
| std::optional<base::Value> actual = FindValue(kStringPrefName, out); |
| ASSERT_TRUE(actual); |
| // The user-controlled value should be synced, not the managed one! |
| EXPECT_EQ(*actual, user_value); |
| |
| // An incoming sync transaction should change the user value, not the managed |
| // value. |
| base::Value sync_value("http://crbug.com"); |
| syncer::SyncChangeList list; |
| list.push_back( |
| MakeRemoteChange(kStringPrefName, sync_value, SyncChange::ACTION_UPDATE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); |
| |
| EXPECT_EQ(managed_value, *prefs_.GetManagedPref(kStringPrefName)); |
| EXPECT_EQ(sync_value, *prefs_.GetUserPref(kStringPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, DynamicManagedPreferences) { |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| out.clear(); |
| base::Value initial_value("http://example.com/initial"); |
| GetPrefs()->Set(kStringPrefName, initial_value); |
| std::optional<base::Value> actual(FindValue(kStringPrefName, out)); |
| ASSERT_TRUE(actual); |
| EXPECT_EQ(initial_value, *actual); |
| |
| // Switch kHomePage to managed and set a different value. |
| base::Value managed_value("http://example.com/managed"); |
| GetTestingPrefService()->SetManagedPref(kStringPrefName, |
| managed_value.Clone()); |
| |
| // The pref value should be the one dictated by policy. |
| EXPECT_EQ(managed_value, GetPreferenceValue(kStringPrefName)); |
| |
| // Switch kHomePage back to unmanaged. |
| GetTestingPrefService()->RemoveManagedPref(kStringPrefName); |
| |
| // The original value should be picked up. |
| EXPECT_EQ(initial_value, GetPreferenceValue(kStringPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, DynamicManagedPreferencesWithSyncChange) { |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| out.clear(); |
| |
| base::Value initial_value("http://example.com/initial"); |
| GetPrefs()->Set(kStringPrefName, initial_value); |
| std::optional<base::Value> actual(FindValue(kStringPrefName, out)); |
| EXPECT_EQ(initial_value, *actual); |
| |
| // Switch kHomePage to managed and set a different value. |
| base::Value managed_value("http://example.com/managed"); |
| GetTestingPrefService()->SetManagedPref(kStringPrefName, |
| managed_value.Clone()); |
| |
| // Change the sync value. |
| base::Value sync_value("http://example.com/sync"); |
| syncer::SyncChangeList list; |
| list.push_back( |
| MakeRemoteChange(kStringPrefName, sync_value, SyncChange::ACTION_UPDATE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); |
| |
| // The pref value should still be the one dictated by policy. |
| EXPECT_EQ(managed_value, GetPreferenceValue(kStringPrefName)); |
| |
| // Switch kHomePage back to unmanaged. |
| GetTestingPrefService()->RemoveManagedPref(kStringPrefName); |
| |
| // Sync value should be picked up. |
| EXPECT_EQ(sync_value, GetPreferenceValue(kStringPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, DynamicManagedDefaultPreferences) { |
| const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); |
| EXPECT_TRUE(pref->IsDefaultValue()); |
| syncer::SyncChangeList out; |
| InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); |
| |
| EXPECT_TRUE(IsRegistered(kStringPrefName)); |
| EXPECT_TRUE(pref->IsDefaultValue()); |
| EXPECT_FALSE(FindValue(kStringPrefName, out)); |
| out.clear(); |
| |
| // Switch kHomePage to managed and set a different value. |
| base::Value managed_value("http://example.com/managed"); |
| GetTestingPrefService()->SetManagedPref(kStringPrefName, |
| managed_value.Clone()); |
| // The pref value should be the one dictated by policy. |
| EXPECT_EQ(managed_value, GetPreferenceValue(kStringPrefName)); |
| EXPECT_FALSE(pref->IsDefaultValue()); |
| // There should be no synced value. |
| EXPECT_FALSE(FindValue(kStringPrefName, out)); |
| // Switch kHomePage back to unmanaged. |
| GetTestingPrefService()->RemoveManagedPref(kStringPrefName); |
| // The original value should be picked up. |
| EXPECT_TRUE(pref->IsDefaultValue()); |
| // There should still be no synced value. |
| EXPECT_FALSE(FindValue(kStringPrefName, out)); |
| } |
| |
| TEST_F(PrefServiceSyncableTest, DeletePreference) { |
| prefs_.SetString(kStringPrefName, kExampleUrl0); |
| const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); |
| EXPECT_FALSE(pref->IsDefaultValue()); |
| |
| InitWithNoSyncData(); |
| |
| base::Value null_value; |
| syncer::SyncChangeList list; |
| list.push_back( |
| MakeRemoteChange(kStringPrefName, null_value, SyncChange::ACTION_DELETE)); |
| pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); |
| EXPECT_TRUE(pref->IsDefaultValue()); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // The Chrome OS tests exercise pref model association that happens in the |
| // constructor of PrefServiceSyncable. The tests must register prefs first, |
| // then create the PrefServiceSyncable object. The tests live in this file |
| // because they share utility code with the cross-platform tests. |
| class PrefServiceSyncableChromeOsTest : public testing::Test { |
| public: |
| PrefServiceSyncableChromeOsTest() |
| : pref_registry_(base::MakeRefCounted<PrefRegistrySyncable>()), |
| pref_notifier_(new PrefNotifierImpl), |
| user_prefs_(base::MakeRefCounted<TestingPrefStore>()), |
| managed_prefs_(base::MakeRefCounted<TestingPrefStore>()), |
| supervised_user_prefs_(base::MakeRefCounted<TestingPrefStore>()), |
| extension_prefs_(base::MakeRefCounted<TestingPrefStore>()), |
| command_line_prefs_(base::MakeRefCounted<TestingPrefStore>()), |
| recommended_prefs_(base::MakeRefCounted<TestingPrefStore>()), |
| client_(base::MakeRefCounted<TestPrefModelAssociatorClient>()) {} |
| |
| void CreatePrefService() { |
| // Register prefs of various types. |
| pref_registry_->RegisterStringPref(kUnsyncedPreferenceName, std::string()); |
| pref_registry_->RegisterStringPref(kBrowserPrefName, std::string(), |
| PrefRegistrySyncable::SYNCABLE_PREF); |
| pref_registry_->RegisterStringPref( |
| kBrowserPriorityPrefName, std::string(), |
| PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); |
| pref_registry_->RegisterStringPref(kOsPrefName, std::string(), |
| PrefRegistrySyncable::SYNCABLE_OS_PREF); |
| pref_registry_->RegisterStringPref( |
| kOsPriorityPrefName, std::string(), |
| PrefRegistrySyncable::SYNCABLE_OS_PRIORITY_PREF); |
| |
| // Create the PrefServiceSyncable after prefs are registered, which is the |
| // order used in production. |
| prefs_ = std::make_unique<PrefServiceSyncable>( |
| std::unique_ptr<PrefNotifierImpl>(pref_notifier_), |
| std::make_unique<PrefValueStore>( |
| managed_prefs_.get(), supervised_user_prefs_.get(), |
| extension_prefs_.get(), command_line_prefs_.get(), |
| user_prefs_.get(), recommended_prefs_.get(), |
| pref_registry_->defaults().get(), pref_notifier_), |
| user_prefs_, pref_registry_, client_, |
| /*read_error_callback=*/base::DoNothing(), |
| /*async=*/false); |
| } |
| |
| void InitSyncForType(DataType type) { |
| syncer::SyncDataList empty_data; |
| std::optional<syncer::ModelError> error = |
| prefs_->GetSyncableService(type)->MergeDataAndStartSyncing( |
| type, empty_data, std::make_unique<TestSyncProcessorStub>(nullptr)); |
| EXPECT_FALSE(error.has_value()); |
| } |
| |
| void InitSyncForAllTypes() { |
| for (DataType type : kAllPreferenceDataTypes) { |
| InitSyncForType(type); |
| } |
| } |
| |
| DataTypeSet GetRegisteredDataTypes(const std::string& pref_name) { |
| DataTypeSet registered_types; |
| for (DataType type : kAllPreferenceDataTypes) { |
| if (static_cast<PrefModelAssociator*>(prefs_->GetSyncableService(type)) |
| ->IsPrefRegistered(pref_name)) { |
| registered_types.Put(type); |
| } |
| } |
| return registered_types; |
| } |
| |
| SyncData MakeRemoteSyncData(const std::string& name, |
| base::ValueView value, |
| syncer::DataType data_type) { |
| std::string serialized; |
| JSONStringValueSerializer json(&serialized); |
| EXPECT_TRUE(json.Serialize(value)); |
| sync_pb::EntitySpecifics entity; |
| sync_pb::PreferenceSpecifics* pref = |
| PrefModelAssociator::GetMutableSpecifics(data_type, &entity); |
| pref->set_name(name); |
| pref->set_value(serialized); |
| return SyncData::CreateRemoteData( |
| entity, syncer::ClientTagHash::FromUnhashed(data_type, name)); |
| } |
| |
| protected: |
| scoped_refptr<PrefRegistrySyncable> pref_registry_; |
| raw_ptr<PrefNotifierImpl, DanglingUntriaged> |
| pref_notifier_; // Owned by |prefs_|. |
| scoped_refptr<TestingPrefStore> user_prefs_; |
| scoped_refptr<TestingPrefStore> managed_prefs_; |
| scoped_refptr<TestingPrefStore> supervised_user_prefs_; |
| scoped_refptr<TestingPrefStore> extension_prefs_; |
| scoped_refptr<TestingPrefStore> command_line_prefs_; |
| scoped_refptr<TestingPrefStore> recommended_prefs_; |
| scoped_refptr<TestPrefModelAssociatorClient> client_; |
| std::unique_ptr<PrefServiceSyncable> prefs_; |
| }; |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, IsPrefRegistered) { |
| CreatePrefService(); |
| EXPECT_TRUE(GetRegisteredDataTypes(kUnsyncedPreferenceName).empty()); |
| EXPECT_EQ(DataTypeSet({syncer::PREFERENCES}), |
| GetRegisteredDataTypes(kBrowserPrefName)); |
| EXPECT_EQ(DataTypeSet({syncer::PRIORITY_PREFERENCES}), |
| GetRegisteredDataTypes(kBrowserPriorityPrefName)); |
| EXPECT_EQ(DataTypeSet({syncer::OS_PREFERENCES}), |
| GetRegisteredDataTypes(kOsPrefName)); |
| EXPECT_EQ(DataTypeSet({syncer::OS_PRIORITY_PREFERENCES}), |
| GetRegisteredDataTypes(kOsPriorityPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, IsSyncing) { |
| CreatePrefService(); |
| InitSyncForType(syncer::PREFERENCES); |
| EXPECT_TRUE(prefs_->IsSyncing()); |
| EXPECT_FALSE(prefs_->IsPrioritySyncing()); |
| EXPECT_FALSE(prefs_->AreOsPrefsSyncing()); |
| EXPECT_FALSE(prefs_->AreOsPriorityPrefsSyncing()); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, IsPrioritySyncing) { |
| CreatePrefService(); |
| InitSyncForType(syncer::PRIORITY_PREFERENCES); |
| EXPECT_FALSE(prefs_->IsSyncing()); |
| EXPECT_TRUE(prefs_->IsPrioritySyncing()); |
| EXPECT_FALSE(prefs_->AreOsPrefsSyncing()); |
| EXPECT_FALSE(prefs_->AreOsPriorityPrefsSyncing()); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, AreOsPrefsSyncing) { |
| CreatePrefService(); |
| InitSyncForType(syncer::OS_PREFERENCES); |
| EXPECT_FALSE(prefs_->IsSyncing()); |
| EXPECT_FALSE(prefs_->IsPrioritySyncing()); |
| EXPECT_TRUE(prefs_->AreOsPrefsSyncing()); |
| EXPECT_FALSE(prefs_->AreOsPriorityPrefsSyncing()); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, AreOsPriorityPrefsSyncing) { |
| CreatePrefService(); |
| InitSyncForType(syncer::OS_PRIORITY_PREFERENCES); |
| EXPECT_FALSE(prefs_->IsSyncing()); |
| EXPECT_FALSE(prefs_->IsPrioritySyncing()); |
| EXPECT_FALSE(prefs_->AreOsPrefsSyncing()); |
| EXPECT_TRUE(prefs_->AreOsPriorityPrefsSyncing()); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, IsPrefSynced_OsPref) { |
| CreatePrefService(); |
| InitSyncForAllTypes(); |
| auto* associator = static_cast<PrefModelAssociator*>( |
| prefs_->GetSyncableService(syncer::OS_PREFERENCES)); |
| EXPECT_FALSE(associator->IsPrefSyncedForTesting(kOsPrefName)); |
| |
| syncer::SyncChangeList list; |
| list.push_back(MakeRemoteChange(kOsPrefName, base::Value("value"), |
| SyncChange::ACTION_ADD, |
| syncer::OS_PREFERENCES)); |
| associator->ProcessSyncChanges(FROM_HERE, list); |
| EXPECT_TRUE(associator->IsPrefSyncedForTesting(kOsPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, IsPrefSynced_OsPriorityPref) { |
| CreatePrefService(); |
| InitSyncForAllTypes(); |
| auto* associator = static_cast<PrefModelAssociator*>( |
| prefs_->GetSyncableService(syncer::OS_PRIORITY_PREFERENCES)); |
| EXPECT_FALSE(associator->IsPrefSyncedForTesting(kOsPriorityPrefName)); |
| |
| syncer::SyncChangeList list; |
| list.push_back(MakeRemoteChange(kOsPriorityPrefName, base::Value("value"), |
| SyncChange::ACTION_ADD, |
| syncer::OS_PRIORITY_PREFERENCES)); |
| associator->ProcessSyncChanges(FROM_HERE, list); |
| EXPECT_TRUE(associator->IsPrefSyncedForTesting(kOsPriorityPrefName)); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, SyncedPrefObserver_OsPref) { |
| CreatePrefService(); |
| InitSyncForAllTypes(); |
| |
| TestSyncedPrefObserver observer; |
| prefs_->AddSyncedPrefObserver(kOsPrefName, &observer); |
| |
| prefs_->SetString(kOsPrefName, "value"); |
| EXPECT_EQ(kOsPrefName, observer.last_pref_); |
| EXPECT_EQ(1, observer.changed_count_); |
| |
| prefs_->RemoveSyncedPrefObserver(kOsPrefName, &observer); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, SyncedPrefObserver_OsPriorityPref) { |
| CreatePrefService(); |
| InitSyncForAllTypes(); |
| |
| TestSyncedPrefObserver observer; |
| prefs_->AddSyncedPrefObserver(kOsPriorityPrefName, &observer); |
| |
| prefs_->SetString(kOsPriorityPrefName, "value"); |
| EXPECT_EQ(kOsPriorityPrefName, observer.last_pref_); |
| EXPECT_EQ(1, observer.changed_count_); |
| |
| prefs_->RemoveSyncedPrefObserver(kOsPriorityPrefName, &observer); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, |
| UpdatesFromOldClientsAreIgnored_Startup) { |
| CreatePrefService(); |
| TestSyncedPrefObserver observer; |
| prefs_->AddSyncedPrefObserver(kOsPrefName, &observer); |
| |
| // Simulate an old client that has `kOsPrefName` registered as SYNCABLE_PREF |
| // instead of SYNCABLE_OS_PREF. |
| syncer::SyncDataList list; |
| list.push_back(CreateRemoteSyncData(kOsPrefName, base::Value("new_value"))); |
| |
| // Simulate the first sync at startup of the legacy browser prefs DataType. |
| auto* browser_associator = static_cast<PrefModelAssociator*>( |
| prefs_->GetSyncableService(syncer::PREFERENCES)); |
| syncer::SyncChangeList outgoing_changes; |
| browser_associator->MergeDataAndStartSyncing( |
| syncer::PREFERENCES, list, |
| std::make_unique<TestSyncProcessorStub>(&outgoing_changes)); |
| |
| // No outgoing changes were triggered. |
| EXPECT_TRUE(outgoing_changes.empty()); |
| |
| // The value from the old client was not applied. |
| EXPECT_NE("new_value", prefs_->GetString(kOsPrefName)); |
| |
| // The pref is not considered to be syncing, because it still has its default |
| // value. |
| EXPECT_FALSE(browser_associator->IsPrefSyncedForTesting(kOsPrefName)); |
| |
| // Observers were not notified of changes. |
| EXPECT_EQ(0, observer.changed_count_); |
| |
| prefs_->RemoveSyncedPrefObserver(kOsPrefName, &observer); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, |
| UpdatesFromOldClientsAreIgnored_Update) { |
| CreatePrefService(); |
| InitSyncForAllTypes(); |
| TestSyncedPrefObserver observer; |
| prefs_->AddSyncedPrefObserver(kOsPrefName, &observer); |
| |
| syncer::SyncChangeList list; |
| // Simulate an old client that has `kOsPrefName` registered as SYNCABLE_PREF |
| // instead of SYNCABLE_OS_PREF. |
| list.push_back(MakeRemoteChange(kOsPrefName, base::Value("new_value"), |
| SyncChange::ACTION_ADD, syncer::PREFERENCES)); |
| |
| // Simulate a sync update after startup. |
| prefs_->GetSyncableService(syncer::PREFERENCES) |
| ->ProcessSyncChanges(FROM_HERE, list); |
| |
| // Update was not applied. |
| EXPECT_NE("new_value", prefs_->GetString(kOsPrefName)); |
| |
| // Observers were not notified of changes. |
| EXPECT_EQ(0, observer.changed_count_); |
| |
| prefs_->RemoveSyncedPrefObserver(kOsPrefName, &observer); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, |
| SyncedPrefObserver_OsPrefIsChangedFromSync) { |
| CreatePrefService(); |
| prefs_->SetString(kOsPrefName, "default_value"); |
| |
| TestSyncedPrefObserver observer; |
| prefs_->AddSyncedPrefObserver(kOsPrefName, &observer); |
| |
| TestPrefServiceSyncableObserver pref_service_sync_observer; |
| pref_service_sync_observer.SetSyncedPrefObserver(&observer); |
| prefs_->AddObserver(&pref_service_sync_observer); |
| |
| // Simulate that `kOsPrefName` is registered as SYNCABLE_PREF |
| syncer::SyncDataList list; |
| list.push_back(MakeRemoteSyncData(kOsPrefName, base::Value("new_value"), |
| syncer::OS_PREFERENCES)); |
| |
| // Simulate the first sync at startup. |
| syncer::SyncChangeList outgoing_changes; |
| prefs_->GetSyncableService(syncer::OS_PREFERENCES) |
| ->MergeDataAndStartSyncing( |
| syncer::OS_PREFERENCES, list, |
| std::make_unique<TestSyncProcessorStub>(&outgoing_changes)); |
| |
| EXPECT_EQ(kOsPrefName, observer.synced_pref_); |
| EXPECT_EQ(1, observer.sync_started_count_); |
| EXPECT_TRUE(pref_service_sync_observer.is_syncing_changed()); |
| |
| prefs_->RemoveObserver(&pref_service_sync_observer); |
| prefs_->RemoveSyncedPrefObserver(kOsPrefName, &observer); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, |
| SyncedPrefObserver_OsPrefIsNotChangedFromSync) { |
| CreatePrefService(); |
| prefs_->SetString(kOsPrefName, "default_value"); |
| |
| TestSyncedPrefObserver observer; |
| prefs_->AddSyncedPrefObserver(kOsPrefName, &observer); |
| |
| TestPrefServiceSyncableObserver pref_service_sync_observer; |
| pref_service_sync_observer.SetSyncedPrefObserver(&observer); |
| prefs_->AddObserver(&pref_service_sync_observer); |
| |
| // Simulate that `kOsPrefName` is registered as SYNCABLE_PREF |
| syncer::SyncDataList list; |
| list.push_back(MakeRemoteSyncData(kOsPrefName, base::Value("new_value"), |
| syncer::OS_PREFERENCES)); |
| |
| // Simulate the first sync at startup. |
| syncer::SyncChangeList outgoing_changes; |
| prefs_->GetSyncableService(syncer::OS_PREFERENCES) |
| ->MergeDataAndStartSyncing( |
| syncer::OS_PREFERENCES, list, |
| std::make_unique<TestSyncProcessorStub>(&outgoing_changes)); |
| |
| EXPECT_EQ(kOsPrefName, observer.synced_pref_); |
| EXPECT_EQ(1, observer.sync_started_count_); |
| EXPECT_TRUE(pref_service_sync_observer.is_syncing_changed()); |
| |
| prefs_->RemoveObserver(&pref_service_sync_observer); |
| prefs_->RemoveSyncedPrefObserver(kOsPrefName, &observer); |
| } |
| |
| TEST_F(PrefServiceSyncableChromeOsTest, SyncedPrefObserver_EmptyCloud) { |
| CreatePrefService(); |
| prefs_->SetString(kOsPrefName, "new_value"); |
| |
| TestSyncedPrefObserver observer; |
| prefs_->AddSyncedPrefObserver(kOsPrefName, &observer); |
| |
| // Simulate the first sync at startup. |
| syncer::SyncChangeList outgoing_changes; |
| prefs_->GetSyncableService(syncer::OS_PREFERENCES) |
| ->MergeDataAndStartSyncing( |
| syncer::OS_PREFERENCES, syncer::SyncDataList(), |
| std::make_unique<TestSyncProcessorStub>(&outgoing_changes)); |
| |
| EXPECT_EQ("", observer.synced_pref_); |
| EXPECT_EQ(0, observer.sync_started_count_); |
| |
| prefs_->RemoveSyncedPrefObserver(kOsPrefName, &observer); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| class PrefServiceSyncableFactoryTest : public PrefServiceSyncableTest { |
| public: |
| PrefServiceSyncableFactoryTest() { |
| pref_service_syncable_factory_.set_user_prefs(user_prefs_); |
| pref_service_syncable_factory_.SetAccountPrefStore(account_prefs_); |
| } |
| |
| protected: |
| PrefServiceSyncableFactory pref_service_syncable_factory_; |
| scoped_refptr<TestingPrefStore> user_prefs_ = |
| base::MakeRefCounted<TestingPrefStore>(); |
| scoped_refptr<TestingPrefStore> account_prefs_ = |
| base::MakeRefCounted<TestingPrefStore>(); |
| }; |
| |
| TEST_F(PrefServiceSyncableFactoryTest, |
| ShouldCreateSyncServiceWithoutDualLayerStoreIfFeatureDisabled) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndDisableFeature( |
| switches::kEnablePreferencesAccountStorage); |
| auto pref_service = |
| pref_service_syncable_factory_.CreateSyncable(prefs_.registry()); |
| EXPECT_FALSE(static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| EXPECT_FALSE( |
| static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::PRIORITY_PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| #if BUILDFLAG(IS_CHROMEOS) |
| EXPECT_FALSE(static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::OS_PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| EXPECT_FALSE( |
| static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::OS_PRIORITY_PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| #endif |
| } |
| |
| TEST_F(PrefServiceSyncableFactoryTest, |
| ShouldCreateSyncServiceWithDualLayerStoreIfFeatureEnabled) { |
| base::test::ScopedFeatureList feature_list( |
| switches::kEnablePreferencesAccountStorage); |
| auto pref_service = |
| pref_service_syncable_factory_.CreateSyncable(prefs_.registry()); |
| EXPECT_TRUE(static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| EXPECT_TRUE( |
| static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::PRIORITY_PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| #if BUILDFLAG(IS_CHROMEOS) |
| EXPECT_TRUE(static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::OS_PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| EXPECT_TRUE( |
| static_cast<PrefModelAssociator*>( |
| pref_service->GetSyncableService(syncer::OS_PRIORITY_PREFERENCES)) |
| ->IsUsingDualLayerUserPrefStoreForTesting()); |
| #endif |
| } |
| |
| class PrefServiceSyncableFactoryTestWithAlwaysSyncingPrefs |
| : public PrefServiceSyncableFactoryTest { |
| public: |
| PrefServiceSyncableFactoryTestWithAlwaysSyncingPrefs() { |
| feature_list_.InitWithFeatures( |
| {switches::kEnablePreferencesAccountStorage, |
| syncer::kSyncSupportAlwaysSyncingPriorityPreferences}, |
| /*disabled_features=*/{}); |
| pref_service_syncable_factory_.SetPrefModelAssociatorClient(client_); |
| |
| // Register test prefs. |
| prefs_.registry()->RegisterStringPref( |
| kBrowserPrefName, std::string(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| prefs_.registry()->RegisterStringPref( |
| kBrowserPriorityPrefName, std::string(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); |
| prefs_.registry()->RegisterStringPref( |
| kAlwaysSyncingPriorityPrefName, std::string(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| scoped_refptr<TestPrefModelAssociatorClient> client_ = |
| base::MakeRefCounted<TestPrefModelAssociatorClient>(); |
| }; |
| |
| TEST_F(PrefServiceSyncableFactoryTestWithAlwaysSyncingPrefs, |
| ShouldReadPriorityPrefsFromAccountStoreRightAway) { |
| account_prefs_->SetValue(kBrowserPrefName, base::Value("pref"), |
| /*flags=*/0); |
| account_prefs_->SetValue(kBrowserPriorityPrefName, |
| base::Value("priority_pref"), |
| /*flags=*/0); |
| account_prefs_->SetValue(kAlwaysSyncingPriorityPrefName, |
| base::Value("always_syncing_priority_pref"), |
| /*flags=*/0); |
| // Simulate sync enabled in previous run. |
| { |
| std::unique_ptr<PrefServiceSyncable> pref_service = |
| pref_service_syncable_factory_.CreateSyncable(prefs_.registry()); |
| syncer::TestSyncService sync_service; |
| sync_service.GetUserSettings()->SetSelectedTypes( |
| /*sync_everything=*/false, {syncer::UserSelectableType::kPreferences}); |
| pref_service->OnSyncServiceInitialized(&sync_service); |
| sync_service.Shutdown(); |
| } |
| |
| // New browser run. |
| std::unique_ptr<PrefServiceSyncable> pref_service = |
| pref_service_syncable_factory_.CreateSyncable(prefs_.registry()); |
| |
| // No OnSyncServiceInitialized() has been called yet. However, all the account |
| // pref values should be available immediately, even though the user selected |
| // types can't be queried from the SyncService yet. |
| EXPECT_THAT(pref_service->GetUserPrefValue(kBrowserPrefName), |
| testing::Pointee(testing::Eq("pref"))); |
| EXPECT_THAT(pref_service->GetUserPrefValue(kBrowserPriorityPrefName), |
| testing::Pointee(testing::Eq("priority_pref"))); |
| EXPECT_THAT(pref_service->GetUserPrefValue(kAlwaysSyncingPriorityPrefName), |
| testing::Pointee(testing::Eq("always_syncing_priority_pref"))); |
| } |
| |
| // This is not a real-life scenario since account values cannot exist prior to |
| // sync being enabled before. |
| TEST_F(PrefServiceSyncableFactoryTestWithAlwaysSyncingPrefs, |
| ShouldReadPrefsAndAlwaysSyncingPriorityPrefsFromAccountStoreRightAway) { |
| // Fresh browser run. |
| account_prefs_->SetValue(kBrowserPrefName, base::Value("pref"), |
| /*flags=*/0); |
| account_prefs_->SetValue(kBrowserPriorityPrefName, |
| base::Value("priority_pref"), |
| /*flags=*/0); |
| account_prefs_->SetValue(kAlwaysSyncingPriorityPrefName, |
| base::Value("always_syncing_priority_pref"), |
| /*flags=*/0); |
| std::unique_ptr<PrefServiceSyncable> pref_service = |
| pref_service_syncable_factory_.CreateSyncable(prefs_.registry()); |
| |
| // No OnSyncServiceInitialized() has been called yet. Given that this is a |
| // fresh run, user selected types are not available till it can be queried |
| // from the SyncService. In such case, only regular priority prefs account |
| // values are not returned. Account values of non-priority prefs and the |
| // always syncing priority prefs are available right away. |
| EXPECT_THAT(pref_service->GetUserPrefValue(kBrowserPrefName), |
| testing::Pointee(testing::Eq("pref"))); |
| EXPECT_THAT(pref_service->GetUserPrefValue(kAlwaysSyncingPriorityPrefName), |
| testing::Pointee(testing::Eq("always_syncing_priority_pref"))); |
| EXPECT_FALSE(pref_service->GetUserPrefValue(kBrowserPriorityPrefName)); |
| } |
| |
| } // namespace |
| |
| } // namespace sync_preferences |