blob: 22f6ea3220e34c8eab85b029a891d36ef13e532d [file] [log] [blame]
// 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