// 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/time/time.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/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,
                        const base::Value& value) 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, ForkForIncognitoSharesRegisteredPrefTypes) {
  constexpr char kInt64PrefName[] = "int64_pref_name";
  constexpr char kTimePrefName[] = "time_pref_name";
  prefs_.registry()->RegisterInt64Pref(kInt64PrefName, 0);

  scoped_refptr<PrefRegistrySyncable> forked_registry =
      prefs_.registry()->ForkForIncognito();
  prefs_.registry()->RegisterTimePref(kTimePrefName, base::Time());

  EXPECT_EQ(PrefRegistry::RegisteredPrefType::kOther,
            forked_registry->GetRegisteredPrefType(kStringPrefName));
  EXPECT_EQ(PrefRegistry::RegisteredPrefType::kInt64,
            forked_registry->GetRegisteredPrefType(kInt64PrefName));
  EXPECT_EQ(PrefRegistry::RegisteredPrefType::kTime,
            forked_registry->GetRegisteredPrefType(kTimePrefName));
}

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::ListValue().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::ListValue().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_,
                   base::MakeRefCounted<TestingPrefStore>(),
                   base::MakeRefCounted<TestingPrefStore>(),
                   base::MakeRefCounted<TestingPrefStore>(),
                   base::MakeRefCounted<TestingPrefStore>(),
                   user_prefs_,
                   pref_registry_->defaults(),
                   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::ListValue().Append(kExampleUrl1).Append(kExampleUrl2);
  syncer::SyncDataList in;
  AddToRemoteDataList(kMergeableListPrefName, urls_to_restore, &in);

  syncer::SyncChangeList out;
  InitWithSyncDataTakeOutput(in, &out);

  auto expected_urls = base::ListValue()
                           .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::ListValue().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::ListValue().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::ListValue().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::ListValue().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::DictValue().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::DictValue()
                           .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_, supervised_user_prefs_, extension_prefs_,
            command_line_prefs_, user_prefs_, recommended_prefs_,
            pref_registry_->defaults(), 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},
                                   /*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
