| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/sync_bookmarks/bookmark_model_merger.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/guid.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/test/test_bookmark_client.h" |
| #include "components/favicon/core/test/mock_favicon_service.h" |
| #include "components/sync/base/unique_position.h" |
| #include "components/sync_bookmarks/bookmark_specifics_conversions.h" |
| #include "components/sync_bookmarks/switches.h" |
| #include "components/sync_bookmarks/synced_bookmark_tracker.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| using testing::Eq; |
| using testing::IsNull; |
| using testing::Ne; |
| using testing::NotNull; |
| using testing::UnorderedElementsAre; |
| |
| namespace sync_bookmarks { |
| |
| namespace { |
| |
| // Copy of BookmarksGUIDDuplicates. |
| enum class ExpectedBookmarksGUIDDuplicates { |
| kMatchingUrls = 0, |
| kMatchingFolders = 1, |
| kDifferentUrls = 2, |
| kDifferentFolders = 3, |
| kDifferentTypes = 4, |
| }; |
| |
| const char kBookmarkBarId[] = "bookmark_bar_id"; |
| const char kBookmarkBarTag[] = "bookmark_bar"; |
| |
| // Fork of enum RemoteBookmarkUpdateError. |
| enum class ExpectedRemoteBookmarkUpdateError { |
| kInvalidSpecifics = 1, |
| kInvalidUniquePosition = 2, |
| kMissingParentEntity = 4, |
| kUnexpectedGuid = 9, |
| kParentNotFolder = 10, |
| kMaxValue = kParentNotFolder, |
| }; |
| |
| // |*arg| must be of type std::vector<std::unique_ptr<bookmarks::BookmarkNode>>. |
| MATCHER_P(ElementRawPointersAre, expected_raw_ptr, "") { |
| if (arg.size() != 1) { |
| return false; |
| } |
| return arg[0].get() == expected_raw_ptr; |
| } |
| |
| // |*arg| must be of type std::vector<std::unique_ptr<bookmarks::BookmarkNode>>. |
| MATCHER_P2(ElementRawPointersAre, expected_raw_ptr0, expected_raw_ptr1, "") { |
| if (arg.size() != 2) { |
| return false; |
| } |
| return arg[0].get() == expected_raw_ptr0 && arg[1].get() == expected_raw_ptr1; |
| } |
| |
| class UpdateResponseDataBuilder { |
| public: |
| UpdateResponseDataBuilder(const std::string& server_id, |
| const std::string& parent_id, |
| const std::string& title, |
| const syncer::UniquePosition& unique_position) { |
| data_.id = server_id; |
| data_.parent_id = parent_id; |
| data_.unique_position = unique_position.ToProto(); |
| data_.is_folder = true; |
| |
| sync_pb::BookmarkSpecifics* bookmark_specifics = |
| data_.specifics.mutable_bookmark(); |
| bookmark_specifics->set_legacy_canonicalized_title(title); |
| bookmark_specifics->set_full_title(title); |
| |
| SetGuid(base::GUID::GenerateRandomV4()); |
| } |
| |
| UpdateResponseDataBuilder& SetUrl(const GURL& url) { |
| data_.is_folder = false; |
| data_.specifics.mutable_bookmark()->set_url(url.spec()); |
| return *this; |
| } |
| |
| UpdateResponseDataBuilder& SetLegacyTitleOnly() { |
| data_.specifics.mutable_bookmark()->clear_full_title(); |
| return *this; |
| } |
| |
| UpdateResponseDataBuilder& SetFavicon(const GURL& favicon_url, |
| const std::string& favicon_data) { |
| data_.specifics.mutable_bookmark()->set_icon_url(favicon_url.spec()); |
| data_.specifics.mutable_bookmark()->set_favicon(favicon_data); |
| return *this; |
| } |
| |
| UpdateResponseDataBuilder& SetGuid(const base::GUID& guid) { |
| const std::string& guid_str = guid.AsLowercaseString(); |
| data_.originator_client_item_id = guid_str; |
| data_.specifics.mutable_bookmark()->set_guid(guid_str); |
| return *this; |
| } |
| |
| syncer::UpdateResponseData Build() { |
| syncer::UpdateResponseData response_data; |
| response_data.entity = std::move(data_); |
| // Similar to what's done in the loopback_server. |
| response_data.response_version = 0; |
| return response_data; |
| } |
| |
| private: |
| syncer::EntityData data_; |
| }; |
| |
| syncer::UpdateResponseData CreateUpdateResponseData( |
| const std::string& server_id, |
| const std::string& parent_id, |
| const std::string& title, |
| const std::string& url, |
| bool is_folder, |
| const syncer::UniquePosition& unique_position, |
| base::Optional<base::GUID> guid = base::nullopt, |
| const std::string& icon_url = std::string(), |
| const std::string& icon_data = std::string()) { |
| UpdateResponseDataBuilder builder(server_id, parent_id, title, |
| unique_position); |
| if (guid) { |
| builder.SetGuid(*guid); |
| } |
| if (!is_folder) { |
| builder.SetUrl(GURL(url)); |
| } |
| builder.SetFavicon(GURL(icon_url), icon_data); |
| |
| return builder.Build(); |
| } |
| |
| syncer::UpdateResponseData CreateBookmarkBarNodeUpdateData() { |
| syncer::EntityData data; |
| data.id = kBookmarkBarId; |
| data.server_defined_unique_tag = kBookmarkBarTag; |
| |
| data.specifics.mutable_bookmark(); |
| |
| syncer::UpdateResponseData response_data; |
| response_data.entity = std::move(data); |
| // Similar to what's done in the loopback_server. |
| response_data.response_version = 0; |
| return response_data; |
| } |
| |
| syncer::UniquePosition PositionOf(const bookmarks::BookmarkNode* node, |
| const SyncedBookmarkTracker& tracker) { |
| const SyncedBookmarkTracker::Entity* entity = |
| tracker.GetEntityForBookmarkNode(node); |
| return syncer::UniquePosition::FromProto( |
| entity->metadata()->unique_position()); |
| } |
| |
| bool PositionsInTrackerMatchModel(const bookmarks::BookmarkNode* node, |
| const SyncedBookmarkTracker& tracker) { |
| if (node->children().empty()) { |
| return true; |
| } |
| syncer::UniquePosition last_pos = |
| PositionOf(node->children().front().get(), tracker); |
| for (size_t i = 1; i < node->children().size(); ++i) { |
| syncer::UniquePosition pos = PositionOf(node->children()[i].get(), tracker); |
| if (pos.LessThan(last_pos)) { |
| DLOG(ERROR) << "Position of " << node->children()[i]->GetTitle() |
| << " is less than position of " |
| << node->children()[i - 1]->GetTitle(); |
| return false; |
| } |
| last_pos = pos; |
| } |
| return std::all_of(node->children().cbegin(), node->children().cend(), |
| [&tracker](const auto& child) { |
| return PositionsInTrackerMatchModel(child.get(), |
| tracker); |
| }); |
| } |
| |
| std::unique_ptr<SyncedBookmarkTracker> Merge( |
| syncer::UpdateResponseDataList updates, |
| bookmarks::BookmarkModel* bookmark_model) { |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState()); |
| testing::NiceMock<favicon::MockFaviconService> favicon_service; |
| BookmarkModelMerger(std::move(updates), bookmark_model, &favicon_service, |
| tracker.get()) |
| .Merge(); |
| return tracker; |
| } |
| |
| static syncer::UniquePosition MakeRandomPosition() { |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| return syncer::UniquePosition::InitialPosition(suffix); |
| } |
| |
| } // namespace |
| |
| TEST(BookmarkModelMergerTest, ShouldMergeLocalAndRemoteModels) { |
| const size_t kMaxEntries = 1000; |
| |
| 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 std::string kUrl1 = "http://www.url1.com"; |
| const std::string kUrl2 = "http://www.url2.com"; |
| const std::string kUrl3 = "http://www.url3.com"; |
| const std::string kUrl4 = "http://www.url4.com"; |
| const std::string kAnotherUrl2 = "http://www.another-url2.com"; |
| |
| const std::string kFolder1Id = "Folder1Id"; |
| const std::string kFolder3Id = "Folder3Id"; |
| const std::string kUrl1Id = "Url1Id"; |
| const std::string kUrl2Id = "Url2Id"; |
| const std::string kUrl3Id = "Url3Id"; |
| const std::string kUrl4Id = "Url4Id"; |
| |
| // -------- 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<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kFolder1Title)); |
| |
| const bookmarks::BookmarkNode* folder2 = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/1, |
| base::UTF8ToUTF16(kFolder2Title)); |
| |
| bookmark_model->AddURL( |
| /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kUrl1Title), |
| GURL(kUrl1)); |
| bookmark_model->AddURL( |
| /*parent=*/folder1, /*index=*/1, base::UTF8ToUTF16(kUrl2Title), |
| GURL(kUrl2)); |
| bookmark_model->AddURL( |
| /*parent=*/folder2, /*index=*/0, base::UTF8ToUTF16(kUrl3Title), |
| GURL(kUrl3)); |
| bookmark_model->AddURL( |
| /*parent=*/folder2, /*index=*/1, base::UTF8ToUTF16(kUrl4Title), |
| GURL(kUrl4)); |
| |
| // -------- The remote 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) |
| |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition posFolder1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition posFolder3 = |
| syncer::UniquePosition::After(posFolder1, suffix); |
| |
| syncer::UniquePosition posUrl1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition posUrl2 = |
| syncer::UniquePosition::After(posUrl1, suffix); |
| |
| syncer::UniquePosition posUrl3 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition posUrl4 = |
| syncer::UniquePosition::After(posUrl3, suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder1)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kUrl1Id, /*parent_id=*/kFolder1Id, kUrl1Title, kUrl1, |
| /*is_folder=*/false, /*unique_position=*/posUrl1)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kUrl2Id, /*parent_id=*/kFolder1Id, kUrl2Title, kAnotherUrl2, |
| /*is_folder=*/false, /*unique_position=*/posUrl2)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder3)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kUrl3Id, /*parent_id=*/kFolder3Id, kUrl3Title, kUrl3, |
| /*is_folder=*/false, /*unique_position=*/posUrl3)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kUrl4Id, /*parent_id=*/kFolder3Id, kUrl4Title, kUrl4, |
| /*is_folder=*/false, /*unique_position=*/posUrl4)); |
| |
| // -------- 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) |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(3u)); |
| |
| // Verify Folder 1. |
| EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder1Title))); |
| ASSERT_THAT(bookmark_bar_node->children()[0]->children().size(), Eq(3u)); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl1Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->url(), |
| Eq(GURL(kUrl1))); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[1]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl2Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[1]->url(), |
| Eq(GURL(kAnotherUrl2))); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[2]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl2Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[2]->url(), |
| Eq(GURL(kUrl2))); |
| |
| // Verify Folder 3. |
| EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder3Title))); |
| ASSERT_THAT(bookmark_bar_node->children()[1]->children().size(), Eq(2u)); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl3Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->url(), |
| Eq(GURL(kUrl3))); |
| EXPECT_THAT(bookmark_bar_node->children()[1]->children()[1]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl4Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[1]->children()[1]->url(), |
| Eq(GURL(kUrl4))); |
| |
| // Verify Folder 2. |
| EXPECT_THAT(bookmark_bar_node->children()[2]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder2Title))); |
| ASSERT_THAT(bookmark_bar_node->children()[2]->children().size(), Eq(2u)); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[2]->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl3Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[2]->children()[0]->url(), |
| Eq(GURL(kUrl3))); |
| EXPECT_THAT(bookmark_bar_node->children()[2]->children()[1]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl4Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[2]->children()[1]->url(), |
| Eq(GURL(kUrl4))); |
| |
| // Verify the tracker contents. |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(11U)); |
| std::vector<const SyncedBookmarkTracker::Entity*> local_changes = |
| tracker->GetEntitiesWithLocalChanges(kMaxEntries); |
| |
| EXPECT_THAT(local_changes.size(), Eq(4U)); |
| std::vector<const bookmarks::BookmarkNode*> nodes_with_local_changes; |
| for (const SyncedBookmarkTracker::Entity* local_change : local_changes) { |
| nodes_with_local_changes.push_back(local_change->bookmark_node()); |
| } |
| // Verify that url2(http://www.url2.com), Folder 2 and children have |
| // corresponding update. |
| EXPECT_THAT(nodes_with_local_changes, |
| UnorderedElementsAre( |
| bookmark_bar_node->children()[0]->children()[2].get(), |
| bookmark_bar_node->children()[2].get(), |
| bookmark_bar_node->children()[2]->children()[0].get(), |
| bookmark_bar_node->children()[2]->children()[1].get())); |
| |
| // Verify positions in tracker. |
| EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker)); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldMergeRemoteReorderToLocalModel) { |
| const size_t kMaxEntries = 1000; |
| |
| const std::string kFolder1Title = "folder1"; |
| const std::string kFolder2Title = "folder2"; |
| const std::string kFolder3Title = "folder3"; |
| |
| const std::string kFolder1Id = "Folder1Id"; |
| const std::string kFolder2Id = "Folder2Id"; |
| const std::string kFolder3Id = "Folder3Id"; |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- folder 2 |
| // |- folder 3 |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kFolder1Title)); |
| |
| bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/1, |
| base::UTF8ToUTF16(kFolder2Title)); |
| |
| bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/2, |
| base::UTF8ToUTF16(kFolder3Title)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- folder 3 |
| // |- folder 2 |
| |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition posFolder1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition posFolder3 = |
| syncer::UniquePosition::After(posFolder1, suffix); |
| syncer::UniquePosition posFolder2 = |
| syncer::UniquePosition::After(posFolder3, suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder1)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder2)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder3)); |
| |
| // -------- The expected merge outcome -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- folder 3 |
| // |- folder 2 |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(3u)); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder1Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder3Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[2]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder2Title))); |
| |
| // Verify the tracker contents. |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(4U)); |
| |
| // There should be no local changes. |
| std::vector<const SyncedBookmarkTracker::Entity*> local_changes = |
| tracker->GetEntitiesWithLocalChanges(kMaxEntries); |
| EXPECT_THAT(local_changes.size(), Eq(0U)); |
| |
| // Verify positions in tracker. |
| EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker)); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldMergeFaviconsForRemoteNodesOnly) { |
| const std::string kTitle1 = "title1"; |
| const GURL kUrl1("http://www.url1.com"); |
| // -------- The local model -------- |
| // bookmark_bar |
| // |- title 1 |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| bookmark_model->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle1), |
| kUrl1); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- title 2 |
| |
| const std::string kTitle2 = "title2"; |
| const std::string kId2 = "Id2"; |
| const GURL kUrl2("http://www.url2.com"); |
| const GURL kIcon2Url("http://www.icon-url.com"); |
| syncer::UniquePosition pos2 = syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId2, /*parent_id=*/kBookmarkBarId, kTitle2, kUrl2.spec(), |
| /*is_folder=*/false, /*unique_position=*/pos2, |
| base::GUID::GenerateRandomV4(), kIcon2Url.spec(), |
| /*icon_data=*/"PNG")); |
| |
| // -------- The expected merge outcome -------- |
| // bookmark_bar |
| // |- title 2 |
| // |- title 1 |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState()); |
| testing::NiceMock<favicon::MockFaviconService> favicon_service; |
| |
| // Favicon should be set for the remote node. |
| EXPECT_CALL(favicon_service, |
| AddPageNoVisitForBookmark(kUrl2, base::UTF8ToUTF16(kTitle2))); |
| EXPECT_CALL(favicon_service, MergeFavicon(kUrl2, _, _, _, _)); |
| |
| BookmarkModelMerger(std::move(updates), bookmark_model.get(), |
| &favicon_service, tracker.get()) |
| .Merge(); |
| } |
| |
| // This tests that canonical titles produced by legacy clients are properly |
| // matched. Legacy clients append blank space to empty titles. |
| TEST(BookmarkModelMergerTest, |
| ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyCanonicalTitle) { |
| const std::string kLocalTitle = ""; |
| const std::string kRemoteTitle = " "; |
| const std::string kId = "Id"; |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kLocalTitle)); |
| ASSERT_TRUE(folder); |
| |
| // -------- The remote model -------- |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(UpdateResponseDataBuilder(/*server_id=*/kId, |
| /*parent_id=*/kBookmarkBarId, |
| kRemoteTitle, |
| /*unique_position=*/pos) |
| .SetLegacyTitleOnly() |
| .Build()); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // Both titles should have matched against each other and only node is in the |
| // model and the tracker. |
| EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U)); |
| } |
| |
| // This tests that truncated titles produced by legacy clients are properly |
| // matched. |
| TEST(BookmarkModelMergerTest, |
| ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyTruncatedTitle) { |
| const std::string kLocalLongTitle = |
| "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst" |
| "uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN" |
| "OPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh" |
| "ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB" |
| "CDEFGHIJKLMNOPQRSTUVWXYZ"; |
| const std::string kRemoteTruncatedTitle = |
| "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst" |
| "uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN" |
| "OPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh" |
| "ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU"; |
| const std::string kId = "Id"; |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kLocalLongTitle)); |
| ASSERT_TRUE(folder); |
| |
| // -------- The remote model -------- |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTruncatedTitle, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/pos)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState()); |
| testing::NiceMock<favicon::MockFaviconService> favicon_service; |
| BookmarkModelMerger(std::move(updates), bookmark_model.get(), |
| &favicon_service, tracker.get()) |
| .Merge(); |
| |
| // Both titles should have matched against each other and only node is in the |
| // model and the tracker. |
| EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U)); |
| } |
| |
| TEST(BookmarkModelMergerTest, |
| ShouldMergeNodesWhenRemoteHasLegacyTruncatedTitleInFullTitle) { |
| const std::string kLocalLongTitle(300, 'A'); |
| const std::string kRemoteTruncatedFullTitle(255, 'A'); |
| const std::string kId = "Id"; |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kLocalLongTitle)); |
| ASSERT_TRUE(folder); |
| |
| // -------- The remote model -------- |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, |
| kRemoteTruncatedFullTitle, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/pos)); |
| |
| updates.back().entity.specifics.mutable_bookmark()->set_full_title( |
| kRemoteTruncatedFullTitle); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState()); |
| testing::NiceMock<favicon::MockFaviconService> favicon_service; |
| BookmarkModelMerger(std::move(updates), bookmark_model.get(), |
| &favicon_service, tracker.get()) |
| .Merge(); |
| |
| // Both titles should have matched against each other and only node is in the |
| // model and the tracker. |
| EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U)); |
| } |
| |
| // This test checks that local node with truncated title will merge with remote |
| // node which has full title. |
| TEST(BookmarkModelMergerTest, |
| ShouldMergeLocalAndRemoteNodesWhenLocalHasLegacyTruncatedTitle) { |
| const std::string kRemoteFullTitle(300, 'A'); |
| const std::string kLocalTruncatedTitle(255, 'A'); |
| const std::string kId = "Id"; |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kLocalTruncatedTitle)); |
| ASSERT_TRUE(folder); |
| |
| // -------- The remote model -------- |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, |
| sync_bookmarks::FullTitleToLegacyCanonicalizedTitle(kRemoteFullTitle), |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/pos)); |
| ASSERT_EQ( |
| kLocalTruncatedTitle, |
| updates.back().entity.specifics.bookmark().legacy_canonicalized_title()); |
| |
| updates.back().entity.specifics.mutable_bookmark()->set_full_title( |
| kRemoteFullTitle); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState()); |
| testing::NiceMock<favicon::MockFaviconService> favicon_service; |
| BookmarkModelMerger(std::move(updates), bookmark_model.get(), |
| &favicon_service, tracker.get()) |
| .Merge(); |
| |
| // Both titles should have matched against each other and only node is in the |
| // model and the tracker. |
| EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U)); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldMergeAndUseRemoteGUID) { |
| const std::string kId = "Id"; |
| const std::string kTitle = "Title"; |
| const base::GUID kRemoteGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle)); |
| ASSERT_TRUE(folder); |
| |
| // -------- The remote model -------- |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kRemoteGuid)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // Node should have been replaced and GUID should be set to that stored in the |
| // specifics. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 1u); |
| const bookmarks::BookmarkNode* bookmark = |
| bookmark_model->bookmark_bar_node()->children()[0].get(); |
| EXPECT_EQ(bookmark->guid(), kRemoteGuid); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull()); |
| } |
| |
| TEST(BookmarkModelMergerTest, |
| ShouldMergeAndKeepOldGUIDWhenRemoteGUIDIsInvalid) { |
| const std::string kId = "Id"; |
| const std::string kTitle = "Title"; |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle)); |
| ASSERT_TRUE(folder); |
| const base::GUID old_guid = folder->guid(); |
| |
| // -------- The remote model -------- |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/std::string(), |
| /*is_folder=*/true, |
| /*unique_position=*/MakeRandomPosition(), base::GUID())); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // Node should not have been replaced and GUID should not have been set to |
| // that stored in the specifics, as it was invalid. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 1u); |
| const bookmarks::BookmarkNode* bookmark = |
| bookmark_model->bookmark_bar_node()->children()[0].get(); |
| EXPECT_EQ(bookmark->guid(), old_guid); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull()); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldMergeBookmarkByGUID) { |
| const std::string kId = "Id"; |
| const std::string kLocalTitle = "Title 1"; |
| const std::string kRemoteTitle = "Title 2"; |
| const std::string kUrl = "http://www.foo.com/"; |
| const base::GUID kGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark(kGuid/kLocalTitle) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kLocalTitle), |
| GURL(kUrl), /*meta_info=*/nullptr, base::Time::Now(), kGuid); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark(kGuid/kRemoteTitle) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTitle, |
| /*url=*/kUrl, |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // |- bookmark(kGuid/kRemoteTitle) |
| |
| // Node should have been merged. |
| EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kRemoteTitle)); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull()); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldMergeBookmarkByGUIDAndReparent) { |
| const std::string kId = "Id"; |
| const std::string kLocalTitle = "Title 1"; |
| const std::string kRemoteTitle = "Title 2"; |
| const std::string kUrl = "http://www.foo.com/"; |
| const base::GUID kGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder |
| // | - bookmark(kGuid) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, u"Folder Title"); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16(kLocalTitle), |
| GURL(kUrl), /*meta_info=*/nullptr, base::Time::Now(), kGuid); |
| ASSERT_TRUE(folder); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder)); |
| ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- bookmark(kGuid) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTitle, |
| /*url=*/kUrl, |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - bookmark(kGuid/kRemoteTitle) |
| // | - folder |
| |
| // Node should have been merged and the local node should have been |
| // reparented. |
| EXPECT_THAT(bookmark_bar_node->children(), |
| ElementRawPointersAre(bookmark, folder)); |
| EXPECT_EQ(folder->children().size(), 0u); |
| EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kRemoteTitle)); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull()); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder), NotNull()); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldMergeFolderByGUIDAndNotSemantics) { |
| const std::string kFolderId = "Folder Id"; |
| const std::string kTitle1 = "Title 1"; |
| const std::string kTitle2 = "Title 2"; |
| const std::string kUrl = "http://www.foo.com/"; |
| const base::GUID kGuid1 = base::GUID::GenerateRandomV4(); |
| const base::GUID kGuid2 = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder 1 (kGuid1/kTitle1) |
| // | - folder 2 (kGuid2/kTitle2) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle1), |
| /*meta_info=*/nullptr, kGuid1); |
| const bookmarks::BookmarkNode* folder2 = bookmark_model->AddFolder( |
| /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kTitle2), |
| /*meta_info=*/nullptr, kGuid2); |
| ASSERT_TRUE(folder1); |
| ASSERT_TRUE(folder2); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder1)); |
| ASSERT_THAT(folder1->children(), ElementRawPointersAre(folder2)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - folder (kGuid2/kTitle1) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| // Add a remote folder to correspond to the local folder by GUID and |
| // semantics. |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolderId, /*parent_id=*/kBookmarkBarId, kTitle1, |
| /*url=*/"", |
| /*is_folder=*/true, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid2)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - folder 2 (kGuid2/kTitle1) |
| // | - folder 1 (kGuid1/kTitle1) |
| |
| // Node should have been merged with its GUID match. |
| EXPECT_THAT(bookmark_bar_node->children(), |
| ElementRawPointersAre(folder2, folder1)); |
| EXPECT_EQ(folder1->guid(), kGuid1); |
| EXPECT_EQ(folder1->GetTitle(), base::UTF8ToUTF16(kTitle1)); |
| EXPECT_EQ(folder1->children().size(), 0u); |
| EXPECT_EQ(folder2->guid(), kGuid2); |
| EXPECT_EQ(folder2->GetTitle(), base::UTF8ToUTF16(kTitle1)); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder1), NotNull()); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder2), NotNull()); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldIgnoreChildrenForNonFolderNodes) { |
| const std::string kParentId = "parent_id"; |
| const std::string kChildId = "child_id"; |
| const std::string kParentTitle = "Parent Title"; |
| const std::string kChildTitle = "Child Title"; |
| const base::GUID kGuid1 = base::GUID::GenerateRandomV4(); |
| const base::GUID kGuid2 = base::GUID::GenerateRandomV4(); |
| const std::string kUrl1 = "http://www.foo.com/"; |
| const std::string kUrl2 = "http://www.bar.com/"; |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid1/kParentTitle, not a folder) |
| // | - bookmark |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| const syncer::UniquePosition pos1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| const syncer::UniquePosition pos2 = |
| syncer::UniquePosition::After(pos1, suffix); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kParentId, /*parent_id=*/kBookmarkBarId, kParentTitle, |
| /*url=*/kUrl1, |
| /*is_folder=*/false, |
| /*unique_position=*/pos1, |
| /*guid=*/kGuid1)); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kChildId, /*parent_id=*/kParentId, kChildTitle, |
| /*url=*/kUrl2, |
| /*is_folder=*/false, |
| /*unique_position=*/pos2, |
| /*guid=*/kGuid2)); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid1/kParentTitle) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| |
| ASSERT_EQ(bookmark_bar_node->children().size(), 1u); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid1); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(), |
| base::UTF8ToUTF16(kParentTitle)); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 0u); |
| EXPECT_EQ(tracker->TrackedEntitiesCountForTest(), 2U); |
| } |
| |
| TEST( |
| BookmarkModelMergerTest, |
| ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithSemanticsNodeFirst) { |
| const std::string kFolderId1 = "Folder Id 1"; |
| const std::string kFolderId2 = "Folder Id 2"; |
| const std::string kOriginalTitle = "Original Title"; |
| const std::string kNewTitle = "New Title"; |
| const base::GUID kGuid1 = base::GUID::GenerateRandomV4(); |
| const base::GUID kGuid2 = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder (kGuid1/kOriginalTitle) |
| // | - bookmark |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kOriginalTitle), /*meta_info=*/nullptr, kGuid1); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/folder, /*index=*/0, u"Bookmark Title", |
| GURL("http://foo.com/")); |
| ASSERT_TRUE(folder); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder)); |
| ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - folder (kGuid2/kOriginalTitle) |
| // | - folder (kGuid1/kNewTitle) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition pos1 = syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition pos2 = syncer::UniquePosition::After(pos1, suffix); |
| |
| // Add a remote folder to correspond to the local folder by semantics and not |
| // GUID. |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolderId1, /*parent_id=*/kBookmarkBarId, kOriginalTitle, |
| /*url=*/"", |
| /*is_folder=*/true, |
| /*unique_position=*/pos1, |
| /*guid=*/kGuid2)); |
| |
| // Add a remote folder to correspond to the local folder by GUID and not |
| // semantics. |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolderId2, /*parent_id=*/kBookmarkBarId, kNewTitle, |
| /*url=*/"", |
| /*is_folder=*/true, |
| /*unique_position=*/pos2, |
| /*guid=*/kGuid1)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - folder (kGuid2/kOriginalTitle) |
| // | - folder (kGuid1/kNewTitle) |
| // | - bookmark |
| |
| // Node should have been merged with its GUID match. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 2u); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid2); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(), |
| base::UTF8ToUTF16(kOriginalTitle)); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 0u); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->guid(), kGuid1); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->GetTitle(), |
| base::UTF8ToUTF16(kNewTitle)); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 1u); |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(4U)); |
| } |
| |
| TEST(BookmarkModelMergerTest, |
| ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithGUIDNodeFirst) { |
| const std::string kFolderId1 = "Folder Id 1"; |
| const std::string kFolderId2 = "Folder Id 2"; |
| const std::string kOriginalTitle = "Original Title"; |
| const std::string kNewTitle = "New Title"; |
| const base::GUID kGuid1 = base::GUID::GenerateRandomV4(); |
| const base::GUID kGuid2 = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder (kGuid1/kOriginalTitle) |
| // | - bookmark |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kOriginalTitle), /*meta_info=*/nullptr, kGuid1); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/folder, /*index=*/0, u"Bookmark Title", |
| GURL("http://foo.com/")); |
| ASSERT_TRUE(folder); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder)); |
| ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - folder (kGuid1/kNewTitle) |
| // | - folder (kGuid2/kOriginalTitle) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition pos1 = syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition pos2 = syncer::UniquePosition::After(pos1, suffix); |
| |
| // Add a remote folder to correspond to the local folder by GUID and not |
| // semantics. |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolderId2, /*parent_id=*/kBookmarkBarId, kNewTitle, |
| /*url=*/"", |
| /*is_folder=*/true, |
| /*unique_position=*/pos1, |
| /*guid=*/kGuid1)); |
| |
| // Add a remote folder to correspond to the local folder by |
| // semantics and not GUID. |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolderId1, /*parent_id=*/kBookmarkBarId, kOriginalTitle, |
| /*url=*/"", |
| /*is_folder=*/true, |
| /*unique_position=*/pos2, |
| /*guid=*/kGuid2)); |
| |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - folder (kGuid1/kNewTitle) |
| // | - folder (kGuid2/kOriginalTitle) |
| |
| // Node should have been merged with its GUID match. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 2u); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid1); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(), |
| base::UTF8ToUTF16(kNewTitle)); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 1u); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->guid(), kGuid2); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->GetTitle(), |
| base::UTF8ToUTF16(kOriginalTitle)); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 0u); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldReplaceBookmarkGUIDWithConflictingURLs) { |
| const std::string kTitle = "Title"; |
| const std::string kUrl1 = "http://www.foo.com/"; |
| const std::string kUrl2 = "http://www.bar.com/"; |
| const base::GUID kGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid/kUril1) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl1), /*meta_info=*/nullptr, base::Time::Now(), kGuid); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid/kUrl2) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( // Remote B |
| /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/kUrl2, |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid)); |
| |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid/kUrl2) |
| // | - bookmark ([new GUID]/kUrl1) |
| |
| // Conflicting node GUID should have been replaced. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 2u); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->url(), kUrl2); |
| EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->url(), kUrl1); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldReplaceBookmarkGUIDWithConflictingTypes) { |
| const std::string kTitle = "Title"; |
| const base::GUID kGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL("http://www.foo.com/"), /*meta_info=*/nullptr, base::Time::Now(), |
| kGuid); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - folder(kGuid) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( // Remote B |
| /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/"", |
| /*is_folder=*/true, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid)); |
| |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - folder (kGuid) |
| // | - bookmark ([new GUID]) |
| |
| // Conflicting node GUID should have been replaced. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 2u); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid); |
| EXPECT_TRUE(bookmark_bar_node->children()[0]->is_folder()); |
| EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid); |
| EXPECT_FALSE(bookmark_bar_node->children()[1]->is_folder()); |
| } |
| |
| TEST(BookmarkModelMergerTest, |
| ShouldReplaceBookmarkGUIDWithConflictingTypesAndLocalChildren) { |
| const base::GUID kGuid1 = base::GUID::GenerateRandomV4(); |
| const base::GUID kGuid2 = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - folder (kGuid1) |
| // | - bookmark (kGuid2) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, u"Folder Title", |
| /*meta_info=*/nullptr, kGuid1); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/folder, /*index=*/0, u"Foo's title", GURL("http://foo.com"), |
| /*meta_info=*/nullptr, base::Time::Now(), kGuid2); |
| ASSERT_TRUE(folder); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder)); |
| ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid1) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, "Bar's title", |
| "http://bar.com/", |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid1)); |
| |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid1) |
| // | - folder ([new GUID]) |
| // | - bookmark (kGuid2) |
| |
| // Conflicting node GUID should have been replaced. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 2u); |
| EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid1); |
| EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid1); |
| EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid2); |
| EXPECT_FALSE(bookmark_bar_node->children()[0]->is_folder()); |
| EXPECT_TRUE(bookmark_bar_node->children()[1]->is_folder()); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 1u); |
| EXPECT_FALSE(bookmark_bar_node->children()[1]->children()[0]->is_folder()); |
| EXPECT_EQ(bookmark_bar_node->children()[1]->children()[0]->guid(), kGuid2); |
| } |
| |
| // Tests that the GUID-based matching algorithm handles well the case where a |
| // local bookmark matches a remote bookmark that is orphan. In this case the |
| // remote node should be ignored and the local bookmark included in the merged |
| // tree. |
| TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteGUIDIfOrphanNode) { |
| const std::string kId = "Id"; |
| const std::string kInexistentParentId = "InexistentParentId"; |
| const std::string kTitle = "Title"; |
| const std::string kUrl = "http://www.foo.com/"; |
| const base::GUID kGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark(kGuid/kTitle) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl), /*meta_info=*/nullptr, base::Time::Now(), kGuid); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // Orphan node: bookmark(kGuid/kTitle) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kInexistentParentId, kTitle, |
| /*url=*/kUrl, |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // |- bookmark(kGuid/kTitle) |
| |
| // The local node should have been tracked. |
| EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kTitle)); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull()); |
| } |
| |
| // Tests that the GUID-based matching algorithm handles well the case where a |
| // local bookmark matches a remote bookmark that contains invalid specifics |
| // (e.g. invalid URL). In this case the remote node should be ignored and the |
| // local bookmark included in the merged tree. |
| TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteGUIDIfInvalidSpecifics) { |
| const std::string kId = "Id"; |
| const std::string kTitle = "Title"; |
| const std::string kLocalUrl = "http://www.foo.com/"; |
| const std::string kInvalidUrl = "invalidurl"; |
| const base::GUID kGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // bookmark_bar |
| // | - bookmark(kGuid/kLocalUrl/kTitle) |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kLocalUrl), /*meta_info=*/nullptr, base::Time::Now(), kGuid); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid/kInvalidURL/kTitle) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/kInvalidUrl, |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/kGuid)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // bookmark_bar |
| // |- bookmark(kGuid/kLocalUrl/kTitle) |
| |
| // The local node should have been tracked. |
| EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| EXPECT_EQ(bookmark->url(), GURL(kLocalUrl)); |
| EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kTitle)); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull()); |
| } |
| |
| // Tests that updates with a GUID that is different to originator client item ID |
| // are ignored. |
| TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteUpdateWithInvalidGUID) { |
| const std::string kId1 = "Id1"; |
| const std::string kId2 = "Id2"; |
| const std::string kTitle1 = "Title1"; |
| const std::string kTitle2 = "Title2"; |
| const std::string kLocalTitle = "LocalTitle"; |
| const std::string kUrl = "http://www.foo.com/"; |
| const base::GUID kGuid = base::GUID::GenerateRandomV4(); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| // | - bookmark(kGuid/kUrl/kLocalTitle) |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kLocalTitle), |
| GURL(kUrl), /*meta_info=*/nullptr, base::Time::Now(), kGuid); |
| ASSERT_TRUE(bookmark); |
| ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark)); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark (kGuid/kUrl/kTitle1) |
| // | - bookmark (kGuid/kUrl/kTitle2) |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition position1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition position2 = |
| syncer::UniquePosition::After(position1, suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId1, /*parent_id=*/kBookmarkBarId, kTitle1, |
| /*url=*/kUrl, |
| /*is_folder=*/false, /*unique_position=*/position1, |
| /*guid=*/kGuid)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kId2, /*parent_id=*/kBookmarkBarId, kTitle2, |
| /*url=*/kUrl, |
| /*is_folder=*/false, /*unique_position=*/position2, |
| /*guid=*/kGuid)); |
| |
| // |originator_client_item_id| cannot itself be duplicated because |
| // ModelTypeWorker guarantees otherwise. |
| updates.back().entity.originator_client_item_id = |
| base::GUID::GenerateRandomV4().AsLowercaseString(); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| // -------- The merged model -------- |
| // | - bookmark (kGuid/kUrl/kTitle1) |
| |
| // The second remote node should have been filtered out. |
| ASSERT_EQ(bookmark_bar_node->children().size(), 1u); |
| const bookmarks::BookmarkNode* merged_bookmark = |
| bookmark_model->bookmark_bar_node()->children()[0].get(); |
| EXPECT_THAT(merged_bookmark->guid(), Eq(kGuid)); |
| EXPECT_THAT(tracker->GetEntityForBookmarkNode(merged_bookmark), NotNull()); |
| } |
| |
| // Regression test for crbug.com/1050776. Verifies that computing the unique |
| // position does not crash when processing local creation of bookmark during |
| // initial merge. |
| TEST(BookmarkModelMergerTest, |
| ShouldProcessLocalCreationWithUntrackedPredecessorNode) { |
| const std::string kFolder1Title = "folder1"; |
| const std::string kFolder2Title = "folder2"; |
| |
| const std::string kUrl1Title = "url1"; |
| const std::string kUrl2Title = "url2"; |
| |
| const std::string kUrl1 = "http://www.url1.com/"; |
| const std::string kUrl2 = "http://www.url2.com/"; |
| |
| const std::string kFolder1Id = "Folder1Id"; |
| const std::string kFolder2Id = "Folder2Id"; |
| const std::string kUrl1Id = "Url1Id"; |
| |
| // It is needed to use at least two folders to reproduce the crash. It is |
| // needed because the bookmarks are processed in the order of remote entities |
| // on the same level of the tree. To start processing of locally created |
| // bookmarks while other remote bookmarks are not processed we need to use at |
| // least one local folder with several urls. |
| // |
| // -------- The local model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- url1(http://www.url1.com) |
| // |- url2(http://www.url2.com) |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kFolder1Title)); |
| const bookmarks::BookmarkNode* folder1_url1_node = bookmark_model->AddURL( |
| /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kUrl1Title), |
| GURL(kUrl1)); |
| bookmark_model->AddURL( |
| /*parent=*/folder1, /*index=*/1, base::UTF8ToUTF16(kUrl2Title), |
| GURL(kUrl2)); |
| |
| // The remote model contains two folders. The first one is the same as in |
| // local model, but it does not contain any urls. The second one has the url1 |
| // from first folder with same GUID. This will cause skip local creation for |
| // |url1| while processing |folder1|. |
| // |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- folder 2 |
| // |- url1(http://www.url1.com) |
| |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| syncer::UniquePosition posFolder1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| syncer::UniquePosition posFolder2 = |
| syncer::UniquePosition::After(posFolder1, suffix); |
| |
| syncer::UniquePosition posUrl1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder1)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder2)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kUrl1Id, /*parent_id=*/kFolder2Id, kUrl1Title, kUrl1, |
| /*is_folder=*/false, /*unique_position=*/posUrl1, |
| folder1_url1_node->guid())); |
| |
| // -------- The expected merge outcome -------- |
| // bookmark_bar |
| // |- folder 1 |
| // |- url2(http://www.url2.com) |
| // |- folder 2 |
| // |- url1(http://www.url1.com) |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(2u)); |
| |
| // Verify Folder 1. |
| EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder1Title))); |
| ASSERT_THAT(bookmark_bar_node->children()[0]->children().size(), Eq(1u)); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl2Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->url(), |
| Eq(GURL(kUrl2))); |
| |
| // Verify Folder 2. |
| EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kFolder2Title))); |
| ASSERT_THAT(bookmark_bar_node->children()[1]->children().size(), Eq(1u)); |
| |
| EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->GetTitle(), |
| Eq(base::ASCIIToUTF16(kUrl1Title))); |
| EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->url(), |
| Eq(GURL(kUrl1))); |
| |
| // Verify the tracker contents. |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(5U)); |
| |
| const size_t kMaxEntries = 1000; |
| std::vector<const SyncedBookmarkTracker::Entity*> local_changes = |
| tracker->GetEntitiesWithLocalChanges(kMaxEntries); |
| |
| ASSERT_THAT(local_changes.size(), Eq(1U)); |
| EXPECT_THAT(local_changes[0]->bookmark_node(), |
| Eq(bookmark_bar_node->children()[0]->children()[0].get())); |
| |
| // Verify positions in tracker. |
| EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker)); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldLogMetricsForInvalidSpecifics) { |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark (<invalid url>) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, "Title", |
| /*url=*/"invalidurl", |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/base::GUID::GenerateRandomV4())); |
| |
| base::HistogramTester histogram_tester; |
| Merge(std::move(updates), bookmark_model.get()); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.ProblematicServerSideBookmarksDuringMerge", |
| /*sample=*/ExpectedRemoteBookmarkUpdateError::kInvalidSpecifics, |
| /*count=*/1); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldLogMetricsForChildrenOfNonFolder) { |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // | - bookmark (url1/Title1) |
| // | - bookmark (url2/Title2) |
| // | - bookmark (url3/Title3) |
| // | - bookmark (url4/Title4) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, "Title1", |
| /*url=*/"http://url1", |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/base::GUID::GenerateRandomV4())); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id2", /*parent_id=*/"Id1", "Title2", |
| /*url=*/"http://url2", |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/base::GUID::GenerateRandomV4())); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id3", /*parent_id=*/"Id1", "Title3", |
| /*url=*/"http://url3", |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/base::GUID::GenerateRandomV4())); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id4", /*parent_id=*/"Id1", "Title4", |
| /*url=*/"http://url4", |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/base::GUID::GenerateRandomV4())); |
| |
| base::HistogramTester histogram_tester; |
| Merge(std::move(updates), bookmark_model.get()); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.ProblematicServerSideBookmarksDuringMerge", |
| /*sample=*/ExpectedRemoteBookmarkUpdateError::kParentNotFolder, |
| /*count=*/3); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldLogMetricsForChildrenOfOrphanUpdates) { |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The remote model -------- |
| // bookmark_bar |
| // Orphan node: bookmark(url1/title1) |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id", /*parent_id=*/"UnknownId", "Title1", |
| /*url=*/"http://url1", |
| /*is_folder=*/false, |
| /*unique_position=*/MakeRandomPosition(), |
| /*guid=*/base::GUID::GenerateRandomV4())); |
| |
| base::HistogramTester histogram_tester; |
| Merge(std::move(updates), bookmark_model.get()); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.ProblematicServerSideBookmarksDuringMerge", |
| /*sample=*/ExpectedRemoteBookmarkUpdateError::kMissingParentEntity, |
| /*count=*/1); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldRemoveMatchingDuplicatesByGUID) { |
| const std::string kTitle1 = "Title 1"; |
| const std::string kTitle2 = "Title 2"; |
| const std::string kUrl = "http://www.url.com/"; |
| |
| const base::GUID kUrlGUID = base::GUID::GenerateRandomV4(); |
| |
| // The remote model has 2 duplicate folders with the same title and 2 |
| // duplicate bookmarks with the same URL. |
| // |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- url1(http://www.url.com, UrlGUID) |
| // |- url2(http://www.url.com, UrlGUID) |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle1, |
| /*url=*/kUrl, |
| /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID)); |
| updates.back().entity.creation_time = |
| base::Time::Now() - base::TimeDelta::FromDays(1); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle2, |
| /*url=*/kUrl, |
| /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID)); |
| updates.back().entity.creation_time = base::Time::Now(); |
| |
| base::HistogramTester histogram_tester; |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| histogram_tester.ExpectBucketCount( |
| "Sync.BookmarksGUIDDuplicates", |
| /*sample=*/ExpectedBookmarksGUIDDuplicates::kMatchingUrls, /*count=*/1); |
| EXPECT_EQ(bookmark_bar_node->children().front()->GetTitle(), |
| base::UTF8ToUTF16(kTitle2)); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldRemoveDifferentDuplicatesByGUID) { |
| const std::string kTitle1 = "Title 1"; |
| const std::string kTitle2 = "Title 2"; |
| const std::string kUrl = "http://www.url.com/"; |
| const std::string kDifferentUrl = "http://www.different-url.com/"; |
| |
| const base::GUID kUrlGUID = base::GUID::GenerateRandomV4(); |
| |
| // The remote model will have 2 duplicate folders with |
| // different titles and 2 duplicate bookmarks with different URLs |
| // |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- url1(http://www.url.com, UrlGUID) |
| // |- url2(http://www.different-url.com, UrlGUID) |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle1, |
| /*url=*/kUrl, |
| /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID)); |
| updates.back().entity.creation_time = base::Time::Now(); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle2, |
| /*url=*/kDifferentUrl, |
| /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID)); |
| updates.back().entity.creation_time = |
| base::Time::Now() - base::TimeDelta::FromDays(1); |
| |
| base::HistogramTester histogram_tester; |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| histogram_tester.ExpectBucketCount( |
| "Sync.BookmarksGUIDDuplicates", |
| /*sample=*/ExpectedBookmarksGUIDDuplicates::kDifferentUrls, /*count=*/1); |
| EXPECT_EQ(bookmark_bar_node->children().front()->GetTitle(), |
| base::UTF8ToUTF16(kTitle1)); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldRemoveMatchingFolderDuplicatesByGUID) { |
| const std::string kTitle = "Title"; |
| |
| const base::GUID kGUID = base::GUID::GenerateRandomV4(); |
| |
| // The remote model has 2 duplicate folders with the same title and 2 |
| // duplicate bookmarks with the same URL. |
| // |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- folder1(Title, GUID) |
| // |- folder2(Title, GUID) |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/"", |
| /*is_folder=*/true, /*unique_position=*/MakeRandomPosition(), kGUID)); |
| updates.back().entity.creation_time = |
| base::Time::Now() - base::TimeDelta::FromDays(1); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/"", |
| /*is_folder=*/true, /*unique_position=*/MakeRandomPosition(), kGUID)); |
| updates.back().entity.creation_time = base::Time::Now(); |
| |
| base::HistogramTester histogram_tester; |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| histogram_tester.ExpectBucketCount( |
| "Sync.BookmarksGUIDDuplicates", |
| /*sample=*/ExpectedBookmarksGUIDDuplicates::kMatchingFolders, |
| /*count=*/1); |
| EXPECT_THAT(tracker->GetEntityForSyncId("Id1"), IsNull()); |
| EXPECT_THAT(tracker->GetEntityForSyncId("Id2"), NotNull()); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldRemoveDifferentFolderDuplicatesByGUID) { |
| const std::string kTitle1 = "Title 1"; |
| const std::string kTitle2 = "Title 2"; |
| |
| const base::GUID kGUID = base::GUID::GenerateRandomV4(); |
| |
| // The remote model has 2 duplicate folders with the same title and 2 |
| // duplicate bookmarks with the same URL. |
| // |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- folder1(Title, GUID) |
| // |- folder11 |
| // |- folder2(Title, GUID) |
| // |- folder21 |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle1, |
| /*url=*/"", |
| /*is_folder=*/true, MakeRandomPosition(), kGUID)); |
| updates.back().entity.creation_time = base::Time::Now(); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id11", /*parent_id=*/"Id1", "Some title", |
| /*url=*/"", /*is_folder=*/true, MakeRandomPosition())); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle2, |
| /*url=*/"", /*is_folder=*/true, MakeRandomPosition(), kGUID)); |
| updates.back().entity.creation_time = |
| base::Time::Now() - base::TimeDelta::FromDays(1); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id21", /*parent_id=*/"Id2", "Some title 2", |
| /*url=*/"", /*is_folder=*/true, MakeRandomPosition())); |
| |
| base::HistogramTester histogram_tester; |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| histogram_tester.ExpectBucketCount( |
| "Sync.BookmarksGUIDDuplicates", |
| /*sample=*/ExpectedBookmarksGUIDDuplicates::kDifferentFolders, |
| /*count=*/1); |
| EXPECT_THAT(tracker->GetEntityForSyncId("Id1"), NotNull()); |
| EXPECT_THAT(tracker->GetEntityForSyncId("Id2"), IsNull()); |
| EXPECT_EQ(bookmark_bar_node->children().front()->GetTitle(), |
| base::UTF8ToUTF16(kTitle1)); |
| EXPECT_EQ(bookmark_bar_node->children().front()->children().size(), 2u); |
| } |
| |
| // This tests ensures maximum depth of the bookmark tree is not exceeded. This |
| // prevents a stack overflow. |
| TEST(BookmarkModelMergerTest, ShouldEnsureLimitDepthOfTree) { |
| const std::string kLocalTitle = "local"; |
| const std::string kRemoteTitle = "remote"; |
| const std::string folderIdPrefix = "folder_"; |
| // Maximum depth to sync bookmarks tree to protect against stack overflow. |
| // This matches |kMaxBookmarkTreeDepth| in bookmark_model_merger.cc. |
| const size_t kMaxBookmarkTreeDepth = 200; |
| const size_t kRemoteUpdatesDepth = kMaxBookmarkTreeDepth + 10; |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The local model -------- |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder( |
| /*parent=*/bookmark_bar_node, /*index=*/0, |
| base::UTF8ToUTF16(kLocalTitle)); |
| ASSERT_TRUE(folder); |
| |
| // -------- The remote model -------- |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| std::string parent_id = kBookmarkBarId; |
| // Create a tree with depth |kRemoteUpdatesDepth| to verify the limit of |
| // kMaxBookmarkTreeDepth is enforced. |
| for (size_t i = 1; i < kRemoteUpdatesDepth; ++i) { |
| std::string folder_id = folderIdPrefix + base::NumberToString(i); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/folder_id, /*parent_id=*/parent_id, kRemoteTitle, |
| /*url=*/"", |
| /*is_folder=*/true, MakeRandomPosition())); |
| parent_id = folder_id; |
| } |
| |
| ASSERT_THAT(updates.size(), Eq(kRemoteUpdatesDepth)); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState()); |
| testing::NiceMock<favicon::MockFaviconService> favicon_service; |
| BookmarkModelMerger(std::move(updates), bookmark_model.get(), |
| &favicon_service, tracker.get()) |
| .Merge(); |
| |
| // Check max depth hasn't been exceeded. Take into account root of the |
| // tracker and bookmark bar. |
| EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), |
| Eq(kMaxBookmarkTreeDepth + 2)); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldReuploadBookmarkOnEmptyGuid) { |
| base::test::ScopedFeatureList override_features; |
| override_features.InitAndEnableFeature( |
| switches::kSyncReuploadBookmarkFullTitles); |
| |
| const std::string kFolder1Title = "folder1"; |
| const std::string kFolder2Title = "folder2"; |
| |
| const std::string kFolder1Id = "Folder1Id"; |
| const std::string kFolder2Id = "Folder2Id"; |
| |
| const std::string suffix = syncer::UniquePosition::RandomSuffix(); |
| const syncer::UniquePosition posFolder1 = |
| syncer::UniquePosition::InitialPosition(suffix); |
| const syncer::UniquePosition posFolder2 = |
| syncer::UniquePosition::After(posFolder1, suffix); |
| |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| // -------- The remote model -------- |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder1, |
| base::GUID::GenerateRandomV4())); |
| |
| // Mimic that the entity didn't have GUID in specifics. This entity should be |
| // reuploaded later. |
| updates.back().entity.is_bookmark_guid_in_specifics_preprocessed = true; |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title, |
| /*url=*/std::string(), |
| /*is_folder=*/true, /*unique_position=*/posFolder2, |
| base::GUID::GenerateRandomV4())); |
| |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| |
| ASSERT_THAT(tracker->GetEntityForSyncId(kFolder1Id), NotNull()); |
| ASSERT_THAT(tracker->GetEntityForSyncId(kFolder2Id), NotNull()); |
| |
| EXPECT_TRUE(tracker->GetEntityForSyncId(kFolder1Id)->IsUnsynced()); |
| EXPECT_FALSE(tracker->GetEntityForSyncId(kFolder2Id)->IsUnsynced()); |
| } |
| |
| TEST(BookmarkModelMergerTest, ShouldRemoveDifferentTypeDuplicatesByGUID) { |
| const std::string kTitle = "Title"; |
| |
| const base::GUID kGUID = base::GUID::GenerateRandomV4(); |
| |
| // The remote model has 2 duplicates, a folder and a URL. |
| // |
| // -------- The remote model -------- |
| // bookmark_bar |
| // |- folder1(GUID) |
| // |- folder11 |
| // |- URL1(GUID) |
| std::unique_ptr<bookmarks::BookmarkModel> bookmark_model = |
| bookmarks::TestBookmarkClient::CreateModel(); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateBookmarkBarNodeUpdateData()); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/"", |
| /*is_folder=*/true, MakeRandomPosition(), kGUID)); |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id11", /*parent_id=*/"Id1", "Some title", |
| /*url=*/"", /*is_folder=*/true, MakeRandomPosition())); |
| |
| updates.push_back(CreateUpdateResponseData( |
| /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle, |
| /*url=*/"http://url1.com", /*is_folder=*/false, MakeRandomPosition(), |
| kGUID)); |
| |
| base::HistogramTester histogram_tester; |
| std::unique_ptr<SyncedBookmarkTracker> tracker = |
| Merge(std::move(updates), bookmark_model.get()); |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model->bookmark_bar_node(); |
| ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u)); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.BookmarksGUIDDuplicates", |
| /*sample=*/ExpectedBookmarksGUIDDuplicates::kDifferentTypes, |
| /*count=*/1); |
| EXPECT_THAT(tracker->GetEntityForSyncId("Id1"), NotNull()); |
| EXPECT_THAT(tracker->GetEntityForSyncId("Id2"), IsNull()); |
| EXPECT_EQ(bookmark_bar_node->children().front()->children().size(), 1u); |
| } |
| |
| } // namespace sync_bookmarks |