// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/check_deref.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/with_feature_override.h"
#include "base/time/time.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data_resolver_factory.h"
#include "chrome/browser/search_engines/template_url_service_test_util.h"
#include "chrome/test/base/testing_profile.h"
#include "components/prefs/testing_pref_service.h"
#include "components/search_engines/keyword_web_data_service.h"
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/search_engines_test_util.h"
#include "components/search_engines/search_terms_data.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_data_util.h"
#include "components/search_engines/template_url_prepopulate_data.h"
#include "components/search_engines/template_url_prepopulate_data_resolver.h"
#include "components/search_engines/template_url_service.h"
#include "components/search_engines/template_url_service_client.h"
#include "components/search_engines/template_url_starter_pack_data.h"
#include "components/search_engines/util.h"
#include "components/signin/public/base/signin_switches.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_data.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/search_engine_specifics.pb.h"
#include "components/sync/test/sync_change_processor_wrapper_for_test.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/url_formatter/url_formatter.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using base::Time;
using testing::AllOf;
using testing::Contains;
using testing::ElementsAre;
using testing::Eq;
using testing::Field;
using testing::IsEmpty;
using testing::IsNull;
using testing::Not;
using testing::NotNull;
using testing::Property;
using testing::ResultOf;

const char kOmniboxScheme[] = "omnibox";

// Extract the GUID from a search engine syncer::SyncData.
std::string GetGUID(const syncer::SyncData& sync_data) {
  return sync_data.GetSpecifics().search_engine().sync_guid();
}

// Extract the URL from a search engine syncer::SyncData.
std::string GetURL(const syncer::SyncData& sync_data) {
  return sync_data.GetSpecifics().search_engine().url();
}

// Extract the keyword from a search engine syncer::SyncData.
std::string GetKeyword(const syncer::SyncData& sync_data) {
  return sync_data.GetSpecifics().search_engine().keyword();
}

// Much like TemplateURLService::CreateSyncDataFromTemplateURL(), but allows the
// caller to override the keyword, URL, or GUID fields with empty strings, in
// order to create custom data that should be handled specially when synced to a
// client.
syncer::SyncData CreateCustomSyncData(const TemplateURL& turl,
                                      const std::u16string& keyword,
                                      const std::string& url,
                                      const std::string& sync_guid,
                                      int prepopulate_id = -1) {
  sync_pb::EntitySpecifics specifics;
  sync_pb::SearchEngineSpecifics* se_specifics =
      specifics.mutable_search_engine();
  se_specifics->set_short_name(base::UTF16ToUTF8(turl.short_name()));
  se_specifics->set_keyword(base::UTF16ToUTF8(keyword));
  se_specifics->set_favicon_url(turl.favicon_url().spec());
  se_specifics->set_url(url);
  se_specifics->set_safe_for_autoreplace(turl.safe_for_autoreplace());
  se_specifics->set_originating_url(turl.originating_url().spec());
  se_specifics->set_date_created(turl.date_created().ToInternalValue());
  se_specifics->set_input_encodings(
      base::JoinString(turl.input_encodings(), ";"));
  se_specifics->set_suggestions_url(turl.suggestions_url());
  se_specifics->set_prepopulate_id(prepopulate_id == -1 ? turl.prepopulate_id()
                                                        : prepopulate_id);
  se_specifics->set_last_modified(turl.last_modified().ToInternalValue());
  se_specifics->set_sync_guid(sync_guid);
  return syncer::SyncData::CreateLocalData(turl.sync_guid(),  // Must be valid!
                                   se_specifics->keyword(), specifics);
}

// TestChangeProcessor --------------------------------------------------------

// Dummy SyncChangeProcessor used to help review what SyncChanges are pushed
// back up to Sync.
class TestChangeProcessor : public syncer::SyncChangeProcessor {
 public:
  TestChangeProcessor();

  TestChangeProcessor(const TestChangeProcessor&) = delete;
  TestChangeProcessor& operator=(const TestChangeProcessor&) = delete;

  ~TestChangeProcessor() override;

  // Store a copy of all the changes passed in so we can examine them later.
  std::optional<syncer::ModelError> ProcessSyncChanges(
      const base::Location& from_here,
      const syncer::SyncChangeList& change_list) override;

  bool contains_guid(const std::string& guid) const {
    return change_map_.count(guid) != 0;
  }

  syncer::SyncChange change_for_guid(const std::string& guid) const {
    DCHECK(contains_guid(guid));
    return change_map_.find(guid)->second;
  }

  size_t change_list_size() { return change_map_.size(); }

  void set_erroneous(bool erroneous) { erroneous_ = erroneous; }

 private:
  // Track the changes received in ProcessSyncChanges.
  std::map<std::string, syncer::SyncChange> change_map_;
  bool erroneous_;
};

TestChangeProcessor::TestChangeProcessor() : erroneous_(false) {
}

TestChangeProcessor::~TestChangeProcessor() = default;

std::optional<syncer::ModelError> TestChangeProcessor::ProcessSyncChanges(
    const base::Location& from_here,
    const syncer::SyncChangeList& change_list) {
  if (erroneous_)
    return syncer::ModelError(FROM_HERE,
                              syncer::ModelError::Type::kGenericTestError);

  change_map_.erase(change_map_.begin(), change_map_.end());
  for (auto iter = change_list.begin(); iter != change_list.end(); ++iter)
    change_map_.emplace(GetGUID(iter->sync_data()), *iter);

  return std::nullopt;
}

class TestTemplateURLServiceClient : public TemplateURLServiceClient {
 public:
  ~TestTemplateURLServiceClient() override = default;

  void Shutdown() override {}
  void SetOwner(TemplateURLService* owner) override {}
  void DeleteAllSearchTermsForKeyword(TemplateURLID id) override {}
  void SetKeywordSearchTermsForURL(const GURL& url,
                                   TemplateURLID id,
                                   const std::u16string& term) override {}
  void AddKeywordGeneratedVisit(const GURL& url) override {}
};

class KeywordsConsumer
    : public WebDataServiceConsumer,
      public base::test::TestFuture<std::vector<TemplateURLData>> {
 public:
  ~KeywordsConsumer() override = default;

  void OnWebDataServiceRequestDone(
      WebDataServiceBase::Handle h,
      std::unique_ptr<WDTypedResult> result) override {
    CHECK_EQ(KEYWORDS_RESULT, result->GetType());
    SetValue(reinterpret_cast<const WDResult<WDKeywordsResult>*>(result.get())
                 ->GetValue()
                 .keywords);
  }
};

}  // namespace

// TemplateURLServiceSyncTest -------------------------------------------------
class TemplateURLServiceSyncTest : public testing::Test {
 public:
  typedef TemplateURLService::SyncDataMap SyncDataMap;

  TemplateURLServiceSyncTest();

  TemplateURLServiceSyncTest(const TemplateURLServiceSyncTest&) = delete;
  TemplateURLServiceSyncTest& operator=(const TemplateURLServiceSyncTest&) =
      delete;

  void SetUp() override;
  void TearDown() override;

  TemplateURLService* model() { return test_util_a_->model(); }
  // For readability, we redefine an accessor for Model A for use in tests that
  // involve syncing two models.
  TemplateURLService* model_a() { return test_util_a_->model(); }
  TemplateURLService* model_b() { return test_util_b_->model(); }
  TestingProfile* profile_a() { return test_util_a_->profile(); }
  TestChangeProcessor* processor() { return sync_processor_.get(); }
  std::unique_ptr<syncer::SyncChangeProcessor> PassProcessor();

  // Verifies the two TemplateURLs are equal.
  // TODO(stevet): Share this with TemplateURLServiceTest.
  void AssertEquals(const TemplateURL& expected,
                    const TemplateURL& actual) const;

  // Expect that two syncer::SyncDataLists have equal contents, in terms of the
  // sync_guid, keyword, and url fields.
  void AssertEquals(const syncer::SyncDataList& data1,
                    const syncer::SyncDataList& data2) const;

  // Convenience helper for creating SyncChanges. Takes ownership of |turl|.
  syncer::SyncChange CreateTestSyncChange(
      syncer::SyncChange::SyncChangeType type,
      std::unique_ptr<TemplateURL> turl) const;

  // Helper that creates some initial sync data. We cheat a little by specifying
  // GUIDs for easy identification later. We also make the last_modified times
  // slightly older than CreateTestTemplateURL's default, to test conflict
  // resolution.
  syncer::SyncDataList CreateInitialSyncData(
      base::Time last_modified = base::Time::FromTimeT(90)) const;

  // Syntactic sugar.
  std::unique_ptr<TemplateURL> Deserialize(const syncer::SyncData& sync_data);

  // Creates a new TemplateURL copying the fields of |turl| but replacing
  // the |url| and |guid| and initializing the date_created and last_modified
  // timestamps to a default value of 100.
  std::unique_ptr<TemplateURL> CopyTemplateURL(const TemplateURLData* turl,
                                               const std::string& url,
                                               const std::string& guid);

  // Executes MergeDataAndStartSyncing and ProcessSyncChanges respectively, and
  // verifies the expected number of calls were made to notify observers. These
  // will clear out previous notify call counts beforehand.
  std::optional<syncer::ModelError> MergeAndExpectNotify(
      syncer::SyncDataList initial_sync_data,
      int expected_notify_count);
  std::optional<syncer::ModelError> MergeAndExpectNotifyAtLeast(
      syncer::SyncDataList initial_sync_data);
  std::optional<syncer::ModelError> ProcessAndExpectNotify(
      syncer::SyncChangeList changes,
      int expected_notify_count);
  std::optional<syncer::ModelError> ProcessAndExpectNotifyAtLeast(
      syncer::SyncChangeList changes);

 protected:
  content::BrowserTaskEnvironment task_environment_;

  // We have two `TestingPrefServiceSimple` to initialize two
  // `TemplateURLServiceTestUtil`.
  TestingPrefServiceSimple local_state_a_;
  TestingPrefServiceSimple local_state_b_;

  // We keep two TemplateURLServices to test syncing between them.
  std::unique_ptr<TemplateURLServiceTestUtil> test_util_a_;
  std::unique_ptr<TemplateURLServiceTestUtil> test_util_b_;

  // Our dummy ChangeProcessor used to inspect changes pushed to Sync.
  std::unique_ptr<TestChangeProcessor> sync_processor_;
  std::unique_ptr<syncer::SyncChangeProcessorWrapperForTest>
      sync_processor_wrapper_;
};

TemplateURLServiceSyncTest::TemplateURLServiceSyncTest()
    : sync_processor_(new TestChangeProcessor),
      sync_processor_wrapper_(new syncer::SyncChangeProcessorWrapperForTest(
          sync_processor_.get())) {}

void TemplateURLServiceSyncTest::SetUp() {
  DefaultSearchManager::SetFallbackSearchEnginesDisabledForTesting(true);
  test_util_a_ = std::make_unique<TemplateURLServiceTestUtil>(local_state_a_);
  // Use ChangeToLoadState() instead of VerifyLoad() so we don't actually pull
  // in the prepopulate data, which the sync tests don't care about (and would
  // just foul them up).
  test_util_a_->ChangeModelToLoadState();
  test_util_a_->ResetObserverCount();

  test_util_b_ = std::make_unique<TemplateURLServiceTestUtil>(local_state_b_);
  test_util_b_->VerifyLoad();
}

void TemplateURLServiceSyncTest::TearDown() {
  test_util_a_.reset();
  DefaultSearchManager::SetFallbackSearchEnginesDisabledForTesting(false);
}

std::unique_ptr<syncer::SyncChangeProcessor>
TemplateURLServiceSyncTest::PassProcessor() {
  return std::move(sync_processor_wrapper_);
}

void TemplateURLServiceSyncTest::AssertEquals(const TemplateURL& expected,
                                              const TemplateURL& actual) const {
  ASSERT_EQ(expected.short_name(), actual.short_name());
  ASSERT_EQ(expected.keyword(), actual.keyword());
  ASSERT_EQ(expected.url(), actual.url());
  ASSERT_EQ(expected.suggestions_url(), actual.suggestions_url());
  ASSERT_EQ(expected.favicon_url(), actual.favicon_url());
  ASSERT_EQ(expected.safe_for_autoreplace(), actual.safe_for_autoreplace());
  ASSERT_EQ(expected.input_encodings(), actual.input_encodings());
  ASSERT_EQ(expected.date_created(), actual.date_created());
  ASSERT_EQ(expected.last_modified(), actual.last_modified());
}

void TemplateURLServiceSyncTest::AssertEquals(
    const syncer::SyncDataList& data1,
    const syncer::SyncDataList& data2) const {
  SyncDataMap map1 = TemplateURLService::CreateGUIDToSyncDataMap(data1);
  SyncDataMap map2 = TemplateURLService::CreateGUIDToSyncDataMap(data2);

  for (auto iter1 = map1.cbegin(); iter1 != map1.cend(); ++iter1) {
    auto iter2 = map2.find(iter1->first);
    if (iter2 != map2.end()) {
      ASSERT_EQ(GetKeyword(iter1->second), GetKeyword(iter2->second));
      ASSERT_EQ(GetURL(iter1->second), GetURL(iter2->second));
      map2.erase(iter2);
    }
  }
  EXPECT_EQ(0U, map2.size());
}

syncer::SyncChange TemplateURLServiceSyncTest::CreateTestSyncChange(
    syncer::SyncChange::SyncChangeType type,
    std::unique_ptr<TemplateURL> turl) const {
  return syncer::SyncChange(
      FROM_HERE, type,
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data()));
}

syncer::SyncDataList TemplateURLServiceSyncTest::CreateInitialSyncData(
    base::Time last_modified) const {
  syncer::SyncDataList list;

  std::unique_ptr<TemplateURL> turl =
      CreateTestTemplateURL(u"key1", "http://key1.com", "guid1", last_modified);
  list.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data()));
  turl =
      CreateTestTemplateURL(u"key2", "http://key2.com", "guid2", last_modified);
  list.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data()));
  turl =
      CreateTestTemplateURL(u"key3", "http://key3.com", "guid3", last_modified);
  list.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data()));

  return list;
}

std::unique_ptr<TemplateURL> TemplateURLServiceSyncTest::Deserialize(
    const syncer::SyncData& sync_data) {
  syncer::SyncChangeList dummy;
  TestTemplateURLServiceClient client;
  return TemplateURLService::CreateTemplateURLFromTemplateURLAndSyncData(
      &client,
      CHECK_DEREF(TemplateURLPrepopulateData::ResolverFactory::GetForProfile(
          profile_a())),
      SearchTermsData(),
      /*existing_turl=*/nullptr, sync_data, &dummy);
}

std::unique_ptr<TemplateURL> TemplateURLServiceSyncTest::CopyTemplateURL(
    const TemplateURLData* turl,
    const std::string& url,
    const std::string& guid) {
  TemplateURLData data = *turl;
  data.SetURL(url);
  data.date_created = Time::FromTimeT(100);
  data.last_modified = Time::FromTimeT(100);
  data.sync_guid = guid;
  return std::make_unique<TemplateURL>(data);
}

std::optional<syncer::ModelError>
TemplateURLServiceSyncTest::MergeAndExpectNotify(
    syncer::SyncDataList initial_sync_data,
    int expected_notify_count) {
  test_util_a_->ResetObserverCount();
  std::optional<syncer::ModelError> error = model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_sync_data, PassProcessor());
  EXPECT_EQ(expected_notify_count, test_util_a_->GetObserverCount());
  return error;
}

std::optional<syncer::ModelError>
TemplateURLServiceSyncTest::MergeAndExpectNotifyAtLeast(
    syncer::SyncDataList initial_sync_data) {
  test_util_a_->ResetObserverCount();
  std::optional<syncer::ModelError> error = model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_sync_data, PassProcessor());
  EXPECT_LE(1, test_util_a_->GetObserverCount());
  return error;
}

std::optional<syncer::ModelError>
TemplateURLServiceSyncTest::ProcessAndExpectNotify(
    syncer::SyncChangeList changes,
    int expected_notify_count) {
  test_util_a_->ResetObserverCount();
  std::optional<syncer::ModelError> error =
      model()->ProcessSyncChanges(FROM_HERE, changes);
  EXPECT_EQ(expected_notify_count, test_util_a_->GetObserverCount());
  return error;
}

std::optional<syncer::ModelError>
TemplateURLServiceSyncTest::ProcessAndExpectNotifyAtLeast(
    syncer::SyncChangeList changes) {
  test_util_a_->ResetObserverCount();
  std::optional<syncer::ModelError> error =
      model()->ProcessSyncChanges(FROM_HERE, changes);
  EXPECT_LE(1, test_util_a_->GetObserverCount());
  return error;
}

// Actual tests ---------------------------------------------------------------

TEST_F(TemplateURLServiceSyncTest, SerializeDeserialize) {
  // Create a TemplateURL and convert it into a sync specific type.
  std::unique_ptr<TemplateURL> turl(
      CreateTestTemplateURL(u"unittest", "http://www.unittest.com/"));
  syncer::SyncData sync_data =
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data());
  // Convert the specifics back to a TemplateURL.
  std::unique_ptr<TemplateURL> deserialized(Deserialize(sync_data));
  EXPECT_TRUE(deserialized.get());
  // Ensure that the original and the deserialized TURLs are equal in values.
  AssertEquals(*turl, *deserialized);
}

TEST_F(TemplateURLServiceSyncTest, StartSyncEmpty) {
  ASSERT_TRUE(model()->GetAllSyncData(syncer::SEARCH_ENGINES).empty());
  MergeAndExpectNotify(syncer::SyncDataList(), 0);

  EXPECT_EQ(0U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTest, MergeIntoEmpty) {
  ASSERT_TRUE(model()->GetAllSyncData(syncer::SEARCH_ENGINES).empty());
  syncer::SyncDataList initial_data = CreateInitialSyncData();
  MergeAndExpectNotify(initial_data, 1);

  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  // We expect the model to have accepted all of the initial sync data. Search
  // through the model using the GUIDs to ensure that they're present.
  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
      iter != initial_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    EXPECT_TRUE(model()->GetTemplateURLForGUID(guid));
  }

  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTest, ProcessChangesEmptyModel) {
  // We initially have no data.
  MergeAndExpectNotify({}, 0);

  // Set up a bunch of ADDs.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"key1", "http://key1.com", "guid1")));
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"key2", "http://key2.com", "guid2")));
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"key3", "http://key3.com", "guid3")));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_EQ(0U, processor()->change_list_size());
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));
}

TEST_F(TemplateURLServiceSyncTest, ProcessChangesNoConflicts) {
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Process different types of changes, without conflicts.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"key4", "http://key4.com", "guid4")));
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"newkeyword", "http://new.com", "guid2")));
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_DELETE,
      CreateTestTemplateURL(u"key3", "http://key3.com", "guid3")));
  ProcessAndExpectNotify(changes, 1);

  // Add one, remove one, update one, so the number shouldn't change.
  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_EQ(0U, processor()->change_list_size());
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid2");
  EXPECT_TRUE(turl);
  EXPECT_EQ(u"newkeyword", turl->keyword());
  EXPECT_EQ("http://new.com", turl->url());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid3"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid4"));
}

TEST_F(TemplateURLServiceSyncTest,
       ProcessChangesWithDuplicateKeywordsSyncWins) {
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Process different types of changes, with duplicate keywords. Note that all
  // this data has a newer timestamp, so Sync will win in these scenarios.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"key2", "http://new.com", "aaa")));
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"key3", "http://key3.com", "guid1")));
  ProcessAndExpectNotify(changes, 1);

  // Add one, update one, so we're up to 4.
  ASSERT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // aaa duplicates the keyword of guid2 and wins. guid2 still has its keyword,
  // but is shadowed by aaa.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("aaa"));
  EXPECT_EQ(model()->GetTemplateURLForGUID("aaa"),
            model()->GetTemplateURLForKeyword(u"key2"));
  TemplateURL* guid2_turl = model()->GetTemplateURLForGUID("guid2");
  ASSERT_TRUE(guid2_turl);
  ASSERT_EQ(u"key2", guid2_turl->keyword());
  // guid1 update duplicates the keyword of guid3 and wins. guid3 still has its
  // keyword but is shadowed by guid3 now.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_EQ(model()->GetTemplateURLForGUID("guid1"),
            model()->GetTemplateURLForKeyword(u"key3"));
  TemplateURL* guid3_turl = model()->GetTemplateURLForGUID("guid3");
  ASSERT_TRUE(guid3_turl);
  EXPECT_EQ(u"key3", guid3_turl->keyword());

  // Sync is always newer here, so it should always win. But we DO NOT create
  // new sync updates in response to processing sync changes. That could cause
  // an infinite loop. Instead, on next startup, we will merge changes anyways.
  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTest,
       ProcessChangesWithDuplicateKeywordsLocalWins) {
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Process different types of changes, with duplicate keywords. Note that all
  // this data has an older timestamp, so the local data will win in this case.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"key2", "http://new.com", "aaa",
                            base::Time::FromTimeT(10))));
  // Update the keyword of engine with GUID "guid1" to "key3", which will
  // duplicate the keyword of engine with GUID "guid3".
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"key3", "http://key3.com", "guid1",
                            base::Time::FromTimeT(10))));
  ProcessAndExpectNotify(changes, 1);

  // Add one, update one, so we're up to 4.
  ASSERT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // aaa duplicates the keyword of guid2 and loses. It still exists, and kept
  // its keyword as "key2", but it's NOT best TemplateURL for "key2".
  TemplateURL* aaa_turl = model()->GetTemplateURLForGUID("aaa");
  ASSERT_TRUE(aaa_turl);
  EXPECT_EQ(u"key2", aaa_turl->keyword());

  TemplateURL* guid2_turl = model()->GetTemplateURLForGUID("guid2");
  ASSERT_TRUE(guid2_turl);
  EXPECT_NE(aaa_turl, guid2_turl);
  EXPECT_EQ(guid2_turl, model()->GetTemplateURLForKeyword(u"key2"));

  // guid1 update duplicates the keyword of guid3 and loses. It updates its
  // keyword to "key3", but is NOT the best TemplateURL for "key3".
  TemplateURL* guid1_turl = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(guid1_turl);
  EXPECT_EQ(u"key3", guid1_turl->keyword());
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));
  EXPECT_EQ(model()->GetTemplateURLForGUID("guid3"),
            model()->GetTemplateURLForKeyword(u"key3"));

  // Local data wins twice, but we specifically DO NOT push updates to Sync
  // in response to processing sync updates. That can cause an infinite loop.
  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTest, ProcessTemplateURLChange) {
  // Ensure that ProcessTemplateURLChange is called and pushes the correct
  // changes to Sync whenever local changes are made to TemplateURLs.
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Add a new search engine.
  model()->Add(CreateTestTemplateURL(u"baidu", "http://baidu.cn", "new"));
  EXPECT_EQ(1U, processor()->change_list_size());
  ASSERT_TRUE(processor()->contains_guid("new"));
  syncer::SyncChange change = processor()->change_for_guid("new");
  EXPECT_EQ(syncer::SyncChange::ACTION_ADD, change.change_type());
  EXPECT_EQ("baidu", GetKeyword(change.sync_data()));
  EXPECT_EQ("http://baidu.cn", GetURL(change.sync_data()));

  // Change a keyword.
  TemplateURL* existing_turl = model()->GetTemplateURLForGUID("guid1");
  model()->ResetTemplateURL(existing_turl, existing_turl->short_name(), u"k",
                            existing_turl->url());
  EXPECT_EQ(1U, processor()->change_list_size());
  ASSERT_TRUE(processor()->contains_guid("guid1"));
  change = processor()->change_for_guid("guid1");
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, change.change_type());
  EXPECT_EQ("k", GetKeyword(change.sync_data()));

  // Remove an existing search engine.
  existing_turl = model()->GetTemplateURLForGUID("guid2");
  model()->Remove(existing_turl);
  EXPECT_EQ(1U, processor()->change_list_size());
  ASSERT_TRUE(processor()->contains_guid("guid2"));
  change = processor()->change_for_guid("guid2");
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, change.change_type());
}

TEST_F(TemplateURLServiceSyncTest, ProcessChangesWithLocalExtensions) {
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Add some extension keywords locally.
  model()->RegisterExtensionControlledTURL("extension1", "unittest", "keyword1",
                                           "http://extension1", Time(), false);
  TemplateURL* extension1 = model()->GetTemplateURLForKeyword(u"keyword1");
  ASSERT_TRUE(extension1);
  EXPECT_EQ(0U, processor()->change_list_size());

  model()->RegisterExtensionControlledTURL("extension2", "unittest", "keyword2",
                                           "http://extension2", Time(), false);
  TemplateURL* extension2 = model()->GetTemplateURLForKeyword(u"keyword2");
  ASSERT_TRUE(extension2);
  EXPECT_EQ(0U, processor()->change_list_size());

  // Create some sync changes that will conflict with the extension keywords.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"keyword1", "http://aaa.com", std::string(),
                            base::Time::FromTimeT(100), true,
                            TemplateURLData::PolicyOrigin::kNoPolicy, 0)));
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"keyword2", "http://bbb.com")));
  ProcessAndExpectNotify(changes, 1);

  // Because aaa.com was marked as replaceable, it was removed in favor of the
  // extension engine.
  EXPECT_FALSE(model()->GetTemplateURLForHost("aaa.com"));
  // But bbb.com was marked as non-replaceable, so it coexists with extension2.
  EXPECT_TRUE(model()->GetTemplateURLForHost("bbb.com"));

  // The extensions should continue to take precedence over the normal
  // user-created engines.
  EXPECT_EQ(extension1, model()->GetTemplateURLForKeyword(u"keyword1"));
  EXPECT_EQ(extension2, model()->GetTemplateURLForKeyword(u"keyword2"));
}

TEST_F(TemplateURLServiceSyncTest, DuplicateEncodingsRemoved) {
  // Create a sync entry with duplicate encodings.
  syncer::SyncDataList initial_data;

  TemplateURLData data;
  data.SetShortName(u"test");
  data.SetKeyword(u"keyword");
  data.SetURL("http://test/%s");
  data.input_encodings.push_back("UTF-8");
  data.input_encodings.push_back("UTF-8");
  data.input_encodings.push_back("UTF-16");
  data.input_encodings.push_back("UTF-8");
  data.input_encodings.push_back("Big5");
  data.input_encodings.push_back("UTF-16");
  data.input_encodings.push_back("Big5");
  data.input_encodings.push_back("Windows-1252");
  data.date_created = Time::FromTimeT(100);
  data.last_modified = Time::FromTimeT(100);
  data.sync_guid = "keyword";
  std::unique_ptr<TemplateURL> turl(new TemplateURL(data));
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data()));

  // Now try to sync the data locally.
  MergeAndExpectNotify(initial_data, 1);

  // The entry should have been added, with duplicate encodings removed.
  TemplateURL* keyword = model()->GetTemplateURLForKeyword(u"keyword");
  ASSERT_FALSE(keyword == nullptr);
  EXPECT_EQ(4U, keyword->input_encodings().size());

  // We should also have gotten a corresponding UPDATE pushed upstream.
  EXPECT_GE(processor()->change_list_size(), 1U);
  ASSERT_TRUE(processor()->contains_guid("keyword"));
  syncer::SyncChange keyword_change = processor()->change_for_guid("keyword");
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, keyword_change.change_type());
  EXPECT_EQ("UTF-8;UTF-16;Big5;Windows-1252", keyword_change.sync_data().
      GetSpecifics().search_engine().input_encodings());
}

TEST_F(TemplateURLServiceSyncTest, MergeTwoClientsBasic) {
  // Start off B with some empty data.
  model_b()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                      CreateInitialSyncData(), PassProcessor());

  // Merge A and B. All of B's data should transfer over to A, which initially
  // has no data.
  std::unique_ptr<syncer::SyncChangeProcessorWrapperForTest> delegate_b(
      new syncer::SyncChangeProcessorWrapperForTest(model_b()));
  model_a()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, model_b()->GetAllSyncData(syncer::SEARCH_ENGINES),
      std::move(delegate_b));

  // They should be consistent.
  AssertEquals(model_a()->GetAllSyncData(syncer::SEARCH_ENGINES),
               model_b()->GetAllSyncData(syncer::SEARCH_ENGINES));
}

TEST_F(TemplateURLServiceSyncTest, MergeTwoClientsDupesAndConflicts) {
  // Start off B with some empty data.
  model_b()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                      CreateInitialSyncData(), PassProcessor());

  // Set up A so we have some interesting duplicates and conflicts.
  model_a()->Add(CreateTestTemplateURL(u"key4", "http://key4.com",
                                       "guid4"));  // Added
  model_a()->Add(CreateTestTemplateURL(u"key2", "http://key2.com",
                                       "guid2"));  // Merge - Copy of guid2.
  model_a()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", "guid5",
      base::Time::FromTimeT(10)));  // Merge - Dupe of guid3.
  model_a()->Add(
      CreateTestTemplateURL(u"key1", "http://key6.com", "guid6",
                            base::Time::FromTimeT(10)));  // Conflict with guid1

  // Merge A and B.
  std::unique_ptr<syncer::SyncChangeProcessorWrapperForTest> delegate_b(
      new syncer::SyncChangeProcessorWrapperForTest(model_b()));
  model_a()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, model_b()->GetAllSyncData(syncer::SEARCH_ENGINES),
      std::move(delegate_b));

  // They should be consistent.
  AssertEquals(model_a()->GetAllSyncData(syncer::SEARCH_ENGINES),
               model_b()->GetAllSyncData(syncer::SEARCH_ENGINES));
}

TEST_F(TemplateURLServiceSyncTest, StopSyncing) {
  std::optional<syncer::ModelError> merge_error =
      MergeAndExpectNotify(CreateInitialSyncData(), 1);
  ASSERT_FALSE(merge_error.has_value());
  model()->StopSyncing(syncer::SEARCH_ENGINES);

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"newkeyword", "http://new.com", "guid2")));
  // Because the sync data is never applied locally, there should not be any
  // notification.
  std::optional<syncer::ModelError> process_error =
      ProcessAndExpectNotify(changes, 0);
  EXPECT_TRUE(process_error.has_value());

  // Ensure that the sync changes were not accepted.
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"newkeyword"));
}

TEST_F(TemplateURLServiceSyncTest, SyncErrorOnInitialSync) {
  processor()->set_erroneous(true);
  // Error happens after local changes are applied, still expect a notify.
  std::optional<syncer::ModelError> merge_error =
      MergeAndExpectNotify(CreateInitialSyncData(), 1);
  EXPECT_TRUE(merge_error.has_value());

  // Ensure that if the initial merge was erroneous, then subsequence attempts
  // to push data into the local model are rejected, since the model was never
  // successfully associated with Sync in the first place.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"newkeyword", "http://new.com", "guid2")));
  processor()->set_erroneous(false);
  std::optional<syncer::ModelError> process_error =
      ProcessAndExpectNotify(changes, 0);
  EXPECT_TRUE(process_error.has_value());

  // Ensure that the sync changes were not accepted.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"newkeyword"));
}

TEST_F(TemplateURLServiceSyncTest, SyncErrorOnLaterSync) {
  // Ensure that if the SyncProcessor succeeds in the initial merge, but fails
  // in future ProcessSyncChanges, we still return an error.
  std::optional<syncer::ModelError> merge_error =
      MergeAndExpectNotify(CreateInitialSyncData(), 1);
  ASSERT_FALSE(merge_error.has_value());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"newkeyword", "http://new.com", "guid2")));
  processor()->set_erroneous(true);
  // Because changes make it to local before the error, still need to notify.
  std::optional<syncer::ModelError> process_error =
      ProcessAndExpectNotify(changes, 1);
  EXPECT_TRUE(process_error.has_value());
}

TEST_F(TemplateURLServiceSyncTest, SyncedDefaultAlreadySetOnStartup) {
  // Start with the default set to something in the model before we start
  // syncing.
  const char kGUID[] = "initdefault";
  model()->Add(CreateTestTemplateURL(
      u"what", "http://thewhat.com/{searchTerms}", kGUID));
  model()->SetUserSelectedDefaultSearchProvider(
      model()->GetTemplateURLForGUID(kGUID));

  const TemplateURL* default_search = model()->GetDefaultSearchProvider();
  ASSERT_TRUE(default_search);

  auto* prefs = profile_a()->GetTestingPrefService();
  ASSERT_TRUE(prefs);
  prefs->SetString(prefs::kDefaultSearchProviderGUID, kGUID);

  EXPECT_EQ(default_search, model()->GetDefaultSearchProvider());

  // Now sync the initial data.
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Ensure that the new entries were added and the default has not changed.
  ASSERT_EQ(default_search, model()->GetDefaultSearchProvider());
  ASSERT_EQ(kGUID, model()->GetDefaultSearchProvider()->sync_guid());
}

TEST_F(TemplateURLServiceSyncTest, SyncWithManagedDefaultSearch) {
  // First start off with a few entries and make sure we can set an unmanaged
  // default search provider.
  MergeAndExpectNotify(CreateInitialSyncData(), 1);
  model()->SetUserSelectedDefaultSearchProvider(
      model()->GetTemplateURLForGUID("guid2"));

  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  ASSERT_FALSE(model()->is_default_search_managed());
  ASSERT_TRUE(model()->GetDefaultSearchProvider());

  // Change the default search provider to a managed one.
  TemplateURLData managed;
  managed.SetShortName(u"manageddefault");
  managed.SetKeyword(u"manageddefault");
  managed.SetURL("http://manageddefault.com/search?t={searchTerms}");
  managed.favicon_url = GURL("http://manageddefault.com/icon.jpg");
  managed.input_encodings = {"UTF-16", "UTF-32"};
  managed.alternate_urls = {"http://manageddefault.com/search#t={searchTerms}"};

  SetManagedDefaultSearchPreferences(managed, true, test_util_a_->profile());
  const TemplateURL* dsp_turl = model()->GetDefaultSearchProvider();

  EXPECT_TRUE(model()->is_default_search_managed());

  // Add a new entry from Sync. It should still sync in despite the default
  // being managed.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"newkeyword", "http://new.com/{searchTerms}",
                            "newdefault")));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // Change default search provider GUID to point to the new entry and ensure
  // that the DSP remains managed.
  auto* prefs = profile_a()->GetTestingPrefService();
  ASSERT_TRUE(prefs);
  prefs->SetString(prefs::kDefaultSearchProviderGUID, "newdefault");

  EXPECT_EQ(dsp_turl, model()->GetDefaultSearchProvider());
  EXPECT_TRUE(model()->is_default_search_managed());

  // Go unmanaged. Ensure that the DSP changes to the expected pending entry
  // from Sync.
  const TemplateURL* expected_default =
      model()->GetTemplateURLForGUID("newdefault");
  RemoveManagedDefaultSearchPreferences(test_util_a_->profile());

  EXPECT_EQ(expected_default, model()->GetDefaultSearchProvider());
}

TEST_F(TemplateURLServiceSyncTest, OverridePrefWithExtensionDefaultSearch) {
  // Add third-party default search engine.
  TemplateURL* user_dse = model()->Add(CreateTestTemplateURL(
      u"some_keyword", "http://new.com/{searchTerms}", "guid"));
  ASSERT_TRUE(user_dse);
  model()->SetUserSelectedDefaultSearchProvider(user_dse);
  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());

  // Change the default search provider to an extension one.
  std::unique_ptr<TemplateURLData> extension =
      GenerateDummyTemplateURLData("extensiondefault");
  const TemplateURL* ext_dse =
      test_util_a_->AddExtensionControlledTURL(std::make_unique<TemplateURL>(
          *extension, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, "ext_id",
          Time(), true));
  EXPECT_EQ(ext_dse, model()->GetDefaultSearchProvider());

  // Update the custom search engine that was default but now is hidden by
  // |ext_dse|.
  model()->ResetTemplateURL(user_dse, u"New search engine", u"new_keyword",
                            "http://new.com/{searchTerms}");

  // Change kSyncedDefaultSearchProviderGUID to point to an nonexisting entry.
  // It can happen when the prefs are synced but the search engines are not.
  // That step is importnt because otherwise RemoveExtensionControlledTURL below
  // will not overwrite the GUID and won't trigger a recursion call.
  auto* prefs = profile_a()->GetTestingPrefService();
  ASSERT_TRUE(prefs);
  prefs->SetString(prefs::kDefaultSearchProviderGUID, "remote_default_guid");

  // The search engine is still the same.
  EXPECT_EQ(ext_dse, model()->GetDefaultSearchProvider());

  // Remove extension DSE. Ensure that the DSP changes to the existing search
  // engine. It should not cause a crash.
  test_util_a_->RemoveExtensionControlledTURL("ext_id");

  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());
}

// Check that keyword conflict between synced engine and extension engine is
// resolved correctly.
TEST_F(TemplateURLServiceSyncTest, ExtensionAndNormalEngineConflict) {
  // Start with empty model.
  MergeAndExpectNotify({}, 0);
  const std::u16string kCommonKeyword = u"common_keyword";
  // Change the default search provider to an extension one.
  std::unique_ptr<TemplateURLData> extension =
      GenerateDummyTemplateURLData("common_keyword");
  auto ext_dse = std::make_unique<TemplateURL>(
      *extension, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, "ext", Time(),
      true);
  const TemplateURL* extension_turl =
      test_util_a_->AddExtensionControlledTURL(std::move(ext_dse));
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());
  EXPECT_EQ(extension_turl, model()->GetTemplateURLForKeyword(kCommonKeyword));

  // Add through sync normal engine with the same keyword as extension.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(kCommonKeyword, "http://normal.com", "normal_guid",
                            base::Time::FromTimeT(10))));
  ProcessAndExpectNotify(changes, 1);

  // Expect new engine synced in and kept keyword.
  const TemplateURL* normal_turl =
      model()->GetTemplateURLForGUID("normal_guid");
  ASSERT_TRUE(normal_turl);
  EXPECT_EQ(kCommonKeyword, normal_turl->keyword());
  EXPECT_EQ(TemplateURL::NORMAL, normal_turl->type());

  // Check that extension engine remains default and is accessible by keyword.
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());
  EXPECT_EQ(extension_turl, model()->GetTemplateURLForKeyword(kCommonKeyword));

  // Update through sync normal engine changing keyword to nonconflicting value.
  changes.clear();
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"nonconflicting_keyword", "http://normal.com",
                            "normal_guid", base::Time::FromTimeT(11))));
  ProcessAndExpectNotify(changes, 1);
  normal_turl = model()->GetTemplateURLForGUID("normal_guid");
  ASSERT_TRUE(normal_turl);
  EXPECT_EQ(u"nonconflicting_keyword", normal_turl->keyword());
  // Check that extension engine remains default and is accessible by keyword.
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());
  EXPECT_EQ(extension_turl, model()->GetTemplateURLForKeyword(kCommonKeyword));

  // Update through sync normal engine changing keyword back to conflicting
  // value.
  changes.clear();
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(kCommonKeyword, "http://normal.com", "normal_guid",
                            base::Time::FromTimeT(12))));
  ProcessAndExpectNotify(changes, 1);
  normal_turl = model()->GetTemplateURLForGUID("normal_guid");
  ASSERT_TRUE(normal_turl);
  EXPECT_EQ(kCommonKeyword, normal_turl->keyword());

  // Check extension engine still remains default.
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());
  EXPECT_EQ(extension_turl, model()->GetTemplateURLForKeyword(kCommonKeyword));

  // Remove extension engine and expect that normal engine can be acessed by
  // keyword.
  test_util_a_->RemoveExtensionControlledTURL("ext");
  EXPECT_EQ(model()->GetTemplateURLForGUID("normal_guid"),
            model()->GetTemplateURLForKeyword(kCommonKeyword));
}

TEST_F(TemplateURLServiceSyncTest, DeleteBogusData) {
  // Create a couple of bogus entries to sync.
  syncer::SyncDataList initial_data;
  std::unique_ptr<TemplateURL> turl =
      CreateTestTemplateURL(u"key1", "http://key1.com", "guid1");
  initial_data.push_back(CreateCustomSyncData(
      *turl, turl->keyword(), std::string(), turl->sync_guid()));
  turl = CreateTestTemplateURL(u"key2", "http://key2.com");
  initial_data.push_back(
      CreateCustomSyncData(*turl, turl->keyword(), turl->url(), std::string()));
  turl = CreateTestTemplateURL(u"key3", "http://key3.com", "guid3");
  initial_data.push_back(CreateCustomSyncData(*turl, std::u16string(),
                                              turl->url(), turl->sync_guid()));

  // Now try to sync the data locally.
  MergeAndExpectNotify(initial_data, 0);

  // Nothing should have been added, and all bogus entries should be marked for
  // deletion.
  EXPECT_EQ(0U, model()->GetTemplateURLs().size());
  EXPECT_EQ(3U, processor()->change_list_size());
  ASSERT_TRUE(processor()->contains_guid("guid1"));
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE,
            processor()->change_for_guid("guid1").change_type());
  ASSERT_TRUE(processor()->contains_guid(std::string()));
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE,
            processor()->change_for_guid(std::string()).change_type());
  ASSERT_TRUE(processor()->contains_guid("guid3"));
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE,
            processor()->change_for_guid("guid3").change_type());
}

TEST_F(TemplateURLServiceSyncTest, SyncBaseURLs) {
  // Verify that bringing in a remote TemplateURL that uses Google base URLs
  // causes it to get a local keyword that matches the local base URL.
  syncer::SyncDataList initial_data;
  std::unique_ptr<TemplateURL> turl(CreateTestTemplateURL(
      u"google.co.uk", "{google:baseURL}search?q={searchTerms}", "guid"));
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  TemplateURL* synced_turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(synced_turl);
  EXPECT_EQ(u"google.com", synced_turl->keyword());
  EXPECT_EQ(0U, processor()->change_list_size());

  // Remote updates to this URL's keyword should be silently ignored.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"google.de",
                            "{google:baseURL}search?q={searchTerms}", "guid")));
  ProcessAndExpectNotify(changes, 1);
  EXPECT_EQ(u"google.com", synced_turl->keyword());
  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTest, MergePrepopulatedEngine) {
  std::unique_ptr<TemplateURLData> default_turl(
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetFallbackSearch());

  // Merge with an initial list containing a prepopulated engine with a wrong
  // URL.
  syncer::SyncDataList list;
  std::unique_ptr<TemplateURL> sync_turl = CopyTemplateURL(
      default_turl.get(), "http://wrong.url.com?q={searchTerms}", "default");
  list.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(sync_turl->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, list,
                                    PassProcessor());

  const TemplateURL* result_turl = model()->GetTemplateURLForGUID("default");
  EXPECT_TRUE(result_turl);
  EXPECT_EQ(default_turl->keyword(), result_turl->keyword());
  EXPECT_EQ(default_turl->short_name(), result_turl->short_name());
  EXPECT_EQ(default_turl->url(), result_turl->url());
}

TEST_F(TemplateURLServiceSyncTest, AddPrepopulatedEngine) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList(), PassProcessor());

  std::unique_ptr<TemplateURLData> default_turl(
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetFallbackSearch());
  std::unique_ptr<TemplateURL> sync_turl = CopyTemplateURL(
      default_turl.get(), "http://wrong.url.com?q={searchTerms}", "default");

  // Add a prepopulated engine with a wrong URL.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(syncer::SyncChange::ACTION_ADD,
                                         std::move(sync_turl)));
  ProcessAndExpectNotify(changes, 1);

  const TemplateURL* result_turl = model()->GetTemplateURLForGUID("default");
  EXPECT_TRUE(result_turl);
  EXPECT_EQ(default_turl->keyword(), result_turl->keyword());
  EXPECT_EQ(default_turl->short_name(), result_turl->short_name());
  EXPECT_EQ(default_turl->url(), result_turl->url());
}

TEST_F(TemplateURLServiceSyncTest, UpdatePrepopulatedEngine) {
  std::unique_ptr<TemplateURLData> default_turl(
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetFallbackSearch());

  TemplateURLData data = *default_turl;
  data.SetURL("http://old.wrong.url.com?q={searchTerms}");
  data.sync_guid = "default";
  model()->Add(std::make_unique<TemplateURL>(data));

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList(), PassProcessor());

  std::unique_ptr<TemplateURL> sync_turl =
      CopyTemplateURL(default_turl.get(),
                      "http://new.wrong.url.com?q={searchTerms}", "default");

  // Update the engine in the model, which is prepopulated, with a new one.
  // Both have wrong URLs, but it should still get corrected.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(syncer::SyncChange::ACTION_UPDATE,
                                         std::move(sync_turl)));
  ProcessAndExpectNotify(changes, 1);

  const TemplateURL* result_turl = model()->GetTemplateURLForGUID("default");
  EXPECT_TRUE(result_turl);
  EXPECT_EQ(default_turl->keyword(), result_turl->keyword());
  EXPECT_EQ(default_turl->short_name(), result_turl->short_name());
  EXPECT_EQ(default_turl->url(), result_turl->url());
}

TEST_F(TemplateURLServiceSyncTest, MergeEditedPrepopulatedEngine) {
  std::unique_ptr<TemplateURLData> default_turl(
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetFallbackSearch());

  TemplateURLData data(*default_turl);
  data.safe_for_autoreplace = false;
  data.SetKeyword(u"new_kw");
  data.SetShortName(u"my name");
  data.SetURL("http://wrong.url.com?q={searchTerms}");
  data.date_created = Time::FromTimeT(50);
  data.last_modified = Time::FromTimeT(50);
  data.sync_guid = "default";
  model()->Add(std::make_unique<TemplateURL>(data));

  data.date_created = Time::FromTimeT(100);
  data.last_modified = Time::FromTimeT(100);
  std::unique_ptr<TemplateURL> sync_turl(new TemplateURL(data));
  syncer::SyncDataList list;
  list.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(sync_turl->data()));
  MergeAndExpectNotify(list, 1);

  const TemplateURL* result_turl = model()->GetTemplateURLForGUID("default");
  EXPECT_TRUE(result_turl);
  EXPECT_EQ(u"new_kw", result_turl->keyword());
  EXPECT_EQ(u"my name", result_turl->short_name());
  EXPECT_EQ(default_turl->url(), result_turl->url());
}

TEST_F(TemplateURLServiceSyncTest, MergeConflictingPrepopulatedEngine) {
  std::unique_ptr<TemplateURLData> default_turl(
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetFallbackSearch());

  TemplateURLData data(*default_turl);
  data.SetKeyword(u"old_kw");
  data.SetShortName(u"my name");
  data.SetURL("http://wrong.url.com?q={searchTerms}");
  data.safe_for_autoreplace = true;
  data.date_created = Time::FromTimeT(50);
  data.last_modified = Time::FromTimeT(50);
  data.prepopulate_id = 1;
  data.sync_guid = "default";
  model()->Add(std::make_unique<TemplateURL>(data));

  TemplateURLData new_data(*default_turl);
  new_data.SetKeyword(u"new_kw");
  new_data.SetShortName(u"my name");
  new_data.SetURL("http://wrong.url.com?q={searchTerms}");
  new_data.safe_for_autoreplace = false;
  new_data.date_created = Time::FromTimeT(100);
  new_data.last_modified = Time::FromTimeT(100);
  new_data.prepopulate_id = 1;
  new_data.sync_guid = "different_guid";

  // Test that a remote TemplateURL can override a local TemplateURL not yet
  // known to sync.
  std::unique_ptr<TemplateURL> sync_turl =
      std::make_unique<TemplateURL>(new_data);
  syncer::SyncDataList list;
  list.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(sync_turl->data()));
  MergeAndExpectNotify(list, 1);

  TemplateURL* result_turl = model()->GetTemplateURLForGUID("different_guid");
  EXPECT_TRUE(result_turl);
  EXPECT_EQ(u"new_kw", result_turl->keyword());
  EXPECT_EQ(u"my name", result_turl->short_name());
  EXPECT_EQ(default_turl->url(), result_turl->url());

  // Reset the state of the service.
  model()->Remove(result_turl);
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  sync_processor_wrapper_ =
      std::make_unique<syncer::SyncChangeProcessorWrapperForTest>(
          sync_processor_.get());

  // Now test that a remote TemplateURL can override the attributes of the local
  // default search provider.
  TemplateURL* existing_default = new TemplateURL(data);
  model()->Add(base::WrapUnique(existing_default));
  model()->SetUserSelectedDefaultSearchProvider(existing_default);

  // Default changing code invokes notify multiple times, difficult to fix.
  MergeAndExpectNotifyAtLeast(list);

  const TemplateURL* final_turl = model()->GetDefaultSearchProvider();
  EXPECT_TRUE(final_turl);
  EXPECT_EQ(u"new_kw", final_turl->keyword());
  EXPECT_EQ(u"my name", final_turl->short_name());
  EXPECT_EQ(default_turl->url(), final_turl->url());
}

TEST_F(TemplateURLServiceSyncTest, MergePrepopulatedEngineWithChangedKeyword) {
  const TemplateURLData default_data =
      *TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
           ->GetFallbackSearch();

  // Add a prepopulated search engine and mark it as default.
  model()->Add(std::make_unique<TemplateURL>(default_data));
  ASSERT_EQ(1u, model()->GetTemplateURLs().size());
  model()->SetUserSelectedDefaultSearchProvider(model()->GetTemplateURLs()[0]);
  ASSERT_EQ(model()->GetTemplateURLs()[0], model()->GetDefaultSearchProvider());

  // Now Sync data comes in changing the keyword.
  TemplateURLData changed_data(default_data);
  changed_data.SetKeyword(u"new_kw");
  changed_data.last_modified += base::Minutes(10);
  // It's important to set |safe_for_autoreplace| to false, which marks the
  // update as a manual user update. Without this,
  // TemplateURLService::UpdateTemplateURLIfPrepopulated would reset changes to
  // the keyword or search URL.
  changed_data.safe_for_autoreplace = false;
  // Since we haven't synced on this device before, the incoming data will have
  // a different guid (even though it's based on the exact same prepopulated
  // engine).
  changed_data.sync_guid = "different_guid";

  syncer::SyncDataList list{
      TemplateURLService::CreateSyncDataFromTemplateURLData(changed_data)};
  MergeAndExpectNotify(list, 1);

  // Make sure that no duplicate was created, that the local GUID was updated to
  // the one from Sync, and the keyword was updated.
  EXPECT_EQ(1u, model()->GetTemplateURLs().size());
  EXPECT_FALSE(model()->GetTemplateURLForGUID(default_data.sync_guid));
  TemplateURL* result_turl = model()->GetTemplateURLForGUID("different_guid");
  ASSERT_TRUE(result_turl);
  EXPECT_EQ(u"new_kw", result_turl->keyword());
  // Also make sure that prefs::kSyncedDefaultSearchProviderGUID was updated to
  // point to the new GUID.
  EXPECT_EQ(profile_a()->GetTestingPrefService()->GetString(
                prefs::kDefaultSearchProviderGUID),
            "different_guid");
}

TEST_F(TemplateURLServiceSyncTest, MergeNonEditedPrepopulatedEngine) {
  std::unique_ptr<TemplateURLData> default_turl(
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetFallbackSearch());

  TemplateURLData data(*default_turl);
  data.safe_for_autoreplace = true;  // Can be replaced with built-in values.
  data.SetKeyword(u"new_kw");
  data.SetShortName(u"my name");
  data.SetURL("http://wrong.url.com?q={searchTerms}");
  data.date_created = Time::FromTimeT(50);
  data.last_modified = Time::FromTimeT(50);
  data.sync_guid = "default";
  model()->Add(std::make_unique<TemplateURL>(data));

  data.date_created = Time::FromTimeT(100);
  data.last_modified = Time::FromTimeT(100);
  std::unique_ptr<TemplateURL> sync_turl(new TemplateURL(data));
  syncer::SyncDataList list;
  list.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(sync_turl->data()));
  MergeAndExpectNotify(list, 1);

  const TemplateURL* result_turl = model()->GetTemplateURLForGUID("default");
  EXPECT_TRUE(result_turl);
  EXPECT_EQ(default_turl->keyword(), result_turl->keyword());
  EXPECT_EQ(default_turl->short_name(), result_turl->short_name());
  EXPECT_EQ(default_turl->url(), result_turl->url());
}

TEST_F(TemplateURLServiceSyncTest, MergePrepopulatedEngineIgnoresId0) {
  // The newly registered keyword will have prepulate_id 0 since that is the
  // default value.
  model()->RegisterExtensionControlledTURL("extension1", "unittest", "keyword1",
                                           "http://extension1", Time(), false);

  // Try to merge in a turl with preopulate_id also set to 0. This should work.
  syncer::SyncDataList initial_data;
  std::unique_ptr<TemplateURL> turl(
      CreateTestTemplateURL(u"what", "http://thewhat.com/{searchTerms}",
                            "normal_guid", base::Time::FromTimeT(10), true,
                            TemplateURLData::PolicyOrigin::kNoPolicy, 0));
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data()));

  MergeAndExpectNotify(initial_data, 1);
}

TEST_F(TemplateURLServiceSyncTest, MergeStarterPackEngine) {
  // Create a starter pack engine to ensure it is merged correctly.
  TemplateURLData data;
  data.SetShortName(u"Bookmarks");
  data.SetKeyword(u"@bookmarks");
  data.SetURL("chrome://bookmarks/?q={searchTerms}");
  data.starter_pack_id = static_cast<int>(
      template_url_starter_pack_data::StarterPackId::kBookmarks);
  data.date_created = Time::FromTimeT(100);
  data.last_modified = Time::FromTimeT(100);
  data.sync_guid = "bookmarks_guid";

  // Create another starter pack engine with an invalid starter pack id. This
  // should not be merged into the model.
  TemplateURLData invalid_data;
  invalid_data.SetShortName(u"Invalid starter pack");
  invalid_data.SetKeyword(u"@invalid");
  invalid_data.SetURL("chrome://bookmarks/?q={searchTerms}");
  invalid_data.starter_pack_id = static_cast<int>(
      template_url_starter_pack_data::StarterPackId::kMaxStarterPackId);
  invalid_data.date_created = Time::FromTimeT(100);
  invalid_data.last_modified = Time::FromTimeT(100);
  invalid_data.sync_guid = "invalid_guid";

  syncer::SyncDataList list{
      TemplateURLService::CreateSyncDataFromTemplateURLData(data),
      TemplateURLService::CreateSyncDataFromTemplateURLData(invalid_data)};
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, list,
                                    PassProcessor());

  // Ensure that the @bookmarks engine gets merged correctly.
  const TemplateURL* result_turl =
      model()->GetTemplateURLForGUID("bookmarks_guid");
  EXPECT_TRUE(result_turl);
  EXPECT_EQ(data.keyword(), result_turl->keyword());
  EXPECT_EQ(data.short_name(), result_turl->short_name());
  EXPECT_EQ(data.url(), result_turl->url());
  EXPECT_EQ(data.starter_pack_id,
            static_cast<int>(result_turl->starter_pack_id()));

  // The @invalid entry has an invalid starter pack ID, ensure that it gets
  // thrown out when received from sync.
  const TemplateURL* invalid_result_turl =
      model()->GetTemplateURLForGUID("invalid_guid");
  EXPECT_FALSE(invalid_result_turl);
}

TEST_F(TemplateURLServiceSyncTest, GUIDUpdatedOnDefaultSearchChange) {
  const char kGUID[] = "initdefault";
  model()->Add(CreateTestTemplateURL(
      u"what", "http://thewhat.com/{searchTerms}", kGUID));
  model()->SetUserSelectedDefaultSearchProvider(
      model()->GetTemplateURLForGUID(kGUID));

  const TemplateURL* default_search = model()->GetDefaultSearchProvider();
  ASSERT_TRUE(default_search);

  const char kNewGUID[] = "newdefault";
  model()->Add(CreateTestTemplateURL(
      u"what", "http://thewhat.com/{searchTerms}", kNewGUID));
  model()->SetUserSelectedDefaultSearchProvider(
      model()->GetTemplateURLForGUID(kNewGUID));

  EXPECT_EQ(kNewGUID, profile_a()->GetTestingPrefService()->GetString(
                          prefs::kDefaultSearchProviderGUID));
}

TEST_F(TemplateURLServiceSyncTest, NonAsciiKeywordDoesNotCrash) {
  model()->Add(CreateTestTemplateURL(u"\U0002f98d", "http://key1.com"));
  MergeAndExpectNotify(CreateInitialSyncData(), 1);
}

TEST_F(TemplateURLServiceSyncTest,
       GetAllSyncDataSkipsUntouchedAutogeneratedEngines) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    /*initial_sync_data=*/{}, PassProcessor());

  // `safe_for_autoreplace` is false. This represents an autogenerated keyword
  // which the user has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  // `safe_for_autoreplace` is false. This represents a keyword which the user
  // has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kFalse));

  // `safe_for_autoreplace` is true. This represents a keyword which the user
  // has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));

  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched. These
  // should not be synced.
  model()->Add(CreateTestTemplateURL(
      u"key4", "http://key4.com", "guid4", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  // `safe_for_autoreplace` is true and `is_active` is `kFalse`. This represents
  // an autogenerated keyword which the user has manually deactivated. These
  // should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key5", "http://key5.com", "guid5", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kFalse));

  // `safe_for_autoreplace` is true and `is_active` is `kTrue`. This represents
  // an autogenerated keyword which the user has manually activated. These
  // should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key6", "http://key6.com", "guid6", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));

  // Only the autogenerated untouched keyword(guid4) is missing.
  EXPECT_THAT(
      model()->GetAllSyncData(syncer::SEARCH_ENGINES),
      ElementsAre(ResultOf(GetGUID, "guid1"), ResultOf(GetGUID, "guid2"),
                  ResultOf(GetGUID, "guid3"), ResultOf(GetGUID, "guid5"),
                  ResultOf(GetGUID, "guid6")));
}

TEST_F(TemplateURLServiceSyncTest, MergeIgnoresUntouchedAutogeneratedKeywords) {
  syncer::SyncDataList initial_data;

  // `safe_for_autoreplace` is false. This represents an autogenerated keyword
  // which the user has modified. These should be synced.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  // `safe_for_autoreplace` is false. This represents a keyword which the user
  // has modified. These should be synced.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kFalse)
          ->data()));

  // `safe_for_autoreplace` is true. This represents a keyword which the user
  // has modified. These should be synced.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kTrue)
          ->data()));

  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched. These
  // should not be synced.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key4", "http://key4.com", "guid4", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  // `safe_for_autoreplace` is true and `is_active` is `kFalse`. This represents
  // an autogenerated keyword which the user has manually deactivated. These
  // should be synced.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key5", "http://key5.com", "guid5", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kFalse)
          ->data()));

  // `safe_for_autoreplace` is true and `is_active` is `kTrue`. This represents
  // an autogenerated keyword which the user has manually activated. These
  // should be synced.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key6", "http://key6.com", "guid6", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kTrue)
          ->data()));

  base::HistogramTester histogram_tester;

  // Now try to sync the data locally.
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  EXPECT_EQ(5U, model()->GetTemplateURLs().size());
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid5"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid6"));
  // Untouched autogenerated keyword is ignored.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid4"));

  // No sync change was committed to the processor.
  EXPECT_EQ(0U, processor()->change_list_size());

  EXPECT_THAT(
      histogram_tester.GetAllSamples(
          "Sync.SearchEngine.RemoteSearchEngineIsUntouchedAutogenerated"),
      ElementsAre(base::Bucket(false, 5), base::Bucket(true, 1)));
}

TEST_F(TemplateURLServiceSyncTest,
       ShouldLogRemoteUntouchedAutogeneratedKeywordsDuringMerge) {
  syncer::SyncDataList initial_data;

  // All the below keywords are untouched autogenerated keywords, given that
  // `safe_for_autoreplace` is true (implying that the keyword is autogenerated)
  // and `is_active` is `kUnspecified` (implying that the keyword is untouched).
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  // Prepopulated keyword.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/99999,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  // Starter pack keyword.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/1, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  base::HistogramTester histogram_tester;

  // Now try to sync the data locally.
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  // All the above keywords are untouched autogenerated keywords. All such
  // except for prepopulated engines are ignored (see crbug.com/404407977).
  EXPECT_EQ(1u, model()->GetTemplateURLs().size());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid3"));
  // No sync change was committed to the processor.
  EXPECT_EQ(0U, processor()->change_list_size());

  EXPECT_THAT(
      histogram_tester.GetAllSamples(
          "Sync.SearchEngine.RemoteSearchEngineIsUntouchedAutogenerated"),
      base::BucketsAre(base::Bucket(false, 0), base::Bucket(true, 3)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.RemoteUntouchedAutogenerated."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
  // Only one of the above keywords is a starter pack keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.RemoteUntouchedAutogenerated."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
}

TEST_F(TemplateURLServiceSyncTest,
       ProcessSyncChangesIgnoresUntouchedAutogeneratedKeywords) {
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  syncer::SyncChangeList changes;
  // `safe_for_autoreplace` is false. This represents an autogenerated keyword
  // which the user has modified. These should be synced.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)));

  // `safe_for_autoreplace` is false. This represents a keyword which the user
  // has modified. These should be synced.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kFalse)));

  // `safe_for_autoreplace` is true. This represents a keyword which the user
  // has modified. These should be synced.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kTrue)));

  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched. These
  // should not be synced.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key4", "http://key4.com", "guid4", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)));

  // `safe_for_autoreplace` is true and `is_active` is `kFalse`. This represents
  // an autogenerated keyword which the user has manually deactivated. These
  // should be synced.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key5", "http://key5.com", "guid5", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kFalse)));

  // `safe_for_autoreplace` is true and `is_active` is `kTrue`. This represents
  // an autogenerated keyword which the user has manually activated. These
  // should be synced.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key6", "http://key6.com", "guid6", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kTrue)));

  base::HistogramTester histogram_tester;

  // Now try to sync the data locally.
  ASSERT_FALSE(model()->ProcessSyncChanges(FROM_HERE, changes));

  EXPECT_EQ(5U, model()->GetTemplateURLs().size());
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid5"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid6"));
  // Untouched autogenerated keyword is ignored.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid4"));

  // No sync change was committed to the processor.
  EXPECT_EQ(0U, processor()->change_list_size());

  EXPECT_THAT(
      histogram_tester.GetAllSamples(
          "Sync.SearchEngine.RemoteSearchEngineIsUntouchedAutogenerated"),
      ElementsAre(base::Bucket(false, 5), base::Bucket(true, 1)));
}

TEST_F(TemplateURLServiceSyncTest,
       ShouldLogRemoteUntouchedAutogeneratedKeywordsUponProcessSyncChanges) {
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  syncer::SyncChangeList changes;
  // All the below keywords are untouched autogenerated keywords, given that
  // `safe_for_autoreplace` is true (implying that the keyword is autogenerated)
  // and `is_active` is `kUnspecified` (implying that the keyword is untouched).
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)));

  // Prepopulated keyword.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/99999,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)));

  // Starter pack keyword.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/1, TemplateURLData::ActiveStatus::kUnspecified)));

  base::HistogramTester histogram_tester;

  // Now try to sync the data locally.
  ASSERT_FALSE(model()->ProcessSyncChanges(FROM_HERE, changes));

  // All the above keywords are untouched autogenerated keywords. All such
  // except prepopulated engines are ignored (see crbug.com/404407977).
  EXPECT_EQ(1u, model()->GetTemplateURLs().size());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid3"));

  // No sync change was committed to the processor.
  EXPECT_EQ(0U, processor()->change_list_size());

  EXPECT_THAT(
      histogram_tester.GetAllSamples(
          "Sync.SearchEngine.RemoteSearchEngineIsUntouchedAutogenerated"),
      base::BucketsAre(base::Bucket(false, 0), base::Bucket(true, 3)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.RemoteUntouchedAutogenerated."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
  // Only one of the above keywords is a starter pack keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.RemoteUntouchedAutogenerated."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
}

TEST_F(TemplateURLServiceSyncTest,
       AddingUntouchedAutogeneratedKeywordsSendsNoUpdate) {
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  // `safe_for_autoreplace` is false. This represents an autogenerated keyword
  // which the user has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_EQ(1U, processor()->change_list_size());
  EXPECT_TRUE(processor()->contains_guid("guid1"));

  // `safe_for_autoreplace` is false. This represents a keyword which the user
  // has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kFalse));
  EXPECT_EQ(1U, processor()->change_list_size());
  EXPECT_TRUE(processor()->contains_guid("guid2"));

  // `safe_for_autoreplace` is true. This represents a keyword which the user
  // has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));
  EXPECT_EQ(1U, processor()->change_list_size());
  EXPECT_TRUE(processor()->contains_guid("guid3"));

  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched. These
  // should not be synced.
  model()->Add(CreateTestTemplateURL(
      u"key4", "http://key4.com", "guid4", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_FALSE(processor()->contains_guid("guid4"));

  // `safe_for_autoreplace` is true and `is_active` is `kFalse`. This represents
  // an autogenerated keyword which the user has manually deactivated. These
  // should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key5", "http://key5.com", "guid5", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kFalse));
  EXPECT_EQ(1U, processor()->change_list_size());
  EXPECT_TRUE(processor()->contains_guid("guid5"));

  // `safe_for_autoreplace` is true and `is_active` is `kTrue`. This represents
  // an autogenerated keyword which the user has manually activated. These
  // should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key6", "http://key6.com", "guid6", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));
  EXPECT_EQ(1U, processor()->change_list_size());
  EXPECT_TRUE(processor()->contains_guid("guid6"));
}

TEST_F(TemplateURLServiceSyncTest,
       UpdatingUntouchedAutogeneratedKeywordsSendsUpdate) {
  // Ensure that ProcessTemplateURLChange is called and pushes the correct
  // changes to Sync whenever local changes are made to TemplateURLs.
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched. These
  // should not be synced.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      u"key", "http://key.com", "guid", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_EQ(0U, processor()->change_list_size());
  EXPECT_FALSE(processor()->contains_guid("guid"));

  // Change a keyword.
  ASSERT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  model()->ResetTemplateURL(turl, turl->short_name(), u"newkey", turl->url());

  EXPECT_FALSE(turl->safe_for_autoreplace());
  EXPECT_EQ(turl->is_active(), TemplateURLData::ActiveStatus::kTrue);
  EXPECT_EQ(1U, processor()->change_list_size());
  ASSERT_TRUE(processor()->contains_guid("guid"));
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
}

TEST_F(TemplateURLServiceSyncTest,
       DeletingUntouchedAutogeneratedKeywordsSendsNoUpdate) {
  // Ensure that ProcessTemplateURLChange is called and pushes the correct
  // changes to Sync whenever local changes are made to TemplateURLs.
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  // `safe_for_autoreplace` is false. This represents an autogenerated keyword
  // which the user has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  // `safe_for_autoreplace` is false. This represents a keyword which the user
  // has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kFalse));
  // `safe_for_autoreplace` is true. This represents a keyword which the user
  // has modified. These should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));
  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched. These
  // should not be synced.
  model()->Add(CreateTestTemplateURL(
      u"key4", "http://key4.com", "guid4", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  // `safe_for_autoreplace` is true and `is_active` is `kFalse`. This represents
  // an autogenerated keyword which the user has manually deactivated. These
  // should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key5", "http://key5.com", "guid5", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kFalse));
  // `safe_for_autoreplace` is true and `is_active` is `kTrue`. This represents
  // an autogenerated keyword which the user has manually activated. These
  // should be synced.
  model()->Add(CreateTestTemplateURL(
      u"key6", "http://key6.com", "guid6", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));

  // Remove the search engines.
  for (const std::string& guid :
       {"guid1", "guid2", "guid3", "guid5", "guid6"}) {
    ASSERT_TRUE(model()->GetTemplateURLForGUID(guid));
    model()->Remove(model()->GetTemplateURLForGUID(guid));
    EXPECT_EQ(1U, processor()->change_list_size());
    ASSERT_TRUE(processor()->contains_guid(guid));
    EXPECT_EQ(processor()->change_for_guid(guid).change_type(),
              syncer::SyncChange::ACTION_DELETE);
  }

  // Removing the autogenerated untouched keyword sends no sync update.
  ASSERT_TRUE(model()->GetTemplateURLForGUID("guid4"));
  model()->Remove(model()->GetTemplateURLForGUID("guid4"));
  EXPECT_FALSE(processor()->contains_guid("guid4"));
}

class TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines
    : public TemplateURLServiceSyncTest {
 public:
  TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines() {
    feature_list_.InitAndDisableFeature(
        syncer::kSeparateLocalAndAccountSearchEngines);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataBasic) {
  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));
  model()->Add(CreateTestTemplateURL(u"key3", "http://key3.com"));
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);

  EXPECT_EQ(3U, all_sync_data.size());

  for (syncer::SyncDataList::const_iterator iter = all_sync_data.begin();
       iter != all_sync_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized(Deserialize(*iter));
    AssertEquals(*service_turl, *deserialized);
  }
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataNoManagedEngines) {
  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));
  model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", std::string(), base::Time::FromTimeT(100),
      false, TemplateURLData::PolicyOrigin::kDefaultSearchProvider));
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);

  EXPECT_EQ(2U, all_sync_data.size());

  for (syncer::SyncDataList::const_iterator iter = all_sync_data.begin();
       iter != all_sync_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized(Deserialize(*iter));
    ASSERT_FALSE(service_turl->CreatedByPolicy());
    AssertEquals(*service_turl, *deserialized);
  }
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataWithOmniboxExtension) {
  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));
  std::string fake_id("blahblahblah");
  std::string fake_url = std::string(kOmniboxScheme) + "://" + fake_id;
  model()->RegisterExtensionControlledTURL(fake_id, "unittest", "key3",
                                           fake_url, Time(), false);
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);

  EXPECT_EQ(2U, all_sync_data.size());

  for (syncer::SyncDataList::const_iterator iter = all_sync_data.begin();
       iter != all_sync_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized(Deserialize(*iter));
    AssertEquals(*service_turl, *deserialized);
  }
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataWithSearchOverrideExtension) {
  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));

  // Change default search provider to an extension one.
  std::unique_ptr<TemplateURLData> extension =
      GenerateDummyTemplateURLData("extension");
  auto ext_dse = std::make_unique<TemplateURL>(
      *extension, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, "ext", Time(),
      true);
  test_util_a_->AddExtensionControlledTURL(std::move(ext_dse));

  const TemplateURL* ext_turl = model()->GetDefaultSearchProvider();
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());

  // Extension default search must not be synced across browsers.
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);
  EXPECT_EQ(2U, all_sync_data.size());

  for (auto sync_data : all_sync_data) {
    std::string guid = GetGUID(sync_data);
    const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized = Deserialize(sync_data);
    AssertEquals(*service_turl, *deserialized);
    EXPECT_NE(TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION,
              deserialized->type());
    EXPECT_NE(ext_turl->keyword(), deserialized->keyword());
    EXPECT_NE(ext_turl->short_name(), deserialized->short_name());
    EXPECT_NE(ext_turl->url(), deserialized->url());
  }
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       LocalDefaultWinsConflict) {
  // We expect that the local default always wins keyword conflict resolution.
  const std::u16string keyword(u"key1");
  const std::string url("http://whatever.com/{searchTerms}");
  TemplateURL* default_turl = model()->Add(CreateTestTemplateURL(
      keyword, url, "whateverguid", base::Time::FromTimeT(10)));
  model()->SetUserSelectedDefaultSearchProvider(default_turl);

  syncer::SyncDataList initial_data = CreateInitialSyncData();
  // The guid1 entry should be different from the default but conflict in the
  // keyword.
  std::unique_ptr<TemplateURL> turl =
      CreateTestTemplateURL(keyword, "http://key1.com/{searchTerms}", "guid1",
                            base::Time::FromTimeT(90));
  initial_data[0] =
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data());
  MergeAndExpectNotify(initial_data, 1);

  // Since the local default was not yet synced, it should be merged with the
  // conflicting TemplateURL. However, its values should have been preserved
  // since it would have won conflict resolution due to being the default.
  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  const TemplateURL* winner = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(winner);
  EXPECT_EQ(model()->GetDefaultSearchProvider(), winner);
  EXPECT_EQ(keyword, winner->keyword());
  EXPECT_EQ(url, winner->url());
  ASSERT_TRUE(processor()->contains_guid("guid1"));
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE,
            processor()->change_for_guid("guid1").change_type());
  EXPECT_EQ(url, GetURL(processor()->change_for_guid("guid1").sync_data()));

  // There is no loser, as the two were merged together. The local sync_guid
  // should no longer be found in the model.
  const TemplateURL* loser = model()->GetTemplateURLForGUID("whateverguid");
  ASSERT_FALSE(loser);
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeAddFromOlderSyncData) {
  // GUIDs all differ, so this is data to be added from Sync, but the timestamps
  // from Sync are older. Set up the local data so that one is a dupe, one has a
  // conflicting keyword, and the last has no conflicts (a clean ADD).
  // Duplicate keyword, same hostname
  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com", "localguid1",
                                     base::Time::FromTimeT(100)));

  // Duplicate keyword, different hostname
  model()->Add(CreateTestTemplateURL(u"key2", "http://expected.com",
                                     "localguid2", base::Time::FromTimeT(100)));

  // Add
  model()->Add(
      CreateTestTemplateURL(u"unique", "http://unique.com", "localguid3"));

  ASSERT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // The dupe and conflict results in merges, as local values are always merged
  // with sync values if there is a keyword conflict. The unique keyword should
  // be added.
  EXPECT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // The key1 duplicate keyword results in the local copy winning. Ensure that
  // Sync's copy was not added, and the local copy is pushed upstream to Sync as
  // an update. The local copy should have received the sync data's GUID.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  // Check changes for the UPDATE.
  ASSERT_TRUE(processor()->contains_guid("guid1"));
  syncer::SyncChange guid1_change = processor()->change_for_guid("guid1");
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, guid1_change.change_type());
  // The local sync_guid should no longer be found.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid1"));

  // The key2 duplicate keyword results in a merge, with the values of the local
  // copy winning, so ensure it retains the original URL, and that an update to
  // the sync guid is pushed upstream to Sync.
  const TemplateURL* guid2 = model()->GetTemplateURLForGUID("guid2");
  ASSERT_TRUE(guid2);
  EXPECT_EQ(u"key2", guid2->keyword());
  EXPECT_EQ("http://expected.com", guid2->url());
  // Check changes for the UPDATE.
  ASSERT_TRUE(processor()->contains_guid("guid2"));
  syncer::SyncChange guid2_change = processor()->change_for_guid("guid2");
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, guid2_change.change_type());
  EXPECT_EQ("key2", GetKeyword(guid2_change.sync_data()));
  EXPECT_EQ("http://expected.com", GetURL(guid2_change.sync_data()));
  // The local sync_guid should no longer be found.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid2"));

  // The last TemplateURL should have had no conflicts and was just added. It
  // should not have replaced the third local TemplateURL.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("localguid3"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));

  // Two UPDATEs and one ADD.
  EXPECT_EQ(3U, processor()->change_list_size());
  // One ADDs should be pushed up to Sync.
  ASSERT_TRUE(processor()->contains_guid("localguid3"));
  EXPECT_EQ(syncer::SyncChange::ACTION_ADD,
            processor()->change_for_guid("localguid3").change_type());
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeAddFromNewerSyncData) {
  // GUIDs all differ, so Sync may overtake some entries, but the timestamps
  // from Sync are newer. Set up the local data so that one is a dupe, one has a
  // conflicting keyword, and the last has no conflicts (a clean ADD).
  // Duplicate keyword, same hostname
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "localguid1", base::Time::FromTimeT(10),
      false, TemplateURLData::PolicyOrigin::kNoPolicy, 111));

  // Duplicate keyword, different hostname
  model()->Add(CreateTestTemplateURL(
      u"key2", "http://expected.com", "localguid2", base::Time::FromTimeT(10),
      false, TemplateURLData::PolicyOrigin::kNoPolicy, 112));

  // Add
  model()->Add(CreateTestTemplateURL(
      u"unique", "http://unique.com", "localguid3", base::Time::FromTimeT(10),
      false, TemplateURLData::PolicyOrigin::kNoPolicy, 113));

  ASSERT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // The duplicate keywords results in merges. The unique keyword be added to
  // the model.
  EXPECT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // The key1 duplicate keyword results in Sync's copy winning. Ensure that
  // Sync's copy replaced the local copy.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid1"));
  EXPECT_FALSE(processor()->contains_guid("guid1"));
  EXPECT_FALSE(processor()->contains_guid("localguid1"));

  // The key2 duplicate keyword results in Sync's copy winning, so ensure it
  // retains the original keyword and is added. The local copy should be
  // removed.
  const TemplateURL* guid2_sync = model()->GetTemplateURLForGUID("guid2");
  ASSERT_TRUE(guid2_sync);
  EXPECT_EQ(u"key2", guid2_sync->keyword());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid2"));

  // The last TemplateURL should have had no conflicts and was just added. It
  // should not have replaced the third local TemplateURL.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("localguid3"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));

  // One ADD.
  EXPECT_EQ(1U, processor()->change_list_size());
  // One ADDs should be pushed up to Sync.
  ASSERT_TRUE(processor()->contains_guid("localguid3"));
  EXPECT_EQ(syncer::SyncChange::ACTION_ADD,
            processor()->change_for_guid("localguid3").change_type());
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeIgnoresPolicyAndPlayAPIEngines) {
  // Add a policy-created engine.
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "localguid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false,
      /*policy_origin=*/
      TemplateURLData::PolicyOrigin::kDefaultSearchProvider));

  {
    auto play_api_engine = CreateTestTemplateURL(
        u"key2", "http://key2.com", "localguid2", base::Time::FromTimeT(100));
    TemplateURLData data(play_api_engine->data());
    data.regulatory_origin = RegulatoryExtensionType::kAndroidEEA;
    play_api_engine = std::make_unique<TemplateURL>(data);
    model()->Add(std::move(play_api_engine));
  }

  ASSERT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // The policy engine should be ignored when it comes to conflict resolution.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("localguid1"));

  // The Play API engine should be ignored when it comes to conflict resolution.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("localguid2"));
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeInAllNewData) {
  model()->Add(CreateTestTemplateURL(u"abc.com", "http://abc.com", "abc"));
  model()->Add(CreateTestTemplateURL(u"def.com", "http://def.com", "def"));
  model()->Add(CreateTestTemplateURL(u"xyz.com", "http://xyz.com", "xyz"));
  ASSERT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  syncer::SyncDataList initial_data = CreateInitialSyncData();
  MergeAndExpectNotify(initial_data, 1);

  EXPECT_EQ(6U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  // We expect the model to have accepted all of the initial sync data. Search
  // through the model using the GUIDs to ensure that they're present.
  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
       iter != initial_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    EXPECT_TRUE(model()->GetTemplateURLForGUID(guid));
  }
  // All the original TemplateURLs should also remain in the model.
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"abc.com"));
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"def.com"));
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"xyz.com"));
  // Ensure that Sync received the expected changes.
  EXPECT_EQ(3U, processor()->change_list_size());
  EXPECT_TRUE(processor()->contains_guid("abc"));
  EXPECT_TRUE(processor()->contains_guid("def"));
  EXPECT_TRUE(processor()->contains_guid("xyz"));
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeSyncIsTheSame) {
  // The local data is the same as the sync data merged in. i.e. - There have
  // been no changes since the last time we synced. Even the last_modified
  // timestamps are the same.
  syncer::SyncDataList initial_data = CreateInitialSyncData();
  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
       iter != initial_data.end(); ++iter) {
    model()->Add(Deserialize(*iter));
  }
  ASSERT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  MergeAndExpectNotify(initial_data, 0);

  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
       iter != initial_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    EXPECT_TRUE(model()->GetTemplateURLForGUID(guid));
  }
  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeInSyncTemplateURL) {
  // An enumeration used to indicate which TemplateURL test value is expected
  // for a particular test result.
  enum ExpectedTemplateURL {
    LOCAL,
    SYNC,
    BOTH,
    NEITHER,
  };

  // Sets up and executes a MergeInSyncTemplateURL test given a number of
  // expected start and end states:
  //  * |conflict_winner| denotes which TemplateURL should win the
  //    conflict.
  //  * |synced_at_start| denotes which of the TemplateURLs should known
  //    to Sync.
  //  * |update_sent| denotes which TemplateURL should have an
  //    ACTION_UPDATE sent to the server after the merge.
  //  * |present_in_model| denotes which TemplateURL should be found in
  //    the model after the merge.
  //  * If |keywords_conflict| is true, the TemplateURLs are set up with
  //    the same keyword.
  struct TestCases {
    ExpectedTemplateURL conflict_winner;
    ExpectedTemplateURL synced_at_start;
    ExpectedTemplateURL update_sent;
    ExpectedTemplateURL present_in_model;
    bool keywords_conflict;
    size_t final_num_turls;
  };
  const auto test_cases = std::to_array<TestCases>({
      // Both are synced and the new sync entry is better: Local is left as-is,
      // and the Sync is added.
      {SYNC, BOTH, NEITHER, BOTH, true, 2},
      // Both are synced and the local entry is better: Sync is still added to
      // the model.
      {LOCAL, BOTH, NEITHER, BOTH, true, 2},
      // Local was not known to Sync and the new sync entry is better: Sync is
      // added. Local is removed. No updates.
      {SYNC, SYNC, NEITHER, SYNC, true, 1},
      // Local was not known to sync and the local entry is better: Local is
      // updated with sync GUID, Sync is not added. UPDATE sent for Sync.
      {LOCAL, SYNC, SYNC, SYNC, true, 1},
      // No conflicting keyword. Both should be added with their original
      // keywords, with no updates sent. Note that MergeDataAndStartSyncing is
      // responsible for creating the ACTION_ADD for the local TemplateURL.
      {NEITHER, SYNC, NEITHER, BOTH, false, 2},
  });

  for (size_t i = 0; i < std::size(test_cases); ++i) {
    SCOPED_TRACE(testing::Message() << "Case #" << i << std::endl);

    // Assert all the valid states of ExpectedTemplateURLs.
    ASSERT_FALSE(test_cases[i].conflict_winner == BOTH);
    ASSERT_FALSE(test_cases[i].synced_at_start == NEITHER);
    ASSERT_FALSE(test_cases[i].synced_at_start == LOCAL);
    ASSERT_FALSE(test_cases[i].update_sent == BOTH);
    ASSERT_FALSE(test_cases[i].present_in_model == NEITHER);

    const std::u16string local_keyword = u"localkeyword";
    const std::u16string sync_keyword =
        test_cases[i].keywords_conflict ? local_keyword : u"synckeyword";
    const std::string local_url = "www.localurl.com";
    const std::string sync_url = "www.syncurl.com";
    const base::Time local_last_modified = base::Time::FromTimeT(100);
    const base::Time sync_last_modified =
        base::Time::FromTimeT(test_cases[i].conflict_winner == SYNC ? 110 : 90);
    const std::string local_guid = "local_guid";
    const std::string sync_guid = "sync_guid";

    // Initialize expectations.
    std::u16string expected_local_keyword = local_keyword;
    std::u16string expected_sync_keyword = sync_keyword;

    // Create the data and run the actual test.
    TemplateURL* local_turl = model()->Add(CreateTestTemplateURL(
        local_keyword, local_url, local_guid, local_last_modified));
    std::unique_ptr<TemplateURL> sync_turl(CreateTestTemplateURL(
        sync_keyword, sync_url, sync_guid, sync_last_modified));

    SyncDataMap sync_data;
    if (test_cases[i].synced_at_start == SYNC ||
        test_cases[i].synced_at_start == BOTH) {
      sync_data[sync_turl->sync_guid()] =
          TemplateURLService::CreateSyncDataFromTemplateURLData(
              sync_turl->data());
    }
    if (test_cases[i].synced_at_start == BOTH) {
      sync_data[local_turl->sync_guid()] =
          TemplateURLService::CreateSyncDataFromTemplateURLData(
              local_turl->data());
    }
    SyncDataMap initial_data;
    initial_data[local_turl->sync_guid()] =
        TemplateURLService::CreateSyncDataFromTemplateURLData(
            local_turl->data());

    syncer::SyncChangeList change_list;
    test_util_a_->ResetObserverCount();
    ASSERT_EQ(1u, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
    model()->MergeInSyncTemplateURL(sync_turl.get(), sync_data, &change_list,
                                    &initial_data);
    EXPECT_EQ(test_cases[i].final_num_turls,
              model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
    EXPECT_EQ(1, test_util_a_->GetObserverCount());

    // Check for expected updates, if any.
    std::string expected_update_guid;
    if (test_cases[i].update_sent == LOCAL) {
      expected_update_guid = local_guid;
    } else if (test_cases[i].update_sent == SYNC) {
      expected_update_guid = sync_guid;
    }
    if (!expected_update_guid.empty()) {
      ASSERT_EQ(1U, change_list.size());
      EXPECT_EQ(expected_update_guid, GetGUID(change_list[0].sync_data()));
      EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE,
                change_list[0].change_type());
    } else {
      EXPECT_EQ(0U, change_list.size());
    }

    // Check for TemplateURLs expected in the model. Note that this is checked
    // by GUID rather than the initial pointer, as a merge could occur (the
    // Sync TemplateURL overtakes the local one). Also remove the present
    // TemplateURL when done so the next test case starts with a clean slate.
    if (test_cases[i].present_in_model == LOCAL ||
        test_cases[i].present_in_model == BOTH) {
      ASSERT_TRUE(model()->GetTemplateURLForGUID(local_guid));
      EXPECT_EQ(expected_local_keyword, local_turl->keyword());
      EXPECT_EQ(local_url, local_turl->url());
      EXPECT_EQ(local_last_modified, local_turl->last_modified());
      model()->Remove(model()->GetTemplateURLForGUID(local_guid));
    }
    if (test_cases[i].present_in_model == SYNC ||
        test_cases[i].present_in_model == BOTH) {
      ASSERT_TRUE(model()->GetTemplateURLForGUID(sync_guid));
      EXPECT_EQ(expected_sync_keyword, sync_turl->keyword());
      EXPECT_EQ(sync_url, sync_turl->url());
      EXPECT_EQ(sync_last_modified, sync_turl->last_modified());
      model()->Remove(model()->GetTemplateURLForGUID(sync_guid));
    }
  }
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeTwiceWithSameSyncData) {
  // Ensure that a second merge with the same data as the first does not
  // actually update the local data.
  syncer::SyncDataList initial_data;
  initial_data.push_back(CreateInitialSyncData()[0]);

  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com", "guid1",
                                     base::Time::FromTimeT(10)));  // earlier

  std::optional<syncer::ModelError> error =
      MergeAndExpectNotify(initial_data, 1);
  ASSERT_FALSE(error.has_value());

  // We should have updated the original TemplateURL with Sync's version.
  // Keep a copy of it so we can compare it after we re-merge.
  TemplateURL* guid1_url = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(guid1_url);
  std::unique_ptr<TemplateURL> updated_turl(new TemplateURL(guid1_url->data()));
  EXPECT_EQ(Time::FromTimeT(90), updated_turl->last_modified());

  // Modify a single field of the initial data. This should not be updated in
  // the second merge, as the last_modified timestamp remains the same.
  std::unique_ptr<TemplateURL> temp_turl(Deserialize(initial_data[0]));
  TemplateURLData data(temp_turl->data());
  data.SetShortName(u"SomethingDifferent");
  temp_turl = std::make_unique<TemplateURL>(data);
  initial_data.clear();
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(temp_turl->data()));

  // Remerge the data again. This simulates shutting down and syncing again
  // at a different time, but the cloud data has not changed.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  sync_processor_wrapper_ =
      std::make_unique<syncer::SyncChangeProcessorWrapperForTest>(
          sync_processor_.get());
  error = MergeAndExpectNotify(initial_data, 0);
  ASSERT_FALSE(error.has_value());

  // Check that the TemplateURL was not modified.
  const TemplateURL* reupdated_turl = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(reupdated_turl);
  AssertEquals(*updated_turl, *reupdated_turl);
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeUpdateFromSync) {
  // The local data is the same as the sync data merged in, but timestamps have
  // changed. Ensure the right fields are merged in.
  syncer::SyncDataList initial_data;
  TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      u"abc.com", "http://abc.com", "abc", base::Time::FromTimeT(9000)));
  model()->Add(CreateTestTemplateURL(u"xyz.com", "http://xyz.com", "xyz",
                                     base::Time::FromTimeT(9000)));

  std::unique_ptr<TemplateURL> turl1_newer = CreateTestTemplateURL(
      u"abc.com", "http://abc.ca", "abc", base::Time::FromTimeT(9999));
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      turl1_newer->data()));

  std::unique_ptr<TemplateURL> turl2_older = CreateTestTemplateURL(
      u"xyz.com", "http://xyz.ca", "xyz", base::Time::FromTimeT(8888));
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      turl2_older->data()));

  ASSERT_EQ(2U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  MergeAndExpectNotify(initial_data, 1);

  // Both were local updates, so we expect the same count.
  EXPECT_EQ(2U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // Check that the first replaced the initial abc TemplateURL.
  EXPECT_EQ(turl1, model()->GetTemplateURLForGUID("abc"));
  EXPECT_EQ("http://abc.ca", turl1->url());

  // Check that the second produced an upstream update to the xyz TemplateURL.
  EXPECT_EQ(1U, processor()->change_list_size());
  ASSERT_TRUE(processor()->contains_guid("xyz"));
  syncer::SyncChange change = processor()->change_for_guid("xyz");
  EXPECT_TRUE(change.change_type() == syncer::SyncChange::ACTION_UPDATE);
  EXPECT_EQ("http://xyz.com", GetURL(change.sync_data()));
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       PreSyncDeletes) {
  model()->pre_sync_deletes_.insert("guid1");
  model()->pre_sync_deletes_.insert("guid2");
  model()->pre_sync_deletes_.insert("aaa");
  model()->Add(CreateTestTemplateURL(u"whatever", "http://key1.com", "bbb"));
  ASSERT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  MergeAndExpectNotify(CreateInitialSyncData(), 1);
  EXPECT_EQ(2U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // We expect the model to have GUIDs {bbb, guid3} after our initial merge.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("bbb"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));
  syncer::SyncChange change = processor()->change_for_guid("guid1");
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, change.change_type());
  change = processor()->change_for_guid("guid2");
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, change.change_type());
  // "aaa" should have been pruned out on account of not being from Sync.
  EXPECT_FALSE(processor()->contains_guid("aaa"));
  // The set of pre-sync deletes should be cleared so they're not reused if
  // MergeDataAndStartSyncing gets called again.
  EXPECT_TRUE(model()->pre_sync_deletes_.empty());
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       PreSyncUpdates) {
  const char kNewKeyword[] = "somethingnew";
  const char16_t kNewKeyword16[] = u"somethingnew";
  // Fetch the prepopulate search engines so we know what they are.
  std::vector<std::unique_ptr<TemplateURLData>> prepop_turls =
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetPrepopulatedEngines();

  std::vector<std::unique_ptr<TemplateURLData>> starter_pack_turls =
      template_url_starter_pack_data::GetStarterPackEngines();

  // We have to prematurely exit this test if for some reason this machine does
  // not have any prepopulate TemplateURLs.
  ASSERT_FALSE(prepop_turls.empty());

  // Create a copy of the first TemplateURL with a really old timestamp and a
  // new keyword. Add it to the model.
  TemplateURLData data_copy(*prepop_turls[0]);
  data_copy.last_modified = Time::FromTimeT(10);
  std::u16string original_keyword = data_copy.keyword();
  data_copy.SetKeyword(kNewKeyword16);
  // Set safe_for_autoreplace to false so our keyword survives.
  data_copy.safe_for_autoreplace = false;
  model()->Add(std::make_unique<TemplateURL>(data_copy));

  // Merge the prepopulate search engines.
  base::Time pre_merge_time = base::Time::Now();
  base::RunLoop().RunUntilIdle();
  test_util_a_->ResetModel(true);

  // The newly added search engine should have been safely merged, with an
  // updated time.
  TemplateURL* added_turl = model()->GetTemplateURLForKeyword(kNewKeyword16);
  ASSERT_TRUE(added_turl);
  base::Time new_timestamp = added_turl->last_modified();
  EXPECT_GE(new_timestamp, pre_merge_time);
  std::string sync_guid = added_turl->sync_guid();

  // Bring down a copy of the prepopulate engine from Sync with the old values,
  // including the old timestamp and the same GUID. Ensure that it loses
  // conflict resolution against the local value, and an update is sent to the
  // server. The new timestamp should be preserved.
  syncer::SyncDataList initial_data;
  data_copy.SetKeyword(original_keyword);
  data_copy.sync_guid = sync_guid;
  std::unique_ptr<TemplateURL> sync_turl(new TemplateURL(data_copy));
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(sync_turl->data()));

  ASSERT_EQ(prepop_turls.size() + starter_pack_turls.size(),
            model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  EXPECT_EQ(prepop_turls.size() + starter_pack_turls.size(),
            model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  ASSERT_EQ(added_turl, model()->GetTemplateURLForKeyword(kNewKeyword16));
  EXPECT_EQ(new_timestamp, added_turl->last_modified());
  syncer::SyncChange change = processor()->change_for_guid(sync_guid);
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, change.change_type());
  EXPECT_EQ(kNewKeyword,
            change.sync_data().GetSpecifics().search_engine().keyword());
  EXPECT_EQ(
      new_timestamp,
      base::Time::FromInternalValue(
          change.sync_data().GetSpecifics().search_engine().last_modified()));
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       SyncWithExtensionDefaultSearch) {
  // First start off with a few entries and make sure we can set an extension
  // default search provider.
  MergeAndExpectNotify(CreateInitialSyncData(), 1);
  model()->SetUserSelectedDefaultSearchProvider(
      model()->GetTemplateURLForGUID("guid2"));

  // Expect one change because of user default engine change.
  const size_t pending_changes = processor()->change_list_size();
  EXPECT_EQ(1U, pending_changes);
  ASSERT_TRUE(processor()->contains_guid("guid2"));
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE,
            processor()->change_for_guid("guid2").change_type());

  const size_t sync_engines_count =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES).size();
  EXPECT_EQ(3U, sync_engines_count);
  ASSERT_TRUE(model()->GetDefaultSearchProvider());

  // Change the default search provider to an extension one.
  std::unique_ptr<TemplateURLData> extension =
      GenerateDummyTemplateURLData("extensiondefault");
  auto ext_dse = std::make_unique<TemplateURL>(
      *extension, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, "ext", Time(),
      true);
  test_util_a_->AddExtensionControlledTURL(std::move(ext_dse));

  const TemplateURL* dsp_turl = model()->GetDefaultSearchProvider();
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());

  // Extension-related changes to the DSE should not be synced as search engine
  // changes.
  EXPECT_EQ(pending_changes, processor()->change_list_size());
  EXPECT_EQ(sync_engines_count,
            model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // Add a new entry from Sync. It should still sync in despite the default
  // being extension controlled.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"newkeyword", "http://new.com/{searchTerms}",
                            "newdefault")));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // Change kSyncedDefaultSearchProviderGUID to point to the new entry and
  // ensure that the DSP remains extension controlled.
  auto* prefs = profile_a()->GetTestingPrefService();
  ASSERT_TRUE(prefs);
  prefs->SetString(prefs::kDefaultSearchProviderGUID, "newdefault");

  EXPECT_EQ(dsp_turl, model()->GetDefaultSearchProvider());
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());

  // Remove extension DSE. Ensure that the DSP changes to the expected pending
  // entry from Sync.
  const TemplateURL* expected_default =
      model()->GetTemplateURLForGUID("newdefault");
  test_util_a_->RemoveExtensionControlledTURL("ext");

  EXPECT_EQ(expected_default, model()->GetDefaultSearchProvider());
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       SyncedDefaultAlreadySetOnStartup) {
  // Start with the default set to something in the model before we start
  // syncing.
  const char kGUID[] = "initdefault";
  model()->Add(CreateTestTemplateURL(
      u"what", "http://thewhat.com/{searchTerms}", kGUID));
  model()->SetUserSelectedDefaultSearchProvider(
      model()->GetTemplateURLForGUID(kGUID));

  const TemplateURL* default_search = model()->GetDefaultSearchProvider();
  ASSERT_TRUE(default_search);

  auto* prefs = profile_a()->GetTestingPrefService();
  ASSERT_TRUE(prefs);
  prefs->SetString(prefs::kDefaultSearchProviderGUID, kGUID);

  EXPECT_EQ(default_search, model()->GetDefaultSearchProvider());

  // Now sync the initial data.
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Ensure that the new entries were added and the default has not changed.
  EXPECT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  ASSERT_EQ(default_search, model()->GetDefaultSearchProvider());
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       ShouldLogLocalUntouchedAutogeneratedKeywordsDuringMerge) {
  // All the below keywords are untouched autogenerated keywords, given that
  // `safe_for_autoreplace` is true (implying that the keyword is autogenerated)
  // and `is_active` is `kUnspecified` (implying that the keyword is untouched).
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  // Prepopulated keyword.
  model()->Add(CreateTestTemplateURL(
      u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/99999, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  // Starter pack keyword.
  model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/1,
      TemplateURLData::ActiveStatus::kUnspecified));

  base::HistogramTester histogram_tester;

  // All the above keywords are untouched autogenerated keywords. All such
  // except prepopulated engines are ignored (see crbug.com/404407977).
  EXPECT_THAT(model()->GetAllSyncData(syncer::SEARCH_ENGINES),
              ElementsAre(Property(
                  &syncer::SyncData::GetSpecifics,
                  Property(&sync_pb::EntitySpecifics::search_engine,
                           Property(&sync_pb::SearchEngineSpecifics::sync_guid,
                                    "guid2")))));

  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.LocalUntouchedAutogenerated."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
  // Only one of the above keywords is a starter pack keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.LocalUntouchedAutogenerated."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
}

// This test verifies the logging in the following cases:
// 1. Whether a keyword is an untouched autogenerated keyword is logged upon
// every add, update and delete.
// 2. Whether an untouched autogenerated keyword is a prepopulated keyword.
// 3. Whether an untouched autogenerated keyword is a starter pack keyword.
// This test first adds different types of keywords, then updates them and
// finally deletes them, verifying that the histograms are logged correctly in
// each of these cases.
TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       ShouldLogUntouchedAutogeneratedKeywordsWhenChanged) {
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  base::HistogramTester histogram_tester;

  // Not an untouched keyword.
  TemplateURL* turl0 = model()->Add(CreateTestTemplateURL(
      u"key0", "http://key0.com", "guid0", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));
  ASSERT_TRUE(processor()->contains_guid("guid0"));
  EXPECT_THAT(processor()->change_for_guid("guid0"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_ADD));

  // All the below keywords are untouched autogenerated keywords, given that
  // `safe_for_autoreplace` is true (implying that the keyword is autogenerated)
  // and `is_active` is `kUnspecified` (implying that the keyword is untouched).
  TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_FALSE(processor()->contains_guid("guid1"));

  // Starter pack keyword.
  TemplateURL* turl2 = model()->Add(CreateTestTemplateURL(
      u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/1,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_FALSE(processor()->contains_guid("guid2"));

  // Prepopulated keyword.
  TemplateURL* turl3 = model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/99999, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  ASSERT_TRUE(processor()->contains_guid("guid3"));
  EXPECT_THAT(processor()->change_for_guid("guid3"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_ADD));

  // Only one of the above keywords is not an untouched autogenerated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedAdded"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 3)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedAdded."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
  // Only one of the above keywords is a starter pack keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedAdded."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));

  // Update a non-untouched autogenerated keyword.
  ASSERT_EQ(turl0, model()->GetTemplateURLForGUID("guid0"));
  model()->UpdateTemplateURLVisitTime(turl0);
  EXPECT_THAT(processor()->change_for_guid("guid0"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_UPDATE));

  // Update the above untouched autogenerated keywords.
  ASSERT_EQ(turl1, model()->GetTemplateURLForGUID("guid1"));
  model()->UpdateTemplateURLVisitTime(turl1);
  EXPECT_FALSE(processor()->contains_guid("guid1"));

  ASSERT_EQ(turl2, model()->GetTemplateURLForGUID("guid2"));
  model()->UpdateTemplateURLVisitTime(turl2);
  EXPECT_FALSE(processor()->contains_guid("guid2"));

  ASSERT_EQ(turl3, model()->GetTemplateURLForGUID("guid3"));
  model()->UpdateTemplateURLVisitTime(turl3);
  EXPECT_THAT(processor()->change_for_guid("guid3"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_UPDATE));

  // Only one of the above keywords is not an untouched autogenerated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedUpdated"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 3)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedUpdated."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
  // Only one of the above keywords is a starter pack keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedUpdated."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));

  // Delete the above keywords.
  model()->Remove(turl0);
  EXPECT_THAT(processor()->change_for_guid("guid0"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_DELETE));
  model()->Remove(turl1);
  EXPECT_FALSE(processor()->contains_guid("guid1"));
  model()->Remove(turl2);
  EXPECT_FALSE(processor()->contains_guid("guid2"));
  model()->Remove(turl3);
  EXPECT_THAT(processor()->change_for_guid("guid3"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_DELETE));

  // Only one of the above keywords is not an untouched autogenerated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedDeleted"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 3)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedDeleted."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
  // Only one of the above keywords is a starter pack keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedDeleted."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
}

// Regression test for crbug.com/405298133.
TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       MergeIgnoresLocalUntouchedAutogeneratedKeywords) {
  // Untouched autogenerated keyword.
  const TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      u"localkey1", "http://localkey1.com", "guid1", base::Time::FromTimeT(10),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  // Untouched autogenerated keyword.
  const TemplateURL* turl2 = model()->Add(CreateTestTemplateURL(
      u"localkey2", "http://localkey2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  // Not an untouched autogenerated keyword.
  const TemplateURL* turl3 = model()->Add(CreateTestTemplateURL(
      u"localkey3", "http://localkey3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  syncer::SyncDataList initial_data;
  // Not an untouched autogenerated keyword and more recent than `turl1`.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"accountkey1", "http://accountkey1.com", "guid1",
          base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));
  // Not an untouched autogenerated keyword but less recent than `turl2`.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"accountkey2", "http://accountkey2.com", "guid2",
          base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));
  // Not an untouched autogenerated keyword but less recent than `turl3`.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"accountkey3", "http://accountkey3.com", "guid3",
          base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  ASSERT_FALSE(model()
                   ->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                              initial_data, PassProcessor())
                   .has_value());

  // For `guid1`, the account keyword wins since it is more recent.
  ASSERT_EQ(turl1, model()->GetTemplateURLForGUID("guid1"));
  EXPECT_EQ(turl1->keyword(), u"accountkey1");
  EXPECT_FALSE(processor()->contains_guid("guid1"));
  // For `guid2`, the account keyword wins even though it is less recent.
  ASSERT_EQ(turl2, model()->GetTemplateURLForGUID("guid2"));
  EXPECT_EQ(turl2->keyword(), u"accountkey2");
  EXPECT_FALSE(processor()->contains_guid("guid2"));
  // For `guid3`, the local keyword wins since it is more recent. This is also
  // committed to the processor since it has not been filtered out as it's not
  // an untouched autogenerated keyword.
  ASSERT_EQ(turl3, model()->GetTemplateURLForGUID("guid3"));
  EXPECT_EQ(turl3->keyword(), u"localkey3");
  ASSERT_TRUE(processor()->contains_guid("guid3"));
  EXPECT_EQ(processor()->change_for_guid("guid3").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
  EXPECT_EQ(processor()
                ->change_for_guid("guid3")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .keyword(),
            "localkey3");
}

TEST_F(TemplateURLServiceSyncTestWithoutSeparateLocalAndAccountSearchEngines,
       SyncMergeDeletesDefault) {
  // If the value from Sync is a duplicate of the local default and is newer, it
  // should safely replace the local value and set as the new default.
  TemplateURL* default_turl = model()->Add(
      CreateTestTemplateURL(u"key1", "http://key1.com/{searchTerms}",
                            "whateverguid", base::Time::FromTimeT(10)));
  model()->SetUserSelectedDefaultSearchProvider(default_turl);

  syncer::SyncDataList initial_data = CreateInitialSyncData();
  // The guid1 entry should be a duplicate of the default.
  std::unique_ptr<TemplateURL> turl(
      CreateTestTemplateURL(u"key1", "http://key1.com/{searchTerms}", "guid1",
                            base::Time::FromTimeT(90)));
  initial_data[0] =
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data());
  MergeAndExpectNotify(initial_data, 1);

  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("whateverguid"));
  EXPECT_EQ(model()->GetDefaultSearchProvider(),
            model()->GetTemplateURLForGUID("guid1"));
}

class TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines
    : public TemplateURLServiceSyncTest {
 public:
  TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines()
      : feature_list_(syncer::kSeparateLocalAndAccountSearchEngines) {}

  KeywordWebDataService* web_data_service() {
    return test_util_a_->web_data_service();
  }

  std::vector<TemplateURLData> GetKeywordsFromDatabase() {
    KeywordsConsumer consumer;
    test_util_a_->web_data_service()->GetKeywords(&consumer);
    return consumer.Get();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataPriorToSyncStart) {
  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));
  model()->Add(CreateTestTemplateURL(u"key3", "http://key3.com"));
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);

  EXPECT_TRUE(all_sync_data.empty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       GetAllSyncData) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));
  model()->Add(CreateTestTemplateURL(u"key3", "http://key3.com"));
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);

  EXPECT_EQ(3U, all_sync_data.size());

  for (syncer::SyncDataList::const_iterator iter = all_sync_data.begin();
       iter != all_sync_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized(Deserialize(*iter));
    AssertEquals(*service_turl, *deserialized);
  }
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataNoManagedEngines) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));
  model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", std::string(), base::Time::FromTimeT(100),
      false, TemplateURLData::PolicyOrigin::kDefaultSearchProvider));
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);

  EXPECT_EQ(2U, all_sync_data.size());

  for (syncer::SyncDataList::const_iterator iter = all_sync_data.begin();
       iter != all_sync_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized(Deserialize(*iter));
    ASSERT_FALSE(service_turl->CreatedByPolicy());
    AssertEquals(*service_turl, *deserialized);
  }
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataWithOmniboxExtension) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));
  std::string fake_id("blahblahblah");
  std::string fake_url = std::string(kOmniboxScheme) + "://" + fake_id;
  model()->RegisterExtensionControlledTURL(fake_id, "unittest", "key3",
                                           fake_url, Time(), false);
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);

  EXPECT_EQ(2U, all_sync_data.size());

  for (syncer::SyncDataList::const_iterator iter = all_sync_data.begin();
       iter != all_sync_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized(Deserialize(*iter));
    AssertEquals(*service_turl, *deserialized);
  }
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataWithSearchOverrideExtension) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com"));
  model()->Add(CreateTestTemplateURL(u"key2", "http://key2.com"));

  // Change default search provider to an extension one.
  std::unique_ptr<TemplateURLData> extension =
      GenerateDummyTemplateURLData("extension");
  auto ext_dse = std::make_unique<TemplateURL>(
      *extension, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, "ext", Time(),
      true);
  test_util_a_->AddExtensionControlledTURL(std::move(ext_dse));

  const TemplateURL* ext_turl = model()->GetDefaultSearchProvider();
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());

  // Extension default search must not be synced across browsers.
  syncer::SyncDataList all_sync_data =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES);
  EXPECT_EQ(2U, all_sync_data.size());

  for (auto sync_data : all_sync_data) {
    std::string guid = GetGUID(sync_data);
    const TemplateURL* service_turl = model()->GetTemplateURLForGUID(guid);
    std::unique_ptr<TemplateURL> deserialized = Deserialize(sync_data);
    AssertEquals(*service_turl, *deserialized);
    EXPECT_NE(TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION,
              deserialized->type());
    EXPECT_NE(ext_turl->keyword(), deserialized->keyword());
    EXPECT_NE(ext_turl->short_name(), deserialized->short_name());
    EXPECT_NE(ext_turl->url(), deserialized->url());
  }
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeIntoEmpty) {
  ASSERT_TRUE(model()->GetAllSyncData(syncer::SEARCH_ENGINES).empty());
  syncer::SyncDataList initial_data = CreateInitialSyncData();
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  EXPECT_EQ(3U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  // We expect the model to have accepted all of the initial sync data. Search
  // through the model using the GUIDs to ensure that they're present.
  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
       iter != initial_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    EXPECT_TRUE(model()->GetTemplateURLForGUID(guid));
  }

  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeInAllNewData) {
  model()->Add(CreateTestTemplateURL(u"abc.com", "http://abc.com", "abc"));
  model()->Add(CreateTestTemplateURL(u"def.com", "http://def.com", "def"));
  model()->Add(CreateTestTemplateURL(u"xyz.com", "http://xyz.com", "xyz"));

  syncer::SyncDataList initial_data = CreateInitialSyncData();
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  // We expect the model to have accepted all of the initial sync data. Search
  // through the model using the GUIDs to ensure that they're present.
  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
       iter != initial_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    EXPECT_TRUE(model()->GetTemplateURLForGUID(guid));
  }
  // All the original TemplateURLs should also remain in the model.
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"abc.com"));
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"def.com"));
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"xyz.com"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeSyncIsTheSame) {
  // The local data is the same as the sync data merged in. i.e. - There have
  // been no changes since the last time we synced. Even the last_modified
  // timestamps are the same.
  syncer::SyncDataList initial_data = CreateInitialSyncData();
  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
       iter != initial_data.end(); ++iter) {
    model()->Add(std::make_unique<TemplateURL>(Deserialize(*iter)->data()));
  }

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  for (syncer::SyncDataList::const_iterator iter = initial_data.begin();
       iter != initial_data.end(); ++iter) {
    std::string guid = GetGUID(*iter);
    const TemplateURL* turl = model()->GetTemplateURLForGUID(guid);
    ASSERT_TRUE(turl);
    EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  }
  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeAddFromOlderSyncData) {
  // GUIDs all differ, so this is data to be added from Sync, but the timestamps
  // from Sync are older. Set up the local data so that one is a dupe, one has a
  // conflicting keyword, and the last has no conflicts (a clean ADD).
  // Duplicate keyword, same hostname
  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com", "localguid1",
                                     base::Time::FromTimeT(100)));

  // Duplicate keyword, different hostname
  model()->Add(CreateTestTemplateURL(u"key2", "http://expected.com",
                                     "localguid2", base::Time::FromTimeT(100)));

  // Add
  model()->Add(
      CreateTestTemplateURL(u"unique", "http://unique.com", "localguid3"));

  // The dupe and conflict results in merges, as local values are always merged
  // with sync values if there is a keyword conflict. The unique keyword should
  // be added.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    CreateInitialSyncData(), PassProcessor());

  // The key1 duplicate keyword results in the local copy winning. Ensure that
  // Sync's copy was not added. The local copy should have received the sync
  // data's GUID.
  const TemplateURL* guid1 = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(guid1);
  EXPECT_TRUE(guid1->GetLocalData());
  EXPECT_TRUE(guid1->GetAccountData());
  // The local sync_guid should no longer be found.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid1"));

  // The key2 duplicate keyword results in a merge, with the values of the local
  // copy winning, so ensure it retains the original URL.
  const TemplateURL* guid2 = model()->GetTemplateURLForGUID("guid2");
  ASSERT_TRUE(guid2);
  EXPECT_TRUE(guid2->GetLocalData());
  EXPECT_TRUE(guid2->GetAccountData());
  EXPECT_EQ(u"key2", guid2->keyword());
  EXPECT_EQ("http://expected.com", guid2->url());
  // The local sync_guid should no longer be found.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid2"));

  // The last TemplateURL should have had no conflicts and was just added. It
  // should not have replaced the third local TemplateURL.
  const TemplateURL* localguid3 = model()->GetTemplateURLForGUID("localguid3");
  ASSERT_TRUE(localguid3);
  EXPECT_FALSE(localguid3->GetAccountData());

  const TemplateURL* guid3 = model()->GetTemplateURLForGUID("guid3");
  ASSERT_TRUE(guid3);
  EXPECT_FALSE(guid3->GetLocalData());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeUpdateFromSync) {
  // The local data is the same as the sync data merged in, but timestamps have
  // changed. Ensure the right fields are merged in.
  syncer::SyncDataList initial_data;
  TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      u"abc.com", "http://abc.com", "abc", base::Time::FromTimeT(9000)));
  const TemplateURL* turl2 = model()->Add(CreateTestTemplateURL(
      u"xyz.com", "http://xyz.com", "xyz", base::Time::FromTimeT(9000)));

  std::unique_ptr<TemplateURL> turl1_newer = CreateTestTemplateURL(
      u"abc.com", "http://abc.ca", "abc", base::Time::FromTimeT(9999));
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      turl1_newer->data()));

  std::unique_ptr<TemplateURL> turl2_older = CreateTestTemplateURL(
      u"xyz.com", "http://xyz.ca", "xyz", base::Time::FromTimeT(8888));
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      turl2_older->data()));

  MergeAndExpectNotify(initial_data, 1);

  // Check that the first overrides the local abc TemplateURL.
  EXPECT_EQ(turl1, model()->GetTemplateURLForGUID("abc"));
  EXPECT_EQ("http://abc.ca", turl1->url());

  // Check that the second is overridden by the local xyz TemplateURL.
  EXPECT_EQ(turl2, model()->GetTemplateURLForGUID("xyz"));
  EXPECT_EQ("http://xyz.com", turl2->url());

  // No changes were sent to Sync.
  EXPECT_EQ(0U, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeAddFromNewerSyncData) {
  // GUIDs all differ, so Sync may overtake some entries, but the timestamps
  // from Sync are newer. Set up the local data so that one is a dupe, one has a
  // conflicting keyword, and the last has no conflicts (a clean ADD).
  // Duplicate keyword, same hostname
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "localguid1", base::Time::FromTimeT(10),
      false, TemplateURLData::PolicyOrigin::kNoPolicy, 111));

  // Duplicate keyword, different hostname
  model()->Add(CreateTestTemplateURL(
      u"key2", "http://expected.com", "localguid2", base::Time::FromTimeT(10),
      false, TemplateURLData::PolicyOrigin::kNoPolicy, 112));

  // Add
  model()->Add(CreateTestTemplateURL(
      u"unique", "http://unique.com", "localguid3", base::Time::FromTimeT(10),
      false, TemplateURLData::PolicyOrigin::kNoPolicy, 113));

  // The duplicate keywords results in merges. The unique keyword be added to
  // the model.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    CreateInitialSyncData(), PassProcessor());

  // The key1 duplicate keyword results in Sync's copy winning. Ensure that
  // Sync's copy overrid the local copy.
  const TemplateURL* guid1 = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(guid1);
  EXPECT_TRUE(guid1->GetLocalData());
  EXPECT_TRUE(guid1->GetAccountData());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid1"));

  // The key2 duplicate keyword results in Sync's copy winning, so ensure it
  // retains the original keyword and is added.
  const TemplateURL* guid2_sync = model()->GetTemplateURLForGUID("guid2");
  ASSERT_TRUE(guid2_sync);
  EXPECT_TRUE(guid2_sync->GetLocalData());
  EXPECT_TRUE(guid2_sync->GetAccountData());
  EXPECT_EQ(u"key2", guid2_sync->keyword());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid2"));

  // The last TemplateURL should have had no conflicts and was just added. It
  // should not have replaced the third local TemplateURL.
  const TemplateURL* localguid3 = model()->GetTemplateURLForGUID("localguid3");
  ASSERT_TRUE(localguid3);
  EXPECT_FALSE(localguid3->GetAccountData());

  const TemplateURL* guid3 = model()->GetTemplateURLForGUID("guid3");
  ASSERT_TRUE(guid3);
  EXPECT_FALSE(guid3->GetLocalData());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeIgnoresPolicyAndPlayAPIEngines) {
  // Add a policy-created engine.
  model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "localguid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false,
      /*created_by_policy=*/
      TemplateURLData::PolicyOrigin::kDefaultSearchProvider));

  {
    auto play_api_engine = CreateTestTemplateURL(
        u"key2", "http://key2.com", "localguid2", base::Time::FromTimeT(100));
    TemplateURLData data(play_api_engine->data());
    data.regulatory_origin = RegulatoryExtensionType::kAndroidEEA;
    play_api_engine = std::make_unique<TemplateURL>(data);
    model()->Add(std::move(play_api_engine));
  }

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    CreateInitialSyncData(), PassProcessor());

  // The policy engine should be ignored when it comes to conflict resolution.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("localguid1"));

  // The Play API engine should be ignored when it comes to conflict resolution.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid2"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("localguid2"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeInSyncTemplateURL) {
  // An enumeration used to indicate which TemplateURL test value is expected
  // for a particular test result.
  enum ExpectedTemplateURL {
    LOCAL,
    SYNC,
    BOTH,
    NEITHER,
  };

  // Sets up and executes a MergeInSyncTemplateURL test given a number of
  // expected start and end states:
  //  * |conflict_winner| denotes which TemplateURL should win the
  //    conflict.
  //  * |synced_at_start| denotes which of the TemplateURLs should known
  //    to Sync.
  //  * |update_sent| denotes which TemplateURL should have an
  //    ACTION_UPDATE sent to the server after the merge.
  //  * |present_in_model| denotes which TemplateURL should be found in
  //    the model after the merge.
  //  * If |keywords_conflict| is true, the TemplateURLs are set up with
  //    the same keyword.
  struct TestCases {
    ExpectedTemplateURL conflict_winner;
    ExpectedTemplateURL synced_at_start;
    ExpectedTemplateURL update_sent;
    ExpectedTemplateURL present_in_model;
    bool keywords_conflict;
    size_t final_num_turls;
  };
  const auto test_cases = std::to_array<TestCases>({
      // Both are synced and the new sync entry is better: Local is left as-is,
      // and the Sync is added.
      {SYNC, BOTH, NEITHER, BOTH, true, 2},
      // Both are synced and the local entry is better: Sync is still added to
      // the model.
      {LOCAL, BOTH, NEITHER, BOTH, true, 2},
      // Local was not known to Sync and the new sync entry is better: Sync is
      // added. Local is removed. No updates.
      {SYNC, SYNC, NEITHER, SYNC, true, 1},
      // Local was not known to sync and the local entry is better: Local is
      // updated with sync GUID, Sync is not added. UPDATE sent for Sync.
      {LOCAL, SYNC, SYNC, SYNC, true, 1},
      // No conflicting keyword. Both should be added with their original
      // keywords, with no updates sent. Note that MergeDataAndStartSyncing is
      // responsible for creating the ACTION_ADD for the local TemplateURL.
      {NEITHER, SYNC, NEITHER, BOTH, false, 2},
  });

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, {},
                                    std::move(sync_processor_));

  for (size_t i = 0; i < std::size(test_cases); ++i) {
    SCOPED_TRACE(testing::Message() << "Case #" << i << std::endl);

    // Assert all the valid states of ExpectedTemplateURLs.
    ASSERT_FALSE(test_cases[i].conflict_winner == BOTH);
    ASSERT_FALSE(test_cases[i].synced_at_start == NEITHER);
    ASSERT_FALSE(test_cases[i].synced_at_start == LOCAL);
    ASSERT_FALSE(test_cases[i].update_sent == BOTH);
    ASSERT_FALSE(test_cases[i].present_in_model == NEITHER);

    const std::u16string local_keyword = u"localkeyword";
    const std::u16string sync_keyword =
        test_cases[i].keywords_conflict ? local_keyword : u"synckeyword";
    const std::string local_url = "www.localurl.com";
    const std::string sync_url = "www.syncurl.com";
    const base::Time local_last_modified = base::Time::FromTimeT(100);
    const base::Time sync_last_modified =
        base::Time::FromTimeT(test_cases[i].conflict_winner == SYNC ? 110 : 90);
    const std::string local_guid = "local_guid";
    const std::string sync_guid = "sync_guid";

    // Initialize expectations.
    std::u16string expected_local_keyword = local_keyword;
    std::u16string expected_sync_keyword = sync_keyword;

    // Create the data and run the actual test.
    TemplateURL* local_turl = model()->Add(CreateTestTemplateURL(
        local_keyword, local_url, local_guid, local_last_modified));
    std::unique_ptr<TemplateURL> sync_turl(CreateTestTemplateURL(
        sync_keyword, sync_url, sync_guid, sync_last_modified));

    SyncDataMap sync_data;
    if (test_cases[i].synced_at_start == SYNC ||
        test_cases[i].synced_at_start == BOTH) {
      sync_data[sync_turl->sync_guid()] =
          TemplateURLService::CreateSyncDataFromTemplateURLData(
              sync_turl->data());
    }
    if (test_cases[i].synced_at_start == BOTH) {
      sync_data[local_turl->sync_guid()] =
          TemplateURLService::CreateSyncDataFromTemplateURLData(
              local_turl->data());
    }
    SyncDataMap initial_data;
    initial_data[local_turl->sync_guid()] =
        TemplateURLService::CreateSyncDataFromTemplateURLData(
            local_turl->data());

    syncer::SyncChangeList change_list;
    test_util_a_->ResetObserverCount();
    model()->MergeInSyncTemplateURL(sync_turl.get(), sync_data, &change_list,
                                    &initial_data);
    EXPECT_EQ(test_cases[i].final_num_turls,
              model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
    EXPECT_EQ(1, test_util_a_->GetObserverCount());

    // Check for expected updates, if any.
    std::string expected_update_guid;
    if (test_cases[i].update_sent == LOCAL) {
      expected_update_guid = local_guid;
    } else if (test_cases[i].update_sent == SYNC) {
      expected_update_guid = sync_guid;
    }
    ASSERT_EQ(0U, change_list.size());

    // Check for TemplateURLs expected in the model. Note that this is checked
    // by GUID rather than the initial pointer, as a merge could occur (the
    // Sync TemplateURL overtakes the local one). Also remove the present
    // TemplateURL when done so the next test case starts with a clean slate.
    if (test_cases[i].present_in_model == LOCAL ||
        test_cases[i].present_in_model == BOTH) {
      ASSERT_TRUE(model()->GetTemplateURLForGUID(local_guid));
      EXPECT_EQ(expected_local_keyword, local_turl->keyword());
      EXPECT_EQ(local_url, local_turl->url());
      EXPECT_EQ(local_last_modified, local_turl->last_modified());
      model()->Remove(model()->GetTemplateURLForGUID(local_guid));
    }
    if (test_cases[i].present_in_model == SYNC ||
        test_cases[i].present_in_model == BOTH) {
      ASSERT_TRUE(model()->GetTemplateURLForGUID(sync_guid));
      EXPECT_EQ(expected_sync_keyword, sync_turl->keyword());
      EXPECT_EQ(sync_url, sync_turl->url());
      EXPECT_EQ(sync_last_modified, sync_turl->last_modified());
      model()->Remove(model()->GetTemplateURLForGUID(sync_guid));
    }
  }
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeTwiceWithSameSyncData) {
  // Ensure that a second merge with the same data as the first updates the
  // effective data, since upon sync stop the account data should have been
  // removed.
  syncer::SyncDataList initial_data;
  initial_data.push_back(CreateInitialSyncData()[0]);

  model()->Add(CreateTestTemplateURL(u"key1", "http://key1.com", "guid1",
                                     base::Time::FromTimeT(10)));  // earlier

  std::optional<syncer::ModelError> error =
      MergeAndExpectNotify(initial_data, 1);
  ASSERT_FALSE(error.has_value());

  // Account data is added into the turl.
  TemplateURL* guid1_url = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(guid1_url);
  std::unique_ptr<TemplateURL> updated_turl(
      new TemplateURL(guid1_url->GetLocalData(), guid1_url->GetAccountData()));
  EXPECT_EQ(Time::FromTimeT(90), updated_turl->last_modified());

  // Modify a single field of the initial data.
  std::unique_ptr<TemplateURL> temp_turl(Deserialize(initial_data[0]));
  TemplateURLData data(temp_turl->data());
  data.SetShortName(u"SomethingDifferent");
  temp_turl = std::make_unique<TemplateURL>(data);
  initial_data.clear();
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(temp_turl->data()));

  // Remerge the data again. This simulates shutting down and syncing again
  // at a different time, but the cloud data has not changed.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  sync_processor_wrapper_ =
      std::make_unique<syncer::SyncChangeProcessorWrapperForTest>(
          sync_processor_.get());
  error = MergeAndExpectNotify(initial_data, 1);
  ASSERT_FALSE(error.has_value());

  // Check that the account data was applied again.
  const TemplateURL* reupdated_turl = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(reupdated_turl);
  ASSERT_NE(updated_turl->data(), reupdated_turl->data());
  ASSERT_EQ(reupdated_turl->short_name(), u"SomethingDifferent");
  ASSERT_EQ(updated_turl->short_name(), u"unittest");
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       SyncMergeUpdatesDefault) {
  // If the value from Sync is a duplicate of the local default and is newer, it
  // should safely replace the local value and set as the new default.
  TemplateURL* default_turl = model()->Add(
      CreateTestTemplateURL(u"key1", "http://key1.com/{searchTerms}",
                            "whateverguid", base::Time::FromTimeT(10)));
  model()->SetUserSelectedDefaultSearchProvider(default_turl);
  ASSERT_EQ(model()->GetDefaultSearchProvider(), default_turl);

  syncer::SyncDataList initial_data = CreateInitialSyncData();
  // The guid1 entry should be a duplicate of the default.
  std::unique_ptr<TemplateURL> turl(
      CreateTestTemplateURL(u"key1", "http://key1.com/{searchTerms}", "guid1",
                            base::Time::FromTimeT(90)));
  initial_data[0] =
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data());
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    CreateInitialSyncData(), PassProcessor());

  ASSERT_EQ(model()->GetDefaultSearchProvider(), default_turl);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("whateverguid"));
  EXPECT_EQ(model()->GetDefaultSearchProvider(),
            model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(default_turl->GetLocalData());
  // Account data itself is not merged, only the guid of the local turl is
  // updated.
  EXPECT_FALSE(default_turl->GetAccountData());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       LocalDefaultWinsConflict) {
  // We expect that the local default always wins keyword conflict resolution.
  const std::u16string keyword(u"key1");
  const std::string url("http://whatever.com/{searchTerms}");
  TemplateURL* default_turl = model()->Add(CreateTestTemplateURL(
      keyword, url, "whateverguid", base::Time::FromTimeT(10)));
  model()->SetUserSelectedDefaultSearchProvider(default_turl);

  syncer::SyncDataList initial_data = CreateInitialSyncData();
  // The guid1 entry should be different from the default but conflict in the
  // keyword.
  std::unique_ptr<TemplateURL> turl =
      CreateTestTemplateURL(keyword, "http://key1.com/{searchTerms}", "guid1",
                            base::Time::FromTimeT(90));
  initial_data[0] =
      TemplateURLService::CreateSyncDataFromTemplateURLData(turl->data());
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    CreateInitialSyncData(), PassProcessor());

  // Since the local default was not yet synced, it should be merged with the
  // conflicting TemplateURL. However, its values should have been preserved
  // since it would have won conflict resolution due to being the default.
  const TemplateURL* winner = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(winner);
  EXPECT_EQ(model()->GetDefaultSearchProvider(), winner);
  EXPECT_EQ(keyword, winner->keyword());
  EXPECT_EQ(url, winner->url());
  EXPECT_TRUE(winner->GetLocalData());
  // Account data is not merged and basically ignored.
  EXPECT_FALSE(winner->GetAccountData());

  // There is no loser, as the two were merged together. The local sync_guid
  // should no longer be found in the model.
  const TemplateURL* loser = model()->GetTemplateURLForGUID("whateverguid");
  ASSERT_FALSE(loser);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       PreSyncDeletes) {
  model()->pre_sync_deletes_.insert("guid1");
  model()->pre_sync_deletes_.insert("guid2");
  model()->pre_sync_deletes_.insert("aaa");
  model()->Add(CreateTestTemplateURL(u"whatever", "http://key1.com", "bbb"));
  MergeAndExpectNotify(CreateInitialSyncData(), 1);

  // Model shouldhave GUIDs {bbb, guid3} after initial merge.
  EXPECT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_TRUE(model()->GetTemplateURLForGUID("bbb"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid3"));
  syncer::SyncChange change = processor()->change_for_guid("guid1");
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, change.change_type());
  change = processor()->change_for_guid("guid2");
  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, change.change_type());
  // "aaa" should have been pruned out on account of not being from Sync.
  EXPECT_FALSE(processor()->contains_guid("aaa"));
  // The set of pre-sync deletes should be cleared so they're not reused if
  // MergeDataAndStartSyncing gets called again.
  EXPECT_TRUE(model()->pre_sync_deletes_.empty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       PreSyncUpdates) {
  const char16_t kNewKeyword16[] = u"somethingnew";
  // Fetch the prepopulate search engines so we know what they are.
  std::vector<std::unique_ptr<TemplateURLData>> prepop_turls =
      TemplateURLPrepopulateData::ResolverFactory::GetForProfile(profile_a())
          ->GetPrepopulatedEngines();

  std::vector<std::unique_ptr<TemplateURLData>> starter_pack_turls =
      template_url_starter_pack_data::GetStarterPackEngines();

  // We have to prematurely exit this test if for some reason this machine does
  // not have any prepopulate TemplateURLs.
  ASSERT_FALSE(prepop_turls.empty());

  // Create a copy of the first TemplateURL with a really old timestamp and a
  // new keyword. Add it to the model.
  TemplateURLData data_copy(*prepop_turls[0]);
  data_copy.last_modified = Time::FromTimeT(10);
  std::u16string original_keyword = data_copy.keyword();
  data_copy.SetKeyword(kNewKeyword16);
  // Set safe_for_autoreplace to false so our keyword survives.
  data_copy.safe_for_autoreplace = false;
  model()->Add(std::make_unique<TemplateURL>(data_copy));

  // Merge the prepopulate search engines.
  base::Time pre_merge_time = base::Time::Now();
  base::RunLoop().RunUntilIdle();
  test_util_a_->ResetModel(true);

  // The newly added search engine should have been safely merged, with an
  // updated time.
  TemplateURL* added_turl = model()->GetTemplateURLForKeyword(kNewKeyword16);
  ASSERT_TRUE(added_turl);
  base::Time new_timestamp = added_turl->last_modified();
  EXPECT_GE(new_timestamp, pre_merge_time);
  std::string sync_guid = added_turl->sync_guid();

  // Bring down a copy of the prepopulate engine from Sync with the old values,
  // including the old timestamp and the same GUID. Ensure that it loses
  // conflict resolution against the local value. The new timestamp should be
  // preserved.
  syncer::SyncDataList initial_data;
  data_copy.SetKeyword(original_keyword);
  data_copy.sync_guid = sync_guid;
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(data_copy));

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  ASSERT_EQ(added_turl, model()->GetTemplateURLForKeyword(kNewKeyword16));
  EXPECT_EQ(new_timestamp, added_turl->last_modified());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       SyncWithExtensionDefaultSearch) {
  // First start off with a few entries and make sure we can set an extension
  // default search provider.
  MergeAndExpectNotify(CreateInitialSyncData(), 1);
  model()->SetUserSelectedDefaultSearchProvider(
      model()->GetTemplateURLForGUID("guid2"));

  const size_t sync_engines_count =
      model()->GetAllSyncData(syncer::SEARCH_ENGINES).size();
  EXPECT_EQ(3U, sync_engines_count);
  ASSERT_TRUE(model()->GetDefaultSearchProvider());

  // Change the default search provider to an extension one.
  std::unique_ptr<TemplateURLData> extension =
      GenerateDummyTemplateURLData("extensiondefault");
  auto ext_dse = std::make_unique<TemplateURL>(
      *extension, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, "ext", Time(),
      true);
  test_util_a_->AddExtensionControlledTURL(std::move(ext_dse));

  const TemplateURL* dsp_turl = model()->GetDefaultSearchProvider();
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());

  // Extension-related changes to the DSE should not be synced as search engine
  // changes.
  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_EQ(sync_engines_count,
            model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // Add a new entry from Sync. It should still sync in despite the default
  // being extension controlled.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"newkeyword", "http://new.com/{searchTerms}",
                            "newdefault")));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(4U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // Change default search provider GUID to point to the new entry and ensure
  // that the DSP remains extension controlled.
  auto* prefs = profile_a()->GetTestingPrefService();
  ASSERT_TRUE(prefs);
  prefs->SetString(prefs::kDefaultSearchProviderGUID, "newdefault");

  EXPECT_EQ(dsp_turl, model()->GetDefaultSearchProvider());
  EXPECT_TRUE(model()->IsExtensionControlledDefaultSearch());

  // Remove extension DSE. Ensure that the DSP changes to the expected pending
  // entry from Sync.
  const TemplateURL* expected_default =
      model()->GetTemplateURLForGUID("newdefault");
  test_util_a_->RemoveExtensionControlledTURL("ext");

  EXPECT_EQ(expected_default, model()->GetDefaultSearchProvider());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       StopSyncingRemovesAccountOnlyTemplateURLs) {
  // Add local and account template urls.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid"));
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_THAT(model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                                initial_data, PassProcessor()),
              Eq(std::nullopt));
  ASSERT_TRUE(model()->GetTemplateURLForGUID("accountguid"));
  ASSERT_TRUE(model()->GetTemplateURLForGUID("localguid"));

  base::HistogramTester histogram_tester;
  model()->StopSyncing(syncer::SEARCH_ENGINES);

  // Only account template urls should get removed.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_TRUE(model()->GetTemplateURLForGUID("localguid"));
  // Logged when removing the account only turl.
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.HasLocalDataDuringStopSyncing2", false, 1);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       StopSyncingRemovesAccountData) {
  // Add local and account template urls.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_THAT(model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                                initial_data, PassProcessor()),
              Eq(std::nullopt));

  // Account value wins as it has a more recent last_modified time.
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  ASSERT_EQ(turl->keyword(), u"accountkey");
  ASSERT_EQ(turl->url(), "http://accounturl.com");

  base::HistogramTester histogram_tester;
  model()->StopSyncing(syncer::SEARCH_ENGINES);

  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.HasLocalDataDuringStopSyncing2", true, 1);
  // Only account data is removed.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid"));
  EXPECT_EQ(turl->keyword(), u"localkey");
  EXPECT_EQ(turl->url(), "http://localurl.com");
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       StopSyncingDoesNotRemoveLocalData) {
  // Add local and account template urls.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"abc", /*url=*/"http://abc.com", /*guid=*/"guid1",
      /*last_modified=*/base::Time::FromTimeT(100)));
  std::optional<syncer::ModelError> merge_error =
      model()->MergeDataAndStartSyncing(
          syncer::SEARCH_ENGINES,
          CreateInitialSyncData(/*last_modified=*/base::Time::FromTimeT(10)),
          PassProcessor());
  ASSERT_FALSE(merge_error.has_value());

  // Local value wins as it has a more recent last_modified time.
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(turl);
  ASSERT_EQ(turl->keyword(), u"abc");
  ASSERT_EQ(turl->url(), "http://abc.com");

  model()->StopSyncing(syncer::SEARCH_ENGINES);

  // Account data is removed, but local value still persists.
  EXPECT_TRUE(model()->GetTemplateURLForGUID("guid1"));
  EXPECT_EQ(turl->keyword(), u"abc");
  EXPECT_EQ(turl->url(), "http://abc.com");
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       StopSyncingRemovesAccountValueOfPreexistingDefaultSearchProvider) {
  // Add local template url.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));
  // Set `turl` as the default search provider.
  model()->SetUserSelectedDefaultSearchProvider(turl);

  syncer::SyncDataList initial_data;
  // Add an account template url with the same GUID.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/turl->sync_guid(),
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  // Account keyword has more recent timestamp and thus wins.
  ASSERT_FALSE(model()->GetTemplateURLForKeyword(u"localkey"));
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"accountkey"));
  ASSERT_EQ(turl, model()->GetDefaultSearchProvider());

  ASSERT_THAT(turl->GetLocalData(),
              Optional(Property(&TemplateURLData::keyword, u"localkey")));
  ASSERT_THAT(turl->GetAccountData(),
              Optional(Property(&TemplateURLData::keyword, u"accountkey")));

  base::HistogramTester histogram_tester;
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetDefaultSearchProvider());
  // The local value takes over.
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"accountkey"));
  EXPECT_THAT(turl->GetLocalData(),
              Optional(Property(&TemplateURLData::keyword, u"localkey")));
  // The account value is removed.
  EXPECT_FALSE(turl->GetAccountData());
  // The histogram is not logged since a local value already existed.
  histogram_tester.ExpectTotalCount(
      "Sync.SearchEngine.AccountDefaultSearchEngineCopiedToLocal", 0);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       StopSyncingRemovesAccountValueOfNewlySetDefaultSearchProvider) {
  // Add local template url.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));

  // Add another local template url, which is the default search provider.
  TemplateURL* dse = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey2", /*url=*/"http://localurl2.com",
      /*guid=*/"guid2",
      /*last_modified=*/base::Time::FromTimeT(10)));
  model()->SetUserSelectedDefaultSearchProvider(dse);

  syncer::SyncDataList initial_data;
  // Add an account template url with the same GUID as the local non-default
  // one.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/turl->sync_guid(),
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  // Account keyword has more recent timestamp and thus wins over the
  // non-default local keyword.
  ASSERT_FALSE(model()->GetTemplateURLForKeyword(u"localkey"));
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"accountkey"));
  ASSERT_NE(turl, model()->GetDefaultSearchProvider());

  // Set `turl` as the default search provider.
  model()->SetUserSelectedDefaultSearchProvider(turl);

  ASSERT_EQ(turl, model()->GetDefaultSearchProvider());
  ASSERT_THAT(turl->GetLocalData(),
              Optional(Property(&TemplateURLData::keyword, u"localkey")));
  ASSERT_THAT(turl->GetAccountData(),
              Optional(Property(&TemplateURLData::keyword, u"accountkey")));

  base::HistogramTester histogram_tester;
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  // The default search provider has not changed.
  EXPECT_EQ(turl, model()->GetDefaultSearchProvider());
  // The local value takes over.
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"accountkey"));
  EXPECT_THAT(turl->GetLocalData(),
              Optional(Property(&TemplateURLData::keyword, u"localkey")));
  // The account value is removed.
  EXPECT_FALSE(turl->GetAccountData());
  // The histogram is not logged since a local value already existed.
  histogram_tester.ExpectTotalCount(
      "Sync.SearchEngine.AccountDefaultSearchEngineCopiedToLocal", 0);
}

// Regression test for crbug.com/401189582.
// Tests that an account-only default search provider is copied to local upon
// sync stop.
TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       StopSyncingCopiesAccountValueToLocalForAccountDefaultSearchProvider) {
  syncer::SyncDataList initial_data;
  // Add an account template url.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_NE(turl, model()->GetDefaultSearchProvider());

  // Set `turl` as the default search provider.
  model()->SetUserSelectedDefaultSearchProvider(turl);

  ASSERT_EQ(turl, model()->GetDefaultSearchProvider());
  ASSERT_FALSE(turl->GetLocalData());
  ASSERT_THAT(turl->GetAccountData(),
              Optional(Property(&TemplateURLData::keyword, u"accountkey")));

  base::HistogramTester histogram_tester;
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetDefaultSearchProvider());
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.AccountDefaultSearchEngineCopiedToLocal", true, 1);
  // Since only the account value existed, it was copied to local to avoid any
  // unsafe behavior.
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"accountkey"));
  EXPECT_THAT(turl->GetLocalData(),
              Optional(Property(&TemplateURLData::keyword, u"accountkey")));
  // The account data is moved to local.
  EXPECT_FALSE(turl->GetAccountData());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ProcessSyncUpdatesHandlesAdd) {
  MergeAndExpectNotify(syncer::SyncDataList{}, 0);

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(u"accountkey", "http://accounturl.com",
                            "accountguid")));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_TRUE(model()->GetTemplateURLForGUID("accountguid"));

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ProcessSyncUpdatesHandlesAddUponConflict) {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));

  MergeAndExpectNotify(syncer::SyncDataList{}, 0);

  syncer::SyncChangeList changes;
  changes.push_back(
      CreateTestSyncChange(syncer::SyncChange::ACTION_ADD,
                           CreateTestTemplateURL(
                               /*keyword=*/u"accountkey",
                               /*url=*/"http://accounturl.com", /*guid=*/"guid",
                               /*last_modified=*/base::Time::FromTimeT(100))));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  EXPECT_TRUE(turl->GetLocalData());
  EXPECT_TRUE(turl->GetAccountData());
  EXPECT_EQ(u"accountkey", turl->keyword());
  EXPECT_EQ("http://accounturl.com", turl->url());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_EQ(u"localkey", model()->GetTemplateURLForGUID("guid")->keyword());
  EXPECT_EQ("http://localurl.com",
            model()->GetTemplateURLForGUID("guid")->url());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ProcessSyncUpdatesErrorsUponRemoveWhenNoAccountData) {
  MergeAndExpectNotify(syncer::SyncDataList{}, 0);

  // Add a template url.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));

  syncer::SyncChangeList changes;
  // DELETE for a non-existent account turl.
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_DELETE,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))));
  std::optional<syncer::ModelError> error = ProcessAndExpectNotify(changes, 0);
  // ProcessSyncUpdates() returns an error.
  EXPECT_TRUE(error.has_value());

  EXPECT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_TRUE(turl->GetLocalData());
  EXPECT_TRUE(turl->GetAccountData());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ProcessSyncUpdatesHandlesRemove) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  MergeAndExpectNotify(initial_data, 1);

  ASSERT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  ASSERT_TRUE(model()->GetTemplateURLForGUID("accountguid"));

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_DELETE,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(0U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ProcessSyncUpdatesHandlesRemoveWhenConflict) {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  MergeAndExpectNotify(initial_data, 1);

  ASSERT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  EXPECT_EQ(u"accountkey", turl->keyword());
  EXPECT_EQ("http://accounturl.com", turl->url());

  syncer::SyncChangeList changes;
  changes.push_back(
      CreateTestSyncChange(syncer::SyncChange::ACTION_DELETE,
                           CreateTestTemplateURL(
                               /*keyword=*/u"accountkey",
                               /*url=*/"http://accounturl.com", /*guid=*/"guid",
                               /*last_modified=*/base::Time::FromTimeT(100))));
  ProcessAndExpectNotify(changes, 1);

  EXPECT_EQ(0U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
  ASSERT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_EQ(u"localkey", turl->keyword());
  EXPECT_EQ("http://localurl.com", turl->url());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_EQ(u"localkey", model()->GetTemplateURLForGUID("guid")->keyword());
  EXPECT_EQ("http://localurl.com",
            model()->GetTemplateURLForGUID("guid")->url());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ProcessSyncUpdatesHandlesUpdate) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  MergeAndExpectNotify(initial_data, 1);

  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_TRUE(turl);
  ASSERT_FALSE(turl->GetLocalData());
  EXPECT_EQ(u"accountkey", turl->keyword());
  EXPECT_EQ("http://accounturl.com", turl->url());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(u"newkey", "http://newurl.com", "accountguid")));
  ProcessAndExpectNotify(changes, 1);

  ASSERT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_FALSE(turl->GetLocalData());
  EXPECT_EQ(u"newkey", turl->keyword());
  EXPECT_EQ("http://newurl.com", turl->url());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ProcessSyncUpdatesHandlesUpdateWhenConflict) {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  MergeAndExpectNotify(initial_data, 1);

  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  EXPECT_EQ(u"accountkey", turl->keyword());
  EXPECT_EQ("http://accounturl.com", turl->url());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(
          /*keyword=*/u"newkey", /*url=*/"http://newurl.com", /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))));
  ProcessAndExpectNotify(changes, 1);

  ASSERT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_EQ(u"newkey", turl->keyword());
  EXPECT_EQ("http://newurl.com", turl->url());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_EQ(u"localkey", model()->GetTemplateURLForGUID("guid")->keyword());
  EXPECT_EQ("http://localurl.com",
            model()->GetTemplateURLForGUID("guid")->url());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       GetAllSyncDataDoesNotCountLocalOnlySearchEngines) {
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key1", /*url=*/"http://url1.com", /*guid=*/"guid1"));
  EXPECT_EQ(0u, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  EXPECT_EQ(0u, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key2", /*url=*/"http://url2.com", /*guid=*/"guid2"));
  EXPECT_EQ(1u, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       AddOnlyLocalValueIfNotSyncing) {
  const TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key1", /*url=*/"http://url1.com", /*guid=*/"guid1"));

  ASSERT_TRUE(turl1);
  EXPECT_THAT(GetKeywordsFromDatabase(), Contains(turl1->data()));
  EXPECT_EQ(turl1, model()->GetTemplateURLForGUID("guid1"));
  EXPECT_FALSE(turl1->GetAccountData());

  std::unique_ptr<TemplateURL> local_turl = CreateTestTemplateURL(
      /*keyword=*/u"localkeyword", /*url=*/"http://localurl.com",
      /*guid=*/"guid2-local", /*last_modified=*/base::Time::FromTimeT(10));
  std::unique_ptr<TemplateURL> account_turl = CreateTestTemplateURL(
      /*keyword=*/u"accountkeyword", /*url=*/"http://accounturl.com",
      /*guid=*/"guid2-account", /*last_modified=*/base::Time::FromTimeT(100));
  const TemplateURL* turl2 = model()->Add(
      std::make_unique<TemplateURL>(local_turl->data(), account_turl->data()));
  ASSERT_TRUE(turl2);
  EXPECT_THAT(GetKeywordsFromDatabase(), Contains(turl2->data()));
  EXPECT_EQ(turl2, model()->GetTemplateURLForGUID("guid2-local"));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid2-account"));
  EXPECT_FALSE(turl2->GetAccountData());

  EXPECT_FALSE(model()->Add(std::make_unique<TemplateURL>(
      std::nullopt,
      CreateTestTemplateURL(
          /*keyword=*/u"key3", /*url=*/"http://url3.com", /*guid=*/"guid3")
          ->data())));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Field(&TemplateURLData::sync_guid, "guid3"))));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid3"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       AddOnlyAccountValueIfFromSync) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_TRUE(turl);
  EXPECT_FALSE(turl->GetLocalData());
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Field(&TemplateURLData::sync_guid, "accountguid"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       DualWriteUponAddingLocalOnlySearchEngine) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  base::HistogramTester histogram_tester;
  // Add a template url.
  const TemplateURLData data =
      CreateTestTemplateURL(
          /*keyword=*/u"abc", /*url=*/"http://abc.com", /*guid=*/"guid")
          ->data();
  model()->Add(std::make_unique<TemplateURL>(data, std::nullopt));

  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.AddedKeywordHasAccountData", false, 1);
  // Both local and account values should have been populated.
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Contains(Field(&TemplateURLData::sync_guid, "guid")));
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_ADD);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       DualWriteUponAddingAccountOnlySearchEngine) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  base::HistogramTester histogram_tester;
  // Add a template url.
  const TemplateURLData data =
      CreateTestTemplateURL(
          /*keyword=*/u"abc", /*url=*/"http://abc.com", /*guid=*/"guid")
          ->data();
  model()->Add(std::make_unique<TemplateURL>(std::nullopt, data));

  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.AddedKeywordHasAccountData", true, 1);
  // Both local and account values should have been populated.
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Contains(Field(&TemplateURLData::sync_guid, "guid")));
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_ADD);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       DualWriteUponAddingSearchEngineWithLocalAndAccountData) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  base::HistogramTester histogram_tester;
  // Add a template url.
  const TemplateURLData data =
      CreateTestTemplateURL(
          /*keyword=*/u"abc", /*url=*/"http://abc.com", /*guid=*/"guid")
          ->data();
  model()->Add(std::make_unique<TemplateURL>(data, data));

  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.AddedKeywordHasAccountData", true, 1);
  // Both local and account values should have been populated.
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Contains(Field(&TemplateURLData::sync_guid, "guid")));
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_ADD);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       RemoveLocalOnlySearchEngine) {
  // Add a local search engine.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid"));

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  ASSERT_THAT(GetKeywordsFromDatabase(), Contains(turl->data()));
  model()->Remove(turl);

  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"localkey"));
  // Nothing should be committed since there was no account data.
  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Field(&TemplateURLData::sync_guid, "localguid"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       RemoveAccountOnlySearchEngine) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());
  ASSERT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Field(&TemplateURLData::sync_guid, "accountguid"))));

  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  model()->Remove(turl);

  ASSERT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Field(&TemplateURLData::sync_guid, "accountguid"))));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"accountkey"));
  // Deletion should be committed.
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_EQ(processor()->change_for_guid("accountguid").change_type(),
            syncer::SyncChange::ACTION_DELETE);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       RemoveSearchEngineWithLocalAndAccountData) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));
  ASSERT_EQ(turl->GetLocalData(), turl->GetAccountData());
  ASSERT_THAT(GetKeywordsFromDatabase(),
              Contains(Field(&TemplateURLData::sync_guid, "guid")));
  ASSERT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_ADD);

  model()->Remove(turl);
  ASSERT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Field(&TemplateURLData::sync_guid, "guid"))));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"key"));
  // Deletion should be committed.
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_DELETE);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       RemoveSearchEngineWithDifferentLocalAndAccountData) {
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://localurl.com", /*guid=*/"localguid"));
  ASSERT_TRUE(model()->GetTemplateURLForGUID("localguid"));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());
  ASSERT_THAT(
      GetKeywordsFromDatabase(),
      Contains(AllOf(Property(&TemplateURLData::keyword, u"key"),
                     Field(&TemplateURLData::sync_guid, "accountguid"))));
  ASSERT_THAT(
      GetKeywordsFromDatabase(),
      Not(Contains(AllOf(Field(&TemplateURLData::sync_guid, "localguid")))));
  ASSERT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_TRUE(turl);

  model()->Remove(turl);
  ASSERT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Property(&TemplateURLData::keyword, u"key"))));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"key"));
  // Deletion should be committed.
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_EQ(processor()->change_for_guid("accountguid").change_type(),
            syncer::SyncChange::ACTION_DELETE);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldAddIntoDatabaseUponUpdateIfNotExistingEarlier) {
  // Start syncing.
  syncer::SyncDataList initial_data;
  // Account-only search engine.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://url.com",
          /*guid=*/"guid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  // Account data is not added to the database.
  ASSERT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Property(&TemplateURLData::keyword, u"key"))));

  TemplateURL* turl = model()->GetTemplateURLForKeyword(u"key");
  // Update the account-only search engine.
  model()->ResetTemplateURL(turl, u"newtitle", u"newkey", "http://newurl.com");

  // This should write the updated data to local and thus add to the database.
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"newkey"));
  EXPECT_TRUE(turl->GetLocalData());
  EXPECT_THAT(
      GetKeywordsFromDatabase(),
      ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                        Property(&TemplateURLData::keyword, u"newkey"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotSendUpdateToSyncIfAccountDataIsUnchanged) {
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(100)));

  // Start syncing.
  syncer::SyncDataList initial_data;
  // Local turl has the more recent last_modified time and thus is the active
  // value.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid", /*last_modified=*/base::Time::FromTimeT(10))
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  const base::Time time_now = base::Time::Now();
  const base::Time null_time;
  ASSERT_NE(time_now, null_time);

  // Update last_visited time for `turl`. This should update the local value.
  model()->UpdateTemplateURLVisitTime(turl);
  EXPECT_NE(turl->GetLocalData(), turl->GetAccountData());
  // Local last_visited has been updated whereas the account last_visited stays
  // null.
  EXPECT_GE(turl->GetLocalData()->last_visited, time_now);
  EXPECT_EQ(turl->GetAccountData()->last_visited, null_time);

  // No change is committed since only the local data was updated.
  EXPECT_EQ(0u, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldDualWriteUponResetTemplateURLIfLocalOnly) {
  // Local only search engine.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));

  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  // Update the local-only search engine.
  model()->ResetTemplateURL(turl, u"newtitle", u"newkey", "http://newurl.com");

  // This should update and write the new value to both local and account.
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"key"));
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"newkey"));
  EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  EXPECT_THAT(turl, Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid"),
                                  Property(&TemplateURL::keyword, u"newkey"))));
  EXPECT_THAT(
      GetKeywordsFromDatabase(),
      ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                        Property(&TemplateURLData::keyword, u"newkey"))));
  // Update should be committed.
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldDualWriteUponResetTemplateURLIfAccountOnly) {
  // Start syncing.
  syncer::SyncDataList initial_data;
  // Account-only search engine.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://url.com",
          /*guid=*/"guid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  // Account search engine is not added to the database.
  ASSERT_THAT(GetKeywordsFromDatabase(),
              Not(Contains(Property(&TemplateURLData::keyword, u"key"))));

  TemplateURL* turl = model()->GetTemplateURLForKeyword(u"key");
  model()->ResetTemplateURL(turl, u"newtitle", u"newkey", "http://newurl.com");

  // This should update and write the new value to both local and account.
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"key"));
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"newkey"));
  EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  EXPECT_THAT(turl, Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid"),
                                  Property(&TemplateURL::keyword, u"newkey"))));
  EXPECT_THAT(
      GetKeywordsFromDatabase(),
      ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                        Property(&TemplateURLData::keyword, u"newkey"))));
  // Update should be committed.
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldDualWriteUponResetTemplateURL) {
  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  // Add a search engine to local and account.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));

  // Newly added search engine is dual written to both local and account.
  ASSERT_EQ(turl->GetLocalData(), turl->GetAccountData());
  ASSERT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                                Property(&TemplateURLData::keyword, u"key"))));
  ASSERT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_ADD);

  model()->ResetTemplateURL(turl, u"newtitle", u"newkey", "http://newurl.com");

  // This should update and write the new value to both local and account.
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"key"));
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"newkey"));
  EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  EXPECT_THAT(turl, Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid"),
                                  Property(&TemplateURL::keyword, u"newkey"))));
  EXPECT_THAT(
      GetKeywordsFromDatabase(),
      ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                        Property(&TemplateURLData::keyword, u"newkey"))));
  // Update should be committed.
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldDualWriteUponSetIsActiveTemplateURL) {
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kFalse));

  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  ASSERT_EQ(turl->is_active(), TemplateURLData::ActiveStatus::kFalse);

  model()->SetIsActiveTemplateURL(turl, /*is_active=*/true);
  // This should update and write the activated turl to both local and account.
  ASSERT_EQ(turl->is_active(), TemplateURLData::ActiveStatus::kTrue);
  EXPECT_EQ(turl->GetLocalData(), turl->GetAccountData());
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                                Field(&TemplateURLData::is_active,
                                      TemplateURLData::ActiveStatus::kTrue))));
  // Update should be committed.
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotDualWriteUponUpdateProviderFavicons) {
  // Local-only search engine.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));

  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  model()->UpdateProviderFavicons(
      GURL("https://enterprise_search.com/q=searchTerm"),
      GURL("https://enterprise_search.com/newfavicon.ico"));

  // This should not write the new value to account.
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_EQ(0u, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotDualWriteUponProcessSyncUpdateChanges) {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  // Account data is winning.
  const TemplateURL* turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_TRUE(turl);
  EXPECT_EQ(u"accountkey", turl->keyword());
  EXPECT_EQ("http://accounturl.com", turl->url());

  // Incoming incremental update.
  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(
          /*keyword=*/u"newkey", /*url=*/"http://newurl.com", /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))));
  ASSERT_FALSE(model()->ProcessSyncChanges(FROM_HERE, changes));

  // This should not write the updated account value to local.
  ASSERT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid"),
                            Property(&TemplateURL::keyword, u"newkey"),
                            Property(&TemplateURL::url, "http://newurl.com"))));
  EXPECT_NE(turl->GetAccountData(), turl->GetLocalData());
  EXPECT_THAT(*turl->GetLocalData(),
              AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                    Property(&TemplateURLData::keyword, u"localkey"),
                    Property(&TemplateURLData::url, "http://localurl.com")));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(
                  Field(&TemplateURLData::sync_guid, "guid"),
                  Property(&TemplateURLData::keyword, u"localkey"),
                  Property(&TemplateURLData::url, "http://localurl.com"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotDualWriteUponStopSyncingWithLocalAndAccountValue) {
  // Add local template url.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  ASSERT_FALSE(model()->GetTemplateURLForKeyword(u"localkey"));
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"accountkey"));
  ASSERT_NE(turl->GetAccountData(), turl->GetLocalData());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  // This should not write the local and the account value to the other store.
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));
  EXPECT_THAT(
      turl, Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid"),
                          Property(&TemplateURL::keyword, u"localkey"),
                          Property(&TemplateURL::url, "http://localurl.com"))));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(
                  Field(&TemplateURLData::sync_guid, "guid"),
                  Property(&TemplateURLData::keyword, u"localkey"),
                  Property(&TemplateURLData::url, "http://localurl.com"))));
  EXPECT_EQ(0u, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotDualWriteUponUpdateTemplateURLVisitTime) {
  TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey1", /*url=*/"http://localurl1.com",
      /*guid=*/"guid1",
      /*last_modified=*/base::Time::FromTimeT(100)));
  TemplateURL* turl2 = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey2", /*url=*/"http://localurl2.com",
      /*guid=*/"guid2", /*last_modified=*/base::Time::FromTimeT(10)));

  // Start syncing.
  syncer::SyncDataList initial_data;
  // Local turl1 has the more recent last_modified time and thus is the active
  // value.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey1", /*url=*/"http://accounturl1.com",
          /*guid=*/"guid1", /*last_modified=*/base::Time::FromTimeT(10))
          ->data()));
  // Account turl2 has the more recent last_modified time and thus is the active
  // value.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey2", /*url=*/"http://accounturl1.com",
          /*guid=*/"guid2", /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  const base::Time time_now = base::Time::Now();
  const base::Time null_time;
  ASSERT_NE(time_now, null_time);

  // Update last_visited time for `turl1`. This should update the local value.
  model()->UpdateTemplateURLVisitTime(turl1);
  ASSERT_TRUE(turl1->GetLocalData());
  ASSERT_TRUE(turl1->GetAccountData());
  EXPECT_NE(turl1->GetLocalData(), turl1->GetAccountData());
  // Local last_visited has been updated whereas the account last_visited stays
  // null.
  EXPECT_GE(turl1->GetLocalData()->last_visited, time_now);
  EXPECT_EQ(turl1->GetAccountData()->last_visited, null_time);
  // No change is committed since only the local data was updated.
  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_THAT(*turl1->GetAccountData(),
              AllOf(Field(&TemplateURLData::sync_guid, "guid1"),
                    Property(&TemplateURLData::keyword, u"accountkey1")));
  EXPECT_THAT(turl1,
              Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid1"),
                            Property(&TemplateURL::keyword, u"localkey1"))));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Contains(AllOf(Field(&TemplateURLData::sync_guid, "guid1"),
                             Property(&TemplateURLData::keyword, u"localkey1"),
                             Field(&TemplateURLData::last_visited,
                                   turl1->last_visited()))));

  // Update last_visited time for `turl2`. This should update only the account
  // value.
  model()->UpdateTemplateURLVisitTime(turl2);
  ASSERT_TRUE(turl2->GetLocalData());
  ASSERT_TRUE(turl2->GetAccountData());
  EXPECT_NE(turl2->GetLocalData(), turl2->GetAccountData());
  // Account last_visited is updated whereas the local last_visited stays null.
  EXPECT_EQ(turl2->GetLocalData()->last_visited, null_time);
  EXPECT_GE(turl2->GetAccountData()->last_visited, time_now);
  // Change is committed since the account data was updated.
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_THAT(turl2,
              Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid2"),
                            Property(&TemplateURL::keyword, u"accountkey2"))));
  EXPECT_THAT(*turl2->GetLocalData(),
              AllOf(Field(&TemplateURLData::sync_guid, "guid2"),
                    Property(&TemplateURLData::keyword, u"localkey2")));
  EXPECT_THAT(
      GetKeywordsFromDatabase(),
      Contains(AllOf(Field(&TemplateURLData::sync_guid, "guid2"),
                     Property(&TemplateURLData::keyword, u"localkey2"),
                     Field(&TemplateURLData::last_visited, null_time))));
}

TEST_F(
    TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
    ShouldNotDualWriteUponUpdateTemplateURLVisitTimeForLocalOnlyTemplateURL) {
  TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey1", /*url=*/"http://localurl1.com",
      /*guid=*/"guid1",
      /*last_modified=*/base::Time::FromTimeT(100)));

  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  const base::Time time_now = base::Time::Now();
  ASSERT_FALSE(time_now.is_null());

  // Update last_visited time for `turl1`. This should update the account value.
  model()->UpdateTemplateURLVisitTime(turl1);

  // No account data is created.
  EXPECT_FALSE(turl1->GetAccountData());
  EXPECT_GE(turl1->GetLocalData()->last_visited, time_now);
  // No change is committed since only the local data was updated.
  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_THAT(turl1,
              Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid1"),
                            Property(&TemplateURL::keyword, u"localkey1"))));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Contains(AllOf(Field(&TemplateURLData::sync_guid, "guid1"),
                             Property(&TemplateURLData::keyword, u"localkey1"),
                             Field(&TemplateURLData::last_visited,
                                   turl1->last_visited()))));
}

TEST_F(
    TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
    ShouldNotDualWriteUponUpdateTemplateURLVisitTimeForAccountOnlyTemplateURL) {
  // Start syncing.
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey1", /*url=*/"http://accounturl1.com",
          /*guid=*/"guid1", /*last_modified=*/base::Time::FromTimeT(10))
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  const base::Time time_now = base::Time::Now();
  ASSERT_FALSE(time_now.is_null());

  TemplateURL* turl1 = model()->GetTemplateURLForGUID("guid1");
  ASSERT_TRUE(turl1);
  // Update last_visited time for `turl1`. This should update the local value.
  model()->UpdateTemplateURLVisitTime(turl1);

  // No local data is created.
  EXPECT_FALSE(turl1->GetLocalData());
  EXPECT_GE(turl1->GetAccountData()->last_visited, time_now);
  // Change is committed since the account data was updated.
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_THAT(turl1,
              Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid1"),
                            Property(&TemplateURL::keyword, u"accountkey1"))));
  // Account search engine is not added to the database.
  EXPECT_THAT(GetKeywordsFromDatabase(), IsEmpty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotDualWriteUponSetUserSelectedDefaultSearchProvider) {
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));
  ASSERT_NE(model()->GetDefaultSearchProvider(), turl);

  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  model()->SetUserSelectedDefaultSearchProvider(turl);
  // Default search engines are not taken care of by sync anymore.
  ASSERT_EQ(model()->GetDefaultSearchProvider(), turl);
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_EQ(0u, processor()->change_list_size());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,

       ShouldNotAddToDatabaseUponInitialMerge) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  EXPECT_EQ(0u, processor()->change_list_size());
  // Account search engine is not added to the database.
  EXPECT_THAT(GetKeywordsFromDatabase(), IsEmpty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldOnlyUpdateSyncGuidUponInitialMergeIfConflict) {
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://localurl.com", /*guid=*/"localguid"));
  ASSERT_TRUE(turl);

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  EXPECT_THAT(turl, Pointee(AllOf(
                        Property(&TemplateURL::sync_guid, "accountguid"),
                        Property(&TemplateURL::url, "http://accounturl.com"))));
  // Only the sync guid is updated, no other fields.
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(
                  Field(&TemplateURLData::sync_guid, "accountguid"),
                  Property(&TemplateURLData::url, "http://localurl.com"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotUpdateDatabaseEntryIfLocalHasSameGuid) {
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid"));
  ASSERT_TRUE(turl);

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_EQ(0u, processor()->change_list_size());

  EXPECT_TRUE(turl->GetLocalData());
  EXPECT_TRUE(turl->GetAccountData());
  EXPECT_THAT(turl, Pointee(AllOf(
                        Property(&TemplateURL::sync_guid, "guid"),
                        Property(&TemplateURL::keyword, u"accountkey"),
                        Property(&TemplateURL::url, "http://accounturl.com"))));
  // Local data is unchanged.
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(
                  Field(&TemplateURLData::sync_guid, "guid"),
                  Property(&TemplateURLData::keyword, u"localkey"),
                  Property(&TemplateURLData::url, "http://localurl.com"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotAddToDatabaseUponIncrementalAdd) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey",
          /*url=*/"http://accounturl.com", /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))));
  model()->ProcessSyncChanges(FROM_HERE, changes);
  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_THAT(
      model()->GetTemplateURLForGUID("accountguid"),
      Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                    Property(&TemplateURL::url, "http://accounturl.com"))));
  // Account search engine is not added to the database.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid")->GetLocalData());
  EXPECT_THAT(GetKeywordsFromDatabase(), IsEmpty());
}

TEST_F(
    TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
    ShouldNotUpdateDatabaseUponIncrementalAddIfConflictWithPreexistingAccountSearchEngine) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey1", /*url=*/"http://accounturl1.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey2",
          /*url=*/"http://accounturl2.com", /*guid=*/"accountguid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  EXPECT_FALSE(turl->GetLocalData());
  EXPECT_THAT(
      turl,
      Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey2"),
                    Property(&TemplateURL::url, "http://accounturl2.com"))));
  // Account search engine is not added to the database.
  EXPECT_THAT(GetKeywordsFromDatabase(), IsEmpty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotUpdateDatabaseEntryIfLocalHasSameGuidUponIncrementalAdd) {
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid"));
  ASSERT_TRUE(turl);

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey",
          /*url=*/"http://accounturl.com", /*guid=*/"guid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  // Local and account data are merged since the sync guid is the same.
  EXPECT_TRUE(turl->GetLocalData());
  EXPECT_TRUE(turl->GetAccountData());
  EXPECT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::sync_guid, "guid"),
                            Property(&TemplateURL::keyword, u"accountkey"))));
  // Database entry is not updated.
  EXPECT_THAT(
      GetKeywordsFromDatabase(),
      ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                        Property(&TemplateURLData::keyword, u"localkey"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotConflictIfSyncGuidIsDifferentUponIncrementalAdd) {
  const TemplateURL* local = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://localurl.com", /*guid=*/"localguid"));
  ASSERT_TRUE(local);

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_ADD,
      CreateTestTemplateURL(
          /*keyword=*/u"key",
          /*url=*/"http://accounturl.com", /*guid=*/"accountguid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  // Local and account data are not merged since the sync guid is different,
  // even though the keyword is the same.
  EXPECT_TRUE(local->GetLocalData());
  EXPECT_FALSE(local->GetAccountData());
  EXPECT_THAT(local, Pointee(AllOf(
                         Property(&TemplateURL::sync_guid, "localguid"),
                         Property(&TemplateURL::keyword, u"key"),
                         Property(&TemplateURL::url, "http://localurl.com"))));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "localguid"),
                                Property(&TemplateURLData::keyword, u"key"))));

  const TemplateURL* account = model()->GetTemplateURLForGUID("accountguid");
  EXPECT_FALSE(account->GetLocalData());
  EXPECT_TRUE(account->GetAccountData());
  EXPECT_THAT(
      account,
      Pointee(AllOf(Property(&TemplateURL::sync_guid, "accountguid"),
                    Property(&TemplateURL::keyword, u"key"),
                    Property(&TemplateURL::url, "http://accounturl.com"))));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              Not(ElementsAre(
                  AllOf(Field(&TemplateURLData::sync_guid, "accountguid")))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotAddToDatabaseUponIncrementalDeletion) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey1", /*url=*/"http://accounturl1.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());
  ASSERT_THAT(GetKeywordsFromDatabase(), IsEmpty());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_DELETE,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey2",
          /*url=*/"http://accounturl2.com", /*guid=*/"accountguid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_THAT(GetKeywordsFromDatabase(), IsEmpty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotUpdateDatabaseUponIncrementalDeletionIfNonExistentAccountData) {
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));
  ASSERT_TRUE(turl);

  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(
      CreateTestSyncChange(syncer::SyncChange::ACTION_DELETE,
                           CreateTestTemplateURL(
                               /*keyword=*/u"key",
                               /*url=*/"http://url.com", /*guid=*/"guid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                                Property(&TemplateURLData::keyword, u"key"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotRemoveLocalUponIncrementalDeletion) {
  // Add a local-only template url.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));

  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(
      CreateTestSyncChange(syncer::SyncChange::ACTION_DELETE,
                           CreateTestTemplateURL(
                               /*keyword=*/u"key",
                               /*url=*/"http://url.com", /*guid=*/"guid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  // Local search engine is not deleted.
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_TRUE(turl->GetLocalData());
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                                Property(&TemplateURLData::keyword, u"key"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotAddToDatabaseUponIncrementalUpdateForNonExistentSearchEngine) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey",
          /*url=*/"http://accounturl.com", /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))));
  model()->ProcessSyncChanges(FROM_HERE, changes);
  EXPECT_EQ(0u, processor()->change_list_size());
  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  EXPECT_FALSE(turl->GetLocalData());
  EXPECT_THAT(
      model()->GetTemplateURLForGUID("accountguid"),
      Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                    Property(&TemplateURL::url, "http://accounturl.com"))));
  // Account search engine is not added to the database.
  EXPECT_THAT(GetKeywordsFromDatabase(), IsEmpty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotAddToDatabaseUponIncrementalUpdate) {
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey1", /*url=*/"http://accounturl1.com",
          /*guid=*/"accountguid")
          ->data()));
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey2",
          /*url=*/"http://accounturl2.com", /*guid=*/"accountguid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_THAT(
      model()->GetTemplateURLForGUID("accountguid"),
      Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey2"),
                    Property(&TemplateURL::url, "http://accounturl2.com"))));
  // Account search engine is not added to the database.
  EXPECT_THAT(GetKeywordsFromDatabase(), IsEmpty());
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotUpdateDatabaseUponIncrementalUpdateIfLocalAndAccountExist) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://url.com", /*guid=*/"guid"));
  ASSERT_TRUE(turl);

  syncer::SyncChangeList changes;
  changes.push_back(CreateTestSyncChange(
      syncer::SyncChange::ACTION_UPDATE,
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey",
          /*url=*/"http://accounturl.com", /*guid=*/"guid")));
  model()->ProcessSyncChanges(FROM_HERE, changes);

  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_TRUE(turl->GetLocalData());
  EXPECT_TRUE(turl->GetAccountData());
  EXPECT_THAT(turl, Pointee(AllOf(
                        Property(&TemplateURL::keyword, u"accountkey"),
                        Property(&TemplateURL::url, "http://accounturl.com"))));
  // Local data is unchanged.
  EXPECT_THAT(*turl->GetLocalData(),
              AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                    Property(&TemplateURLData::keyword, u"key")));
  EXPECT_THAT(GetKeywordsFromDatabase(),
              ElementsAre(AllOf(Field(&TemplateURLData::sync_guid, "guid"),
                                Property(&TemplateURLData::keyword, u"key"))));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeIgnoresUntouchedAutogeneratedKeywords) {
  syncer::SyncDataList initial_data;
  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"key", "http://url.com", "guid", base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/true,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  // This search engine should be ignored.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("guid"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       AddingUntouchedAutogeneratedKeywordsSendsNoUpdate) {
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      u"key", "http://url.com", "guid", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  ASSERT_THAT(
      turl,
      Pointee(AllOf(Property(&TemplateURL::safe_for_autoreplace, true),
                    Property(&TemplateURL::is_active,
                             TemplateURLData::ActiveStatus::kUnspecified))));
  // No account data should be created.
  EXPECT_FALSE(turl->GetAccountData());
  // Nothing is committed to the server.
  EXPECT_EQ(processor()->change_list_size(), 0u);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       UpdatingUntouchedAutogeneratedKeywordsSendsUpdate) {
  // `safe_for_autoreplace` is true and `is_active` is `kUnspecified`. This
  // represents an autogenerated keyword which the user has not touched.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      u"key", "http://url.com", "guid", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"key"));
  // No account data is created.
  EXPECT_FALSE(turl->GetAccountData());
  // Nothing is committed to the server.
  EXPECT_EQ(0U, processor()->change_list_size());

  // Change a keyword.
  model()->ResetTemplateURL(turl, turl->short_name(), u"newkey", turl->url());

  // `safe_for_autoreplace` changes to false and the keyword is marked active,
  // since the keyword was manually updated.
  EXPECT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"newkey"),
                            Property(&TemplateURL::safe_for_autoreplace, false),
                            Property(&TemplateURL::is_active,
                                     TemplateURLData::ActiveStatus::kTrue))));
  // Both local and account are created.
  EXPECT_EQ(turl->GetAccountData(), turl->GetLocalData());

  // The change is committed to the server.
  ASSERT_TRUE(processor()->contains_guid("guid"));
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
}

class TemplateURLServiceSyncMergeTest : public TemplateURLServiceSyncTest {
 public:
  void ShouldOverrideLocalWithSameGuidIfBetter();
  void ShouldNotOverrideLocalWithSameGuidIfNotBetter();
  void ShouldOverrideDuplicateLocalIfBetter();
  void ShouldNotOverrideDuplicateLocalIfNotBetter();
  void ShouldNotOverrideDuplicateLocalDefaultSearchProvider();
  void ShouldUpdateConflictingDefaultSearchEngineIfBetter();
  void ShouldNotUpdateConflictingDefaultSearchEngineIfNotBetter();
  void ShouldUpdateConflictingStarterPackSearchEngineIfBetter();
  void ShouldNotUpdateConflictingStarterPackSearchEngineIfNotBetter();
  void ShouldUpdateConflictingPrepopulatedSearchEngineIfBetter();
  void ShouldNotUpdateConflictingPrepopulatedSearchEngineIfNotBetter();
};

class
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled
    : public TemplateURLServiceSyncMergeTest {
 public:
  TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled() {
    feature_list_.InitAndDisableFeature(
        syncer::kSeparateLocalAndAccountSearchEngines);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

class
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled
    : public TemplateURLServiceSyncMergeTest {
 public:
  TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled()
      : feature_list_(syncer::kSeparateLocalAndAccountSearchEngines) {}

 private:
  base::test::ScopedFeatureList feature_list_;
};

void TemplateURLServiceSyncMergeTest::
    ShouldOverrideLocalWithSameGuidIfBetter() {
  // Add local template url.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(10)));
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  // Account keyword has more recent timestamp and thus wins.
  ASSERT_FALSE(model()->GetTemplateURLForKeyword(u"localkey"));
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"accountkey"));

  // Nothing is committed to the server.
  ASSERT_EQ(0u, processor()->change_list_size());
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldOverrideLocalWithSameGuidIfBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldOverrideLocalWithSameGuidIfBetter());

  // Stopping sync should leave the sync value.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"localkey"));
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"accountkey"));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldOverrideLocalWithSameGuidIfBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldOverrideLocalWithSameGuidIfBetter());

  // Account keyword should not replace but only override the local keyword.
  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"accountkey");
  EXPECT_THAT(turl->GetLocalData(),
              Optional(Property(&TemplateURLData::keyword, u"localkey")));

  // Stopping sync should remove the sync value.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"accountkey"));
}

void TemplateURLServiceSyncMergeTest::
    ShouldNotOverrideLocalWithSameGuidIfNotBetter() {
  // Add local template url.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com", /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(100)));
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(10))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  // Local keyword has a more recent timestamp and thus wins.
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));
  ASSERT_FALSE(model()->GetTemplateURLForKeyword(u"accountkey"));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldNotOverrideLocalWithSameGuidIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldNotOverrideLocalWithSameGuidIfNotBetter());

  // Local keyword is committed to the server.
  ASSERT_TRUE(processor()->contains_guid("guid"));
  EXPECT_EQ(processor()->change_for_guid("guid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
  EXPECT_EQ(processor()
                ->change_for_guid("guid")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .keyword(),
            "localkey");

  // Stopping sync should not affect the value.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_TRUE(model()->GetTemplateURLForKeyword(u"localkey"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"accountkey"));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldNotOverrideLocalWithSameGuidIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldNotOverrideLocalWithSameGuidIfNotBetter());

  EXPECT_EQ(processor()->change_list_size(), 0u);

  // Account keyword is not ignored but is only overridden by the local keyword.
  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"localkey");
  EXPECT_THAT(turl->GetAccountData(),
              Optional(Property(&TemplateURLData::keyword, u"accountkey")));

  // Stopping sync should remove the sync value.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetTemplateURLForKeyword(u"localkey"));
  EXPECT_FALSE(model()->GetTemplateURLForKeyword(u"accountkey"));
}

void TemplateURLServiceSyncMergeTest::ShouldOverrideDuplicateLocalIfBetter() {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://localurl.com", /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(10)));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  // Nothing is committed to the server.
  ASSERT_EQ(0u, processor()->change_list_size());
  // Account keyword wins.
  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_THAT(turl,
              Pointee(Property(&TemplateURL::url, "http://accounturl.com")));
  ASSERT_FALSE(model()->GetTemplateURLForGUID("localguid"));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldOverrideDuplicateLocalIfBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldOverrideDuplicateLocalIfBetter());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  // Local keyword has been removed and the account keyword stays.
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_THAT(model()->GetTemplateURLForGUID("accountguid"),
              Pointee(Property(&TemplateURL::url, "http://accounturl.com")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldOverrideDuplicateLocalIfBetter) {
  base::HistogramTester histogram_tester;
  ASSERT_NO_FATAL_FAILURE(ShouldOverrideDuplicateLocalIfBetter());

  // Sync guid of the local keyword should be updated.
  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"key");
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Field(&TemplateURLData::sync_guid, "accountguid"),
                     Property(&TemplateURLData::url, "http://localurl.com"))));
  EXPECT_THAT(turl->GetAccountData(),
              Optional(AllOf(
                  Field(&TemplateURLData::sync_guid, "accountguid"),
                  Property(&TemplateURLData::url, "http://accounturl.com"))));
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.DuplicateIsDefaultSearchProvider", false, 1);

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  // Account keyword is removed, but the local keyword stays behind, with
  // updated sync guid.
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_THAT(turl,
              Pointee(Property(&TemplateURL::url, "http://localurl.com")));
}

void TemplateURLServiceSyncMergeTest::
    ShouldNotOverrideDuplicateLocalIfNotBetter() {
  // Add local template url.
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://localurl.com", /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(100)));
  ASSERT_EQ(turl, model()->GetTemplateURLForKeyword(u"key"));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(10))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  ASSERT_FALSE(processor()->contains_guid("localguid"));
  // Sync guid of local turl is updated.
  ASSERT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  ASSERT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  ASSERT_EQ(turl->url(), "http://localurl.com");
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldNotOverrideDuplicateLocalIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldNotOverrideDuplicateLocalIfNotBetter());

  // Local keyword is committed to the server.
  ASSERT_TRUE(processor()->contains_guid("accountguid"));
  EXPECT_EQ(processor()->change_for_guid("accountguid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
  EXPECT_EQ(processor()
                ->change_for_guid("accountguid")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .url(),
            "http://localurl.com");

  // Stopping sync should not change anything.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_THAT(model()->GetTemplateURLForGUID("accountguid"),
              Pointee(Property(&TemplateURL::url, "http://localurl.com")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldNotOverrideDuplicateLocalIfNotBetter) {
  base::HistogramTester histogram_tester;
  ASSERT_NO_FATAL_FAILURE(ShouldNotOverrideDuplicateLocalIfNotBetter());

  // Nothing is committed to the server.
  EXPECT_FALSE(processor()->contains_guid("accountguid"));

  // Sync guid of the local keyword should be updated.
  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"key");
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Field(&TemplateURLData::sync_guid, "accountguid"),
                     Property(&TemplateURLData::url, "http://localurl.com"))));
  EXPECT_THAT(turl->GetAccountData(),
              Optional(AllOf(
                  Field(&TemplateURLData::sync_guid, "accountguid"),
                  Property(&TemplateURLData::url, "http://accounturl.com"))));
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.DuplicateIsDefaultSearchProvider", false, 1);

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  // Account keyword is removed, but the local keyword stays behind, with
  // updated sync guid.
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_THAT(turl,
              Pointee(Property(&TemplateURL::url, "http://localurl.com")));
}

void TemplateURLServiceSyncMergeTest::
    ShouldNotOverrideDuplicateLocalDefaultSearchProvider() {
  // Add local template url.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"key", /*url=*/"http://localurl.com", /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(10)));
  model()->SetUserSelectedDefaultSearchProvider(turl);

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"key", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100))
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  ASSERT_FALSE(processor()->contains_guid("localguid"));
  // Sync guid of local turl is updated.
  ASSERT_EQ(turl, model()->GetDefaultSearchProvider());
  ASSERT_EQ(turl->sync_guid(), "accountguid");
  ASSERT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  ASSERT_EQ(turl->url(), "http://localurl.com");
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldNotOverrideDuplicateLocalDefaultSearchProvider) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotOverrideDuplicateLocalDefaultSearchProvider());

  // Local keyword is committed to the server.
  ASSERT_TRUE(processor()->contains_guid("accountguid"));
  EXPECT_EQ(processor()->change_for_guid("accountguid").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
  EXPECT_EQ(processor()
                ->change_for_guid("accountguid")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .url(),
            "http://localurl.com");

  // Stopping sync should not change anything.
  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  EXPECT_THAT(turl,
              Pointee(Property(&TemplateURL::url, "http://localurl.com")));
  EXPECT_EQ(turl, model()->GetDefaultSearchProvider());
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldNotOverrideDuplicateLocalDefaultSearchProvider) {
  base::HistogramTester histogram_tester;
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotOverrideDuplicateLocalDefaultSearchProvider());

  // Nothing is committed to the server.
  EXPECT_EQ(processor()->change_list_size(), 0u);

  // Sync guid of the local keyword should be updated.
  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"key");
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Field(&TemplateURLData::sync_guid, "accountguid"),
                     Property(&TemplateURLData::url, "http://localurl.com"))));
  // Account data is ignored.
  EXPECT_FALSE(turl->GetAccountData());
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.DuplicateIsDefaultSearchProvider", true, 1);

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  // The local keyword stays behind with updated sync guid.
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_EQ(turl, model()->GetDefaultSearchProvider());
  EXPECT_THAT(turl,
              Pointee(Property(&TemplateURL::url, "http://localurl.com")));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
}

void TemplateURLServiceSyncMergeTest::
    ShouldUpdateConflictingDefaultSearchEngineIfBetter() {
  // Add local template url.
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(10),
      /*safe_for_autoreplace=*/false,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/99999, /*starter_pack_id=*/0));
  model()->SetUserSelectedDefaultSearchProvider(turl);

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/99999, /*starter_pack_id=*/0)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  ASSERT_EQ(turl, model()->GetDefaultSearchProvider());
  ASSERT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  ASSERT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  ASSERT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
  ASSERT_EQ(0u, processor()->change_list_size());
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldUpdateConflictingDefaultSearchEngineIfBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldUpdateConflictingDefaultSearchEngineIfBetter());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  const TemplateURL* turl = model()->GetDefaultSearchProvider();
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldUpdateConflictingDefaultSearchEngineIfBetter) {
  ASSERT_NO_FATAL_FAILURE(ShouldUpdateConflictingDefaultSearchEngineIfBetter());

  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"accountkey");
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"localkey"),
                     Field(&TemplateURLData::sync_guid, "accountguid"))));
  EXPECT_THAT(
      turl->GetAccountData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"accountkey"),
                     Field(&TemplateURLData::sync_guid, "accountguid"))));

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->GetDefaultSearchProvider());
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
}

void TemplateURLServiceSyncMergeTest::
    ShouldNotUpdateConflictingDefaultSearchEngineIfNotBetter() {
  // Add local template url.
  TemplateURL* local = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/99999, /*starter_pack_id=*/0));
  model()->SetUserSelectedDefaultSearchProvider(local);

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/false,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/99999, /*starter_pack_id=*/0)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));
  ASSERT_FALSE(processor()->contains_guid("accountguid"));

  ASSERT_EQ(local, model()->GetDefaultSearchProvider());
  ASSERT_THAT(local,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                            Property(&TemplateURL::sync_guid, "localguid"))));

  const TemplateURL* account = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_NE(local, account);
  ASSERT_THAT(account, Pointee(Property(&TemplateURL::keyword, u"accountkey")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldNotUpdateConflictingDefaultSearchEngineIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotUpdateConflictingDefaultSearchEngineIfNotBetter());

  // Local turl is committed to the server as-is.
  ASSERT_TRUE(processor()->contains_guid("localguid"));
  EXPECT_EQ(processor()->change_for_guid("localguid").change_type(),
            syncer::SyncChange::ACTION_ADD);
  EXPECT_EQ(processor()
                ->change_for_guid("localguid")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .url(),
            "http://localurl.com");

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  EXPECT_THAT(local,
              AllOf(Pointee(Property(&TemplateURL::keyword, u"localkey")),
                    Eq(model()->GetDefaultSearchProvider())));
  EXPECT_THAT(model()->GetTemplateURLForGUID("accountguid"),
              Pointee(Property(&TemplateURL::keyword, u"accountkey")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldNotUpdateConflictingDefaultSearchEngineIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotUpdateConflictingDefaultSearchEngineIfNotBetter());

  // Nothing is committed to the server.
  EXPECT_EQ(processor()->change_list_size(), 0u);

  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  EXPECT_FALSE(local->GetAccountData());
  EXPECT_THAT(local,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                            Property(&TemplateURL::sync_guid, "localguid"))));

  const TemplateURL* account = model()->GetTemplateURLForGUID("accountguid");
  EXPECT_FALSE(account->GetLocalData());
  EXPECT_THAT(account,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(local, model()->GetTemplateURLForGUID("localguid"));
  EXPECT_THAT(local,
              AllOf(Pointee(Property(&TemplateURL::keyword, u"localkey")),
                    Eq(model()->GetDefaultSearchProvider())));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
}

void TemplateURLServiceSyncMergeTest::
    ShouldUpdateConflictingStarterPackSearchEngineIfBetter() {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(10),
      /*safe_for_autoreplace=*/false,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/1));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/0, /*starter_pack_id=*/1)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  ASSERT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  const TemplateURL* turl = model()->FindStarterPackTemplateURL(
      /*starter_pack_id=*/template_url_starter_pack_data::StarterPackId::
          kBookmarks);
  ASSERT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
  ASSERT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  ASSERT_EQ(0u, processor()->change_list_size());
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldUpdateConflictingStarterPackSearchEngineIfBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldUpdateConflictingStarterPackSearchEngineIfBetter());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  const TemplateURL* turl = model()->FindStarterPackTemplateURL(
      /*starter_pack_id=*/template_url_starter_pack_data::StarterPackId::
          kBookmarks);
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldUpdateConflictingStarterPackSearchEngineIfBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldUpdateConflictingStarterPackSearchEngineIfBetter());

  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"accountkey");
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"localkey"),
                     Field(&TemplateURLData::sync_guid, "accountguid"))));
  EXPECT_THAT(
      turl->GetAccountData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"accountkey"),
                     Field(&TemplateURLData::sync_guid, "accountguid"))));

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(turl, model()->FindStarterPackTemplateURL(
                      /*starter_pack_id=*/template_url_starter_pack_data::
                          StarterPackId::kBookmarks));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
}

void TemplateURLServiceSyncMergeTest::
    ShouldNotUpdateConflictingStarterPackSearchEngineIfNotBetter() {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/1));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/false,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/0, /*starter_pack_id=*/1)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  ASSERT_FALSE(processor()->contains_guid("accountguid"));
  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  ASSERT_THAT(local, Pointee(Property(&TemplateURL::keyword, u"localkey")));
  ASSERT_EQ(local, model()->FindStarterPackTemplateURL(
                       /*starter_pack_id=*/template_url_starter_pack_data::
                           StarterPackId::kBookmarks));
  const TemplateURL* account = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_THAT(account, Pointee(Property(&TemplateURL::keyword, u"accountkey")));
  ASSERT_NE(local, account);
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldNotUpdateConflictingStarterPackSearchEngineIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotUpdateConflictingStarterPackSearchEngineIfNotBetter());

  // Local turl is committed to the server as-is.
  ASSERT_TRUE(processor()->contains_guid("localguid"));
  EXPECT_EQ(processor()->change_for_guid("localguid").change_type(),
            syncer::SyncChange::ACTION_ADD);
  EXPECT_EQ(processor()
                ->change_for_guid("localguid")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .url(),
            "http://localurl.com");

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  EXPECT_THAT(local,
              AllOf(Pointee(Property(&TemplateURL::keyword, u"localkey")),
                    Eq(model()->FindStarterPackTemplateURL(
                        /*starter_pack_id=*/template_url_starter_pack_data::
                            StarterPackId::kBookmarks))));
  EXPECT_THAT(model()->GetTemplateURLForGUID("accountguid"),
              Pointee(Property(&TemplateURL::keyword, u"accountkey")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldNotUpdateConflictingStarterPackSearchEngineIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotUpdateConflictingStarterPackSearchEngineIfNotBetter());

  // Nothing is committed to the server.
  EXPECT_EQ(processor()->change_list_size(), 0u);

  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  EXPECT_FALSE(local->GetAccountData());
  EXPECT_THAT(local,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                            Property(&TemplateURL::sync_guid, "localguid"))));

  const TemplateURL* account = model()->GetTemplateURLForGUID("accountguid");
  EXPECT_FALSE(account->GetLocalData());
  EXPECT_THAT(account,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(local, model()->GetTemplateURLForGUID("localguid"));
  EXPECT_THAT(local,
              AllOf(Pointee(Property(&TemplateURL::keyword, u"localkey")),
                    Eq(model()->FindStarterPackTemplateURL(
                        /*starter_pack_id=*/template_url_starter_pack_data::
                            StarterPackId::kBookmarks))));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
}

void TemplateURLServiceSyncMergeTest::
    ShouldUpdateConflictingPrepopulatedSearchEngineIfBetter() {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(10),
      /*safe_for_autoreplace=*/false,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/99999, /*starter_pack_id=*/0));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/99999, /*starter_pack_id=*/0)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  ASSERT_EQ(processor()->change_list_size(), 0u);
  ASSERT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  const TemplateURL* turl = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_THAT(turl, Pointee(Property(&TemplateURL::keyword, u"accountkey")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldUpdateConflictingPrepopulatedSearchEngineIfBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldUpdateConflictingPrepopulatedSearchEngineIfBetter());

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_THAT(model()->GetTemplateURLForGUID("accountguid"),
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldUpdateConflictingPrepopulatedSearchEngineIfBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldUpdateConflictingPrepopulatedSearchEngineIfBetter());

  const TemplateURL* turl = model()->GetTemplateURLForKeyword(u"accountkey");
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"localkey"),
                     Field(&TemplateURLData::sync_guid, "accountguid"))));
  EXPECT_THAT(
      turl->GetAccountData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"accountkey"),
                     Field(&TemplateURLData::sync_guid, "accountguid"))));

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_FALSE(model()->GetTemplateURLForGUID("localguid"));
  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("accountguid"));
  EXPECT_FALSE(turl->GetAccountData());
  EXPECT_THAT(turl,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));
}

void TemplateURLServiceSyncMergeTest::
    ShouldNotUpdateConflictingPrepopulatedSearchEngineIfNotBetter() {
  // Add local template url.
  model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"localguid",
      /*last_modified=*/base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/99999, /*starter_pack_id=*/0));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"accountguid",
          /*last_modified=*/base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/false,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/99999, /*starter_pack_id=*/0)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  ASSERT_FALSE(processor()->contains_guid("accountguid"));
  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  ASSERT_THAT(local, Pointee(Property(&TemplateURL::keyword, u"localkey")));
  const TemplateURL* account = model()->GetTemplateURLForGUID("accountguid");
  ASSERT_THAT(account, Pointee(Property(&TemplateURL::keyword, u"accountkey")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesDisabled,
    ShouldNotUpdateConflictingPrepopulatedSearchEngineIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotUpdateConflictingPrepopulatedSearchEngineIfNotBetter());

  // Local turl is committed to the server as-is.
  ASSERT_TRUE(processor()->contains_guid("localguid"));
  EXPECT_EQ(processor()->change_for_guid("localguid").change_type(),
            syncer::SyncChange::ACTION_ADD);
  EXPECT_EQ(processor()
                ->change_for_guid("localguid")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .url(),
            "http://localurl.com");

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  EXPECT_EQ(local->keyword(), u"localkey");
  EXPECT_THAT(model()->GetTemplateURLForGUID("accountguid"),
              Pointee(Property(&TemplateURL::keyword, u"accountkey")));
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldNotUpdateConflictingPrepopulatedSearchEngineIfNotBetter) {
  ASSERT_NO_FATAL_FAILURE(
      ShouldNotUpdateConflictingPrepopulatedSearchEngineIfNotBetter());

  // Nothing is committed to the server.
  ASSERT_EQ(0u, processor()->change_list_size());

  const TemplateURL* local = model()->GetTemplateURLForGUID("localguid");
  EXPECT_FALSE(local->GetAccountData());
  EXPECT_THAT(local,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                            Property(&TemplateURL::sync_guid, "localguid"))));

  const TemplateURL* account = model()->GetTemplateURLForGUID("accountguid");
  EXPECT_FALSE(account->GetLocalData());
  EXPECT_THAT(account,
              Pointee(AllOf(Property(&TemplateURL::keyword, u"accountkey"),
                            Property(&TemplateURL::sync_guid, "accountguid"))));

  model()->StopSyncing(syncer::SEARCH_ENGINES);
  EXPECT_EQ(local, model()->GetTemplateURLForGUID("localguid"));
  EXPECT_EQ(local->keyword(), u"localkey");
  EXPECT_FALSE(model()->GetTemplateURLForGUID("accountguid"));
}

// Regression test for crbug.com/405036427.
TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldNotCrashForUntouchedAutogeneratedSearchEnginesContainingAccountDataUponGetAllSyncData) {
  const TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      /*is_active=*/TemplateURLData::ActiveStatus::kUnspecified));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/true,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/0, /*starter_pack_id=*/0,
          /*is_active=*/TemplateURLData::ActiveStatus::kTrue)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_THAT(
      turl,
      Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                    Property(&TemplateURL::is_active,
                             TemplateURLData::ActiveStatus::kUnspecified))));
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"localkey"),
                     Field(&TemplateURLData::is_active,
                           TemplateURLData::ActiveStatus::kUnspecified))));
  EXPECT_THAT(turl->GetAccountData(),
              Optional(AllOf(Property(&TemplateURLData::keyword, u"accountkey"),
                             Field(&TemplateURLData::is_active,
                                   TemplateURLData::ActiveStatus::kTrue))));
  // Should not crash.
  EXPECT_EQ(1U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());

  // Should not crash.
  model()->Remove(turl);
  EXPECT_EQ(0U, model()->GetAllSyncData(syncer::SEARCH_ENGINES).size());
}

TEST_F(
    TemplateURLServiceSyncMergeTestWithSeparateLocalAndAccountSearchEnginesEnabled,
    ShouldOnlyCommitAccountDataUponProcessSyncChanges) {
  TemplateURL* turl = model()->Add(CreateTestTemplateURL(
      /*keyword=*/u"localkey", /*url=*/"http://localurl.com",
      /*guid=*/"guid",
      /*last_modified=*/base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true,
      /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      /*is_active=*/TemplateURLData::ActiveStatus::kUnspecified));

  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          /*keyword=*/u"accountkey", /*url=*/"http://accounturl.com",
          /*guid=*/"guid",
          /*last_modified=*/base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/true,
          /*policy_origin=*/TemplateURLData::PolicyOrigin::kNoPolicy,
          /*prepopulate_id=*/0, /*starter_pack_id=*/0,
          /*is_active=*/TemplateURLData::ActiveStatus::kTrue)
          ->data()));
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, initial_data, PassProcessor()));

  EXPECT_EQ(turl, model()->GetTemplateURLForGUID("guid"));
  EXPECT_THAT(
      turl,
      Pointee(AllOf(Property(&TemplateURL::keyword, u"localkey"),
                    Property(&TemplateURL::is_active,
                             TemplateURLData::ActiveStatus::kUnspecified))));
  EXPECT_THAT(
      turl->GetLocalData(),
      Optional(AllOf(Property(&TemplateURLData::keyword, u"localkey"),
                     Field(&TemplateURLData::is_active,
                           TemplateURLData::ActiveStatus::kUnspecified))));
  EXPECT_THAT(turl->GetAccountData(),
              Optional(AllOf(Property(&TemplateURLData::keyword, u"accountkey"),
                             Field(&TemplateURLData::is_active,
                                   TemplateURLData::ActiveStatus::kTrue))));

  model()->Remove(turl);

  ASSERT_EQ(1u, processor()->change_list_size());
  // Should commit the account data.
  EXPECT_EQ(processor()
                ->change_for_guid("guid")
                .sync_data()
                .GetSpecifics()
                .search_engine()
                .keyword(),
            "accountkey");
}

// This test verifies the logging in the following cases:
// 1. Whether a keyword is an untouched autogenerated keyword is logged upon
// every add, update and delete.
// 2. Whether an untouched autogenerated keyword is a prepopulated keyword.
// 3. Whether an untouched autogenerated keyword is a starter pack keyword.
// This test first adds different types of keywords, then updates them and
// finally deletes them, verifying that the histograms are logged correctly in
// each of these cases.
TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldLogUntouchedAutogeneratedKeywordsWhenChanged) {
  ASSERT_FALSE(model()->MergeDataAndStartSyncing(
      syncer::SEARCH_ENGINES, syncer::SyncDataList{}, PassProcessor()));

  base::HistogramTester histogram_tester;

  // Not an untouched keyword.
  TemplateURL* turl0 = model()->Add(CreateTestTemplateURL(
      u"key0", "http://key0.com", "guid0", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kTrue));
  EXPECT_EQ(turl0->GetLocalData(), turl0->GetAccountData());
  ASSERT_TRUE(processor()->contains_guid("guid0"));
  EXPECT_THAT(processor()->change_for_guid("guid0"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_ADD));

  // All the below keywords are untouched autogenerated keywords, given that
  // `safe_for_autoreplace` is true (implying that the keyword is autogenerated)
  // and `is_active` is `kUnspecified` (implying that the keyword is untouched).
  TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      u"key1", "http://key1.com", "guid1", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_FALSE(turl1->GetAccountData());
  EXPECT_FALSE(processor()->contains_guid("guid1"));

  // Starter pack keyword.
  TemplateURL* turl2 = model()->Add(CreateTestTemplateURL(
      u"key2", "http://key2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/1,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_FALSE(turl2->GetAccountData());
  EXPECT_FALSE(processor()->contains_guid("guid2"));

  // Prepopulated keyword.
  TemplateURL* turl3 = model()->Add(CreateTestTemplateURL(
      u"key3", "http://key3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/99999, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  EXPECT_EQ(turl3->GetLocalData(), turl3->GetAccountData());
  ASSERT_TRUE(processor()->contains_guid("guid3"));
  EXPECT_THAT(processor()->change_for_guid("guid3"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_ADD));

  // Only one of the above keywords is not an untouched autogenerated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedAdded"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 3)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedAdded."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));
  // Only one of the above keywords is a starter pack keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedAdded."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 2), base::Bucket(true, 1)));

  // Update a non-untouched autogenerated keyword.
  ASSERT_EQ(turl0, model()->GetTemplateURLForGUID("guid0"));
  model()->UpdateTemplateURLVisitTime(turl0);
  EXPECT_THAT(processor()->change_for_guid("guid0"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_UPDATE));

  // Update the above untouched autogenerated keywords.
  // `turl1` does not log the histograms because it has no account data.
  ASSERT_EQ(turl1, model()->GetTemplateURLForGUID("guid1"));
  model()->UpdateTemplateURLVisitTime(turl1);
  EXPECT_FALSE(turl1->GetAccountData());
  EXPECT_FALSE(processor()->contains_guid("guid1"));

  // `turl2` does not log the histograms because it has no account data.
  ASSERT_EQ(turl2, model()->GetTemplateURLForGUID("guid2"));
  model()->UpdateTemplateURLVisitTime(turl2);
  EXPECT_FALSE(turl2->GetAccountData());
  EXPECT_FALSE(processor()->contains_guid("guid2"));

  ASSERT_EQ(turl3, model()->GetTemplateURLForGUID("guid3"));
  model()->UpdateTemplateURLVisitTime(turl3);
  EXPECT_THAT(processor()->change_for_guid("guid3"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_UPDATE));

  // Note that `turl1` and `turl2` are not logged because they have no account
  // data.
  // Only one of the above keywords is not an untouched autogenerated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedUpdated"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 1)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedUpdated."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 0), base::Bucket(true, 1)));
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedUpdated."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 0)));

  // Delete the above keywords.
  model()->Remove(turl0);
  EXPECT_THAT(processor()->change_for_guid("guid0"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_DELETE));
  model()->Remove(turl1);
  EXPECT_FALSE(processor()->contains_guid("guid1"));
  model()->Remove(turl2);
  EXPECT_FALSE(processor()->contains_guid("guid2"));
  model()->Remove(turl3);
  EXPECT_THAT(processor()->change_for_guid("guid3"),
              Property(&syncer::SyncChange::change_type,
                       syncer::SyncChange::ACTION_DELETE));

  // Note that `turl1` and `turl2` are not logged because they have no account
  // data.
  // Only one of the above keywords is not an untouched autogenerated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedDeleted"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 1)));
  // Only one of the above keywords is a prepopulated keyword.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedDeleted."
                  "IsPrepopulatedEntry"),
              base::BucketsAre(base::Bucket(false, 0), base::Bucket(true, 1)));
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "Sync.SearchEngine.UntouchedAutogeneratedDeleted."
                  "IsStarterPackEntry"),
              base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 0)));
}

// Regression test for crbug.com/405298133.
TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       MergeIgnoresLocalUntouchedAutogeneratedKeywords) {
  // Untouched autogenerated keyword.
  const TemplateURL* turl1 = model()->Add(CreateTestTemplateURL(
      u"localkey1", "http://localkey1.com", "guid1", base::Time::FromTimeT(10),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  // Untouched autogenerated keyword.
  const TemplateURL* turl2 = model()->Add(CreateTestTemplateURL(
      u"localkey2", "http://localkey2.com", "guid2", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/true, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));
  // Not an untouched autogenerated keyword.
  const TemplateURL* turl3 = model()->Add(CreateTestTemplateURL(
      u"localkey3", "http://localkey3.com", "guid3", base::Time::FromTimeT(100),
      /*safe_for_autoreplace=*/false, TemplateURLData::PolicyOrigin::kNoPolicy,
      /*prepopulate_id=*/0, /*starter_pack_id=*/0,
      TemplateURLData::ActiveStatus::kUnspecified));

  syncer::SyncDataList initial_data;
  // Not an untouched autogenerated keyword and more recent than `turl1`.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"accountkey1", "http://accountkey1.com", "guid1",
          base::Time::FromTimeT(100),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));
  // Not an untouched autogenerated keyword but less recent than `turl2`.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"accountkey2", "http://accountkey2.com", "guid2",
          base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));
  // Not an untouched autogenerated keyword but less recent than `turl3`.
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(
          u"accountkey3", "http://accountkey3.com", "guid3",
          base::Time::FromTimeT(10),
          /*safe_for_autoreplace=*/false,
          TemplateURLData::PolicyOrigin::kNoPolicy, /*prepopulate_id=*/0,
          /*starter_pack_id=*/0, TemplateURLData::ActiveStatus::kUnspecified)
          ->data()));

  ASSERT_FALSE(model()
                   ->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                              initial_data, PassProcessor())
                   .has_value());

  // For `guid1`, the account keyword wins since it is more recent.
  ASSERT_EQ(turl1, model()->GetTemplateURLForGUID("guid1"));
  EXPECT_EQ(turl1->keyword(), u"accountkey1");
  EXPECT_FALSE(processor()->contains_guid("guid1"));
  // For `guid2`, the account keyword wins even though it is less recent.
  ASSERT_EQ(turl2, model()->GetTemplateURLForGUID("guid2"));
  EXPECT_EQ(turl2->keyword(), u"accountkey2");
  EXPECT_FALSE(processor()->contains_guid("guid2"));
  // For `guid3`, the local keyword wins since it is more recent. This is also
  // committed to the processor since it has not been filtered out as it's not
  // an untouched autogenerated keyword.
  ASSERT_EQ(turl3, model()->GetTemplateURLForGUID("guid3"));
  EXPECT_EQ(turl3->keyword(), u"localkey3");
  EXPECT_FALSE(processor()->contains_guid("guid3"));
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotRemoveAccountDataUponBrowserShutdown) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  TemplateURLData data1 =
      CreateTestTemplateURL(u"key1", "http://key1.com", "guid1")->data();
  // Add a local-and-account search engine.
  const TemplateURL* turl1 =
      model()->Add(std::make_unique<TemplateURL>(data1, data1));
  ASSERT_TRUE(turl1->GetAccountData());
  ASSERT_TRUE(turl1->GetLocalData());

  base::HistogramTester histogram_tester;
  model()->OnBrowserShutdown(syncer::SEARCH_ENGINES);

  ASSERT_EQ(turl1, model()->GetTemplateURLForGUID("guid1"));
  EXPECT_TRUE(turl1->GetAccountData());
  histogram_tester.ExpectTotalCount(
      "Sync.SearchEngine.HasLocalDataDuringStopSyncing2", 0);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldRemoveAccountDataUponStopSyncing) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  TemplateURLData data1 =
      CreateTestTemplateURL(u"key1", "http://key1.com", "guid1")->data();
  // Add a local-and-account search engine.
  const TemplateURL* turl1 =
      model()->Add(std::make_unique<TemplateURL>(data1, data1));

  ASSERT_TRUE(turl1->GetAccountData());
  ASSERT_TRUE(turl1->GetLocalData());

  base::HistogramTester histogram_tester;
  model()->StopSyncing(syncer::SEARCH_ENGINES);

  ASSERT_EQ(turl1, model()->GetTemplateURLForGUID("guid1"));
  EXPECT_FALSE(turl1->GetAccountData());
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.HasLocalDataDuringStopSyncing2", true, 1);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldLogCommittedChangesUponSyncStart) {
  syncer::SyncDataList initial_data;
  // This should lead to a deletion commit (because of empty url).
  syncer::SyncData sync_data1 =
      TemplateURLService::CreateSyncDataFromTemplateURLData(
          CreateTestTemplateURL(u"key1", /*url=*/"https://key1.com", "guid1")
              ->data());
  const_cast<sync_pb::EntitySpecifics&>(sync_data1.GetSpecifics())
      .mutable_search_engine()
      ->set_url("");
  initial_data.push_back(sync_data1);

  // This should lead to an update commit.
  TemplateURLData data2 =
      CreateTestTemplateURL(u"key2", "http://key2.com", "guid2")->data();
  data2.input_encodings = {"UTF-8", "UTF-16", "UTF-16", "UTF-8"};
  initial_data.push_back(
      TemplateURLService::CreateSyncDataFromTemplateURLData(data2));

  base::HistogramTester histogram_tester;
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  ASSERT_EQ(processor()->change_list_size(), 2u);
  ASSERT_TRUE(processor()->contains_guid("guid1"));
  ASSERT_EQ(processor()->change_for_guid("guid1").change_type(),
            syncer::SyncChange::ACTION_DELETE);
  ASSERT_FALSE(model()->GetTemplateURLForGUID("guid1"));
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.ChangesCommittedUponSyncStart_Deleted", /*sample=*/1,
      /*expected_bucket_count=*/1);
  ASSERT_TRUE(processor()->contains_guid("guid2"));
  ASSERT_EQ(processor()->change_for_guid("guid2").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
  ASSERT_THAT(model()->GetTemplateURLForGUID("guid2"),
              Pointee(Property(&TemplateURL::input_encodings,
                               std::vector<std::string>{"UTF-8", "UTF-16"})));
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.ChangesCommittedUponSyncStart_Updated", /*sample=*/1,
      /*expected_bucket_count=*/1);

  // No adds are committed upon sync start.
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.ChangesCommittedUponSyncStart_Added", /*sample=*/0,
      /*expected_bucket_count=*/1);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldLogCommittedChangesUponIncrementalUpdate) {
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES,
                                    syncer::SyncDataList{}, PassProcessor());

  syncer::SyncChangeList changes;
  // This should lead to an deletion commit (because of empty url).
  syncer::SyncData sync_data1 =
      TemplateURLService::CreateSyncDataFromTemplateURLData(
          CreateTestTemplateURL(u"key1", /*url=*/"https://key1.com", "guid1")
              ->data());
  const_cast<sync_pb::EntitySpecifics&>(sync_data1.GetSpecifics())
      .mutable_search_engine()
      ->set_url("");
  changes.emplace_back(FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
                       sync_data1);
  // This should lead to an update commit.
  TemplateURLData data2 =
      CreateTestTemplateURL(u"key2", "http://key2.com", "guid2")->data();
  data2.input_encodings = {"UTF-8", "UTF-16", "UTF-16", "UTF-8"};
  changes.push_back(CreateTestSyncChange(syncer::SyncChange::ACTION_UPDATE,
                                         std::make_unique<TemplateURL>(data2)));

  base::HistogramTester histogram_tester;
  model()->ProcessSyncChanges(FROM_HERE, changes);

  ASSERT_EQ(processor()->change_list_size(), 2u);
  ASSERT_TRUE(processor()->contains_guid("guid1"));
  ASSERT_EQ(processor()->change_for_guid("guid1").change_type(),
            syncer::SyncChange::ACTION_DELETE);
  ASSERT_FALSE(model()->GetTemplateURLForGUID("guid1"));
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.ChangesCommittedUponIncrementalUpdate_Deleted",
      /*sample=*/1,
      /*expected_bucket_count=*/1);
  ASSERT_TRUE(processor()->contains_guid("guid2"));
  ASSERT_EQ(processor()->change_for_guid("guid2").change_type(),
            syncer::SyncChange::ACTION_UPDATE);
  ASSERT_THAT(model()->GetTemplateURLForGUID("guid2"),
              Pointee(Property(&TemplateURL::input_encodings,
                               std::vector<std::string>{"UTF-8", "UTF-16"})));
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.ChangesCommittedUponIncrementalUpdate_Updated",
      /*sample=*/1,
      /*expected_bucket_count=*/1);

  // No adds are committed upon sync start.
  histogram_tester.ExpectUniqueSample(
      "Sync.SearchEngine.ChangesCommittedUponIncrementalUpdate_Added",
      /*sample=*/0,
      /*expected_bucket_count=*/1);
}

TEST_F(TemplateURLServiceSyncTestWithSeparateLocalAndAccountSearchEngines,
       ShouldNotCommitFaviconOnlyChanges) {
  // Add a local-only search engine.
  TemplateURL* local_turl = model()->Add(
      CreateTestTemplateURL(u"localkey", "http://localkey.com", "localguid",
                            base::Time::FromTimeT(100)));

  // Add an account-only search engine.
  syncer::SyncDataList initial_data;
  initial_data.push_back(TemplateURLService::CreateSyncDataFromTemplateURLData(
      CreateTestTemplateURL(u"keyword", "http://keyword.com", "guid",
                            base::Time::FromTimeT(100))
          ->data()));
  // Start syncing.
  model()->MergeDataAndStartSyncing(syncer::SEARCH_ENGINES, initial_data,
                                    PassProcessor());

  base::HistogramTester histogram_tester;

  // Local-only search engines: no affect since they have no account data.
  ASSERT_EQ(local_turl, model()->GetTemplateURLForGUID("localguid"));
  ASSERT_EQ(local_turl->GetAccountData(), std::nullopt);
  TemplateURLData data = local_turl->data();
  // Update the favicon URL.
  data.favicon_url = GURL("http://localfavicon.com");
  model()->UpdateData(local_turl, data);
  ASSERT_EQ(local_turl->favicon_url(), GURL("http://localfavicon.com"));
  ASSERT_EQ(local_turl->GetAccountData(), std::nullopt);
  ASSERT_EQ(0u, processor()->change_list_size());
  histogram_tester.ExpectTotalCount("Sync.SearchEngine.FaviconOnlyUpdate", 0);
  // Update any other field.
  data.SetKeyword(u"newkeyword");
  model()->UpdateData(local_turl, data);
  ASSERT_EQ(local_turl->keyword(), u"newkeyword");
  EXPECT_EQ(0u, processor()->change_list_size());
  histogram_tester.ExpectTotalCount("Sync.SearchEngine.FaviconOnlyUpdate", 0);

  // Search engines with account data.
  TemplateURL* account_turl = model()->GetTemplateURLForGUID("guid");
  ASSERT_NE(account_turl, nullptr);
  ASSERT_EQ(account_turl->GetLocalData(), std::nullopt);
  // Update the favicon URL.
  data = account_turl->data();
  data.favicon_url = GURL("http://favicon.com");
  model()->UpdateData(account_turl, data);
  ASSERT_EQ(account_turl->favicon_url(), GURL("http://favicon.com"));
  EXPECT_EQ(0u, processor()->change_list_size());
  EXPECT_THAT(
      histogram_tester.GetAllSamples("Sync.SearchEngine.FaviconOnlyUpdate"),
      base::BucketsAre(base::Bucket(true, 1)));

  // Update any other field.
  data.SetKeyword(u"newkeyword");
  model()->UpdateData(account_turl, data);
  ASSERT_EQ(account_turl->keyword(), u"newkeyword");
  EXPECT_EQ(1u, processor()->change_list_size());
  EXPECT_THAT(
      histogram_tester.GetAllSamples("Sync.SearchEngine.FaviconOnlyUpdate"),
      base::BucketsAre(base::Bucket(false, 1), base::Bucket(true, 1)));
}
