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

#include "components/sync_bookmarks/bookmark_remote_updates_handler.h"

#include <memory>
#include <string>
#include <utility>

#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/test_bookmark_client.h"
#include "components/favicon/core/test/mock_favicon_service.h"
#include "components/sync/base/hash_util.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/unique_position.h"
#include "components/sync/model/conflict_resolution.h"
#include "components/sync/protocol/unique_position.pb.h"
#include "components/sync_bookmarks/bookmark_model_merger.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::ASCIIToUTF16;
using testing::_;
using testing::ElementsAre;
using testing::Eq;
using testing::IsNull;
using testing::NotNull;

namespace sync_bookmarks {

namespace {

// The parent tag for children of the root entity. Entities with this parent are
// referred to as top level entities.
const char kRootParentId[] = "0";
const char kBookmarksRootId[] = "root_id";
const char kBookmarkBarId[] = "bookmark_bar_id";
const char kBookmarkBarTag[] = "bookmark_bar";
const char kMobileBookmarksId[] = "synced_bookmarks_id";
const char kMobileBookmarksTag[] = "synced_bookmarks";
const char kOtherBookmarksId[] = "other_bookmarks_id";
const char kOtherBookmarksTag[] = "other_bookmarks";

syncer::UpdateResponseData CreateUpdateResponseData(
    const std::string& server_id,
    const std::string& parent_id,
    const std::string& title,
    bool is_deletion,
    int version,
    const syncer::UniquePosition& unique_position) {
  syncer::EntityData data;
  data.id = server_id;
  data.parent_id = parent_id;
  data.unique_position = unique_position.ToProto();

  // EntityData would be considered a deletion if its specifics hasn't been set.
  if (!is_deletion) {
    sync_pb::BookmarkSpecifics* bookmark_specifics =
        data.specifics.mutable_bookmark();
    bookmark_specifics->set_title(title);
  }
  data.is_folder = true;
  syncer::UpdateResponseData response_data;
  response_data.entity = data.PassToPtr();
  response_data.response_version = version;
  return response_data;
}

// Overload that assign a random position. Should only be used when position
// doesn't matter.
syncer::UpdateResponseData CreateUpdateResponseData(
    const std::string& server_id,
    const std::string& parent_id,
    bool is_deletion,
    int version = 0) {
  return CreateUpdateResponseData(server_id, parent_id, /*title=*/server_id,
                                  is_deletion, version,
                                  syncer::UniquePosition::InitialPosition(
                                      syncer::UniquePosition::RandomSuffix()));
}

syncer::UpdateResponseData CreateBookmarkRootUpdateData() {
  syncer::EntityData data;
  data.id = syncer::ModelTypeToRootTag(syncer::BOOKMARKS);
  data.parent_id = kRootParentId;
  data.server_defined_unique_tag =
      syncer::ModelTypeToRootTag(syncer::BOOKMARKS);

  data.specifics.mutable_bookmark();

  syncer::UpdateResponseData response_data;
  response_data.entity = data.PassToPtr();
  // Similar to what's done in the loopback_server.
  response_data.response_version = 0;
  return response_data;
}

syncer::UpdateResponseData CreatePermanentFolderUpdateData(
    const std::string& id,
    const std::string& tag) {
  syncer::EntityData data;
  data.id = id;
  data.parent_id = "root_id";
  data.server_defined_unique_tag = tag;

  data.specifics.mutable_bookmark();

  syncer::UpdateResponseData response_data;
  response_data.entity = data.PassToPtr();
  // Similar to what's done in the loopback_server.
  response_data.response_version = 0;
  return response_data;
}

syncer::UpdateResponseDataList CreatePermanentFoldersUpdateData() {
  return {
      CreatePermanentFolderUpdateData(kBookmarkBarId, kBookmarkBarTag),
      CreatePermanentFolderUpdateData(kOtherBookmarksId, kOtherBookmarksTag),
      CreatePermanentFolderUpdateData(kMobileBookmarksId, kMobileBookmarksTag)};
}

std::unique_ptr<sync_pb::EntityMetadata> CreateEntityMetadata(
    const std::string& server_id,
    const syncer::UniquePosition& unique_position) {
  auto metadata = std::make_unique<sync_pb::EntityMetadata>();
  metadata->set_server_id(server_id);
  *metadata->mutable_unique_position() = unique_position.ToProto();
  metadata->set_is_deleted(false);
  return metadata;
}

std::unique_ptr<sync_pb::EntityMetadata> CreateEntityMetadata(
    const std::string& server_id) {
  return CreateEntityMetadata(server_id,
                              syncer::UniquePosition::InitialPosition(
                                  syncer::UniquePosition::RandomSuffix()));
}

class BookmarkRemoteUpdatesHandlerWithInitialMergeTest : public testing::Test {
 public:
  BookmarkRemoteUpdatesHandlerWithInitialMergeTest()
      : bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()),
        tracker_(std::vector<NodeMetadataPair>(),
                 std::make_unique<sync_pb::ModelTypeState>()),
        updates_handler_(bookmark_model_.get(), &favicon_service_, &tracker_) {
    const syncer::UpdateResponseDataList permanent_folder_updates =
        CreatePermanentFoldersUpdateData();
    BookmarkModelMerger(&permanent_folder_updates, bookmark_model_.get(),
                        &favicon_service_, &tracker_)
        .Merge();
  }

  bookmarks::BookmarkModel* bookmark_model() { return bookmark_model_.get(); }
  SyncedBookmarkTracker* tracker() { return &tracker_; }
  favicon::MockFaviconService* favicon_service() { return &favicon_service_; }
  BookmarkRemoteUpdatesHandler* updates_handler() { return &updates_handler_; }

 private:
  std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_;
  SyncedBookmarkTracker tracker_;
  testing::NiceMock<favicon::MockFaviconService> favicon_service_;
  BookmarkRemoteUpdatesHandler updates_handler_;
};

TEST(BookmarkRemoteUpdatesHandlerReorderUpdatesTest, ShouldIgnoreRootNodes) {
  syncer::UpdateResponseDataList updates;
  updates.push_back(CreateBookmarkRootUpdateData());
  std::vector<const syncer::UpdateResponseData*> ordered_updates =
      BookmarkRemoteUpdatesHandler::ReorderUpdatesForTest(&updates);
  // Root node update should be filtered out.
  EXPECT_THAT(ordered_updates.size(), Eq(0U));
}

TEST(BookmarkRemoteUpdatesHandlerReorderUpdatesTest,
     ShouldReorderParentsUpdateBeforeChildrenAndBothBeforeDeletions) {
  // Prepare creation updates to build this structure:
  // bookmark_bar
  //  |- node0
  //    |- node1
  //      |- node2
  // and another sub hierarchy under node3 that won't receive any update.
  // node4
  //  |- node5
  // and a deletion for node6 under node3.

  // Constuct the updates list to have deletion first, and then all creations in
  // reverse shuffled order (from child to parent).

  std::vector<std::string> ids;
  for (int i = 0; i < 7; i++) {
    ids.push_back("node" + base::NumberToString(i));
  }
  // Construct updates list
  syncer::UpdateResponseDataList updates;
  updates.push_back(CreateUpdateResponseData(/*server_id=*/ids[6],
                                             /*parent_id=*/ids[3],
                                             /*is_deletion=*/true));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/ids[5],
                                             /*parent_id=*/ids[4],
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/ids[2],
                                             /*parent_id=*/ids[1],
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/ids[1],
                                             /*parent_id=*/ids[0],
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/ids[4],
                                             /*parent_id=*/ids[3],
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/ids[0],
                                             /*parent_id=*/kBookmarksRootId,
                                             /*is_deletion=*/false));

  std::vector<const syncer::UpdateResponseData*> ordered_updates =
      BookmarkRemoteUpdatesHandler::ReorderUpdatesForTest(&updates);

  // No update should be dropped.
  ASSERT_THAT(ordered_updates.size(), Eq(6U));

  // Updates should be ordered such that parent node update comes first, and
  // deletions come last.
  // node4 --> node5 --> node0 --> node1 --> node2 --> node6.
  // This is test is over verifying since the order requirements are
  // within subtrees only. (e.g it doesn't matter whether node1 comes before or
  // after node4). However, it's implemented this way for simplicity.
  EXPECT_THAT(ordered_updates[0]->entity.value().id, Eq(ids[4]));
  EXPECT_THAT(ordered_updates[1]->entity.value().id, Eq(ids[5]));
  EXPECT_THAT(ordered_updates[2]->entity.value().id, Eq(ids[0]));
  EXPECT_THAT(ordered_updates[3]->entity.value().id, Eq(ids[1]));
  EXPECT_THAT(ordered_updates[4]->entity.value().id, Eq(ids[2]));
  EXPECT_THAT(ordered_updates[5]->entity.value().id, Eq(ids[6]));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldProcessRandomlyOrderedCreations) {
  // Prepare creation updates to construct this structure:
  // bookmark_bar
  //  |- node0
  //    |- node1
  //      |- node2

  const std::string kId0 = "id0";
  const std::string kId1 = "id1";
  const std::string kId2 = "id2";

  // Constuct the updates list to have creations randomly ordered.
  syncer::UpdateResponseDataList updates;
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId2,
                                             /*parent_id=*/kId1,
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
                                             /*parent_id=*/kBookmarkBarId,
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId1,
                                             /*parent_id=*/kId0,
                                             /*is_deletion=*/false));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // All nodes should be tracked including the "bookmark bar", "other
  // bookmarks" node and "mobile bookmarks".
  EXPECT_THAT(tracker()->TrackedEntitiesCountForTest(), Eq(6U));

  // All nodes should have been added to the model.
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(1));
  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetTitle(),
              Eq(ASCIIToUTF16(kId0)));
  ASSERT_THAT(bookmark_bar_node->GetChild(0)->child_count(), Eq(1));
  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetChild(0)->GetTitle(),
              Eq(ASCIIToUTF16(kId1)));
  ASSERT_THAT(bookmark_bar_node->GetChild(0)->GetChild(0)->child_count(),
              Eq(1));
  EXPECT_THAT(
      bookmark_bar_node->GetChild(0)->GetChild(0)->GetChild(0)->GetTitle(),
      Eq(ASCIIToUTF16(kId2)));
  EXPECT_THAT(
      bookmark_bar_node->GetChild(0)->GetChild(0)->GetChild(0)->child_count(),
      Eq(0));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldProcessRandomlyOrderedDeletions) {
  // Prepare creation updates to construct this structure:
  // bookmark_bar
  //  |- node0
  //    |- node1
  //      |- node2

  const std::string kId0 = "id0";
  const std::string kId1 = "id1";
  const std::string kId2 = "id2";

  // Construct the updates list to create that structure
  syncer::UpdateResponseDataList updates;
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
                                             /*parent_id=*/kBookmarkBarId,
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId1,
                                             /*parent_id=*/kId0,
                                             /*is_deletion=*/false));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId2,
                                             /*parent_id=*/kId1,
                                             /*is_deletion=*/false));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // All nodes should be tracked including the "bookmark bar", "other
  // bookmarks" node and "mobile bookmarks".
  EXPECT_THAT(tracker()->TrackedEntitiesCountForTest(), Eq(6U));

  // Construct the updates list to have random deletions order.
  updates.clear();
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId1,
                                             /*parent_id=*/kId0,
                                             /*is_deletion=*/true,
                                             /*version=*/1));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
                                             /*parent_id=*/kBookmarksRootId,
                                             /*is_deletion=*/true,
                                             /*version=*/1));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId2,
                                             /*parent_id=*/kId1,
                                             /*is_deletion=*/true,
                                             /*version=*/1));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // |tracker| should have only permanent nodes now.
  EXPECT_THAT(tracker()->TrackedEntitiesCountForTest(), Eq(3U));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldPositionRemoteCreations) {
  // Prepare creation updates to construct this structure:
  // bookmark_bar
  //  |- node0
  //  |- node1
  //  |- node2

  const std::string kId0 = "id0";
  const std::string kId1 = "id1";
  const std::string kId2 = "id2";

  syncer::UniquePosition pos0 = syncer::UniquePosition::InitialPosition(
      syncer::UniquePosition::RandomSuffix());
  syncer::UniquePosition pos1 = syncer::UniquePosition::After(
      pos0, syncer::UniquePosition::RandomSuffix());
  syncer::UniquePosition pos2 = syncer::UniquePosition::After(
      pos1, syncer::UniquePosition::RandomSuffix());

  // Constuct the updates list to have creations randomly ordered.
  syncer::UpdateResponseDataList updates;
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId2, /*parent_id=*/kBookmarkBarId, /*title=*/kId2,
      /*is_deletion=*/false, /*version=*/0, /*unique_position=*/pos2));
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
                                             /*parent_id=*/kBookmarkBarId,
                                             /*title=*/kId0,
                                             /*is_deletion=*/false,
                                             /*version=*/0,
                                             /*unique_position=*/pos0));
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId1, /*parent_id=*/kBookmarkBarId, /*title=*/kId1,
      /*is_deletion=*/false, /*version=*/0, /*unique_position=*/pos1));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // All nodes should have been added to the model in the correct order.
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(3));
  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetTitle(),
              Eq(ASCIIToUTF16(kId0)));
  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetTitle(),
              Eq(ASCIIToUTF16(kId1)));
  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetTitle(),
              Eq(ASCIIToUTF16(kId2)));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldPositionRemoteMovesToTheLeft) {
  // Start with structure:
  // bookmark_bar
  //  |- node0
  //  |- node1
  //  |- node2
  //  |- node3
  //  |- node4

  std::vector<std::string> ids;
  std::vector<syncer::UniquePosition> positions;

  syncer::UniquePosition position = syncer::UniquePosition::InitialPosition(
      syncer::UniquePosition::RandomSuffix());
  syncer::UpdateResponseDataList updates;
  for (int i = 0; i < 5; i++) {
    ids.push_back("node" + base::NumberToString(i));
    position = syncer::UniquePosition::After(
        position, syncer::UniquePosition::RandomSuffix());
    positions.push_back(position);
    updates.push_back(CreateUpdateResponseData(
        /*server_id=*/ids[i], /*parent_id=*/kBookmarkBarId, /*title=*/ids[i],
        /*is_deletion=*/false, /*version=*/0,
        /*unique_position=*/positions[i]));
  }

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  EXPECT_THAT(bookmark_bar_node->child_count(), Eq(5));

  // Change it to this structure by moving node3 after node1.
  // bookmark_bar
  //  |- node0
  //  |- node1
  //  |- node3
  //  |- node2
  //  |- node4

  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/ids[3],
      /*parent_id=*/kBookmarkBarId,
      /*title=*/ids[3],
      /*is_deletion=*/false,
      /*version=*/1,
      /*unique_position=*/
      syncer::UniquePosition::Between(positions[1], positions[2],
                                      syncer::UniquePosition::RandomSuffix())));
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // Model should have been updated.
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(5));
  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetTitle(),
              Eq(ASCIIToUTF16(ids[3])));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldPositionRemoteMovesToTheRight) {
  // Start with structure:
  // bookmark_bar
  //  |- node0
  //  |- node1
  //  |- node2
  //  |- node3
  //  |- node4

  std::vector<std::string> ids;
  std::vector<syncer::UniquePosition> positions;

  syncer::UniquePosition position = syncer::UniquePosition::InitialPosition(
      syncer::UniquePosition::RandomSuffix());
  syncer::UpdateResponseDataList updates;
  for (int i = 0; i < 5; i++) {
    ids.push_back("node" + base::NumberToString(i));
    position = syncer::UniquePosition::After(
        position, syncer::UniquePosition::RandomSuffix());
    positions.push_back(position);
    updates.push_back(CreateUpdateResponseData(
        /*server_id=*/ids[i], /*parent_id=*/kBookmarkBarId, /*title=*/ids[i],
        /*is_deletion=*/false, /*version=*/0,
        /*unique_position=*/positions[i]));
  }

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  EXPECT_THAT(bookmark_bar_node->child_count(), Eq(5));

  // Change it to this structure by moving node1 after node3.
  // bookmark_bar
  //  |- node0
  //  |- node2
  //  |- node3
  //  |- node1
  //  |- node4

  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/ids[1],
      /*parent_id=*/kBookmarkBarId,
      /*title=*/ids[1],
      /*is_deletion=*/false,
      /*version=*/1,
      /*unique_position=*/
      syncer::UniquePosition::Between(positions[3], positions[4],
                                      syncer::UniquePosition::RandomSuffix())));
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // Model should have been updated.
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(5));
  EXPECT_THAT(bookmark_bar_node->GetChild(3)->GetTitle(),
              Eq(ASCIIToUTF16(ids[1])));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldPositionRemoteReparenting) {
  // Start with structure:
  // bookmark_bar
  //  |- node0
  //  |- node1
  //  |- node2
  //  |- node3
  //  |- node4

  std::vector<std::string> ids;
  std::vector<syncer::UniquePosition> positions;

  syncer::UniquePosition position = syncer::UniquePosition::InitialPosition(
      syncer::UniquePosition::RandomSuffix());
  syncer::UpdateResponseDataList updates;
  for (int i = 0; i < 5; i++) {
    ids.push_back("node" + base::NumberToString(i));
    position = syncer::UniquePosition::After(
        position, syncer::UniquePosition::RandomSuffix());
    positions.push_back(position);
    updates.push_back(CreateUpdateResponseData(
        /*server_id=*/ids[i], /*parent_id=*/kBookmarkBarId, /*title=*/ids[i],
        /*is_deletion=*/false, /*version=*/0,
        /*unique_position=*/positions[i]));
  }

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  EXPECT_THAT(bookmark_bar_node->child_count(), Eq(5));

  // Change it to this structure by moving node4 under node1.
  // bookmark_bar
  //  |- node0
  //  |- node1
  //    |- node4
  //  |- node2
  //  |- node3

  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/ids[4],
      /*parent_id=*/ids[1],
      /*title=*/ids[4],
      /*is_deletion=*/false,
      /*version=*/1,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // Model should have been updated.
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(4));
  ASSERT_THAT(bookmark_bar_node->GetChild(1)->child_count(), Eq(1));
  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetChild(0)->GetTitle(),
              Eq(ASCIIToUTF16(ids[4])));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldMergeFaviconUponRemoteCreationsWithFavicon) {
  // Prepare creation updates to construct this structure:
  // bookmark_bar
  //  |- node0

  const std::string kTitle = "Title";
  const GURL kUrl("http://www.url.com");
  const GURL kIconUrl("http://www.icon-url.com");

  syncer::UpdateResponseDataList updates;
  syncer::EntityData data;
  data.id = "server_id";
  data.parent_id = kBookmarkBarId;
  data.unique_position = syncer::UniquePosition::InitialPosition(
                             syncer::UniquePosition::RandomSuffix())
                             .ToProto();
  sync_pb::BookmarkSpecifics* bookmark_specifics =
      data.specifics.mutable_bookmark();
  // Use the server id as the title for simplicity.
  bookmark_specifics->set_title(kTitle);
  bookmark_specifics->set_url(kUrl.spec());
  bookmark_specifics->set_icon_url(kIconUrl.spec());
  bookmark_specifics->set_favicon("PNG");
  data.is_folder = false;
  syncer::UpdateResponseData response_data;
  response_data.entity = data.PassToPtr();
  // Similar to what's done in the loopback_server.
  response_data.response_version = 0;

  updates.push_back(response_data);

  EXPECT_CALL(*favicon_service(),
              AddPageNoVisitForBookmark(kUrl, base::UTF8ToUTF16(kTitle)));
  EXPECT_CALL(*favicon_service(), MergeFavicon(kUrl, kIconUrl, _, _, _));
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldDeleteFaviconUponRemoteCreationsWithoutFavicon) {
  // Prepare creation updates to construct this structure:
  // bookmark_bar
  //  |- node0

  const std::string kTitle = "Title";
  const GURL kUrl("http://www.url.com");

  syncer::UpdateResponseDataList updates;
  syncer::EntityData data;
  data.id = "server_id";
  data.parent_id = kBookmarkBarId;
  data.unique_position = syncer::UniquePosition::InitialPosition(
                             syncer::UniquePosition::RandomSuffix())
                             .ToProto();
  sync_pb::BookmarkSpecifics* bookmark_specifics =
      data.specifics.mutable_bookmark();
  // Use the server id as the title for simplicity.
  bookmark_specifics->set_title(kTitle);
  bookmark_specifics->set_url(kUrl.spec());
  data.is_folder = false;
  syncer::UpdateResponseData response_data;
  response_data.entity = data.PassToPtr();
  // Similar to what's done in the loopback_server.
  response_data.response_version = 0;

  updates.push_back(response_data);

  EXPECT_CALL(*favicon_service(),
              DeleteFaviconMappings(ElementsAre(kUrl),
                                    favicon_base::IconType::kFavicon));
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
}

// This tests the case when a local creation is successfully committed to the
// server but the commit respone isn't received for some reason. Further updates
// to that entity should update the sync id in the tracker.
TEST(BookmarkRemoteUpdatesHandlerTest,
     ShouldUpdateSyncIdWhenRecevingAnUpdateForNewlyCreatedLocalNode) {
  const std::string kCacheGuid = "generated_id";
  const std::string kOriginatorClientItemId = "tmp_server_id";
  const std::string kSyncId = "server_id";
  const int64_t kServerVersion = 1000;
  const base::Time kModificationTime(base::Time::Now() -
                                     base::TimeDelta::FromSeconds(1));

  sync_pb::ModelTypeState model_type_state;
  model_type_state.set_cache_guid(kCacheGuid);
  model_type_state.set_initial_sync_done(true);

  std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
      bookmarks::TestBookmarkClient::CreateModel();
  std::vector<NodeMetadataPair> node_metadata_pairs;
  // Add permanent folders.
  node_metadata_pairs.emplace_back(bookmark_model->bookmark_bar_node(),
                                   CreateEntityMetadata(kBookmarkBarId));
  node_metadata_pairs.emplace_back(bookmark_model->other_node(),
                                   CreateEntityMetadata(kOtherBookmarksId));
  node_metadata_pairs.emplace_back(bookmark_model->mobile_node(),
                                   CreateEntityMetadata(kMobileBookmarksId));

  SyncedBookmarkTracker tracker(
      std::move(node_metadata_pairs),
      std::make_unique<sync_pb::ModelTypeState>(model_type_state));
  const sync_pb::UniquePosition unique_position;
  sync_pb::EntitySpecifics specifics;
  sync_pb::BookmarkSpecifics* bookmark_specifics = specifics.mutable_bookmark();
  bookmark_specifics->set_title("Title");
  bookmarks::BookmarkNode node(/*id=*/1, GURL());
  // Track a sync entity (similar to what happens after a local creation). The
  // |originator_client_item_id| is used a temp sync id and mark the entity that
  // it needs to be committed..
  tracker.Add(/*sync_id=*/kOriginatorClientItemId, &node, kServerVersion,
              kModificationTime, unique_position, specifics);
  tracker.IncrementSequenceNumber(/*sync_id=*/kOriginatorClientItemId);

  ASSERT_THAT(tracker.GetEntityForSyncId(kOriginatorClientItemId), NotNull());

  // Now receive an update with the actual server id.
  syncer::UpdateResponseDataList updates;
  syncer::EntityData data;
  data.id = kSyncId;
  data.originator_cache_guid = kCacheGuid;
  data.originator_client_item_id = kOriginatorClientItemId;
  // Set the other required fields.
  data.unique_position = syncer::UniquePosition::InitialPosition(
                             syncer::UniquePosition::RandomSuffix())
                             .ToProto();
  data.specifics = specifics;
  data.is_folder = true;

  syncer::UpdateResponseData response_data;
  response_data.entity = data.PassToPtr();
  // Similar to what's done in the loopback_server.
  response_data.response_version = 0;
  updates.push_back(response_data);

  testing::NiceMock<favicon::MockFaviconService> favicon_service;
  BookmarkRemoteUpdatesHandler updates_handler(bookmark_model.get(),
                                               &favicon_service, &tracker);
  updates_handler.Process(updates, /*got_new_encryption_requirements=*/false);

  // The sync id in the tracker should have been updated.
  EXPECT_THAT(tracker.GetEntityForSyncId(kOriginatorClientItemId), IsNull());
  const SyncedBookmarkTracker::Entity* entity =
      tracker.GetEntityForSyncId(kSyncId);
  ASSERT_THAT(entity, NotNull());
  EXPECT_THAT(entity->metadata()->server_id(), Eq(kSyncId));
  EXPECT_THAT(entity->bookmark_node(), Eq(&node));
}

TEST(BookmarkRemoteUpdatesHandlerTest,
     ShouldRecommitWhenEncryptionIsOutOfDate) {
  std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
      bookmarks::TestBookmarkClient::CreateModel();
  auto model_type_state = std::make_unique<sync_pb::ModelTypeState>();
  model_type_state->set_encryption_key_name("encryption_key_name");
  SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
                                std::move(model_type_state));

  const syncer::UpdateResponseDataList permanent_folder_updates =
      CreatePermanentFoldersUpdateData();
  testing::NiceMock<favicon::MockFaviconService> favicon_service;
  BookmarkModelMerger(&permanent_folder_updates, bookmark_model.get(),
                      &favicon_service, &tracker)
      .Merge();

  const std::string kId0 = "id0";
  syncer::UpdateResponseDataList updates;
  syncer::UpdateResponseData response_data =
      CreateUpdateResponseData(/*server_id=*/kId0,
                               /*parent_id=*/kBookmarkBarId,
                               /*is_deletion=*/false);
  response_data.encryption_key_name = "out_of_date_encryption_key_name";
  updates.push_back(response_data);

  BookmarkRemoteUpdatesHandler updates_handler(bookmark_model.get(),
                                               &favicon_service, &tracker);
  updates_handler.Process(updates, /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker.GetEntityForSyncId(kId0), NotNull());
  EXPECT_THAT(tracker.GetEntityForSyncId(kId0)->IsUnsynced(), Eq(true));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldRecommitWhenGotNewEncryptionRequirements) {
  const std::string kId0 = "id0";

  syncer::UpdateResponseDataList updates;
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
                                             /*parent_id=*/kBookmarkBarId,
                                             /*is_deletion=*/false));
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId0), NotNull());
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId0)->IsUnsynced(), Eq(false));

  updates_handler()->Process({}, /*got_new_encryption_requirements=*/true);
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId0)->IsUnsynced(), Eq(true));
  // Permanent nodes shouldn't be committed. They are only created on the server
  // and synced down.
  EXPECT_THAT(tracker()->GetEntityForSyncId(kBookmarkBarId)->IsUnsynced(),
              Eq(false));
}

TEST(BookmarkRemoteUpdatesHandlerReorderUpdatesTest,
     ShouldNotRecommitWhenEncryptionKeyNameMistmatchWithConflictWithDeletions) {
  std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
      bookmarks::TestBookmarkClient::CreateModel();
  auto model_type_state = std::make_unique<sync_pb::ModelTypeState>();
  model_type_state->set_encryption_key_name("encryption_key_name");
  SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
                                std::move(model_type_state));

  const syncer::UpdateResponseDataList permanent_folder_updates =
      CreatePermanentFoldersUpdateData();
  testing::NiceMock<favicon::MockFaviconService> favicon_service;
  BookmarkModelMerger(&permanent_folder_updates, bookmark_model.get(),
                      &favicon_service, &tracker)
      .Merge();
  // Create the bookmark with same encryption key name.
  const std::string kId = "id";
  const std::string kTitle = "title";
  syncer::UpdateResponseDataList updates;
  syncer::UpdateResponseData response_data =
      CreateUpdateResponseData(/*server_id=*/kId,
                               /*parent_id=*/kBookmarkBarId,
                               /*is_deletion=*/false);
  response_data.encryption_key_name = "encryption_key_name";
  updates.push_back(response_data);

  BookmarkRemoteUpdatesHandler updates_handler(bookmark_model.get(),
                                               &favicon_service, &tracker);
  updates_handler.Process(updates, /*got_new_encryption_requirements=*/false);
  // The bookmark has been added and tracked.
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model->bookmark_bar_node();
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(1));
  ASSERT_THAT(tracker.GetEntityForSyncId(kId), NotNull());

  // Remove the bookmark from the local bookmark model.
  bookmark_model->Remove(bookmark_bar_node->GetChild(0));
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(0));

  // Mark the entity as deleted locally.
  tracker.MarkDeleted(/*sync_id=*/kId);
  tracker.IncrementSequenceNumber(/*sync_id=*/kId);
  ASSERT_THAT(tracker.GetEntityForSyncId(kId)->IsUnsynced(), Eq(true));

  // Push a remote deletion for the same entity with an out of date encryption
  // key name.
  updates.clear();
  response_data = CreateUpdateResponseData(/*server_id=*/kId,
                                           /*parent_id=*/kBookmarkBarId,
                                           /*is_deletion=*/true);
  response_data.encryption_key_name = "out_of_date_encryption_key_name";
  // Increment the server version to make sure the update isn't discarded as
  // reflection.
  response_data.response_version++;
  updates.push_back(response_data);

  base::HistogramTester histogram_tester;
  updates_handler.Process(updates, /*got_new_encryption_requirements=*/false);

  // There should have been conflict, and it should have been resolved by
  // removing local entity since both changes are deletions.
  EXPECT_THAT(tracker.GetEntityForSyncId(kId), IsNull());
  histogram_tester.ExpectBucketCount(
      "Sync.ResolveConflict",
      /*sample=*/syncer::ConflictResolution::CHANGES_MATCH, /*count=*/1);
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldNotRecommitUptoDateEntitiesWhenGotNewEncryptionRequirements) {
  const std::string kId0 = "id0";

  syncer::UpdateResponseDataList updates;
  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
                                             /*parent_id=*/kBookmarkBarId,
                                             /*is_deletion=*/false));
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId0), NotNull());
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId0)->IsUnsynced(), Eq(false));

  // Push another update to for the same entity.
  syncer::UpdateResponseData response_data =
      CreateUpdateResponseData(/*server_id=*/kId0,
                               /*parent_id=*/kBookmarkBarId,
                               /*is_deletion=*/false);

  // Increment the server version to make sure the update isn't discarded as
  // reflection.
  response_data.response_version++;
  updates_handler()->Process({response_data},
                             /*got_new_encryption_requirements=*/true);
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId0)->IsUnsynced(), Eq(false));
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldResolveConflictBetweenLocalAndRemoteDeletionsByMatchingThem) {
  const std::string kId = "id";
  const std::string kTitle = "title";

  syncer::UpdateResponseDataList updates;

  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kTitle,
      /*is_deletion=*/false,
      /*version=*/0,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId), NotNull());
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(1));

  // Remove the bookmark from the local bookmark model.
  bookmark_model()->Remove(bookmark_bar_node->GetChild(0));
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(0));

  // Mark the entity as deleted locally.
  tracker()->MarkDeleted(/*sync_id=*/kId);
  tracker()->IncrementSequenceNumber(/*sync_id=*/kId);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(true));

  // Push a remote deletion for the same entity.
  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/std::string(),
      /*is_deletion=*/true,
      /*version=*/1,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  base::HistogramTester histogram_tester;
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // There should have been conflict, and it should have been resolved by
  // removing local entity since both changes.
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId), IsNull());
  // Make sure the bookmark hasn't been resurrected.
  EXPECT_THAT(bookmark_bar_node->child_count(), Eq(0));

  histogram_tester.ExpectBucketCount(
      "Sync.ResolveConflict",
      /*sample=*/syncer::ConflictResolution::CHANGES_MATCH, /*count=*/1);
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldResolveConflictBetweenLocalUpdateAndRemoteDeletionWithLocal) {
  const std::string kId = "id";
  const std::string kTitle = "title";

  syncer::UpdateResponseDataList updates;

  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kTitle,
      /*is_deletion=*/false,
      /*version=*/0,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId), NotNull());
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));

  // Mark the entity as modified locally.
  tracker()->IncrementSequenceNumber(/*sync_id=*/kId);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(true));

  // Push a remote deletion for the same entity.
  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/std::string(),
      /*is_deletion=*/true,
      /*version=*/1,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  base::HistogramTester histogram_tester;
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // There should have been conflict, and it should have been resolved with the
  // local version that will be committed later.
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(true));
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  EXPECT_THAT(bookmark_bar_node->child_count(), Eq(1));

  histogram_tester.ExpectBucketCount(
      "Sync.ResolveConflict",
      /*sample=*/syncer::ConflictResolution::USE_LOCAL, /*count=*/1);
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldResolveConflictBetweenLocalDeletionAndRemoteUpdateByRemote) {
  const std::string kId = "id";
  const std::string kTitle = "title";

  syncer::UpdateResponseDataList updates;

  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kTitle,
      /*is_deletion=*/false,
      /*version=*/0,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId), NotNull());
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(1));

  // Remove the bookmark from the local bookmark model.
  bookmark_model()->Remove(bookmark_bar_node->GetChild(0));
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(0));

  // Mark the entity as deleted locally.
  tracker()->MarkDeleted(/*sync_id=*/kId);
  tracker()->IncrementSequenceNumber(/*sync_id=*/kId);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(true));

  // Push an update for the same entity.
  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kTitle,
      /*is_deletion=*/false,
      /*version=*/1,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  base::HistogramTester histogram_tester;
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // There should have been conflict, and it should have been resolved with the
  // remote version.
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId)->metadata()->is_deleted(),
              Eq(false));

  // The bookmark should have been resurrected.
  EXPECT_THAT(bookmark_bar_node->child_count(), Eq(1));

  histogram_tester.ExpectBucketCount(
      "Sync.ResolveConflict",
      /*sample=*/syncer::ConflictResolution::USE_REMOTE, /*count=*/1);
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldResolveConflictBetweenLocalAndRemoteUpdatesWithMatchingThem) {
  const std::string kId = "id";
  const std::string kTitle = "title";
  const syncer::UniquePosition kPosition =
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix());

  syncer::UpdateResponseDataList updates;

  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kTitle,
      /*is_deletion=*/false,
      /*version=*/0,
      /*unique_position=*/kPosition));

  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId), NotNull());
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));

  // Mark the entity as modified locally.
  tracker()->IncrementSequenceNumber(/*sync_id=*/kId);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(true));

  // Push an update for the same entity with the same information.
  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kTitle,
      /*is_deletion=*/false,
      /*version=*/1,
      /*unique_position=*/kPosition));

  base::HistogramTester histogram_tester;
  updates_handler()->Process(updates,
                             /*got_new_encryption_requirements=*/false);

  // There should have been conflict but both local and remote updates should
  // match. The conflict should have been resolved.
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));

  histogram_tester.ExpectBucketCount(
      "Sync.ResolveConflict",
      /*sample=*/syncer::ConflictResolution::CHANGES_MATCH, /*count=*/1);
}

TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
       ShouldResolveConflictBetweenLocalAndRemoteUpdatesWithRemote) {
  const std::string kId = "id";
  const std::string kTitle = "title";
  const std::string kNewRemoteTitle = "remote title";

  syncer::UpdateResponseDataList updates;

  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kTitle,
      /*is_deletion=*/false,
      /*version=*/0,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  BookmarkRemoteUpdatesHandler updates_handler(bookmark_model(),
                                               favicon_service(), tracker());
  updates_handler.Process(updates, /*got_new_encryption_requirements=*/false);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId), NotNull());
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));

  // Mark the entity as modified locally.
  tracker()->IncrementSequenceNumber(/*sync_id=*/kId);
  ASSERT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(true));

  // Push an update for the same entity with a new title.
  updates.clear();
  updates.push_back(CreateUpdateResponseData(
      /*server_id=*/kId,
      /*parent_id=*/kBookmarkBarId,
      /*title=*/kNewRemoteTitle,
      /*is_deletion=*/false,
      /*version=*/1,
      /*unique_position=*/
      syncer::UniquePosition::InitialPosition(
          syncer::UniquePosition::RandomSuffix())));

  base::HistogramTester histogram_tester;
  updates_handler.Process(updates, /*got_new_encryption_requirements=*/false);

  // There should have been conflict, and it should have been resolved with the
  // remote version.
  EXPECT_THAT(tracker()->GetEntityForSyncId(kId)->IsUnsynced(), Eq(false));
  const bookmarks::BookmarkNode* bookmark_bar_node =
      bookmark_model()->bookmark_bar_node();
  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(1));
  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetTitle(),
              Eq(ASCIIToUTF16(kNewRemoteTitle)));

  histogram_tester.ExpectBucketCount(
      "Sync.ResolveConflict",
      /*sample=*/syncer::ConflictResolution::USE_REMOTE, /*count=*/1);
}

}  // namespace

}  // namespace sync_bookmarks
