| // Copyright 2023 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_model_merger.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/strings/utf_ostream_operators.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/uuid.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/browser/bookmark_node.h" |
| #include "components/bookmarks/test/test_bookmark_client.h" |
| #include "components/sync_bookmarks/bookmark_model_view.h" |
| #include "components/sync_bookmarks/test_bookmark_model_view.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/abseil-cpp/absl/types/variant.h" |
| #include "url/gurl.h" |
| |
| namespace sync_bookmarks { |
| namespace { |
| |
| using testing::ElementsAre; |
| using testing::IsEmpty; |
| using testing::Ne; |
| |
| MATCHER_P2(MatchesUrl, title, url, "") { |
| if (!arg->is_url()) { |
| *result_listener << "Expected URL bookmark but got folder."; |
| return false; |
| } |
| if (arg->GetTitle() != base::ASCIIToUTF16(title)) { |
| *result_listener << "Expected URL title \"" << title << "\" but got \"" |
| << arg->GetTitle() << "\""; |
| return false; |
| } |
| if (arg->url() != url) { |
| *result_listener << "Expected URL \"" << url << "\" but got \"" |
| << arg->url() << "\""; |
| return false; |
| } |
| return true; |
| } |
| |
| MATCHER_P2(MatchesFolder, title, children_matcher, "") { |
| if (!arg->is_folder()) { |
| *result_listener << "Expected folder but got URL."; |
| return false; |
| } |
| if (arg->GetTitle() != base::ASCIIToUTF16(title)) { |
| *result_listener << "Expected folder title \"" << title << "\" but got \"" |
| << arg->GetTitle() << "\""; |
| return false; |
| } |
| return testing::ExplainMatchResult(children_matcher, arg->children(), |
| result_listener); |
| } |
| |
| MATCHER_P(HasUuid, uuid, "") { |
| return testing::ExplainMatchResult(uuid, arg->uuid(), result_listener); |
| } |
| |
| MATCHER_P3(MatchesUrlWithUuid, title, url, uuid, "") { |
| return testing::ExplainMatchResult(MatchesUrl(title, url), arg, |
| result_listener) && |
| testing::ExplainMatchResult(HasUuid(uuid), arg, result_listener); |
| } |
| |
| MATCHER_P3(MatchesFolderWithUuid, title, uuid, children_matcher, "") { |
| return testing::ExplainMatchResult(MatchesFolder(title, children_matcher), |
| arg, result_listener) && |
| testing::ExplainMatchResult(HasUuid(uuid), arg, result_listener); |
| } |
| |
| // Test class to build bookmark URLs conveniently and compactly in tests. |
| class UrlBuilder { |
| public: |
| UrlBuilder(const std::string& title, const GURL& url) |
| : title_(title), url_(url) {} |
| UrlBuilder(const UrlBuilder&) = default; |
| ~UrlBuilder() = default; |
| |
| UrlBuilder& SetUuid(const base::Uuid& uuid) { |
| uuid_ = uuid; |
| return *this; |
| } |
| |
| void Build(BookmarkModelView* model, |
| const bookmarks::BookmarkNode* parent) const { |
| model->AddURL(parent, parent->children().size(), base::UTF8ToUTF16(title_), |
| url_, /*meta_info=*/nullptr, /*creation_time=*/absl::nullopt, |
| uuid_); |
| } |
| |
| private: |
| const std::string title_; |
| const GURL url_; |
| absl::optional<base::Uuid> uuid_; |
| }; |
| |
| // Test class to build bookmark folders and compactly in tests. |
| class FolderBuilder { |
| public: |
| using FolderOrUrl = absl::variant<FolderBuilder, UrlBuilder>; |
| |
| static void AddChildTo(BookmarkModelView* model, |
| const bookmarks::BookmarkNode* parent, |
| const FolderOrUrl& folder_or_url) { |
| if (absl::holds_alternative<UrlBuilder>(folder_or_url)) { |
| absl::get<UrlBuilder>(folder_or_url).Build(model, parent); |
| } else { |
| CHECK(absl::holds_alternative<FolderBuilder>(folder_or_url)); |
| absl::get<FolderBuilder>(folder_or_url).Build(model, parent); |
| } |
| } |
| |
| static void AddChildrenTo(BookmarkModelView* model, |
| const bookmarks::BookmarkNode* parent, |
| const std::vector<FolderOrUrl>& children) { |
| for (const FolderOrUrl& folder_or_url : children) { |
| AddChildTo(model, parent, folder_or_url); |
| } |
| } |
| |
| explicit FolderBuilder(const std::string& title) : title_(title) {} |
| FolderBuilder(const FolderBuilder&) = default; |
| ~FolderBuilder() = default; |
| |
| FolderBuilder& SetChildren(std::vector<FolderOrUrl> children) { |
| children_ = std::move(children); |
| return *this; |
| } |
| |
| FolderBuilder& SetUuid(const base::Uuid& uuid) { |
| uuid_ = uuid; |
| return *this; |
| } |
| |
| void Build(BookmarkModelView* model, |
| const bookmarks::BookmarkNode* parent) const { |
| const bookmarks::BookmarkNode* folder = model->AddFolder( |
| parent, parent->children().size(), base::UTF8ToUTF16(title_), |
| /*meta_info=*/nullptr, /*creation_time=*/absl::nullopt, uuid_); |
| AddChildrenTo(model, folder, children_); |
| } |
| |
| private: |
| const std::string title_; |
| std::vector<FolderOrUrl> children_; |
| absl::optional<base::Uuid> uuid_; |
| }; |
| |
| std::unique_ptr<TestBookmarkModelView> BuildModel( |
| const std::vector<FolderBuilder::FolderOrUrl>& children_of_bookmark_bar) { |
| auto model = std::make_unique<TestBookmarkModelView>(); |
| FolderBuilder::AddChildrenTo(model.get(), model->bookmark_bar_node(), |
| children_of_bookmark_bar); |
| return model; |
| } |
| |
| } // namespace |
| |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldUploadEntireLocalModelIfAccountModelEmpty) { |
| const std::string kFolder1Title = "folder1"; |
| const std::string kFolder2Title = "folder2"; |
| |
| const std::string kUrl1Title = "url1"; |
| const std::string kUrl2Title = "url2"; |
| const std::string kUrl3Title = "url3"; |
| const std::string kUrl4Title = "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/"); |
| |
| // -------- The local model -------- |
| // 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) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kFolder1Title) |
| .SetChildren({UrlBuilder(kUrl1Title, kUrl1), |
| UrlBuilder(kUrl2Title, kUrl2)}), |
| FolderBuilder(kFolder2Title) |
| .SetChildren({UrlBuilder(kUrl3Title, kUrl3), |
| UrlBuilder(kUrl4Title, kUrl4)})}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| std::unique_ptr<BookmarkModelView> account_model = BuildModel({}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The expected merge outcome -------- |
| // Same as the local model described above. |
| EXPECT_THAT( |
| account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolder(kFolder1Title, |
| ElementsAre(MatchesUrl(kUrl1Title, kUrl1), |
| MatchesUrl(kUrl2Title, kUrl2))), |
| MatchesFolder(kFolder2Title, |
| ElementsAre(MatchesUrl(kUrl3Title, kUrl3), |
| MatchesUrl(kUrl4Title, kUrl4))))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, ShouldIgnoreManagedNodes) { |
| const std::string kUrl1Title = "url1"; |
| const std::string kUrl2Title = "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* local_managed_node = |
| local_client->EnableManagedNode(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // |- url1(http://www.url1.com) |
| // managed_bookmarks |
| // |- url2(http://www.url2.com) |
| TestBookmarkModelView local_model(std::move(local_client)); |
| |
| FolderBuilder::AddChildrenTo(&local_model, local_model.bookmark_bar_node(), |
| {UrlBuilder(kUrl1Title, kUrl1)}); |
| FolderBuilder::AddChildrenTo(&local_model, local_managed_node, |
| {UrlBuilder(kUrl2Title, kUrl2)}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| std::unique_ptr<TestBookmarkModelView> account_model = BuildModel({}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(&local_model, account_model.get()).Merge(); |
| |
| // -------- The expected merge outcome -------- |
| // bookmark_bar |
| // |- url1(http://www.url1.com) |
| // |
| ASSERT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesUrl(kUrl1Title, kUrl1))); |
| |
| // Managed nodes should be excluded from the merge. |
| EXPECT_THAT(account_model->underlying_model()->GetNodesByURL(kUrl2), |
| IsEmpty()); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, ShouldUploadLocalUuid) { |
| const std::string kUrl1Title = "url1"; |
| const GURL kUrl1("http://www.url1.com/"); |
| const base::Uuid kUrl1Uuid = base::Uuid::GenerateRandomV4(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark(kUuid/kLocalTitle) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({UrlBuilder(kUrl1Title, kUrl1).SetUuid(kUrl1Uuid)}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| std::unique_ptr<BookmarkModelView> account_model = BuildModel({}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The expected merge outcome -------- |
| // Same as the local model described above, including the UUID. |
| EXPECT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesUrlWithUuid(kUrl1Title, kUrl1, kUrl1Uuid))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, ShouldNotUploadDuplicateBySemantics) { |
| const std::string kFolder1Title = "folder1"; |
| |
| const std::string kUrl1Title = "url1"; |
| const std::string kUrl2Title = "url2"; |
| const std::string kUrl3Title = "url3"; |
| |
| const GURL kUrl1("http://www.url1.com/"); |
| const GURL kUrl2("http://www.url2.com/"); |
| const GURL kUrl3("http://www.url3.com/"); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- url1(http://www.url1.com) |
| // |- url2(http://www.url2.com) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kFolder1Title) |
| .SetChildren({UrlBuilder(kUrl1Title, kUrl1), |
| UrlBuilder(kUrl2Title, kUrl2)})}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- url2(http://www.url2.com) |
| // |- url3(http://www.url3.com) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kFolder1Title) |
| .SetChildren({UrlBuilder(kUrl2Title, kUrl2), |
| UrlBuilder(kUrl3Title, kUrl3)})}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The expected merge outcome -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- url2(http://www.url2.com) |
| // |- url3(http://www.url3.com) |
| // |- url1(http://www.url1.com) |
| EXPECT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolder( |
| kFolder1Title, ElementsAre(MatchesUrl(kUrl2Title, kUrl2), |
| MatchesUrl(kUrl3Title, kUrl3), |
| MatchesUrl(kUrl1Title, kUrl1))))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, ShouldMergeLocalAndAccountModels) { |
| const std::string kFolder1Title = "folder1"; |
| const std::string kFolder2Title = "folder2"; |
| const std::string kFolder3Title = "folder3"; |
| |
| const std::string kUrl1Title = "url1"; |
| const std::string kUrl2Title = "url2"; |
| const std::string kUrl3Title = "url3"; |
| const std::string kUrl4Title = "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/"); |
| |
| // -------- The local model -------- |
| // 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) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kFolder1Title) |
| .SetChildren({UrlBuilder(kUrl1Title, kUrl1), |
| UrlBuilder(kUrl2Title, kUrl2)}), |
| FolderBuilder(kFolder2Title) |
| .SetChildren({UrlBuilder(kUrl3Title, kUrl3), |
| UrlBuilder(kUrl4Title, kUrl4)})}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- url1(http://www.url1.com) |
| // |- url2(http://www.another-url2.com) |
| // |- folder 3 |
| // |- url3(http://www.url3.com) |
| // |- url4(http://www.url4.com) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kFolder1Title) |
| .SetChildren({UrlBuilder(kUrl1Title, kUrl1), |
| UrlBuilder(kUrl2Title, kAnotherUrl2)}), |
| FolderBuilder(kFolder3Title) |
| .SetChildren({UrlBuilder(kUrl3Title, kUrl3), |
| UrlBuilder(kUrl4Title, kUrl4)})}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- 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 3 |
| // |- url3(http://www.url3.com) |
| // |- url4(http://www.url4.com) |
| // |- folder 2 |
| // |- url3(http://www.url3.com) |
| // |- url4(http://www.url4.com) |
| EXPECT_THAT( |
| account_model->bookmark_bar_node()->children(), |
| ElementsAre( |
| MatchesFolder(kFolder1Title, |
| ElementsAre(MatchesUrl(kUrl1Title, kUrl1), |
| MatchesUrl(kUrl2Title, kAnotherUrl2), |
| MatchesUrl(kUrl2Title, kUrl2))), |
| MatchesFolder(kFolder3Title, |
| ElementsAre(MatchesUrl(kUrl3Title, kUrl3), |
| MatchesUrl(kUrl4Title, kUrl4))), |
| MatchesFolder(kFolder2Title, |
| ElementsAre(MatchesUrl(kUrl3Title, kUrl3), |
| MatchesUrl(kUrl4Title, kUrl4))))); |
| } |
| |
| // This tests that truncated titles produced by legacy clients are properly |
| // matched. |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldMergeLocalAndAccountNodesWhenAccountHasLegacyTruncatedTitle) { |
| const std::string kLocalLongTitle(300, 'A'); |
| const std::string kAccountTruncatedTitle(255, 'A'); |
| |
| // -------- The local model -------- |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kLocalLongTitle)}); |
| |
| // -------- The account model -------- |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kAccountTruncatedTitle)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // Both titles should have matched against each other. |
| EXPECT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolder(kLocalLongTitle, IsEmpty()))); |
| } |
| |
| // This test checks that local node with truncated title will merge with account |
| // node which has full title. |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldMergeLocalAndAccountNodesWhenLocalHasLegacyTruncatedTitle) { |
| const std::string kAccountFullTitle(300, 'A'); |
| const std::string kLocalTruncatedTitle(255, 'A'); |
| |
| // -------- The local model -------- |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kLocalTruncatedTitle)}); |
| |
| // -------- The account model -------- |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kAccountFullTitle)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // 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(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolder(kLocalTruncatedTitle, IsEmpty()))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, ShouldMergeBookmarkByUuid) { |
| const std::string kLocalTitle = "Title 1"; |
| const std::string kAccountTitle = "Title 2"; |
| const GURL kUrl("http://www.foo.com/"); |
| const base::Uuid kUuid = base::Uuid::GenerateRandomV4(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark(kUuid/kLocalTitle) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - bookmark(kUuid/kAccountTitle) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // |- bookmark(kUuid/kLocalTitle) |
| EXPECT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesUrlWithUuid(kLocalTitle, kUrl, kUuid))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldMergeBookmarkByUuidDespiteDifferentParent) { |
| const std::string kFolderTitle = "Folder Title"; |
| const std::string kLocalTitle = "Title 1"; |
| const std::string kAccountTitle = "Title 2"; |
| const GURL kUrl("http://www.foo.com/"); |
| const base::Uuid kUuid = base::Uuid::GenerateRandomV4(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // |- bookmark(kUuid/kLocalTitle) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - folder |
| // | - bookmark(kUuid/kAccountTitle) |
| std::unique_ptr<BookmarkModelView> account_model = BuildModel( |
| {FolderBuilder(kFolderTitle) |
| .SetChildren({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)})}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - folder |
| // | - bookmark(kUuid/kLocalTitle) |
| EXPECT_THAT( |
| account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolder(kFolderTitle, ElementsAre(MatchesUrlWithUuid( |
| kLocalTitle, kUrl, kUuid))))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, ShouldNotMergeBySemanticsIfDifferentParent) { |
| const std::string kFolder1Title = "folder1"; |
| const std::string kFolder2Title = "folder2"; |
| |
| const std::string kUrl1Title = "url1"; |
| const std::string kUrl2Title = "url2"; |
| |
| const GURL kUrl1("http://www.url1.com/"); |
| const GURL kUrl2("http://www.url2.com/"); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- folder 2 |
| // |- url1(http://www.url1.com) |
| std::unique_ptr<BookmarkModelView> local_model = BuildModel( |
| {FolderBuilder(kFolder1Title) |
| .SetChildren({FolderBuilder(kFolder2Title) |
| .SetChildren({UrlBuilder(kUrl1Title, kUrl1)})})}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // |- folder 2 |
| // |- url2(http://www.url2.com) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kFolder2Title) |
| .SetChildren({UrlBuilder(kUrl2Title, kUrl2)})}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // |- folder 2 |
| // |- url2(http://www.url2.com) |
| // |- folder 1 |
| // |- folder 2 |
| // |- url1(http://www.url1.com) |
| EXPECT_THAT( |
| account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolder(kFolder2Title, |
| ElementsAre(MatchesUrl(kUrl2Title, kUrl2))), |
| MatchesFolder(kFolder1Title, |
| ElementsAre(MatchesFolder( |
| kFolder2Title, ElementsAre(MatchesUrl( |
| kUrl1Title, kUrl1))))))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, ShouldMergeFolderByUuidAndNotSemantics) { |
| const std::string kTitle1 = "Title 1"; |
| const std::string kTitle2 = "Title 2"; |
| const GURL kUrl("http://www.foo.com/"); |
| const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4(); |
| const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder 1 (kUuid1/kTitle1) |
| // | - folder 2 (kUuid2/kTitle2) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kTitle1).SetUuid(kUuid1).SetChildren( |
| {FolderBuilder(kTitle2).SetUuid(kUuid2)})}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - folder (kUuid2/kTitle1) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kTitle1).SetUuid(kUuid2)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // 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_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolderWithUuid(kTitle2, kUuid2, IsEmpty()), |
| MatchesFolderWithUuid(kTitle1, kUuid1, IsEmpty()))); |
| } |
| |
| TEST( |
| LocalBookmarkModelMergerTest, |
| ShouldIgnoreFolderSemanticsMatchAndLaterMatchByUuidWithSemanticsNodeFirst) { |
| const std::string kLocalOnlyTitle = "LocalOnlyTitle"; |
| const std::string kMatchingTitle = "MatchingTitle"; |
| const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4(); |
| const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4(); |
| const GURL kUrl("http://foo.com/"); |
| const std::string kUrlTitle = "Bookmark Title"; |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder (kUuid1/kMatchingTitle) |
| // | - folder (kUuid2/kLocalOnlyTitle) |
| // | - bookmark |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kMatchingTitle).SetUuid(kUuid1), |
| FolderBuilder(kLocalOnlyTitle) |
| .SetUuid(kUuid2) |
| .SetChildren({UrlBuilder(kUrlTitle, kUrl)})}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - folder (kUuid2/kMatchingTitle) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kMatchingTitle).SetUuid(kUuid2)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // 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_THAT( |
| account_model->bookmark_bar_node()->children(), |
| ElementsAre( |
| MatchesFolderWithUuid(kLocalOnlyTitle, kUuid2, |
| ElementsAre(MatchesUrl(kUrlTitle, kUrl))), |
| MatchesFolderWithUuid(kMatchingTitle, kUuid1, IsEmpty()))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldIgnoreFolderSemanticsMatchAndLaterMatchByUuidWithUuidNodeFirst) { |
| const std::string kLocalOnlyTitle = "LocalOnlyTitle"; |
| const std::string kMatchingTitle = "MatchingTitle"; |
| const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4(); |
| const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4(); |
| const GURL kUrl("http://foo.com/"); |
| const std::string kUrlTitle = "Bookmark Title"; |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder (kUuid2/kLocalOnlyTitle) |
| // | - bookmark |
| // | - folder (kUuid1/kMatchingTitle) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kLocalOnlyTitle) |
| .SetUuid(kUuid2) |
| .SetChildren({UrlBuilder(kUrlTitle, kUrl)}), |
| FolderBuilder(kMatchingTitle).SetUuid(kUuid1)}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - folder (kUuid2/kMatchingTitle) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kMatchingTitle).SetUuid(kUuid2)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // 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_THAT( |
| account_model->bookmark_bar_node()->children(), |
| ElementsAre( |
| MatchesFolderWithUuid(kLocalOnlyTitle, kUuid2, |
| ElementsAre(MatchesUrl(kUrlTitle, kUrl))), |
| MatchesFolderWithUuid(kMatchingTitle, kUuid1, IsEmpty()))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldReplaceBookmarkUuidWithConflictingURLs) { |
| const std::string kTitle = "Title"; |
| const GURL kUrl1("http://www.foo.com/"); |
| const GURL kUrl2("http://www.bar.com/"); |
| const base::Uuid kUuid = base::Uuid::GenerateRandomV4(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark (kUuid/kUrl1) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({UrlBuilder(kTitle, kUrl1).SetUuid(kUuid)}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - bookmark (kUuid/kUrl2) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({UrlBuilder(kTitle, kUrl2).SetUuid(kUuid)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - bookmark (kUuid/kUrl2) |
| // | - bookmark ([new UUID]/kUrl1) |
| // |
| // The conflicting node UUID should have been replaced. |
| EXPECT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesUrlWithUuid(kTitle, kUrl2, kUuid), |
| MatchesUrlWithUuid(kTitle, kUrl1, Ne(kUuid)))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldReplaceBookmarkUuidWithConflictingTypes) { |
| const GURL kUrl1("http://www.foo.com/"); |
| const std::string kTitle = "Title"; |
| const base::Uuid kUuid = base::Uuid::GenerateRandomV4(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark (kUuid/kUrl1) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({UrlBuilder(kTitle, kUrl1).SetUuid(kUuid)}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - folder(kUuid) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({FolderBuilder(kTitle).SetUuid(kUuid)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - folder (kUuid) |
| // | - bookmark ([new UUID]) |
| // |
| // The conflicting node UUID should have been replaced. |
| EXPECT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesFolderWithUuid(kTitle, kUuid, IsEmpty()), |
| MatchesUrlWithUuid(kTitle, kUrl1, Ne(kUuid)))); |
| } |
| |
| TEST(LocalBookmarkModelMergerTest, |
| ShouldReplaceBookmarkUuidWithConflictingTypesAndLocalChildren) { |
| const std::string kFolderTitle = "Folder Title"; |
| const std::string kUrl1Title = "url1"; |
| const std::string kUrl2Title = "url2"; |
| const GURL kUrl1("http://www.url1.com/"); |
| const GURL kUrl2("http://www.url2.com/"); |
| const base::Uuid kUuid = base::Uuid::GenerateRandomV4(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder (kUuid) |
| // | - bookmark (kUrl1) |
| std::unique_ptr<BookmarkModelView> local_model = |
| BuildModel({FolderBuilder(kFolderTitle) |
| .SetUuid(kUuid) |
| .SetChildren({UrlBuilder(kUrl1Title, kUrl1)})}); |
| |
| // -------- The account model -------- |
| // bookmark_bar |
| // | - bookmark (kUuid/kUrl2) |
| std::unique_ptr<BookmarkModelView> account_model = |
| BuildModel({UrlBuilder(kUrl2Title, kUrl2).SetUuid(kUuid)}); |
| |
| // -------- Exercise the merge logic -------- |
| LocalBookmarkModelMerger(local_model.get(), account_model.get()).Merge(); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - bookmark (kUuid/kUrl2) |
| // | - folder ([new UUID]) |
| // | - bookmark (kUrl1) |
| // |
| // The conflicting node UUID should have been replaced. |
| EXPECT_THAT(account_model->bookmark_bar_node()->children(), |
| ElementsAre(MatchesUrlWithUuid(kUrl2Title, kUrl2, kUuid), |
| MatchesFolderWithUuid( |
| kFolderTitle, Ne(kUuid), |
| ElementsAre(MatchesUrl(kUrl1Title, kUrl1))))); |
| } |
| |
| } // namespace sync_bookmarks |