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

#include "components/sync_bookmarks/local_bookmark_to_account_merger.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#include "base/strings/utf_ostream_operators.h"
#include "base/test/scoped_feature_list.h"
#include "base/uuid.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_test_util.h"
#include "components/bookmarks/test/mock_bookmark_model_observer.h"
#include "components/bookmarks/test/test_bookmark_client.h"
#include "components/bookmarks/test/test_matchers.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/sync_bookmarks/switches.h"
#include "components/sync_bookmarks/test_node_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace sync_bookmarks {
namespace {

using bookmarks::test::IsFolder;
using bookmarks::test::IsFolderWithUuid;
using bookmarks::test::IsUrlBookmark;
using bookmarks::test::IsUrlBookmarkWithUuid;
using sync_bookmarks::test::FolderBuilder;
using sync_bookmarks::test::UrlBuilder;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::Ne;

class LocalBookmarkToAccountMergerTest : public testing::Test {
 protected:
  LocalBookmarkToAccountMergerTest() {
    model_->AddObserver(&observer_);
    model_->CreateAccountPermanentFolders();
  }

  ~LocalBookmarkToAccountMergerTest() override {
    model_->RemoveObserver(&observer_);
  }

  void AddLocalNodes(
      const std::vector<FolderBuilder::FolderOrUrl>& children_of_bookmark_bar,
      const std::vector<FolderBuilder::FolderOrUrl>& children_of_mobile_node =
          {},
      const std::vector<FolderBuilder::FolderOrUrl>& children_of_other_node =
          {}) {
    FolderBuilder::AddChildrenTo(model_.get(), model_->bookmark_bar_node(),
                                 children_of_bookmark_bar);
    FolderBuilder::AddChildrenTo(model_.get(), model_->mobile_node(),
                                 children_of_mobile_node);
    FolderBuilder::AddChildrenTo(model_.get(), model_->other_node(),
                                 children_of_other_node);
  }

  void AddAccountNodes(
      const std::vector<FolderBuilder::FolderOrUrl>& children_of_bookmark_bar) {
    FolderBuilder::AddChildrenTo(model_.get(),
                                 model_->account_bookmark_bar_node(),
                                 children_of_bookmark_bar);
  }

  base::test::ScopedFeatureList feature_list_{
      switches::kSyncEnableBookmarksInTransportMode};
  const std::unique_ptr<bookmarks::BookmarkModel> model_ =
      bookmarks::TestBookmarkClient::CreateModel();
  testing::NiceMock<bookmarks::MockBookmarkModelObserver> observer_;
};

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldUploadLocalNodesIfNoAccountNodes) {
  const std::u16string kFolder1Title = u"folder1";
  const std::u16string kFolder2Title = u"folder2";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";
  const std::u16string kUrl4Title = u"url4";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");
  const GURL kUrl4("http://www.url4.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- folder 1
  //    |- url1(http://www.url1.com)
  //    |- url2(http://www.url2.com)
  //  |- folder 2
  //    |- url3(http://www.url3.com)
  //    |- url4(http://www.url4.com)
  AddLocalNodes({FolderBuilder(kFolder1Title)
                     .SetChildren({UrlBuilder(kUrl1Title, kUrl1),
                                   UrlBuilder(kUrl2Title, kUrl2)}),
                 FolderBuilder(kFolder2Title)
                     .SetChildren({UrlBuilder(kUrl3Title, kUrl3),
                                   UrlBuilder(kUrl4Title, kUrl4)})});

  // -------- Account bookmarks --------
  // bookmark_bar
  AddAccountNodes({});

  // -------- The expected merge outcome --------
  // Same as the local model described above.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder1Title,
                           ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
                                       IsUrlBookmark(kUrl2Title, kUrl2))),
                  IsFolder(kFolder2Title,
                           ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3),
                                       IsUrlBookmark(kUrl4Title, kUrl4)))));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldUploadLocalNodesUnderAllPermanentNodes) {
  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- url1(http://www.url1.com)
  // mobile_node
  //  |- url2(http://www.url2.com)
  // other_node
  //  |- url3(http://www.url3.com)
  AddLocalNodes(/*children_of_bookmark_bar=*/{UrlBuilder(kUrl1Title, kUrl1)},
                /*children_of_mobile_node=*/{UrlBuilder(kUrl2Title, kUrl2)},
                /*children_of_other_node=*/{UrlBuilder(kUrl3Title, kUrl3)});

  // -------- Account bookmarks --------
  // bookmark_bar
  // mobile_node
  // other_node
  AddAccountNodes({});

  // -------- The expected merge outcome --------
  // Same as the local model described above.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(3);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->mobile_node()->children(), IsEmpty());
  EXPECT_THAT(model_->other_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
  EXPECT_THAT(model_->account_mobile_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
  EXPECT_THAT(model_->account_other_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3)));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldIgnoreManagedNodes) {
  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");

  auto local_client = std::make_unique<bookmarks::TestBookmarkClient>();
  bookmarks::BookmarkNode* managed_node = local_client->EnableManagedNode();
  std::unique_ptr<bookmarks::BookmarkModel> model =
      bookmarks::TestBookmarkClient::CreateModelWithClient(
          std::move(local_client));
  model->CreateAccountPermanentFolders();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- url1(http://www.url1.com)
  // managed_bookmarks
  //  |- url2(http://www.url2.com)
  FolderBuilder::AddChildrenTo(model.get(), model->bookmark_bar_node(),
                               {UrlBuilder(kUrl1Title, kUrl1)});
  FolderBuilder::AddChildrenTo(model.get(), managed_node,
                               {UrlBuilder(kUrl2Title, kUrl2)});
  ASSERT_THAT(managed_node->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));

  // -------- Account bookmarks --------
  // bookmark_bar
  FolderBuilder::AddChildrenTo(model.get(), model->account_bookmark_bar_node(),
                               {});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  |- url1(http://www.url1.com)
  //
  LocalBookmarkToAccountMerger(model.get()).MoveAndMergeAllNodes();

  ASSERT_THAT(model->bookmark_bar_node()->children(), IsEmpty());
  ASSERT_THAT(model->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));

  // Managed nodes should be excluded from the merge and be left unmodified.
  ASSERT_THAT(managed_node->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
  EXPECT_THAT(model->GetNodesByURL(kUrl2),
              ElementsAre(managed_node->children()[0].get()));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldUploadLocalUuid) {
  const std::u16string kUrl1Title = u"url1";
  const GURL kUrl1("http://www.url1.com/");
  const base::Uuid kUrl1Uuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kLocalTitle)
  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1).SetUuid(kUrl1Uuid)});

  // -------- Account bookmarks --------
  // bookmark_bar
  AddAccountNodes({});

  // -------- The expected merge outcome --------
  // Same as the local model described above, including the UUID.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmarkWithUuid(kUrl1Title, kUrl1, kUrl1Uuid)));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldDeduplicateBySemantics) {
  const std::u16string kFolder1Title = u"folder1";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- folder 1
  //    |- url1(http://www.url1.com)
  //    |- url2(http://www.url2.com)
  AddLocalNodes({FolderBuilder(kFolder1Title)
                     .SetChildren({UrlBuilder(kUrl1Title, kUrl1),
                                   UrlBuilder(kUrl2Title, kUrl2)})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  |- folder 1
  //    |- url2(http://www.url2.com)
  //    |- url3(http://www.url3.com)
  AddAccountNodes({FolderBuilder(kFolder1Title)
                       .SetChildren({UrlBuilder(kUrl2Title, kUrl2),
                                     UrlBuilder(kUrl3Title, kUrl3)})});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  |- folder 1
  //    |- url2(http://www.url2.com)
  //    |- url3(http://www.url3.com)
  //    |- url1(http://www.url1.com)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder1Title,
                           ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                                       IsUrlBookmark(kUrl3Title, kUrl3),
                                       IsUrlBookmark(kUrl1Title, kUrl1)))));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDeduplicateBySemanticsWhenSelected) {
  const std::u16string kFolder1Title = u"folder1";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- url1(http://www.url1.com)
  //  |- url2(http://www.url2.com)
  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  |- url2(http://www.url2.com)
  //  |- url3(http://www.url3.com)
  AddAccountNodes(
      {UrlBuilder(kUrl2Title, kUrl2), UrlBuilder(kUrl3Title, kUrl3)});

  // -------- The expected merge outcome --------
  // Move(url2)
  //
  // ---- Local bookmarks ----
  //  |- url1(http://www.url1.com)
  //
  // ---- Account bookmarks ----
  // bookmark_bar
  //  |- url2(http://www.url2.com)
  //  |- url3(http://www.url3.com)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get())
      .MoveAndMergeSpecificSubtrees(
          {model_->bookmark_bar_node()->children()[1]->id()});

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                          IsUrlBookmark(kUrl3Title, kUrl3)));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldNotDeduplicateIfDifferentUrls) {
  const std::u16string kFolder1Title = u"folder1";
  const std::u16string kFolder2Title = u"folder2";
  const std::u16string kFolder3Title = u"folder3";
  const std::u16string kFolder4Title = u"folder4";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";
  const std::u16string kUrl4Title = u"url4";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");
  const GURL kUrl4("http://www.url4.com/");

  const GURL kAnotherUrl2("http://www.another-url2.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- folder 1
  //    |- url1(http://www.url1.com)
  //    |- url2(http://www.url2.com)
  //  |- folder 2
  //    |- url3(http://www.url3.com)
  //    |- url4(http://www.url4.com)
  //  |- folder 3
  AddLocalNodes({FolderBuilder(kFolder1Title)
                     .SetChildren({UrlBuilder(kUrl1Title, kUrl1),
                                   UrlBuilder(kUrl2Title, kUrl2)}),
                 FolderBuilder(kFolder2Title)
                     .SetChildren({UrlBuilder(kUrl3Title, kUrl3),
                                   UrlBuilder(kUrl4Title, kUrl4)}),
                 FolderBuilder(kFolder3Title)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  |- folder 1
  //    |- url1(http://www.url1.com)
  //    |- url2(http://www.another-url2.com)
  //  |- folder 4
  //    |- url3(http://www.url3.com)
  //    |- url4(http://www.url4.com)
  AddAccountNodes({FolderBuilder(kFolder1Title)
                       .SetChildren({UrlBuilder(kUrl1Title, kUrl1),
                                     UrlBuilder(kUrl2Title, kAnotherUrl2)}),
                   FolderBuilder(kFolder4Title)
                       .SetChildren({UrlBuilder(kUrl3Title, kUrl3),
                                     UrlBuilder(kUrl4Title, kUrl4)})});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  |- folder 1
  //    |- url1(http://www.url1.com)
  //    |- url2(http://www.another-url2.com)
  //    |- url2(http://www.url2.com)
  //  |- folder 4
  //    |- url3(http://www.url3.com)
  //    |- url4(http://www.url4.com)
  //  |- folder 2
  //    |- url3(http://www.url3.com)
  //    |- url4(http://www.url4.com)
  //  |- folder 3
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(3);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder1Title,
                           ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
                                       IsUrlBookmark(kUrl2Title, kAnotherUrl2),
                                       IsUrlBookmark(kUrl2Title, kUrl2))),
                  IsFolder(kFolder4Title,
                           ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3),
                                       IsUrlBookmark(kUrl4Title, kUrl4))),
                  IsFolder(kFolder2Title,
                           ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3),
                                       IsUrlBookmark(kUrl4Title, kUrl4))),
                  IsFolder(kFolder3Title, IsEmpty())));
}

// This tests that truncated titles produced by legacy clients are properly
// matched.
TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDedupLocalAndAccountNodesWhenAccountHasLegacyTruncatedTitle) {
  const std::u16string kLocalLongTitle(300, 'A');
  const std::u16string kAccountTruncatedTitle(255, 'A');

  // -------- Local bookmarks --------
  AddLocalNodes({FolderBuilder(kLocalLongTitle)});

  // -------- Account bookmarks --------
  AddAccountNodes({FolderBuilder(kAccountTruncatedTitle)});

  // -------- The expected merge outcome --------
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());

  // Both titles should have matched against each other.
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsFolder(kLocalLongTitle, IsEmpty())));
}

// This test checks that local node with truncated title will merge with account
// node which has full title.
TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldMergeLocalAndAccountNodesWhenLocalHasLegacyTruncatedTitle) {
  const std::u16string kAccountFullTitle(300, 'A');
  const std::u16string kLocalTruncatedTitle(255, 'A');

  // -------- Local bookmarks --------
  AddLocalNodes({FolderBuilder(kLocalTruncatedTitle)});

  // -------- Account bookmarks --------
  AddAccountNodes({FolderBuilder(kAccountFullTitle)});

  // -------- The expected merge outcome --------
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());

  // Both titles should have matched against each other. Although the local
  // title is truncated, for simplicity of the algorithm and considering how
  // rare this scenario is, the local one wins.
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsFolder(kLocalTruncatedTitle, IsEmpty())));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldDeduplicateBookmarkByUuid) {
  const std::u16string kLocalTitle = u"Title 1";
  const std::u16string kAccountTitle = u"Title 2";
  const GURL kUrl("http://www.foo.com/");
  const base::Uuid kUuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kLocalTitle)
  AddLocalNodes({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kAccountTitle)
  AddAccountNodes({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  |- bookmark(kUuid/kLocalTitle)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmarkWithUuid(kLocalTitle, kUrl, kUuid)));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDeduplicateBookmarkByUuidWhenSelected) {
  const std::u16string kLocalTitle = u"Title 1";
  const std::u16string kAccountTitle = u"Title 2";
  const GURL kUrl("http://www.foo.com/");
  const base::Uuid kUuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kLocalTitle)
  AddLocalNodes({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kAccountTitle)
  AddAccountNodes({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  |- bookmark(kUuid/kLocalTitle)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get())
      .MoveAndMergeSpecificSubtrees(
          {model_->bookmark_bar_node()->children()[0]->id()});

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmarkWithUuid(kLocalTitle, kUrl, kUuid)));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDeduplicateBySemanticsAfterParentMatchedByUuid) {
  const std::u16string kFolder1Title = u"folder1";
  const std::u16string kFolder2Title = u"folder2";
  const std::u16string kFolder3Title = u"folder3";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");

  const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder 1 (kFolder1Title)
  //  | - folder 2 (kFolder2Uuid/kFolder2Title)
  //    |- url1(http://www.url1.com)
  //    |- url2(http://www.url2.com)
  AddLocalNodes({FolderBuilder(kFolder1Title),
                 FolderBuilder(kFolder2Title)
                     .SetUuid(kFolder2Uuid)
                     .SetChildren({UrlBuilder(kUrl1Title, kUrl1),
                                   UrlBuilder(kUrl2Title, kUrl2)})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder 3 (kFolder3Title)
  //    | - folder 2 (kFolder2Uuid/kFolder2Title)
  //      |- url2(http://www.url1.com)
  //      |- url3(http://www.url3.com)
  AddAccountNodes(
      {FolderBuilder(kFolder3Title)
           .SetChildren({FolderBuilder(kFolder2Title)
                             .SetUuid(kFolder2Uuid)
                             .SetChildren({UrlBuilder(kUrl2Title, kUrl2),
                                           UrlBuilder(kUrl3Title, kUrl3)})})});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  | - folder 3 (kFolder3Title)
  //    | - folder 2 (kFolder2Uuid/kTitle2)
  //      |- url2(http://www.url2.com)
  //      |- url3(http://www.url3.com)
  //      |- url1(http://www.url1.com)
  //  | - folder 1 (kTitle1)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder3Title,
                           ElementsAre(IsFolder(
                               kFolder2Title,
                               ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                                           IsUrlBookmark(kUrl3Title, kUrl3),
                                           IsUrlBookmark(kUrl1Title, kUrl1))))),
                  IsFolder(kFolder1Title, IsEmpty())));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDeduplicateBySemanticsAfterNestedParentMatchedByUuid) {
  const std::u16string kFolder1Title = u"folder1";
  const std::u16string kFolder2Title = u"folder2";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");

  const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder 1 (kFolder1Title)
  //    | - folder 2 (kFolder2Uuid/kFolder2Title)
  //      |- url1(http://www.url1.com)
  //      |- url2(http://www.url2.com)
  AddLocalNodes(
      {FolderBuilder(kFolder1Title)
           .SetChildren({FolderBuilder(kFolder2Title)
                             .SetUuid(kFolder2Uuid)
                             .SetChildren({UrlBuilder(kUrl1Title, kUrl1),
                                           UrlBuilder(kUrl2Title, kUrl2)})})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder 2 (kFolder2Uuid/kFolder2Title)
  //    |- url2(http://www.url2.com)
  //    |- url3(http://www.url3.com)
  AddAccountNodes({FolderBuilder(kFolder2Title)
                       .SetUuid(kFolder2Uuid)
                       .SetChildren({UrlBuilder(kUrl2Title, kUrl2),
                                     UrlBuilder(kUrl3Title, kUrl3)})});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  | - folder 2 (kFolder2Uuid/kTitle2)
  //    |- url2(http://www.url2.com)
  //    |- url3(http://www.url3.com)
  //    |- url1(http://www.url1.com)
  //  | - folder 1 (kTitle1)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder2Title,
                           ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                                       IsUrlBookmark(kUrl3Title, kUrl3),
                                       IsUrlBookmark(kUrl1Title, kUrl1))),
                  IsFolder(kFolder1Title, IsEmpty())));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDeduplicateBySemanticsAfterTwoConsecutiveAncestorsMatchedByUuid) {
  const std::u16string kFolder1Title = u"folder1";
  const std::u16string kFolder2Title = u"folder2";
  const std::u16string kFolder3Title = u"folder3";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");

  const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();
  const base::Uuid kFolder3Uuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder 1 (kFolder1Title)
  //    | - folder 2 (kFolder2Uuid/kFolder2Title)
  //      | - folder 3 (kFolder3Uuid/kFolder3Title)
  //        |- url1(http://www.url1.com)
  //        |- url2(http://www.url2.com)
  AddLocalNodes(
      {FolderBuilder(kFolder1Title)
           .SetChildren(
               {FolderBuilder(kFolder2Title)
                    .SetUuid(kFolder2Uuid)
                    .SetChildren(
                        {FolderBuilder(kFolder3Title)
                             .SetUuid(kFolder3Uuid)
                             .SetChildren(
                                 {UrlBuilder(kUrl1Title, kUrl1),
                                  UrlBuilder(kUrl2Title, kUrl2)})})})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder 2 (kFolder2Uuid/kFolder2Title)
  //    | - folder 3 (kFolder3Uuid/kFolder3Title)
  //      |- url2(http://www.url2.com)
  //      |- url3(http://www.url3.com)
  AddAccountNodes(
      {FolderBuilder(kFolder2Title)
           .SetUuid(kFolder2Uuid)
           .SetChildren({FolderBuilder(kFolder3Title)
                             .SetUuid(kFolder3Uuid)
                             .SetChildren({UrlBuilder(kUrl2Title, kUrl2),
                                           UrlBuilder(kUrl3Title, kUrl3)})})});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  | - folder 2 (kFolder2Uuid/kTitle2)
  //    | - folder 3 (kFolder3Uuid/kTitle3)
  //      |- url2(http://www.url2.com)
  //      |- url3(http://www.url3.com)
  //      |- url1(http://www.url1.com)
  //  | - folder 1 (kTitle1)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(3);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder2Title,
                           ElementsAre(IsFolder(
                               kFolder3Title,
                               ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                                           IsUrlBookmark(kUrl3Title, kUrl3),
                                           IsUrlBookmark(kUrl1Title, kUrl1))))),
                  IsFolder(kFolder1Title, IsEmpty())));
}

TEST_F(
    LocalBookmarkToAccountMergerTest,
    ShouldDeduplicateBySemanticsAfterTwoNonConsecutiveAncestorsMatchedByUuid) {
  const std::u16string kFolder1Title = u"folder1";
  const std::u16string kFolder2Title = u"folder2";
  const std::u16string kFolder3Title = u"folder3";
  const std::u16string kFolder4Title = u"folder4";
  const std::u16string kFolder5Title = u"folder5";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");

  const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();
  const base::Uuid kFolder5Uuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder 1 (kFolder1Title)
  //    | - folder 2 (kFolder2Uuid/kFolder2Title)
  //      | - folder 3 (kFolder3Title)
  //        | - folder 4 (kFolder4Title)
  //          | - folder 5 (kFolder5Uuid/kFolder5Title)
  //            |- url1(http://www.url1.com)
  //            |- url2(http://www.url2.com)
  AddLocalNodes(
      {FolderBuilder(kFolder1Title)
           .SetChildren(
               {FolderBuilder(kFolder2Title)
                    .SetUuid(kFolder2Uuid)
                    .SetChildren(
                        {FolderBuilder(kFolder3Title)
                             .SetChildren(
                                 {FolderBuilder(kFolder4Title)
                                      .SetChildren(
                                          {FolderBuilder(kFolder5Title)
                                               .SetUuid(kFolder5Uuid)
                                               .SetChildren(
                                                   {UrlBuilder(kUrl1Title,
                                                               kUrl1),
                                                    UrlBuilder(
                                                        kUrl2Title,
                                                        kUrl2)})})})})})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder 2 (kFolder2Uuid/kFolder2Title)
  //  | - folder 5 (kFolder5Uuid/kFolder5Title)
  //    |- url2(http://www.url2.com)
  //    |- url3(http://www.url3.com)
  AddAccountNodes({FolderBuilder(kFolder2Title).SetUuid(kFolder2Uuid),
                   FolderBuilder(kFolder5Title)
                       .SetUuid(kFolder5Uuid)
                       .SetChildren({UrlBuilder(kUrl2Title, kUrl2),
                                     UrlBuilder(kUrl3Title, kUrl3)})});

  // -------- The expected merge outcome --------
  // bookmark_bar
  //  | - folder 2 (kFolder2Uuid/kTitle2)
  //    | - folder 3 (kTitle3)
  //      | - folder 4 (kTitle4)
  //  | - folder 5 (kFolder5Uuid/kFolder5Title)
  //      |- url2(http://www.url2.com)
  //      |- url3(http://www.url3.com)
  //      |- url1(http://www.url1.com)
  //  | - folder 1 (kTitle1)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(3);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(3);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder2Title,
                           ElementsAre(IsFolder(
                               kFolder3Title, ElementsAre(IsFolder(
                                                  kFolder4Title, IsEmpty()))))),
                  IsFolder(kFolder5Title,
                           ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                                       IsUrlBookmark(kUrl3Title, kUrl3),
                                       IsUrlBookmark(kUrl1Title, kUrl1))),
                  IsFolder(kFolder1Title, IsEmpty())));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldMergeBookmarkByUuidDespiteDifferentParent) {
  const std::u16string kFolderTitle = u"Folder Title";
  const std::u16string kLocalTitle = u"Title 1";
  const std::u16string kAccountTitle = u"Title 2";
  const GURL kUrl("http://www.foo.com/");
  const base::Uuid kUuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- bookmark(kUuid/kLocalTitle)
  AddLocalNodes({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder
  //    | - bookmark(kUuid/kAccountTitle)
  AddAccountNodes(
      {FolderBuilder(kFolderTitle)
           .SetChildren({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)})});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  | - folder
  //    | - bookmark(kUuid/kLocalTitle)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolderTitle, ElementsAre(IsUrlBookmarkWithUuid(
                                             kLocalTitle, kUrl, kUuid)))));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldNotMergeBySemanticsIfDifferentParent) {
  const std::u16string kFolder1Title = u"folder1";
  const std::u16string kFolder2Title = u"folder2";

  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- folder 1
  //    |- folder 2
  //      |- url1(http://www.url1.com)
  AddLocalNodes(
      {FolderBuilder(kFolder1Title)
           .SetChildren({FolderBuilder(kFolder2Title)
                             .SetChildren({UrlBuilder(kUrl1Title, kUrl1)})})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  |- folder 2
  //    |- url2(http://www.url2.com)
  AddAccountNodes({FolderBuilder(kFolder2Title)
                       .SetChildren({UrlBuilder(kUrl2Title, kUrl2)})});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  |- folder 2
  //    |- url2(http://www.url2.com)
  //  |- folder 1
  //    |- folder 2
  //      |- url1(http://www.url1.com)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolder(kFolder2Title,
                           ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2))),
                  IsFolder(kFolder1Title,
                           ElementsAre(IsFolder(kFolder2Title,
                                                ElementsAre(IsUrlBookmark(
                                                    kUrl1Title, kUrl1)))))));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldMergeFolderByUuidAndNotSemantics) {
  const std::u16string kTitle1 = u"Title 1";
  const std::u16string kTitle2 = u"Title 2";
  const GURL kUrl("http://www.foo.com/");
  const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
  const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder 1 (kUuid1/kTitle1)
  //    | - folder 2 (kUuid2/kTitle2)
  AddLocalNodes({FolderBuilder(kTitle1).SetUuid(kUuid1).SetChildren(
      {FolderBuilder(kTitle2).SetUuid(kUuid2)})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder (kUuid2/kTitle1)
  AddAccountNodes({FolderBuilder(kTitle1).SetUuid(kUuid2)});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  | - folder 2 (kUuid2/kTitle2)
  //  | - folder 1 (kUuid1/kTitle1)
  //
  // The node should have been merged with its UUID match, even if the other
  // candidate matches by semantics.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsFolderWithUuid(kTitle2, kUuid2, IsEmpty()),
                          IsFolderWithUuid(kTitle1, kUuid1, IsEmpty())));
}

TEST_F(
    LocalBookmarkToAccountMergerTest,
    ShouldIgnoreFolderSemanticsMatchAndLaterMatchByUuidWithSemanticsNodeFirst) {
  const std::u16string kLocalOnlyTitle = u"LocalOnlyTitle";
  const std::u16string kMatchingTitle = u"MatchingTitle";
  const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
  const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();
  const GURL kUrl("http://foo.com/");
  const std::u16string kUrlTitle = u"Bookmark Title";

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder (kUuid1/kMatchingTitle)
  //  | - folder (kUuid2/kLocalOnlyTitle)
  //    | - bookmark
  AddLocalNodes({FolderBuilder(kMatchingTitle).SetUuid(kUuid1),
                 FolderBuilder(kLocalOnlyTitle)
                     .SetUuid(kUuid2)
                     .SetChildren({UrlBuilder(kUrlTitle, kUrl)})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder (kUuid2/kMatchingTitle)
  AddAccountNodes({FolderBuilder(kMatchingTitle).SetUuid(kUuid2)});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  | - folder (kUuid2/kLocalOnlyTitle)
  //    | - bookmark
  //  | - folder (kUuid1/kMatchingTitle)
  //
  // The node should have been merged with its UUID match, even if the other
  // candidate matches by semantics.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolderWithUuid(kLocalOnlyTitle, kUuid2,
                                   ElementsAre(IsUrlBookmark(kUrlTitle, kUrl))),
                  IsFolderWithUuid(kMatchingTitle, kUuid1, IsEmpty())));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldIgnoreFolderSemanticsMatchAndLaterMatchByUuidWithUuidNodeFirst) {
  const std::u16string kLocalOnlyTitle = u"LocalOnlyTitle";
  const std::u16string kMatchingTitle = u"MatchingTitle";
  const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
  const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();
  const GURL kUrl("http://foo.com/");
  const std::u16string kUrlTitle = u"Bookmark Title";

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder (kUuid2/kLocalOnlyTitle)
  //    | - bookmark
  //  | - folder (kUuid1/kMatchingTitle)
  AddLocalNodes({FolderBuilder(kLocalOnlyTitle)
                     .SetUuid(kUuid2)
                     .SetChildren({UrlBuilder(kUrlTitle, kUrl)}),
                 FolderBuilder(kMatchingTitle).SetUuid(kUuid1)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder (kUuid2/kMatchingTitle)
  AddAccountNodes({FolderBuilder(kMatchingTitle).SetUuid(kUuid2)});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  | - folder (kUuid2/kLocalOnlyTitle)
  //    | - bookmark
  //  | - folder (kUuid1/kMatchingTitle)
  //
  // The node should have been merged with its UUID match, even if the other
  // candidate matches by semantics.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(
      model_->account_bookmark_bar_node()->children(),
      ElementsAre(IsFolderWithUuid(kLocalOnlyTitle, kUuid2,
                                   ElementsAre(IsUrlBookmark(kUrlTitle, kUrl))),
                  IsFolderWithUuid(kMatchingTitle, kUuid1, IsEmpty())));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldReplaceBookmarkUuidWithConflictingURLs) {
  const std::u16string kTitle = u"Title";
  const GURL kUrl1("http://www.foo.com/");
  const GURL kUrl2("http://www.bar.com/");
  const base::Uuid kUuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark (kUuid/kUrl1)
  AddLocalNodes({UrlBuilder(kTitle, kUrl1).SetUuid(kUuid)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - bookmark (kUuid/kUrl2)
  AddAccountNodes({UrlBuilder(kTitle, kUrl2).SetUuid(kUuid)});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  | - bookmark (kUuid/kUrl2)
  //  | - bookmark ([new UUID]/kUrl1)
  //
  // The conflicting node UUID should have been replaced.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmarkWithUuid(kTitle, kUrl2, kUuid),
                          IsUrlBookmarkWithUuid(kTitle, kUrl1, Ne(kUuid))));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldReplaceBookmarkUuidWithConflictingTypes) {
  const GURL kUrl1("http://www.foo.com/");
  const std::u16string kTitle = u"Title";
  const base::Uuid kUuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark (kUuid/kUrl1)
  AddLocalNodes({UrlBuilder(kTitle, kUrl1).SetUuid(kUuid)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - folder(kUuid)
  AddAccountNodes({FolderBuilder(kTitle).SetUuid(kUuid)});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  | - folder (kUuid)
  //  | - bookmark ([new UUID])
  //
  // The conflicting node UUID should have been replaced.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsFolderWithUuid(kTitle, kUuid, IsEmpty()),
                          IsUrlBookmarkWithUuid(kTitle, kUrl1, Ne(kUuid))));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldReplaceBookmarkUuidWithConflictingTypesAndLocalChildren) {
  const std::u16string kFolderTitle = u"Folder Title";
  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const base::Uuid kUuid = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder (kUuid)
  //    | - bookmark (kUrl1)
  AddLocalNodes({FolderBuilder(kFolderTitle)
                     .SetUuid(kUuid)
                     .SetChildren({UrlBuilder(kUrl1Title, kUrl1)})});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - bookmark (kUuid/kUrl2)
  AddAccountNodes({UrlBuilder(kUrl2Title, kUrl2).SetUuid(kUuid)});

  // -------- The merged account nodes --------
  // bookmark_bar
  //  | - bookmark (kUuid/kUrl2)
  //  | - folder ([new UUID])
  //    | - bookmark (kUrl1)
  //
  // The conflicting node UUID should have been replaced.
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmarkWithUuid(kUrl2Title, kUrl2, kUuid),
                          IsFolderWithUuid(
                              kFolderTitle, Ne(kUuid),
                              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)))));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldRemoveOneChildAtArbitraryIndex) {
  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";
  const std::u16string kUrl4Title = u"url4";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");
  const GURL kUrl4("http://www.url4.com/");

  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
                 UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4)});

  LocalBookmarkToAccountMerger(model_.get())
      .RemoveChildrenAtForTesting(
          /*parent=*/model_->bookmark_bar_node(),
          /*indices_to_remove=*/{2}, FROM_HERE);

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
                          IsUrlBookmark(kUrl2Title, kUrl2),
                          IsUrlBookmark(kUrl4Title, kUrl4)));

  LocalBookmarkToAccountMerger(model_.get())
      .RemoveChildrenAtForTesting(
          /*parent=*/model_->bookmark_bar_node(),
          /*indices_to_remove=*/{0}, FROM_HERE);

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                          IsUrlBookmark(kUrl4Title, kUrl4)));

  LocalBookmarkToAccountMerger(model_.get())
      .RemoveChildrenAtForTesting(
          /*parent=*/model_->bookmark_bar_node(),
          /*indices_to_remove=*/{1}, FROM_HERE);

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldRemoveMultipleChildrenAtArbitraryIndices) {
  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";
  const std::u16string kUrl4Title = u"url4";
  const std::u16string kUrl5Title = u"url5";
  const std::u16string kUrl6Title = u"url6";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");
  const GURL kUrl4("http://www.url4.com/");
  const GURL kUrl5("http://www.url5.com/");
  const GURL kUrl6("http://www.url6.com/");

  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
                 UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4),
                 UrlBuilder(kUrl5Title, kUrl5), UrlBuilder(kUrl6Title, kUrl6)});

  LocalBookmarkToAccountMerger(model_.get())
      .RemoveChildrenAtForTesting(
          /*parent=*/model_->bookmark_bar_node(),
          /*indices_to_remove=*/{0, 2, 3, 5}, FROM_HERE);

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                          IsUrlBookmark(kUrl5Title, kUrl5)));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldRemoveAllChildren) {
  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";
  const std::u16string kUrl3Title = u"url3";
  const std::u16string kUrl4Title = u"url4";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");
  const GURL kUrl3("http://www.url3.com/");
  const GURL kUrl4("http://www.url4.com/");

  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
                 UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4)});

  // When deleting all children, no reordering is expected.
  EXPECT_CALL(observer_, BookmarkNodeChildrenReordered).Times(0);

  LocalBookmarkToAccountMerger(model_.get())
      .RemoveChildrenAtForTesting(
          /*parent=*/model_->bookmark_bar_node(),
          /*indices_to_remove=*/{0, 1, 2, 3}, FROM_HERE);

  EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldDoNothingIfNoNodesSelected) {
  const std::u16string kUrl1Title = u"url1";
  const GURL kUrl1("http://www.url1.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kLocalTitle)
  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1)});

  // -------- Account bookmarks --------
  // bookmark_bar
  AddAccountNodes({});

  // -------- The expected merge outcome --------
  // Unchanged
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeSpecificSubtrees({});

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(), IsEmpty());
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDoNothingIfNonExistentIdSelected) {
  const std::u16string kUrl1Title = u"url1";
  const GURL kUrl1("http://www.url1.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kLocalTitle)
  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1)});

  // -------- Account bookmarks --------
  // bookmark_bar
  AddAccountNodes({});

  // -------- The expected merge outcome --------
  // Unchanged
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get())
      .MoveAndMergeSpecificSubtrees({123456789});

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(), IsEmpty());
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldDoNothingIfAccountNodeIdSelected) {
  const std::u16string kUrl1Title = u"url1";
  const GURL kUrl1("http://www.url1.com/");
  const std::u16string kUrl2Title = u"url2";
  const GURL kUrl2("http://www.url2.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid/kLocalTitle)
  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1)});

  // -------- Account bookmarks --------
  // bookmark_bar
  AddAccountNodes({UrlBuilder(kUrl2Title, kUrl2)});

  // -------- The expected merge outcome --------
  // Unchanged
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get())
      .MoveAndMergeSpecificSubtrees(
          {model_->account_bookmark_bar_node()->children()[0]->id()});

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldMergeSelectedSubsetOfLocalNodes) {
  const std::u16string kUrl1Title = u"url1";
  const GURL kUrl1("http://www.url1.com/");
  const std::u16string kUrl2Title = u"url2";
  const GURL kUrl2("http://www.url2.com/");
  const std::u16string kUrl3Title = u"url3";
  const GURL kUrl3("http://www.url3.com/");
  const std::u16string kUrl4Title = u"url4";
  const GURL kUrl4("http://www.url4.com/");
  const std::u16string kUrl5Title = u"url5";
  const GURL kUrl5("http://www.url5.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUrl1Title)
  //    | - bookmark(kUrl2Title)
  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
                 UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4),
                 UrlBuilder(kUrl5Title, kUrl5)});

  // -------- Account bookmarks --------
  // bookmark_bar
  AddAccountNodes({});

  // -------- The expected merge outcome --------
  // Move(2, 4)
  //
  // ---- Local bookmarks ----
  // bookmark_bar
  //  | - bookmark(kUrl1Title)
  //  | - bookmark(kUrl3Title)
  //  | - bookmark(kUrl5Title)
  //
  // ---- Account bookmarks ----
  // bookmark_bar
  //  | - bookmark(kUrl2Title)
  //  | - bookmark(kUrl4Title)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get())
      .MoveAndMergeSpecificSubtrees(
          {model_->bookmark_bar_node()->children()[1]->id(),
           model_->bookmark_bar_node()->children()[3]->id()});

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
                          IsUrlBookmark(kUrl3Title, kUrl3),
                          IsUrlBookmark(kUrl5Title, kUrl5)));
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
                          IsUrlBookmark(kUrl4Title, kUrl4)));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldDoNothingIfChildNodeSelected) {
  const std::u16string kFolderTitle = u"Folder Title";
  const std::u16string kUrl1Title = u"url1";
  const GURL kUrl1("http://www.url1.com/");

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - folder (kFolderTitle)
  //    | - bookmark (kUrl1Title)
  AddLocalNodes({FolderBuilder(kFolderTitle)
                     .SetChildren({UrlBuilder(kUrl1Title, kUrl1)})});

  // -------- Account bookmarks --------
  // bookmark_bar
  AddAccountNodes({});

  // -------- The expected merge outcome --------
  // Move(kUrl1Title)
  //
  // Unchanged (the child is not moved).
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get())
      .MoveAndMergeSpecificSubtrees(
          {model_->bookmark_bar_node()->children()[0]->children()[0]->id()});

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsFolder(kFolderTitle, ElementsAre(IsUrlBookmark(
                                                     kUrl1Title, kUrl1)))));
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(), IsEmpty());
}

TEST_F(LocalBookmarkToAccountMergerTest,
       ShouldApplySemanticDedupeWhenUUIDMatchForNotSelectedNode) {
  const std::u16string kUrl1Title = u"url1";
  const GURL kUrl1("http://www.url1.com/");
  const std::u16string kUrl2Title = u"url2";
  const GURL kUrl2("http://www.url2.com/");
  const std::u16string kUrl3Title = u"url3";
  const GURL kUrl3("http://www.url3.com/");
  const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
  const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();
  const base::Uuid kUuid3 = base::Uuid::GenerateRandomV4();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid1, kUrl1Title, kUrl1)
  //  | - bookmark(kUuid2, kUrl2Title, kUrl2)
  AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1).SetUuid(kUuid1),
                 UrlBuilder(kUrl2Title, kUrl2).SetUuid(kUuid2)});

  // -------- Account bookmarks --------
  // bookmark_bar
  //  | - bookmark(kUuid2, kUrl1Title, kUrl1)
  //  | - bookmark(kUuid3, kUrl3Title, kUrl3)
  AddAccountNodes({UrlBuilder(kUrl1Title, kUrl1).SetUuid(kUuid2),
                   UrlBuilder(kUrl3Title, kUrl3).SetUuid(kUuid3)});

  // -------- The expected merge outcome --------
  // Move(kUrl1Title)
  // In this case there is a UUID match between the local node with title 2
  // (which isn't selected), and the account node with title 1. The semantic
  // match is still applied for local and account nodes with title 1.
  //
  // ---- Local bookmarks ----
  // bookmark_bar
  //  | - bookmark(kUuid2, kUrl2Title, kUrl2)
  //
  // ---- Account bookmarks ----
  // bookmark_bar
  //  | - bookmark(kUuid2, kUrl1Title, kUrl1)
  //  | - bookmark(kUuid3, kUrl3Title, kUrl3)
  EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
  EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
  EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);

  LocalBookmarkToAccountMerger(model_.get())
      .MoveAndMergeSpecificSubtrees(
          {model_->bookmark_bar_node()->children()[0]->id()});

  EXPECT_THAT(model_->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
  EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmarkWithUuid(kUrl1Title, kUrl1, kUuid2),
                          IsUrlBookmarkWithUuid(kUrl3Title, kUrl3, kUuid3)));
}

TEST_F(LocalBookmarkToAccountMergerTest, ShouldIgnoreManagedNodesWhenSelected) {
  const std::u16string kUrl1Title = u"url1";
  const std::u16string kUrl2Title = u"url2";

  const GURL kUrl1("http://www.url1.com/");
  const GURL kUrl2("http://www.url2.com/");

  auto local_client = std::make_unique<bookmarks::TestBookmarkClient>();
  bookmarks::BookmarkNode* managed_node = local_client->EnableManagedNode();
  std::unique_ptr<bookmarks::BookmarkModel> model =
      bookmarks::TestBookmarkClient::CreateModelWithClient(
          std::move(local_client));
  model->CreateAccountPermanentFolders();

  // -------- Local bookmarks --------
  // bookmark_bar
  //  |- url1(http://www.url1.com)
  // managed_bookmarks
  //  |- url2(http://www.url2.com)
  FolderBuilder::AddChildrenTo(model.get(), model->bookmark_bar_node(),
                               {UrlBuilder(kUrl1Title, kUrl1)});
  FolderBuilder::AddChildrenTo(model.get(), managed_node,
                               {UrlBuilder(kUrl2Title, kUrl2)});
  ASSERT_THAT(managed_node->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));

  // -------- Account bookmarks --------
  // bookmark_bar
  FolderBuilder::AddChildrenTo(model.get(), model->account_bookmark_bar_node(),
                               {});

  // -------- The expected merge outcome --------
  // Unchanged
  LocalBookmarkToAccountMerger(model.get())
      .MoveAndMergeSpecificSubtrees({managed_node->children()[0]->id()});

  ASSERT_THAT(model->bookmark_bar_node()->children(),
              ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
  ASSERT_THAT(model->account_bookmark_bar_node()->children(), IsEmpty());

  // Managed nodes should be excluded from the merge and be left unmodified.
  ASSERT_THAT(managed_node->children(),
              ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
  EXPECT_THAT(model->GetNodesByURL(kUrl2),
              ElementsAre(managed_node->children()[0].get()));
}

}  // namespace

}  // namespace sync_bookmarks
