1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/sync_bookmarks/bookmark_model_merger.h"
6 
7 #include <memory>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/guid.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/test/metrics/histogram_tester.h"
16 #include "base/test/scoped_feature_list.h"
17 #include "components/bookmarks/browser/bookmark_model.h"
18 #include "components/bookmarks/test/test_bookmark_client.h"
19 #include "components/favicon/core/test/mock_favicon_service.h"
20 #include "components/sync/base/unique_position.h"
21 #include "components/sync_bookmarks/bookmark_specifics_conversions.h"
22 #include "components/sync_bookmarks/switches.h"
23 #include "components/sync_bookmarks/synced_bookmark_tracker.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 
27 using testing::_;
28 using testing::Eq;
29 using testing::IsNull;
30 using testing::Ne;
31 using testing::NotNull;
32 using testing::UnorderedElementsAre;
33 
34 namespace sync_bookmarks {
35 
36 namespace {
37 
38 // Copy of BookmarksGUIDDuplicates.
39 enum class ExpectedBookmarksGUIDDuplicates {
40   kMatchingUrls = 0,
41   kMatchingFolders = 1,
42   kDifferentUrls = 2,
43   kDifferentFolders = 3,
44   kDifferentTypes = 4,
45 };
46 
47 const char kBookmarkBarId[] = "bookmark_bar_id";
48 const char kBookmarkBarTag[] = "bookmark_bar";
49 
50 // Fork of enum RemoteBookmarkUpdateError.
51 enum class ExpectedRemoteBookmarkUpdateError {
52   kInvalidSpecifics = 1,
53   kInvalidUniquePosition = 2,
54   kMissingParentEntity = 4,
55   kUnexpectedGuid = 9,
56   kParentNotFolder = 10,
57   kMaxValue = kParentNotFolder,
58 };
59 
60 // |*arg| must be of type std::vector<std::unique_ptr<bookmarks::BookmarkNode>>.
61 MATCHER_P(ElementRawPointersAre, expected_raw_ptr, "") {
62   if (arg.size() != 1) {
63     return false;
64   }
65   return arg[0].get() == expected_raw_ptr;
66 }
67 
68 // |*arg| must be of type std::vector<std::unique_ptr<bookmarks::BookmarkNode>>.
69 MATCHER_P2(ElementRawPointersAre, expected_raw_ptr0, expected_raw_ptr1, "") {
70   if (arg.size() != 2) {
71     return false;
72   }
73   return arg[0].get() == expected_raw_ptr0 && arg[1].get() == expected_raw_ptr1;
74 }
75 
76 class UpdateResponseDataBuilder {
77  public:
UpdateResponseDataBuilder(const std::string & server_id,const std::string & parent_id,const std::string & title,const syncer::UniquePosition & unique_position)78   UpdateResponseDataBuilder(const std::string& server_id,
79                             const std::string& parent_id,
80                             const std::string& title,
81                             const syncer::UniquePosition& unique_position) {
82     data_.id = server_id;
83     data_.parent_id = parent_id;
84     data_.unique_position = unique_position.ToProto();
85     data_.is_folder = true;
86 
87     sync_pb::BookmarkSpecifics* bookmark_specifics =
88         data_.specifics.mutable_bookmark();
89     bookmark_specifics->set_legacy_canonicalized_title(title);
90     bookmark_specifics->set_full_title(title);
91 
92     SetGuid(base::GenerateGUID());
93   }
94 
SetUrl(const GURL & url)95   UpdateResponseDataBuilder& SetUrl(const GURL& url) {
96     data_.is_folder = false;
97     data_.specifics.mutable_bookmark()->set_url(url.spec());
98     return *this;
99   }
100 
SetLegacyTitleOnly()101   UpdateResponseDataBuilder& SetLegacyTitleOnly() {
102     data_.specifics.mutable_bookmark()->clear_full_title();
103     return *this;
104   }
105 
SetFavicon(const GURL & favicon_url,const std::string & favicon_data)106   UpdateResponseDataBuilder& SetFavicon(const GURL& favicon_url,
107                                         const std::string& favicon_data) {
108     data_.specifics.mutable_bookmark()->set_icon_url(favicon_url.spec());
109     data_.specifics.mutable_bookmark()->set_favicon(favicon_data);
110     return *this;
111   }
112 
SetGuid(const std::string & guid)113   UpdateResponseDataBuilder& SetGuid(const std::string& guid) {
114     data_.originator_client_item_id = guid;
115     data_.specifics.mutable_bookmark()->set_guid(guid);
116     return *this;
117   }
118 
Build()119   syncer::UpdateResponseData Build() {
120     syncer::UpdateResponseData response_data;
121     response_data.entity = std::move(data_);
122     // Similar to what's done in the loopback_server.
123     response_data.response_version = 0;
124     return response_data;
125   }
126 
127  private:
128   syncer::EntityData data_;
129 };
130 
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<std::string> guid=base::nullopt,const std::string & icon_url=std::string (),const std::string & icon_data=std::string ())131 syncer::UpdateResponseData CreateUpdateResponseData(
132     const std::string& server_id,
133     const std::string& parent_id,
134     const std::string& title,
135     const std::string& url,
136     bool is_folder,
137     const syncer::UniquePosition& unique_position,
138     base::Optional<std::string> guid = base::nullopt,
139     const std::string& icon_url = std::string(),
140     const std::string& icon_data = std::string()) {
141   UpdateResponseDataBuilder builder(server_id, parent_id, title,
142                                     unique_position);
143   if (guid) {
144     builder.SetGuid(*guid);
145   }
146   if (!is_folder) {
147     builder.SetUrl(GURL(url));
148   }
149   builder.SetFavicon(GURL(icon_url), icon_data);
150 
151   return builder.Build();
152 }
153 
CreateBookmarkBarNodeUpdateData()154 syncer::UpdateResponseData CreateBookmarkBarNodeUpdateData() {
155   syncer::EntityData data;
156   data.id = kBookmarkBarId;
157   data.server_defined_unique_tag = kBookmarkBarTag;
158 
159   data.specifics.mutable_bookmark();
160 
161   syncer::UpdateResponseData response_data;
162   response_data.entity = std::move(data);
163   // Similar to what's done in the loopback_server.
164   response_data.response_version = 0;
165   return response_data;
166 }
167 
PositionOf(const bookmarks::BookmarkNode * node,const SyncedBookmarkTracker & tracker)168 syncer::UniquePosition PositionOf(const bookmarks::BookmarkNode* node,
169                                   const SyncedBookmarkTracker& tracker) {
170   const SyncedBookmarkTracker::Entity* entity =
171       tracker.GetEntityForBookmarkNode(node);
172   return syncer::UniquePosition::FromProto(
173       entity->metadata()->unique_position());
174 }
175 
PositionsInTrackerMatchModel(const bookmarks::BookmarkNode * node,const SyncedBookmarkTracker & tracker)176 bool PositionsInTrackerMatchModel(const bookmarks::BookmarkNode* node,
177                                   const SyncedBookmarkTracker& tracker) {
178   if (node->children().empty()) {
179     return true;
180   }
181   syncer::UniquePosition last_pos =
182       PositionOf(node->children().front().get(), tracker);
183   for (size_t i = 1; i < node->children().size(); ++i) {
184     syncer::UniquePosition pos = PositionOf(node->children()[i].get(), tracker);
185     if (pos.LessThan(last_pos)) {
186       DLOG(ERROR) << "Position of " << node->children()[i]->GetTitle()
187                   << " is less than position of "
188                   << node->children()[i - 1]->GetTitle();
189       return false;
190     }
191     last_pos = pos;
192   }
193   return std::all_of(node->children().cbegin(), node->children().cend(),
194                      [&tracker](const auto& child) {
195                        return PositionsInTrackerMatchModel(child.get(),
196                                                            tracker);
197                      });
198 }
199 
Merge(syncer::UpdateResponseDataList updates,bookmarks::BookmarkModel * bookmark_model)200 std::unique_ptr<SyncedBookmarkTracker> Merge(
201     syncer::UpdateResponseDataList updates,
202     bookmarks::BookmarkModel* bookmark_model) {
203   std::unique_ptr<SyncedBookmarkTracker> tracker =
204       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
205   testing::NiceMock<favicon::MockFaviconService> favicon_service;
206   BookmarkModelMerger(std::move(updates), bookmark_model, &favicon_service,
207                       tracker.get())
208       .Merge();
209   return tracker;
210 }
211 
MakeRandomPosition()212 static syncer::UniquePosition MakeRandomPosition() {
213   const std::string suffix = syncer::UniquePosition::RandomSuffix();
214   return syncer::UniquePosition::InitialPosition(suffix);
215 }
216 
217 }  // namespace
218 
219 // TODO(crbug.com/978430): Parameterize unit tests to account for both
220 // GUID-based and original merge algorithms.
221 
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteModels)222 TEST(BookmarkModelMergerTest, ShouldMergeLocalAndRemoteModels) {
223   const size_t kMaxEntries = 1000;
224 
225   const std::string kFolder1Title = "folder1";
226   const std::string kFolder2Title = "folder2";
227   const std::string kFolder3Title = "folder3";
228 
229   const std::string kUrl1Title = "url1";
230   const std::string kUrl2Title = "url2";
231   const std::string kUrl3Title = "url3";
232   const std::string kUrl4Title = "url4";
233 
234   const std::string kUrl1 = "http://www.url1.com";
235   const std::string kUrl2 = "http://www.url2.com";
236   const std::string kUrl3 = "http://www.url3.com";
237   const std::string kUrl4 = "http://www.url4.com";
238   const std::string kAnotherUrl2 = "http://www.another-url2.com";
239 
240   const std::string kFolder1Id = "Folder1Id";
241   const std::string kFolder3Id = "Folder3Id";
242   const std::string kUrl1Id = "Url1Id";
243   const std::string kUrl2Id = "Url2Id";
244   const std::string kUrl3Id = "Url3Id";
245   const std::string kUrl4Id = "Url4Id";
246 
247   // -------- The local model --------
248   // bookmark_bar
249   //  |- folder 1
250   //    |- url1(http://www.url1.com)
251   //    |- url2(http://www.url2.com)
252   //  |- folder 2
253   //    |- url3(http://www.url3.com)
254   //    |- url4(http://www.url4.com)
255 
256   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
257       bookmarks::TestBookmarkClient::CreateModel();
258 
259   const bookmarks::BookmarkNode* bookmark_bar_node =
260       bookmark_model->bookmark_bar_node();
261   const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder(
262       /*parent=*/bookmark_bar_node, /*index=*/0,
263       base::UTF8ToUTF16(kFolder1Title));
264 
265   const bookmarks::BookmarkNode* folder2 = bookmark_model->AddFolder(
266       /*parent=*/bookmark_bar_node, /*index=*/1,
267       base::UTF8ToUTF16(kFolder2Title));
268 
269   bookmark_model->AddURL(
270       /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kUrl1Title),
271       GURL(kUrl1));
272   bookmark_model->AddURL(
273       /*parent=*/folder1, /*index=*/1, base::UTF8ToUTF16(kUrl2Title),
274       GURL(kUrl2));
275   bookmark_model->AddURL(
276       /*parent=*/folder2, /*index=*/0, base::UTF8ToUTF16(kUrl3Title),
277       GURL(kUrl3));
278   bookmark_model->AddURL(
279       /*parent=*/folder2, /*index=*/1, base::UTF8ToUTF16(kUrl4Title),
280       GURL(kUrl4));
281 
282   // -------- The remote model --------
283   // bookmark_bar
284   //  |- folder 1
285   //    |- url1(http://www.url1.com)
286   //    |- url2(http://www.another-url2.com)
287   //  |- folder 3
288   //    |- url3(http://www.url3.com)
289   //    |- url4(http://www.url4.com)
290 
291   const std::string suffix = syncer::UniquePosition::RandomSuffix();
292   syncer::UniquePosition posFolder1 =
293       syncer::UniquePosition::InitialPosition(suffix);
294   syncer::UniquePosition posFolder3 =
295       syncer::UniquePosition::After(posFolder1, suffix);
296 
297   syncer::UniquePosition posUrl1 =
298       syncer::UniquePosition::InitialPosition(suffix);
299   syncer::UniquePosition posUrl2 =
300       syncer::UniquePosition::After(posUrl1, suffix);
301 
302   syncer::UniquePosition posUrl3 =
303       syncer::UniquePosition::InitialPosition(suffix);
304   syncer::UniquePosition posUrl4 =
305       syncer::UniquePosition::After(posUrl3, suffix);
306 
307   syncer::UpdateResponseDataList updates;
308   updates.push_back(CreateBookmarkBarNodeUpdateData());
309   updates.push_back(CreateUpdateResponseData(
310       /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
311       /*url=*/std::string(),
312       /*is_folder=*/true, /*unique_position=*/posFolder1));
313   updates.push_back(CreateUpdateResponseData(
314       /*server_id=*/kUrl1Id, /*parent_id=*/kFolder1Id, kUrl1Title, kUrl1,
315       /*is_folder=*/false, /*unique_position=*/posUrl1));
316   updates.push_back(CreateUpdateResponseData(
317       /*server_id=*/kUrl2Id, /*parent_id=*/kFolder1Id, kUrl2Title, kAnotherUrl2,
318       /*is_folder=*/false, /*unique_position=*/posUrl2));
319   updates.push_back(CreateUpdateResponseData(
320       /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title,
321       /*url=*/std::string(),
322       /*is_folder=*/true, /*unique_position=*/posFolder3));
323   updates.push_back(CreateUpdateResponseData(
324       /*server_id=*/kUrl3Id, /*parent_id=*/kFolder3Id, kUrl3Title, kUrl3,
325       /*is_folder=*/false, /*unique_position=*/posUrl3));
326   updates.push_back(CreateUpdateResponseData(
327       /*server_id=*/kUrl4Id, /*parent_id=*/kFolder3Id, kUrl4Title, kUrl4,
328       /*is_folder=*/false, /*unique_position=*/posUrl4));
329 
330   // -------- The expected merge outcome --------
331   // bookmark_bar
332   //  |- folder 1
333   //    |- url1(http://www.url1.com)
334   //    |- url2(http://www.another-url2.com)
335   //    |- url2(http://www.url2.com)
336   //  |- folder 3
337   //    |- url3(http://www.url3.com)
338   //    |- url4(http://www.url4.com)
339   //  |- folder 2
340   //    |- url3(http://www.url3.com)
341   //    |- url4(http://www.url4.com)
342 
343   std::unique_ptr<SyncedBookmarkTracker> tracker =
344       Merge(std::move(updates), bookmark_model.get());
345   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(3u));
346 
347   // Verify Folder 1.
348   EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(),
349               Eq(base::ASCIIToUTF16(kFolder1Title)));
350   ASSERT_THAT(bookmark_bar_node->children()[0]->children().size(), Eq(3u));
351 
352   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->GetTitle(),
353               Eq(base::ASCIIToUTF16(kUrl1Title)));
354   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->url(),
355               Eq(GURL(kUrl1)));
356 
357   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[1]->GetTitle(),
358               Eq(base::ASCIIToUTF16(kUrl2Title)));
359   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[1]->url(),
360               Eq(GURL(kAnotherUrl2)));
361 
362   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[2]->GetTitle(),
363               Eq(base::ASCIIToUTF16(kUrl2Title)));
364   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[2]->url(),
365               Eq(GURL(kUrl2)));
366 
367   // Verify Folder 3.
368   EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(),
369               Eq(base::ASCIIToUTF16(kFolder3Title)));
370   ASSERT_THAT(bookmark_bar_node->children()[1]->children().size(), Eq(2u));
371 
372   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->GetTitle(),
373               Eq(base::ASCIIToUTF16(kUrl3Title)));
374   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->url(),
375               Eq(GURL(kUrl3)));
376   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[1]->GetTitle(),
377               Eq(base::ASCIIToUTF16(kUrl4Title)));
378   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[1]->url(),
379               Eq(GURL(kUrl4)));
380 
381   // Verify Folder 2.
382   EXPECT_THAT(bookmark_bar_node->children()[2]->GetTitle(),
383               Eq(base::ASCIIToUTF16(kFolder2Title)));
384   ASSERT_THAT(bookmark_bar_node->children()[2]->children().size(), Eq(2u));
385 
386   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[0]->GetTitle(),
387               Eq(base::ASCIIToUTF16(kUrl3Title)));
388   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[0]->url(),
389               Eq(GURL(kUrl3)));
390   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[1]->GetTitle(),
391               Eq(base::ASCIIToUTF16(kUrl4Title)));
392   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[1]->url(),
393               Eq(GURL(kUrl4)));
394 
395   // Verify the tracker contents.
396   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(11U));
397   std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
398       tracker->GetEntitiesWithLocalChanges(kMaxEntries);
399 
400   EXPECT_THAT(local_changes.size(), Eq(4U));
401   std::vector<const bookmarks::BookmarkNode*> nodes_with_local_changes;
402   for (const SyncedBookmarkTracker::Entity* local_change : local_changes) {
403     nodes_with_local_changes.push_back(local_change->bookmark_node());
404   }
405   // Verify that url2(http://www.url2.com), Folder 2 and children have
406   // corresponding update.
407   EXPECT_THAT(nodes_with_local_changes,
408               UnorderedElementsAre(
409                   bookmark_bar_node->children()[0]->children()[2].get(),
410                   bookmark_bar_node->children()[2].get(),
411                   bookmark_bar_node->children()[2]->children()[0].get(),
412                   bookmark_bar_node->children()[2]->children()[1].get()));
413 
414   // Verify positions in tracker.
415   EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker));
416 }
417 
TEST(BookmarkModelMergerTest,ShouldMergeRemoteReorderToLocalModel)418 TEST(BookmarkModelMergerTest, ShouldMergeRemoteReorderToLocalModel) {
419   const size_t kMaxEntries = 1000;
420 
421   const std::string kFolder1Title = "folder1";
422   const std::string kFolder2Title = "folder2";
423   const std::string kFolder3Title = "folder3";
424 
425   const std::string kFolder1Id = "Folder1Id";
426   const std::string kFolder2Id = "Folder2Id";
427   const std::string kFolder3Id = "Folder3Id";
428 
429   // -------- The local model --------
430   // bookmark_bar
431   //  |- folder 1
432   //  |- folder 2
433   //  |- folder 3
434 
435   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
436       bookmarks::TestBookmarkClient::CreateModel();
437 
438   const bookmarks::BookmarkNode* bookmark_bar_node =
439       bookmark_model->bookmark_bar_node();
440   bookmark_model->AddFolder(
441       /*parent=*/bookmark_bar_node, /*index=*/0,
442       base::UTF8ToUTF16(kFolder1Title));
443 
444   bookmark_model->AddFolder(
445       /*parent=*/bookmark_bar_node, /*index=*/1,
446       base::UTF8ToUTF16(kFolder2Title));
447 
448   bookmark_model->AddFolder(
449       /*parent=*/bookmark_bar_node, /*index=*/2,
450       base::UTF8ToUTF16(kFolder3Title));
451 
452   // -------- The remote model --------
453   // bookmark_bar
454   //  |- folder 1
455   //  |- folder 3
456   //  |- folder 2
457 
458   const std::string suffix = syncer::UniquePosition::RandomSuffix();
459   syncer::UniquePosition posFolder1 =
460       syncer::UniquePosition::InitialPosition(suffix);
461   syncer::UniquePosition posFolder3 =
462       syncer::UniquePosition::After(posFolder1, suffix);
463   syncer::UniquePosition posFolder2 =
464       syncer::UniquePosition::After(posFolder3, suffix);
465 
466   syncer::UpdateResponseDataList updates;
467   updates.push_back(CreateBookmarkBarNodeUpdateData());
468   updates.push_back(CreateUpdateResponseData(
469       /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
470       /*url=*/std::string(),
471       /*is_folder=*/true, /*unique_position=*/posFolder1));
472   updates.push_back(CreateUpdateResponseData(
473       /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title,
474       /*url=*/std::string(),
475       /*is_folder=*/true, /*unique_position=*/posFolder2));
476   updates.push_back(CreateUpdateResponseData(
477       /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title,
478       /*url=*/std::string(),
479       /*is_folder=*/true, /*unique_position=*/posFolder3));
480 
481   // -------- The expected merge outcome --------
482   // bookmark_bar
483   //  |- folder 1
484   //  |- folder 3
485   //  |- folder 2
486 
487   std::unique_ptr<SyncedBookmarkTracker> tracker =
488       Merge(std::move(updates), bookmark_model.get());
489   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(3u));
490 
491   EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(),
492               Eq(base::ASCIIToUTF16(kFolder1Title)));
493   EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(),
494               Eq(base::ASCIIToUTF16(kFolder3Title)));
495   EXPECT_THAT(bookmark_bar_node->children()[2]->GetTitle(),
496               Eq(base::ASCIIToUTF16(kFolder2Title)));
497 
498   // Verify the tracker contents.
499   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(4U));
500 
501   // There should be no local changes.
502   std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
503       tracker->GetEntitiesWithLocalChanges(kMaxEntries);
504   EXPECT_THAT(local_changes.size(), Eq(0U));
505 
506   // Verify positions in tracker.
507   EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker));
508 }
509 
TEST(BookmarkModelMergerTest,ShouldMergeFaviconsForRemoteNodesOnly)510 TEST(BookmarkModelMergerTest, ShouldMergeFaviconsForRemoteNodesOnly) {
511   const std::string kTitle1 = "title1";
512   const GURL kUrl1("http://www.url1.com");
513   // -------- The local model --------
514   // bookmark_bar
515   //  |- title 1
516 
517   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
518       bookmarks::TestBookmarkClient::CreateModel();
519 
520   const bookmarks::BookmarkNode* bookmark_bar_node =
521       bookmark_model->bookmark_bar_node();
522   bookmark_model->AddURL(
523       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle1),
524       kUrl1);
525 
526   // -------- The remote model --------
527   // bookmark_bar
528   //  |- title 2
529 
530   const std::string kTitle2 = "title2";
531   const std::string kId2 = "Id2";
532   const GURL kUrl2("http://www.url2.com");
533   const GURL kIcon2Url("http://www.icon-url.com");
534   syncer::UniquePosition pos2 = syncer::UniquePosition::InitialPosition(
535       syncer::UniquePosition::RandomSuffix());
536 
537   syncer::UpdateResponseDataList updates;
538   updates.push_back(CreateBookmarkBarNodeUpdateData());
539   updates.push_back(CreateUpdateResponseData(
540       /*server_id=*/kId2, /*parent_id=*/kBookmarkBarId, kTitle2, kUrl2.spec(),
541       /*is_folder=*/false, /*unique_position=*/pos2, base::GenerateGUID(),
542       kIcon2Url.spec(),
543       /*icon_data=*/"PNG"));
544 
545   // -------- The expected merge outcome --------
546   // bookmark_bar
547   //  |- title 2
548   //  |- title 1
549 
550   std::unique_ptr<SyncedBookmarkTracker> tracker =
551       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
552   testing::NiceMock<favicon::MockFaviconService> favicon_service;
553 
554   // Favicon should be set for the remote node.
555   EXPECT_CALL(favicon_service,
556               AddPageNoVisitForBookmark(kUrl2, base::UTF8ToUTF16(kTitle2)));
557   EXPECT_CALL(favicon_service, MergeFavicon(kUrl2, _, _, _, _));
558 
559   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
560                       &favicon_service, tracker.get())
561       .Merge();
562 }
563 
564 // This tests that canonical titles produced by legacy clients are properly
565 // matched. Legacy clients append blank space to empty titles.
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyCanonicalTitle)566 TEST(BookmarkModelMergerTest,
567      ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyCanonicalTitle) {
568   const std::string kLocalTitle = "";
569   const std::string kRemoteTitle = " ";
570   const std::string kId = "Id";
571 
572   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
573       bookmarks::TestBookmarkClient::CreateModel();
574 
575   // -------- The local model --------
576   const bookmarks::BookmarkNode* bookmark_bar_node =
577       bookmark_model->bookmark_bar_node();
578   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
579       /*parent=*/bookmark_bar_node, /*index=*/0,
580       base::UTF8ToUTF16(kLocalTitle));
581   ASSERT_TRUE(folder);
582 
583   // -------- The remote model --------
584   const std::string suffix = syncer::UniquePosition::RandomSuffix();
585   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
586 
587   syncer::UpdateResponseDataList updates;
588   updates.push_back(CreateBookmarkBarNodeUpdateData());
589   updates.push_back(UpdateResponseDataBuilder(/*server_id=*/kId,
590                                               /*parent_id=*/kBookmarkBarId,
591                                               kRemoteTitle,
592                                               /*unique_position=*/pos)
593                         .SetLegacyTitleOnly()
594                         .Build());
595 
596   std::unique_ptr<SyncedBookmarkTracker> tracker =
597       Merge(std::move(updates), bookmark_model.get());
598 
599   // Both titles should have matched against each other and only node is in the
600   // model and the tracker.
601   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
602   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
603 }
604 
605 // This tests that truncated titles produced by legacy clients are properly
606 // matched.
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyTruncatedTitle)607 TEST(BookmarkModelMergerTest,
608      ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyTruncatedTitle) {
609   const std::string kLocalLongTitle =
610       "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst"
611       "uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"
612       "OPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
613       "ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB"
614       "CDEFGHIJKLMNOPQRSTUVWXYZ";
615   const std::string kRemoteTruncatedTitle =
616       "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst"
617       "uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"
618       "OPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
619       "ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU";
620   const std::string kId = "Id";
621 
622   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
623       bookmarks::TestBookmarkClient::CreateModel();
624 
625   // -------- The local model --------
626   const bookmarks::BookmarkNode* bookmark_bar_node =
627       bookmark_model->bookmark_bar_node();
628   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
629       /*parent=*/bookmark_bar_node, /*index=*/0,
630       base::UTF8ToUTF16(kLocalLongTitle));
631   ASSERT_TRUE(folder);
632 
633   // -------- The remote model --------
634   const std::string suffix = syncer::UniquePosition::RandomSuffix();
635   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
636 
637   syncer::UpdateResponseDataList updates;
638   updates.push_back(CreateBookmarkBarNodeUpdateData());
639   updates.push_back(CreateUpdateResponseData(
640       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTruncatedTitle,
641       /*url=*/std::string(),
642       /*is_folder=*/true, /*unique_position=*/pos));
643 
644   std::unique_ptr<SyncedBookmarkTracker> tracker =
645       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
646   testing::NiceMock<favicon::MockFaviconService> favicon_service;
647   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
648                       &favicon_service, tracker.get())
649       .Merge();
650 
651   // Both titles should have matched against each other and only node is in the
652   // model and the tracker.
653   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
654   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
655 }
656 
TEST(BookmarkModelMergerTest,ShouldMergeNodesWhenRemoteHasLegacyTruncatedTitleInFullTitle)657 TEST(BookmarkModelMergerTest,
658      ShouldMergeNodesWhenRemoteHasLegacyTruncatedTitleInFullTitle) {
659   const std::string kLocalLongTitle(300, 'A');
660   const std::string kRemoteTruncatedFullTitle(255, 'A');
661   const std::string kId = "Id";
662 
663   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
664       bookmarks::TestBookmarkClient::CreateModel();
665 
666   // -------- The local model --------
667   const bookmarks::BookmarkNode* bookmark_bar_node =
668       bookmark_model->bookmark_bar_node();
669   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
670       /*parent=*/bookmark_bar_node, /*index=*/0,
671       base::UTF8ToUTF16(kLocalLongTitle));
672   ASSERT_TRUE(folder);
673 
674   // -------- The remote model --------
675   const std::string suffix = syncer::UniquePosition::RandomSuffix();
676   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
677 
678   syncer::UpdateResponseDataList updates;
679   updates.push_back(CreateBookmarkBarNodeUpdateData());
680   updates.push_back(CreateUpdateResponseData(
681       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId,
682       kRemoteTruncatedFullTitle,
683       /*url=*/std::string(),
684       /*is_folder=*/true, /*unique_position=*/pos));
685 
686   updates.back().entity.specifics.mutable_bookmark()->set_full_title(
687       kRemoteTruncatedFullTitle);
688 
689   std::unique_ptr<SyncedBookmarkTracker> tracker =
690       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
691   testing::NiceMock<favicon::MockFaviconService> favicon_service;
692   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
693                       &favicon_service, tracker.get())
694       .Merge();
695 
696   // Both titles should have matched against each other and only node is in the
697   // model and the tracker.
698   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
699   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
700 }
701 
702 // This test checks that local node with truncated title will merge with remote
703 // node which has full title.
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteNodesWhenLocalHasLegacyTruncatedTitle)704 TEST(BookmarkModelMergerTest,
705      ShouldMergeLocalAndRemoteNodesWhenLocalHasLegacyTruncatedTitle) {
706   const std::string kRemoteFullTitle(300, 'A');
707   const std::string kLocalTruncatedTitle(255, 'A');
708   const std::string kId = "Id";
709 
710   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
711       bookmarks::TestBookmarkClient::CreateModel();
712 
713   // -------- The local model --------
714   const bookmarks::BookmarkNode* bookmark_bar_node =
715       bookmark_model->bookmark_bar_node();
716   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
717       /*parent=*/bookmark_bar_node, /*index=*/0,
718       base::UTF8ToUTF16(kLocalTruncatedTitle));
719   ASSERT_TRUE(folder);
720 
721   // -------- The remote model --------
722   const std::string suffix = syncer::UniquePosition::RandomSuffix();
723   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
724 
725   syncer::UpdateResponseDataList updates;
726   updates.push_back(CreateBookmarkBarNodeUpdateData());
727   updates.push_back(CreateUpdateResponseData(
728       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId,
729       sync_bookmarks::FullTitleToLegacyCanonicalizedTitle(kRemoteFullTitle),
730       /*url=*/std::string(),
731       /*is_folder=*/true, /*unique_position=*/pos));
732   ASSERT_EQ(
733       kLocalTruncatedTitle,
734       updates.back().entity.specifics.bookmark().legacy_canonicalized_title());
735 
736   updates.back().entity.specifics.mutable_bookmark()->set_full_title(
737       kRemoteFullTitle);
738 
739   std::unique_ptr<SyncedBookmarkTracker> tracker =
740       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
741   testing::NiceMock<favicon::MockFaviconService> favicon_service;
742   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
743                       &favicon_service, tracker.get())
744       .Merge();
745 
746   // Both titles should have matched against each other and only node is in the
747   // model and the tracker.
748   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
749   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
750 }
751 
TEST(BookmarkModelMergerTest,ShouldMergeAndUseRemoteGUID)752 TEST(BookmarkModelMergerTest, ShouldMergeAndUseRemoteGUID) {
753   const std::string kId = "Id";
754   const std::string kTitle = "Title";
755   const std::string kRemoteGuid = base::GenerateGUID();
756 
757   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
758       bookmarks::TestBookmarkClient::CreateModel();
759 
760   // -------- The local model --------
761   const bookmarks::BookmarkNode* bookmark_bar_node =
762       bookmark_model->bookmark_bar_node();
763   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
764       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle));
765   ASSERT_TRUE(folder);
766 
767   // -------- The remote model --------
768   syncer::UpdateResponseDataList updates;
769   updates.push_back(CreateBookmarkBarNodeUpdateData());
770   updates.push_back(CreateUpdateResponseData(
771       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle,
772       /*url=*/std::string(),
773       /*is_folder=*/true, /*unique_position=*/MakeRandomPosition(),
774       /*guid=*/kRemoteGuid));
775 
776   std::unique_ptr<SyncedBookmarkTracker> tracker =
777       Merge(std::move(updates), bookmark_model.get());
778 
779   // Node should have been replaced and GUID should be set to that stored in the
780   // specifics.
781   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
782   const bookmarks::BookmarkNode* bookmark =
783       bookmark_model->bookmark_bar_node()->children()[0].get();
784   EXPECT_EQ(bookmark->guid(), kRemoteGuid);
785   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
786 }
787 
TEST(BookmarkModelMergerTest,ShouldMergeAndKeepOldGUIDWhenRemoteGUIDIsInvalid)788 TEST(BookmarkModelMergerTest,
789      ShouldMergeAndKeepOldGUIDWhenRemoteGUIDIsInvalid) {
790   const std::string kId = "Id";
791   const std::string kTitle = "Title";
792 
793   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
794       bookmarks::TestBookmarkClient::CreateModel();
795 
796   // -------- The local model --------
797   const bookmarks::BookmarkNode* bookmark_bar_node =
798       bookmark_model->bookmark_bar_node();
799   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
800       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle));
801   ASSERT_TRUE(folder);
802   const std::string old_guid = folder->guid();
803 
804   // -------- The remote model --------
805   syncer::UpdateResponseDataList updates;
806   updates.push_back(CreateBookmarkBarNodeUpdateData());
807   updates.push_back(CreateUpdateResponseData(
808       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle,
809       /*url=*/std::string(),
810       /*is_folder=*/true,
811       /*unique_position=*/MakeRandomPosition(),
812       /*guid=*/""));
813 
814   std::unique_ptr<SyncedBookmarkTracker> tracker =
815       Merge(std::move(updates), bookmark_model.get());
816 
817   // Node should not have been replaced and GUID should not have been set to
818   // that stored in the specifics, as it was invalid.
819   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
820   const bookmarks::BookmarkNode* bookmark =
821       bookmark_model->bookmark_bar_node()->children()[0].get();
822   EXPECT_EQ(bookmark->guid(), old_guid);
823   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
824 }
825 
TEST(BookmarkModelMergerTest,ShouldMergeBookmarkByGUID)826 TEST(BookmarkModelMergerTest, ShouldMergeBookmarkByGUID) {
827   const std::string kId = "Id";
828   const std::string kLocalTitle = "Title 1";
829   const std::string kRemoteTitle = "Title 2";
830   const std::string kUrl = "http://www.foo.com/";
831   const std::string kGuid = base::GenerateGUID();
832 
833   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
834       bookmarks::TestBookmarkClient::CreateModel();
835 
836   // -------- The local model --------
837   // bookmark_bar
838   //  | - bookmark(kGuid/kLocalTitle)
839 
840   const bookmarks::BookmarkNode* bookmark_bar_node =
841       bookmark_model->bookmark_bar_node();
842   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
843       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kLocalTitle),
844       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
845   ASSERT_TRUE(bookmark);
846   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
847 
848   // -------- The remote model --------
849   // bookmark_bar
850   //  | - bookmark(kGuid/kRemoteTitle)
851 
852   syncer::UpdateResponseDataList updates;
853   updates.push_back(CreateBookmarkBarNodeUpdateData());
854   updates.push_back(CreateUpdateResponseData(
855       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTitle,
856       /*url=*/kUrl,
857       /*is_folder=*/false,
858       /*unique_position=*/MakeRandomPosition(),
859       /*guid=*/kGuid));
860 
861   std::unique_ptr<SyncedBookmarkTracker> tracker =
862       Merge(std::move(updates), bookmark_model.get());
863 
864   // -------- The merged model --------
865   // bookmark_bar
866   //  |- bookmark(kGuid/kRemoteTitle)
867 
868   // Node should have been merged.
869   EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
870   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kRemoteTitle));
871   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
872 }
873 
TEST(BookmarkModelMergerTest,ShouldMergeBookmarkByGUIDAndReparent)874 TEST(BookmarkModelMergerTest, ShouldMergeBookmarkByGUIDAndReparent) {
875   const std::string kId = "Id";
876   const std::string kLocalTitle = "Title 1";
877   const std::string kRemoteTitle = "Title 2";
878   const std::string kUrl = "http://www.foo.com/";
879   const std::string kGuid = base::GenerateGUID();
880 
881   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
882       bookmarks::TestBookmarkClient::CreateModel();
883 
884   // -------- The local model --------
885   // bookmark_bar
886   //  | - folder
887   //    | - bookmark(kGuid)
888 
889   const bookmarks::BookmarkNode* bookmark_bar_node =
890       bookmark_model->bookmark_bar_node();
891   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
892       /*parent=*/bookmark_bar_node, /*index=*/0,
893       base::UTF8ToUTF16("Folder Title"), nullptr, base::GenerateGUID());
894   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
895       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16(kLocalTitle),
896       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
897   ASSERT_TRUE(folder);
898   ASSERT_TRUE(bookmark);
899   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
900   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
901 
902   // -------- The remote model --------
903   // bookmark_bar
904   //  |- bookmark(kGuid)
905 
906   syncer::UpdateResponseDataList updates;
907   updates.push_back(CreateBookmarkBarNodeUpdateData());
908   updates.push_back(CreateUpdateResponseData(
909       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTitle,
910       /*url=*/kUrl,
911       /*is_folder=*/false,
912       /*unique_position=*/MakeRandomPosition(),
913       /*guid=*/kGuid));
914 
915   std::unique_ptr<SyncedBookmarkTracker> tracker =
916       Merge(std::move(updates), bookmark_model.get());
917 
918   // -------- The merged model --------
919   // bookmark_bar
920   //  | - bookmark(kGuid/kRemoteTitle)
921   //  | - folder
922 
923   // Node should have been merged and the local node should have been
924   // reparented.
925   EXPECT_THAT(bookmark_bar_node->children(),
926               ElementRawPointersAre(bookmark, folder));
927   EXPECT_EQ(folder->children().size(), 0u);
928   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kRemoteTitle));
929   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
930   EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder), NotNull());
931 }
932 
TEST(BookmarkModelMergerTest,ShouldMergeFolderByGUIDAndNotSemantics)933 TEST(BookmarkModelMergerTest, ShouldMergeFolderByGUIDAndNotSemantics) {
934   const std::string kFolderId = "Folder Id";
935   const std::string kTitle1 = "Title 1";
936   const std::string kTitle2 = "Title 2";
937   const std::string kUrl = "http://www.foo.com/";
938   const std::string kGuid1 = base::GenerateGUID();
939   const std::string kGuid2 = base::GenerateGUID();
940 
941   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
942       bookmarks::TestBookmarkClient::CreateModel();
943 
944   // -------- The local model --------
945   // bookmark_bar
946   //  | - folder 1 (kGuid1/kTitle1)
947   //    | - folder 2 (kGuid2/kTitle2)
948 
949   const bookmarks::BookmarkNode* bookmark_bar_node =
950       bookmark_model->bookmark_bar_node();
951   const bookmarks::BookmarkNode* folder1 =
952       bookmark_model->AddFolder(/*parent=*/bookmark_bar_node, /*index=*/0,
953                                 base::UTF8ToUTF16(kTitle1), nullptr, kGuid1);
954   const bookmarks::BookmarkNode* folder2 =
955       bookmark_model->AddFolder(/*parent=*/folder1, /*index=*/0,
956                                 base::UTF8ToUTF16(kTitle2), nullptr, kGuid2);
957   ASSERT_TRUE(folder1);
958   ASSERT_TRUE(folder2);
959   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder1));
960   ASSERT_THAT(folder1->children(), ElementRawPointersAre(folder2));
961 
962   // -------- The remote model --------
963   // bookmark_bar
964   //  | - folder (kGuid2/kTitle1)
965 
966   syncer::UpdateResponseDataList updates;
967   updates.push_back(CreateBookmarkBarNodeUpdateData());
968 
969   // Add a remote folder to correspond to the local folder by GUID and
970   // semantics.
971   updates.push_back(CreateUpdateResponseData(
972       /*server_id=*/kFolderId, /*parent_id=*/kBookmarkBarId, kTitle1,
973       /*url=*/"",
974       /*is_folder=*/true,
975       /*unique_position=*/MakeRandomPosition(),
976       /*guid=*/kGuid2));
977 
978   std::unique_ptr<SyncedBookmarkTracker> tracker =
979       Merge(std::move(updates), bookmark_model.get());
980 
981   // -------- The merged model --------
982   // bookmark_bar
983   //  | - folder 2 (kGuid2/kTitle1)
984   //  | - folder 1 (kGuid1/kTitle1)
985 
986   // Node should have been merged with its GUID match.
987   EXPECT_THAT(bookmark_bar_node->children(),
988               ElementRawPointersAre(folder2, folder1));
989   EXPECT_EQ(folder1->guid(), kGuid1);
990   EXPECT_EQ(folder1->GetTitle(), base::UTF8ToUTF16(kTitle1));
991   EXPECT_EQ(folder1->children().size(), 0u);
992   EXPECT_EQ(folder2->guid(), kGuid2);
993   EXPECT_EQ(folder2->GetTitle(), base::UTF8ToUTF16(kTitle1));
994   EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder1), NotNull());
995   EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder2), NotNull());
996 }
997 
TEST(BookmarkModelMergerTest,ShouldIgnoreChildrenForNonFolderNodes)998 TEST(BookmarkModelMergerTest, ShouldIgnoreChildrenForNonFolderNodes) {
999   const std::string kParentId = "parent_id";
1000   const std::string kChildId = "child_id";
1001   const std::string kParentTitle = "Parent Title";
1002   const std::string kChildTitle = "Child Title";
1003   const std::string kGuid1 = base::GenerateGUID();
1004   const std::string kGuid2 = base::GenerateGUID();
1005   const std::string kUrl1 = "http://www.foo.com/";
1006   const std::string kUrl2 = "http://www.bar.com/";
1007 
1008   // -------- The remote model --------
1009   // bookmark_bar
1010   //  | - bookmark (kGuid1/kParentTitle, not a folder)
1011   //    | - bookmark
1012 
1013   syncer::UpdateResponseDataList updates;
1014   updates.push_back(CreateBookmarkBarNodeUpdateData());
1015 
1016   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1017   const syncer::UniquePosition pos1 =
1018       syncer::UniquePosition::InitialPosition(suffix);
1019   const syncer::UniquePosition pos2 =
1020       syncer::UniquePosition::After(pos1, suffix);
1021 
1022   updates.push_back(CreateUpdateResponseData(
1023       /*server_id=*/kParentId, /*parent_id=*/kBookmarkBarId, kParentTitle,
1024       /*url=*/kUrl1,
1025       /*is_folder=*/false,
1026       /*unique_position=*/pos1,
1027       /*guid=*/kGuid1));
1028 
1029   updates.push_back(CreateUpdateResponseData(
1030       /*server_id=*/kChildId, /*parent_id=*/kParentId, kChildTitle,
1031       /*url=*/kUrl2,
1032       /*is_folder=*/false,
1033       /*unique_position=*/pos2,
1034       /*guid=*/kGuid2));
1035 
1036   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1037       bookmarks::TestBookmarkClient::CreateModel();
1038   std::unique_ptr<SyncedBookmarkTracker> tracker =
1039       Merge(std::move(updates), bookmark_model.get());
1040 
1041   // -------- The merged model --------
1042   // bookmark_bar
1043   //  | - bookmark (kGuid1/kParentTitle)
1044 
1045   const bookmarks::BookmarkNode* bookmark_bar_node =
1046       bookmark_model->bookmark_bar_node();
1047 
1048   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
1049   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid1);
1050   EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(),
1051             base::UTF8ToUTF16(kParentTitle));
1052   EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 0u);
1053   EXPECT_EQ(tracker->TrackedEntitiesCountForTest(), 2U);
1054 }
1055 
TEST(BookmarkModelMergerTest,ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithSemanticsNodeFirst)1056 TEST(
1057     BookmarkModelMergerTest,
1058     ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithSemanticsNodeFirst) {
1059   const std::string kFolderId1 = "Folder Id 1";
1060   const std::string kFolderId2 = "Folder Id 2";
1061   const std::string kOriginalTitle = "Original Title";
1062   const std::string kNewTitle = "New Title";
1063   const std::string kGuid1 = base::GenerateGUID();
1064   const std::string kGuid2 = base::GenerateGUID();
1065 
1066   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1067       bookmarks::TestBookmarkClient::CreateModel();
1068 
1069   // -------- The local model --------
1070   // bookmark_bar
1071   //  | - folder (kGuid1/kOriginalTitle)
1072   //    | - bookmark
1073 
1074   const bookmarks::BookmarkNode* bookmark_bar_node =
1075       bookmark_model->bookmark_bar_node();
1076   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
1077       /*parent=*/bookmark_bar_node, /*index=*/0,
1078       base::UTF8ToUTF16(kOriginalTitle), nullptr, kGuid1);
1079   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1080       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16("Bookmark Title"),
1081       GURL("http://foo.com/"), nullptr, base::Time::Now(),
1082       base::GenerateGUID());
1083   ASSERT_TRUE(folder);
1084   ASSERT_TRUE(bookmark);
1085   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
1086   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
1087 
1088   // -------- The remote model --------
1089   // bookmark_bar
1090   //  | - folder (kGuid2/kOriginalTitle)
1091   //  | - folder (kGuid1/kNewTitle)
1092 
1093   syncer::UpdateResponseDataList updates;
1094   updates.push_back(CreateBookmarkBarNodeUpdateData());
1095 
1096   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1097   syncer::UniquePosition pos1 = syncer::UniquePosition::InitialPosition(suffix);
1098   syncer::UniquePosition pos2 = syncer::UniquePosition::After(pos1, suffix);
1099 
1100   // Add a remote folder to correspond to the local folder by semantics and not
1101   // GUID.
1102   updates.push_back(CreateUpdateResponseData(
1103       /*server_id=*/kFolderId1, /*parent_id=*/kBookmarkBarId, kOriginalTitle,
1104       /*url=*/"",
1105       /*is_folder=*/true,
1106       /*unique_position=*/pos1,
1107       /*guid=*/kGuid2));
1108 
1109   // Add a remote folder to correspond to the local folder by GUID and not
1110   // semantics.
1111   updates.push_back(CreateUpdateResponseData(
1112       /*server_id=*/kFolderId2, /*parent_id=*/kBookmarkBarId, kNewTitle,
1113       /*url=*/"",
1114       /*is_folder=*/true,
1115       /*unique_position=*/pos2,
1116       /*guid=*/kGuid1));
1117 
1118   std::unique_ptr<SyncedBookmarkTracker> tracker =
1119       Merge(std::move(updates), bookmark_model.get());
1120 
1121   // -------- The merged model --------
1122   // bookmark_bar
1123   //  | - folder (kGuid2/kOriginalTitle)
1124   //  | - folder (kGuid1/kNewTitle)
1125   //    | - bookmark
1126 
1127   // Node should have been merged with its GUID match.
1128   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1129   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid2);
1130   EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(),
1131             base::UTF8ToUTF16(kOriginalTitle));
1132   EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 0u);
1133   EXPECT_EQ(bookmark_bar_node->children()[1]->guid(), kGuid1);
1134   EXPECT_EQ(bookmark_bar_node->children()[1]->GetTitle(),
1135             base::UTF8ToUTF16(kNewTitle));
1136   EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 1u);
1137   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(4U));
1138 }
1139 
TEST(BookmarkModelMergerTest,ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithGUIDNodeFirst)1140 TEST(BookmarkModelMergerTest,
1141      ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithGUIDNodeFirst) {
1142   const std::string kFolderId1 = "Folder Id 1";
1143   const std::string kFolderId2 = "Folder Id 2";
1144   const std::string kOriginalTitle = "Original Title";
1145   const std::string kNewTitle = "New Title";
1146   const std::string kGuid1 = base::GenerateGUID();
1147   const std::string kGuid2 = base::GenerateGUID();
1148 
1149   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1150       bookmarks::TestBookmarkClient::CreateModel();
1151 
1152   // -------- The local model --------
1153   // bookmark_bar
1154   //  | - folder (kGuid1/kOriginalTitle)
1155   //    | - bookmark
1156 
1157   const bookmarks::BookmarkNode* bookmark_bar_node =
1158       bookmark_model->bookmark_bar_node();
1159   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
1160       /*parent=*/bookmark_bar_node, /*index=*/0,
1161       base::UTF8ToUTF16(kOriginalTitle), nullptr, kGuid1);
1162   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1163       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16("Bookmark Title"),
1164       GURL("http://foo.com/"), nullptr, base::Time::Now(),
1165       base::GenerateGUID());
1166   ASSERT_TRUE(folder);
1167   ASSERT_TRUE(bookmark);
1168   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
1169   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
1170 
1171   // -------- The remote model --------
1172   // bookmark_bar
1173   //  | - folder (kGuid1/kNewTitle)
1174   //  | - folder (kGuid2/kOriginalTitle)
1175 
1176   syncer::UpdateResponseDataList updates;
1177   updates.push_back(CreateBookmarkBarNodeUpdateData());
1178 
1179   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1180   syncer::UniquePosition pos1 = syncer::UniquePosition::InitialPosition(suffix);
1181   syncer::UniquePosition pos2 = syncer::UniquePosition::After(pos1, suffix);
1182 
1183   // Add a remote folder to correspond to the local folder by GUID and not
1184   // semantics.
1185   updates.push_back(CreateUpdateResponseData(
1186       /*server_id=*/kFolderId2, /*parent_id=*/kBookmarkBarId, kNewTitle,
1187       /*url=*/"",
1188       /*is_folder=*/true,
1189       /*unique_position=*/pos1,
1190       /*guid=*/kGuid1));
1191 
1192   // Add a remote folder to correspond to the local folder by
1193   // semantics and not GUID.
1194   updates.push_back(CreateUpdateResponseData(
1195       /*server_id=*/kFolderId1, /*parent_id=*/kBookmarkBarId, kOriginalTitle,
1196       /*url=*/"",
1197       /*is_folder=*/true,
1198       /*unique_position=*/pos2,
1199       /*guid=*/kGuid2));
1200 
1201   Merge(std::move(updates), bookmark_model.get());
1202 
1203   // -------- The merged model --------
1204   // bookmark_bar
1205   //  | - folder (kGuid1/kNewTitle)
1206   //  | - folder (kGuid2/kOriginalTitle)
1207 
1208   // Node should have been merged with its GUID match.
1209   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1210   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid1);
1211   EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(),
1212             base::UTF8ToUTF16(kNewTitle));
1213   EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 1u);
1214   EXPECT_EQ(bookmark_bar_node->children()[1]->guid(), kGuid2);
1215   EXPECT_EQ(bookmark_bar_node->children()[1]->GetTitle(),
1216             base::UTF8ToUTF16(kOriginalTitle));
1217   EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 0u);
1218 }
1219 
TEST(BookmarkModelMergerTest,ShouldReplaceBookmarkGUIDWithConflictingURLs)1220 TEST(BookmarkModelMergerTest, ShouldReplaceBookmarkGUIDWithConflictingURLs) {
1221   const std::string kTitle = "Title";
1222   const std::string kUrl1 = "http://www.foo.com/";
1223   const std::string kUrl2 = "http://www.bar.com/";
1224   const std::string kGuid = base::GenerateGUID();
1225 
1226   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1227       bookmarks::TestBookmarkClient::CreateModel();
1228 
1229   // -------- The local model --------
1230   // bookmark_bar
1231   //  | - bookmark (kGuid/kUril1)
1232 
1233   const bookmarks::BookmarkNode* bookmark_bar_node =
1234       bookmark_model->bookmark_bar_node();
1235   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1236       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1237       GURL(kUrl1), nullptr, base::Time::Now(), kGuid);
1238   ASSERT_TRUE(bookmark);
1239   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1240 
1241   // -------- The remote model --------
1242   // bookmark_bar
1243   //  | - bookmark (kGuid/kUrl2)
1244 
1245   syncer::UpdateResponseDataList updates;
1246   updates.push_back(CreateBookmarkBarNodeUpdateData());
1247 
1248   updates.push_back(CreateUpdateResponseData(  // Remote B
1249       /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, kTitle,
1250       /*url=*/kUrl2,
1251       /*is_folder=*/false,
1252       /*unique_position=*/MakeRandomPosition(),
1253       /*guid=*/kGuid));
1254 
1255   Merge(std::move(updates), bookmark_model.get());
1256 
1257   // -------- The merged model --------
1258   // bookmark_bar
1259   //  | - bookmark (kGuid/kUrl2)
1260   //  | - bookmark ([new GUID]/kUrl1)
1261 
1262   // Conflicting node GUID should have been replaced.
1263   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1264   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid);
1265   EXPECT_EQ(bookmark_bar_node->children()[0]->url(), kUrl2);
1266   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid);
1267   EXPECT_TRUE(
1268       base::IsValidGUIDOutputString(bookmark_bar_node->children()[1]->guid()));
1269   EXPECT_EQ(bookmark_bar_node->children()[1]->url(), kUrl1);
1270 }
1271 
TEST(BookmarkModelMergerTest,ShouldReplaceBookmarkGUIDWithConflictingTypes)1272 TEST(BookmarkModelMergerTest, ShouldReplaceBookmarkGUIDWithConflictingTypes) {
1273   const std::string kTitle = "Title";
1274   const std::string kGuid = base::GenerateGUID();
1275 
1276   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1277       bookmarks::TestBookmarkClient::CreateModel();
1278 
1279   // -------- The local model --------
1280   // bookmark_bar
1281   //  | - bookmark (kGuid)
1282 
1283   const bookmarks::BookmarkNode* bookmark_bar_node =
1284       bookmark_model->bookmark_bar_node();
1285   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1286       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1287       GURL("http://www.foo.com/"), nullptr, base::Time::Now(), kGuid);
1288   ASSERT_TRUE(bookmark);
1289   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1290 
1291   // -------- The remote model --------
1292   // bookmark_bar
1293   //  | - folder(kGuid)
1294 
1295   syncer::UpdateResponseDataList updates;
1296   updates.push_back(CreateBookmarkBarNodeUpdateData());
1297 
1298   updates.push_back(CreateUpdateResponseData(  // Remote B
1299       /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, kTitle,
1300       /*url=*/"",
1301       /*is_folder=*/true,
1302       /*unique_position=*/MakeRandomPosition(),
1303       /*guid=*/kGuid));
1304 
1305   Merge(std::move(updates), bookmark_model.get());
1306 
1307   // -------- The merged model --------
1308   // bookmark_bar
1309   //  | - folder (kGuid)
1310   //  | - bookmark ([new GUID])
1311 
1312   // Conflicting node GUID should have been replaced.
1313   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1314   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid);
1315   EXPECT_TRUE(bookmark_bar_node->children()[0]->is_folder());
1316   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid);
1317   EXPECT_TRUE(
1318       base::IsValidGUIDOutputString(bookmark_bar_node->children()[1]->guid()));
1319   EXPECT_FALSE(bookmark_bar_node->children()[1]->is_folder());
1320 }
1321 
TEST(BookmarkModelMergerTest,ShouldReplaceBookmarkGUIDWithConflictingTypesAndLocalChildren)1322 TEST(BookmarkModelMergerTest,
1323      ShouldReplaceBookmarkGUIDWithConflictingTypesAndLocalChildren) {
1324   const std::string kGuid = base::GenerateGUID();
1325   const std::string kGuid2 = base::GenerateGUID();
1326 
1327   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1328       bookmarks::TestBookmarkClient::CreateModel();
1329 
1330   // -------- The local model --------
1331   // bookmark_bar
1332   //  | - folder (kGuid)
1333   //    | - bookmark (kGuid2)
1334 
1335   const bookmarks::BookmarkNode* bookmark_bar_node =
1336       bookmark_model->bookmark_bar_node();
1337   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
1338       /*parent=*/bookmark_bar_node, /*index=*/0,
1339       base::UTF8ToUTF16("Folder Title"), nullptr, kGuid);
1340   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1341       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16("Foo's title"),
1342       GURL("http://foo.com"), nullptr, base::Time::Now(), kGuid2);
1343   ASSERT_TRUE(folder);
1344   ASSERT_TRUE(bookmark);
1345   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
1346   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
1347 
1348   // -------- The remote model --------
1349   // bookmark_bar
1350   //  | - bookmark (kGuid)
1351 
1352   syncer::UpdateResponseDataList updates;
1353   updates.push_back(CreateBookmarkBarNodeUpdateData());
1354 
1355   updates.push_back(CreateUpdateResponseData(
1356       /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, "Bar's title",
1357       "http://bar.com/",
1358       /*is_folder=*/false,
1359       /*unique_position=*/MakeRandomPosition(),
1360       /*guid=*/kGuid));
1361 
1362   Merge(std::move(updates), bookmark_model.get());
1363 
1364   // -------- The merged model --------
1365   // bookmark_bar
1366   //  | - bookmark (kGuid)
1367   //  | - folder ([new GUID])
1368   //    | - bookmark (kGuid2)
1369 
1370   // Conflicting node GUID should have been replaced.
1371   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1372   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid);
1373   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid);
1374   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid2);
1375   EXPECT_FALSE(bookmark_bar_node->children()[0]->is_folder());
1376   EXPECT_TRUE(bookmark_bar_node->children()[1]->is_folder());
1377   EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 1u);
1378   EXPECT_FALSE(bookmark_bar_node->children()[1]->children()[0]->is_folder());
1379   EXPECT_EQ(bookmark_bar_node->children()[1]->children()[0]->guid(), kGuid2);
1380 }
1381 
1382 // Tests that the GUID-based matching algorithm handles well the case where a
1383 // local bookmark matches a remote bookmark that is orphan. In this case the
1384 // remote node should be ignored and the local bookmark included in the merged
1385 // tree.
TEST(BookmarkModelMergerTest,ShouldIgnoreRemoteGUIDIfOrphanNode)1386 TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteGUIDIfOrphanNode) {
1387   const std::string kId = "Id";
1388   const std::string kInexistentParentId = "InexistentParentId";
1389   const std::string kTitle = "Title";
1390   const std::string kUrl = "http://www.foo.com/";
1391   const std::string kGuid = base::GenerateGUID();
1392 
1393   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1394       bookmarks::TestBookmarkClient::CreateModel();
1395 
1396   // -------- The local model --------
1397   // bookmark_bar
1398   //  | - bookmark(kGuid/kTitle)
1399 
1400   const bookmarks::BookmarkNode* bookmark_bar_node =
1401       bookmark_model->bookmark_bar_node();
1402   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1403       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1404       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
1405   ASSERT_TRUE(bookmark);
1406   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1407 
1408   // -------- The remote model --------
1409   // bookmark_bar
1410   // Orphan node: bookmark(kGuid/kTitle)
1411 
1412   syncer::UpdateResponseDataList updates;
1413   updates.push_back(CreateBookmarkBarNodeUpdateData());
1414   updates.push_back(CreateUpdateResponseData(
1415       /*server_id=*/kId, /*parent_id=*/kInexistentParentId, kTitle,
1416       /*url=*/kUrl,
1417       /*is_folder=*/false,
1418       /*unique_position=*/MakeRandomPosition(),
1419       /*guid=*/kGuid));
1420 
1421   std::unique_ptr<SyncedBookmarkTracker> tracker =
1422       Merge(std::move(updates), bookmark_model.get());
1423 
1424   // -------- The merged model --------
1425   // bookmark_bar
1426   //  |- bookmark(kGuid/kTitle)
1427 
1428   // The local node should have been tracked.
1429   EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1430   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kTitle));
1431   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
1432 }
1433 
1434 // Tests that the GUID-based matching algorithm handles well the case where a
1435 // local bookmark matches a remote bookmark that contains invalid specifics
1436 // (e.g. invalid URL). In this case the remote node should be ignored and the
1437 // local bookmark included in the merged tree.
TEST(BookmarkModelMergerTest,ShouldIgnoreRemoteGUIDIfInvalidSpecifics)1438 TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteGUIDIfInvalidSpecifics) {
1439   const std::string kId = "Id";
1440   const std::string kTitle = "Title";
1441   const std::string kLocalUrl = "http://www.foo.com/";
1442   const std::string kInvalidUrl = "invalidurl";
1443   const std::string kGuid = base::GenerateGUID();
1444 
1445   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1446       bookmarks::TestBookmarkClient::CreateModel();
1447 
1448   // -------- The local model --------
1449   // bookmark_bar
1450   //  | - bookmark(kGuid/kLocalUrl/kTitle)
1451 
1452   const bookmarks::BookmarkNode* bookmark_bar_node =
1453       bookmark_model->bookmark_bar_node();
1454   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1455       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1456       GURL(kLocalUrl), nullptr, base::Time::Now(), kGuid);
1457   ASSERT_TRUE(bookmark);
1458   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1459 
1460   // -------- The remote model --------
1461   // bookmark_bar
1462   //  | - bookmark (kGuid/kInvalidURL/kTitle)
1463 
1464   syncer::UpdateResponseDataList updates;
1465   updates.push_back(CreateBookmarkBarNodeUpdateData());
1466   updates.push_back(CreateUpdateResponseData(
1467       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle,
1468       /*url=*/kInvalidUrl,
1469       /*is_folder=*/false,
1470       /*unique_position=*/MakeRandomPosition(),
1471       /*guid=*/kGuid));
1472 
1473   std::unique_ptr<SyncedBookmarkTracker> tracker =
1474       Merge(std::move(updates), bookmark_model.get());
1475 
1476   // -------- The merged model --------
1477   // bookmark_bar
1478   //  |- bookmark(kGuid/kLocalUrl/kTitle)
1479 
1480   // The local node should have been tracked.
1481   EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1482   EXPECT_EQ(bookmark->url(), GURL(kLocalUrl));
1483   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kTitle));
1484   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
1485 }
1486 
1487 // Tests that updates with a GUID that is different to originator client item ID
1488 // are ignored.
TEST(BookmarkModelMergerTest,ShouldIgnoreRemoteUpdateWithInvalidGUID)1489 TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteUpdateWithInvalidGUID) {
1490   const std::string kId1 = "Id1";
1491   const std::string kId2 = "Id2";
1492   const std::string kTitle1 = "Title1";
1493   const std::string kTitle2 = "Title2";
1494   const std::string kLocalTitle = "LocalTitle";
1495   const std::string kUrl = "http://www.foo.com/";
1496   const std::string kGuid = base::GenerateGUID();
1497 
1498   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1499       bookmarks::TestBookmarkClient::CreateModel();
1500 
1501   // -------- The local model --------
1502   //  | - bookmark(kGuid/kUrl/kLocalTitle)
1503   const bookmarks::BookmarkNode* bookmark_bar_node =
1504       bookmark_model->bookmark_bar_node();
1505   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1506       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kLocalTitle),
1507       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
1508   ASSERT_TRUE(bookmark);
1509   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1510 
1511   // -------- The remote model --------
1512   // bookmark_bar
1513   //  | - bookmark (kGuid/kUrl/kTitle1)
1514   //  | - bookmark (kGuid/kUrl/kTitle2)
1515   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1516   syncer::UniquePosition position1 =
1517       syncer::UniquePosition::InitialPosition(suffix);
1518   syncer::UniquePosition position2 =
1519       syncer::UniquePosition::After(position1, suffix);
1520 
1521   syncer::UpdateResponseDataList updates;
1522   updates.push_back(CreateBookmarkBarNodeUpdateData());
1523   updates.push_back(CreateUpdateResponseData(
1524       /*server_id=*/kId1, /*parent_id=*/kBookmarkBarId, kTitle1,
1525       /*url=*/kUrl,
1526       /*is_folder=*/false, /*unique_position=*/position1,
1527       /*guid=*/kGuid));
1528   updates.push_back(CreateUpdateResponseData(
1529       /*server_id=*/kId2, /*parent_id=*/kBookmarkBarId, kTitle2,
1530       /*url=*/kUrl,
1531       /*is_folder=*/false, /*unique_position=*/position2,
1532       /*guid=*/kGuid));
1533 
1534   // |originator_client_item_id| cannot itself be duplicated because
1535   // ModelTypeWorker guarantees otherwise.
1536   updates.back().entity.originator_client_item_id = base::GenerateGUID();
1537 
1538   std::unique_ptr<SyncedBookmarkTracker> tracker =
1539       Merge(std::move(updates), bookmark_model.get());
1540 
1541   // -------- The merged model --------
1542   //  | - bookmark (kGuid/kUrl/kTitle1)
1543 
1544   // The second remote node should have been filtered out.
1545   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
1546   const bookmarks::BookmarkNode* merged_bookmark =
1547       bookmark_model->bookmark_bar_node()->children()[0].get();
1548   EXPECT_THAT(merged_bookmark->guid(), Eq(kGuid));
1549   EXPECT_THAT(tracker->GetEntityForBookmarkNode(merged_bookmark), NotNull());
1550 }
1551 
1552 // Regression test for crbug.com/1050776. Verifies that computing the unique
1553 // position does not crash when processing local creation of bookmark during
1554 // initial merge.
TEST(BookmarkModelMergerTest,ShouldProcessLocalCreationWithUntrackedPredecessorNode)1555 TEST(BookmarkModelMergerTest,
1556      ShouldProcessLocalCreationWithUntrackedPredecessorNode) {
1557   const std::string kFolder1Title = "folder1";
1558   const std::string kFolder2Title = "folder2";
1559 
1560   const std::string kUrl1Title = "url1";
1561   const std::string kUrl2Title = "url2";
1562 
1563   const std::string kUrl1 = "http://www.url1.com/";
1564   const std::string kUrl2 = "http://www.url2.com/";
1565 
1566   const std::string kFolder1Id = "Folder1Id";
1567   const std::string kFolder2Id = "Folder2Id";
1568   const std::string kUrl1Id = "Url1Id";
1569 
1570   // It is needed to use at least two folders to reproduce the crash. It is
1571   // needed because the bookmarks are processed in the order of remote entities
1572   // on the same level of the tree. To start processing of locally created
1573   // bookmarks while other remote bookmarks are not processed we need to use at
1574   // least one local folder with several urls.
1575   //
1576   // -------- The local model --------
1577   // bookmark_bar
1578   //  |- folder 1
1579   //    |- url1(http://www.url1.com)
1580   //    |- url2(http://www.url2.com)
1581 
1582   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1583       bookmarks::TestBookmarkClient::CreateModel();
1584 
1585   const bookmarks::BookmarkNode* bookmark_bar_node =
1586       bookmark_model->bookmark_bar_node();
1587   const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder(
1588       /*parent=*/bookmark_bar_node, /*index=*/0,
1589       base::UTF8ToUTF16(kFolder1Title));
1590   const bookmarks::BookmarkNode* folder1_url1_node = bookmark_model->AddURL(
1591       /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kUrl1Title),
1592       GURL(kUrl1));
1593   bookmark_model->AddURL(
1594       /*parent=*/folder1, /*index=*/1, base::UTF8ToUTF16(kUrl2Title),
1595       GURL(kUrl2));
1596 
1597   // The remote model contains two folders. The first one is the same as in
1598   // local model, but it does not contain any urls. The second one has the url1
1599   // from first folder with same GUID. This will cause skip local creation for
1600   // |url1| while processing |folder1|.
1601   //
1602   // -------- The remote model --------
1603   // bookmark_bar
1604   //  |- folder 1
1605   //  |- folder 2
1606   //    |- url1(http://www.url1.com)
1607 
1608   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1609   syncer::UniquePosition posFolder1 =
1610       syncer::UniquePosition::InitialPosition(suffix);
1611   syncer::UniquePosition posFolder2 =
1612       syncer::UniquePosition::After(posFolder1, suffix);
1613 
1614   syncer::UniquePosition posUrl1 =
1615       syncer::UniquePosition::InitialPosition(suffix);
1616 
1617   syncer::UpdateResponseDataList updates;
1618   updates.push_back(CreateBookmarkBarNodeUpdateData());
1619   updates.push_back(CreateUpdateResponseData(
1620       /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
1621       /*url=*/std::string(),
1622       /*is_folder=*/true, /*unique_position=*/posFolder1));
1623   updates.push_back(CreateUpdateResponseData(
1624       /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title,
1625       /*url=*/std::string(),
1626       /*is_folder=*/true, /*unique_position=*/posFolder2));
1627   updates.push_back(CreateUpdateResponseData(
1628       /*server_id=*/kUrl1Id, /*parent_id=*/kFolder2Id, kUrl1Title, kUrl1,
1629       /*is_folder=*/false, /*unique_position=*/posUrl1,
1630       folder1_url1_node->guid()));
1631 
1632   // -------- The expected merge outcome --------
1633   // bookmark_bar
1634   //  |- folder 1
1635   //    |- url2(http://www.url2.com)
1636   //  |- folder 2
1637   //    |- url1(http://www.url1.com)
1638 
1639   std::unique_ptr<SyncedBookmarkTracker> tracker =
1640       Merge(std::move(updates), bookmark_model.get());
1641   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(2u));
1642 
1643   // Verify Folder 1.
1644   EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(),
1645               Eq(base::ASCIIToUTF16(kFolder1Title)));
1646   ASSERT_THAT(bookmark_bar_node->children()[0]->children().size(), Eq(1u));
1647 
1648   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->GetTitle(),
1649               Eq(base::ASCIIToUTF16(kUrl2Title)));
1650   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->url(),
1651               Eq(GURL(kUrl2)));
1652 
1653   // Verify Folder 2.
1654   EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(),
1655               Eq(base::ASCIIToUTF16(kFolder2Title)));
1656   ASSERT_THAT(bookmark_bar_node->children()[1]->children().size(), Eq(1u));
1657 
1658   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->GetTitle(),
1659               Eq(base::ASCIIToUTF16(kUrl1Title)));
1660   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->url(),
1661               Eq(GURL(kUrl1)));
1662 
1663   // Verify the tracker contents.
1664   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(5U));
1665 
1666   const size_t kMaxEntries = 1000;
1667   std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
1668       tracker->GetEntitiesWithLocalChanges(kMaxEntries);
1669 
1670   ASSERT_THAT(local_changes.size(), Eq(1U));
1671   EXPECT_THAT(local_changes[0]->bookmark_node(),
1672               Eq(bookmark_bar_node->children()[0]->children()[0].get()));
1673 
1674   // Verify positions in tracker.
1675   EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker));
1676 }
1677 
TEST(BookmarkModelMergerTest,ShouldLogMetricsForInvalidSpecifics)1678 TEST(BookmarkModelMergerTest, ShouldLogMetricsForInvalidSpecifics) {
1679   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1680       bookmarks::TestBookmarkClient::CreateModel();
1681 
1682   // -------- The remote model --------
1683   // bookmark_bar
1684   //  | - bookmark (<invalid url>)
1685 
1686   syncer::UpdateResponseDataList updates;
1687   updates.push_back(CreateBookmarkBarNodeUpdateData());
1688   updates.push_back(CreateUpdateResponseData(
1689       /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, "Title",
1690       /*url=*/"invalidurl",
1691       /*is_folder=*/false,
1692       /*unique_position=*/MakeRandomPosition(),
1693       /*guid=*/base::GenerateGUID()));
1694 
1695   base::HistogramTester histogram_tester;
1696   Merge(std::move(updates), bookmark_model.get());
1697   histogram_tester.ExpectUniqueSample(
1698       "Sync.ProblematicServerSideBookmarksDuringMerge",
1699       /*sample=*/ExpectedRemoteBookmarkUpdateError::kInvalidSpecifics,
1700       /*count=*/1);
1701 }
1702 
TEST(BookmarkModelMergerTest,ShouldLogMetricsForChildrenOfNonFolder)1703 TEST(BookmarkModelMergerTest, ShouldLogMetricsForChildrenOfNonFolder) {
1704   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1705       bookmarks::TestBookmarkClient::CreateModel();
1706 
1707   // -------- The remote model --------
1708   // bookmark_bar
1709   //  | - bookmark (url1/Title1)
1710   //    | - bookmark (url2/Title2)
1711   //    | - bookmark (url3/Title3)
1712   //    | - bookmark (url4/Title4)
1713 
1714   syncer::UpdateResponseDataList updates;
1715   updates.push_back(CreateBookmarkBarNodeUpdateData());
1716   updates.push_back(CreateUpdateResponseData(
1717       /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, "Title1",
1718       /*url=*/"http://url1",
1719       /*is_folder=*/false,
1720       /*unique_position=*/MakeRandomPosition(),
1721       /*guid=*/base::GenerateGUID()));
1722   updates.push_back(CreateUpdateResponseData(
1723       /*server_id=*/"Id2", /*parent_id=*/"Id1", "Title2",
1724       /*url=*/"http://url2",
1725       /*is_folder=*/false,
1726       /*unique_position=*/MakeRandomPosition(),
1727       /*guid=*/base::GenerateGUID()));
1728   updates.push_back(CreateUpdateResponseData(
1729       /*server_id=*/"Id3", /*parent_id=*/"Id1", "Title3",
1730       /*url=*/"http://url3",
1731       /*is_folder=*/false,
1732       /*unique_position=*/MakeRandomPosition(),
1733       /*guid=*/base::GenerateGUID()));
1734   updates.push_back(CreateUpdateResponseData(
1735       /*server_id=*/"Id4", /*parent_id=*/"Id1", "Title4",
1736       /*url=*/"http://url4",
1737       /*is_folder=*/false,
1738       /*unique_position=*/MakeRandomPosition(),
1739       /*guid=*/base::GenerateGUID()));
1740 
1741   base::HistogramTester histogram_tester;
1742   Merge(std::move(updates), bookmark_model.get());
1743   histogram_tester.ExpectUniqueSample(
1744       "Sync.ProblematicServerSideBookmarksDuringMerge",
1745       /*sample=*/ExpectedRemoteBookmarkUpdateError::kParentNotFolder,
1746       /*count=*/3);
1747 }
1748 
TEST(BookmarkModelMergerTest,ShouldLogMetricsForChildrenOfOrphanUpdates)1749 TEST(BookmarkModelMergerTest, ShouldLogMetricsForChildrenOfOrphanUpdates) {
1750   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1751       bookmarks::TestBookmarkClient::CreateModel();
1752 
1753   // -------- The remote model --------
1754   // bookmark_bar
1755   // Orphan node: bookmark(url1/title1)
1756 
1757   syncer::UpdateResponseDataList updates;
1758   updates.push_back(CreateBookmarkBarNodeUpdateData());
1759   updates.push_back(CreateUpdateResponseData(
1760       /*server_id=*/"Id", /*parent_id=*/"UnknownId", "Title1",
1761       /*url=*/"http://url1",
1762       /*is_folder=*/false,
1763       /*unique_position=*/MakeRandomPosition(),
1764       /*guid=*/base::GenerateGUID()));
1765 
1766   base::HistogramTester histogram_tester;
1767   Merge(std::move(updates), bookmark_model.get());
1768   histogram_tester.ExpectUniqueSample(
1769       "Sync.ProblematicServerSideBookmarksDuringMerge",
1770       /*sample=*/ExpectedRemoteBookmarkUpdateError::kMissingParentEntity,
1771       /*count=*/1);
1772 }
1773 
TEST(BookmarkModelMergerTest,ShouldRemoveMatchingDuplicatesByGUID)1774 TEST(BookmarkModelMergerTest, ShouldRemoveMatchingDuplicatesByGUID) {
1775   const std::string kTitle1 = "Title 1";
1776   const std::string kTitle2 = "Title 2";
1777   const std::string kUrl = "http://www.url.com/";
1778 
1779   const std::string kUrlGUID = base::GenerateGUID();
1780 
1781   // The remote model has 2 duplicate folders with the same title and 2
1782   // duplicate bookmarks with the same URL.
1783   //
1784   // -------- The remote model --------
1785   // bookmark_bar
1786   //  |- url1(http://www.url.com, UrlGUID)
1787   //  |- url2(http://www.url.com, UrlGUID)
1788   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1789       bookmarks::TestBookmarkClient::CreateModel();
1790 
1791   syncer::UpdateResponseDataList updates;
1792   updates.push_back(CreateBookmarkBarNodeUpdateData());
1793 
1794   updates.push_back(CreateUpdateResponseData(
1795       /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle1,
1796       /*url=*/kUrl,
1797       /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID));
1798   updates.back().entity.creation_time =
1799       base::Time::Now() - base::TimeDelta::FromDays(1);
1800   updates.push_back(CreateUpdateResponseData(
1801       /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle2,
1802       /*url=*/kUrl,
1803       /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID));
1804   updates.back().entity.creation_time = base::Time::Now();
1805 
1806   base::HistogramTester histogram_tester;
1807   std::unique_ptr<SyncedBookmarkTracker> tracker =
1808       Merge(std::move(updates), bookmark_model.get());
1809   const bookmarks::BookmarkNode* bookmark_bar_node =
1810       bookmark_model->bookmark_bar_node();
1811   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u));
1812   histogram_tester.ExpectBucketCount(
1813       "Sync.BookmarksGUIDDuplicates",
1814       /*sample=*/ExpectedBookmarksGUIDDuplicates::kMatchingUrls, /*count=*/1);
1815   EXPECT_EQ(bookmark_bar_node->children().front()->GetTitle(),
1816             base::UTF8ToUTF16(kTitle2));
1817 }
1818 
TEST(BookmarkModelMergerTest,ShouldRemoveDifferentDuplicatesByGUID)1819 TEST(BookmarkModelMergerTest, ShouldRemoveDifferentDuplicatesByGUID) {
1820   const std::string kTitle1 = "Title 1";
1821   const std::string kTitle2 = "Title 2";
1822   const std::string kUrl = "http://www.url.com/";
1823   const std::string kDifferentUrl = "http://www.different-url.com/";
1824 
1825   const std::string kUrlGUID = base::GenerateGUID();
1826 
1827   // The remote model will have 2 duplicate folders with
1828   // different titles and 2 duplicate bookmarks with different URLs
1829   //
1830   // -------- The remote model --------
1831   // bookmark_bar
1832   //  |- url1(http://www.url.com, UrlGUID)
1833   //  |- url2(http://www.different-url.com, UrlGUID)
1834   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1835       bookmarks::TestBookmarkClient::CreateModel();
1836 
1837   syncer::UpdateResponseDataList updates;
1838   updates.push_back(CreateBookmarkBarNodeUpdateData());
1839 
1840   updates.push_back(CreateUpdateResponseData(
1841       /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle1,
1842       /*url=*/kUrl,
1843       /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID));
1844   updates.back().entity.creation_time = base::Time::Now();
1845   updates.push_back(CreateUpdateResponseData(
1846       /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle2,
1847       /*url=*/kDifferentUrl,
1848       /*is_folder=*/false, /*unique_position=*/MakeRandomPosition(), kUrlGUID));
1849   updates.back().entity.creation_time =
1850       base::Time::Now() - base::TimeDelta::FromDays(1);
1851 
1852   base::HistogramTester histogram_tester;
1853   std::unique_ptr<SyncedBookmarkTracker> tracker =
1854       Merge(std::move(updates), bookmark_model.get());
1855   const bookmarks::BookmarkNode* bookmark_bar_node =
1856       bookmark_model->bookmark_bar_node();
1857   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u));
1858   histogram_tester.ExpectBucketCount(
1859       "Sync.BookmarksGUIDDuplicates",
1860       /*sample=*/ExpectedBookmarksGUIDDuplicates::kDifferentUrls, /*count=*/1);
1861   EXPECT_EQ(bookmark_bar_node->children().front()->GetTitle(),
1862             base::UTF8ToUTF16(kTitle1));
1863 }
1864 
TEST(BookmarkModelMergerTest,ShouldRemoveMatchingFolderDuplicatesByGUID)1865 TEST(BookmarkModelMergerTest, ShouldRemoveMatchingFolderDuplicatesByGUID) {
1866   const std::string kTitle = "Title";
1867 
1868   const std::string kGUID = base::GenerateGUID();
1869 
1870   // The remote model has 2 duplicate folders with the same title and 2
1871   // duplicate bookmarks with the same URL.
1872   //
1873   // -------- The remote model --------
1874   // bookmark_bar
1875   //  |- folder1(Title, GUID)
1876   //  |- folder2(Title, GUID)
1877   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1878       bookmarks::TestBookmarkClient::CreateModel();
1879 
1880   syncer::UpdateResponseDataList updates;
1881   updates.push_back(CreateBookmarkBarNodeUpdateData());
1882 
1883   updates.push_back(CreateUpdateResponseData(
1884       /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle,
1885       /*url=*/"",
1886       /*is_folder=*/true, /*unique_position=*/MakeRandomPosition(), kGUID));
1887   updates.back().entity.creation_time =
1888       base::Time::Now() - base::TimeDelta::FromDays(1);
1889   updates.push_back(CreateUpdateResponseData(
1890       /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle,
1891       /*url=*/"",
1892       /*is_folder=*/true, /*unique_position=*/MakeRandomPosition(), kGUID));
1893   updates.back().entity.creation_time = base::Time::Now();
1894 
1895   base::HistogramTester histogram_tester;
1896   std::unique_ptr<SyncedBookmarkTracker> tracker =
1897       Merge(std::move(updates), bookmark_model.get());
1898   const bookmarks::BookmarkNode* bookmark_bar_node =
1899       bookmark_model->bookmark_bar_node();
1900   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u));
1901   histogram_tester.ExpectBucketCount(
1902       "Sync.BookmarksGUIDDuplicates",
1903       /*sample=*/ExpectedBookmarksGUIDDuplicates::kMatchingFolders,
1904       /*count=*/1);
1905   EXPECT_THAT(tracker->GetEntityForSyncId("Id1"), IsNull());
1906   EXPECT_THAT(tracker->GetEntityForSyncId("Id2"), NotNull());
1907 }
1908 
TEST(BookmarkModelMergerTest,ShouldRemoveDifferentFolderDuplicatesByGUID)1909 TEST(BookmarkModelMergerTest, ShouldRemoveDifferentFolderDuplicatesByGUID) {
1910   const std::string kTitle1 = "Title 1";
1911   const std::string kTitle2 = "Title 2";
1912 
1913   const std::string kGUID = base::GenerateGUID();
1914 
1915   // The remote model has 2 duplicate folders with the same title and 2
1916   // duplicate bookmarks with the same URL.
1917   //
1918   // -------- The remote model --------
1919   // bookmark_bar
1920   //  |- folder1(Title, GUID)
1921   //    |- folder11
1922   //  |- folder2(Title, GUID)
1923   //    |- folder21
1924   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1925       bookmarks::TestBookmarkClient::CreateModel();
1926 
1927   syncer::UpdateResponseDataList updates;
1928   updates.push_back(CreateBookmarkBarNodeUpdateData());
1929 
1930   updates.push_back(CreateUpdateResponseData(
1931       /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle1,
1932       /*url=*/"",
1933       /*is_folder=*/true, MakeRandomPosition(), kGUID));
1934   updates.back().entity.creation_time = base::Time::Now();
1935   updates.push_back(CreateUpdateResponseData(
1936       /*server_id=*/"Id11", /*parent_id=*/"Id1", "Some title",
1937       /*url=*/"", /*is_folder=*/true, MakeRandomPosition()));
1938 
1939   updates.push_back(CreateUpdateResponseData(
1940       /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle2,
1941       /*url=*/"", /*is_folder=*/true, MakeRandomPosition(), kGUID));
1942   updates.back().entity.creation_time =
1943       base::Time::Now() - base::TimeDelta::FromDays(1);
1944   updates.push_back(CreateUpdateResponseData(
1945       /*server_id=*/"Id21", /*parent_id=*/"Id2", "Some title 2",
1946       /*url=*/"", /*is_folder=*/true, MakeRandomPosition()));
1947 
1948   base::HistogramTester histogram_tester;
1949   std::unique_ptr<SyncedBookmarkTracker> tracker =
1950       Merge(std::move(updates), bookmark_model.get());
1951   const bookmarks::BookmarkNode* bookmark_bar_node =
1952       bookmark_model->bookmark_bar_node();
1953   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u));
1954   histogram_tester.ExpectBucketCount(
1955       "Sync.BookmarksGUIDDuplicates",
1956       /*sample=*/ExpectedBookmarksGUIDDuplicates::kDifferentFolders,
1957       /*count=*/1);
1958   EXPECT_THAT(tracker->GetEntityForSyncId("Id1"), NotNull());
1959   EXPECT_THAT(tracker->GetEntityForSyncId("Id2"), IsNull());
1960   EXPECT_EQ(bookmark_bar_node->children().front()->GetTitle(),
1961             base::UTF8ToUTF16(kTitle1));
1962   EXPECT_EQ(bookmark_bar_node->children().front()->children().size(), 2u);
1963 }
1964 
1965 // This tests ensures maximum depth of the bookmark tree is not exceeded. This
1966 // prevents a stack overflow.
TEST(BookmarkModelMergerTest,ShouldEnsureLimitDepthOfTree)1967 TEST(BookmarkModelMergerTest, ShouldEnsureLimitDepthOfTree) {
1968   const std::string kLocalTitle = "local";
1969   const std::string kRemoteTitle = "remote";
1970   const std::string folderIdPrefix = "folder_";
1971   // Maximum depth to sync bookmarks tree to protect against stack overflow.
1972   // This matches |kMaxBookmarkTreeDepth| in bookmark_model_merger.cc.
1973   const size_t kMaxBookmarkTreeDepth = 200;
1974   const size_t kRemoteUpdatesDepth = kMaxBookmarkTreeDepth + 10;
1975 
1976   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1977       bookmarks::TestBookmarkClient::CreateModel();
1978 
1979   // -------- The local model --------
1980   const bookmarks::BookmarkNode* bookmark_bar_node =
1981       bookmark_model->bookmark_bar_node();
1982   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
1983       /*parent=*/bookmark_bar_node, /*index=*/0,
1984       base::UTF8ToUTF16(kLocalTitle));
1985   ASSERT_TRUE(folder);
1986 
1987   // -------- The remote model --------
1988   syncer::UpdateResponseDataList updates;
1989   updates.push_back(CreateBookmarkBarNodeUpdateData());
1990 
1991   std::string parent_id = kBookmarkBarId;
1992   // Create a tree with depth |kRemoteUpdatesDepth| to verify the limit of
1993   // kMaxBookmarkTreeDepth is enforced.
1994   for (size_t i = 1; i < kRemoteUpdatesDepth; ++i) {
1995     std::string folder_id = folderIdPrefix + base::NumberToString(i);
1996     updates.push_back(CreateUpdateResponseData(
1997         /*server_id=*/folder_id, /*parent_id=*/parent_id, kRemoteTitle,
1998         /*url=*/"",
1999         /*is_folder=*/true, MakeRandomPosition()));
2000     parent_id = folder_id;
2001   }
2002 
2003   ASSERT_THAT(updates.size(), Eq(kRemoteUpdatesDepth));
2004 
2005   std::unique_ptr<SyncedBookmarkTracker> tracker =
2006       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
2007   testing::NiceMock<favicon::MockFaviconService> favicon_service;
2008   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
2009                       &favicon_service, tracker.get())
2010       .Merge();
2011 
2012   // Check max depth hasn't been exceeded. Take into account root of the
2013   // tracker and bookmark bar.
2014   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(),
2015               Eq(kMaxBookmarkTreeDepth + 2));
2016 }
2017 
TEST(BookmarkModelMergerTest,ShouldReuploadBookmarkOnEmptyGuid)2018 TEST(BookmarkModelMergerTest, ShouldReuploadBookmarkOnEmptyGuid) {
2019   base::test::ScopedFeatureList override_features;
2020   override_features.InitAndEnableFeature(
2021       switches::kSyncReuploadBookmarkFullTitles);
2022 
2023   const std::string kFolder1Title = "folder1";
2024   const std::string kFolder2Title = "folder2";
2025 
2026   const std::string kFolder1Id = "Folder1Id";
2027   const std::string kFolder2Id = "Folder2Id";
2028 
2029   const std::string suffix = syncer::UniquePosition::RandomSuffix();
2030   const syncer::UniquePosition posFolder1 =
2031       syncer::UniquePosition::InitialPosition(suffix);
2032   const syncer::UniquePosition posFolder2 =
2033       syncer::UniquePosition::After(posFolder1, suffix);
2034 
2035   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
2036       bookmarks::TestBookmarkClient::CreateModel();
2037 
2038   // -------- The remote model --------
2039   syncer::UpdateResponseDataList updates;
2040   updates.push_back(CreateBookmarkBarNodeUpdateData());
2041   updates.push_back(CreateUpdateResponseData(
2042       /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
2043       /*url=*/std::string(),
2044       /*is_folder=*/true, /*unique_position=*/posFolder1,
2045       base::GenerateGUID()));
2046 
2047   // Mimic that the entity didn't have GUID in specifics. This entity should be
2048   // reuploaded later.
2049   updates.back().entity.is_bookmark_guid_in_specifics_preprocessed = true;
2050 
2051   updates.push_back(CreateUpdateResponseData(
2052       /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title,
2053       /*url=*/std::string(),
2054       /*is_folder=*/true, /*unique_position=*/posFolder2,
2055       base::GenerateGUID()));
2056 
2057   std::unique_ptr<SyncedBookmarkTracker> tracker =
2058       Merge(std::move(updates), bookmark_model.get());
2059 
2060   ASSERT_THAT(tracker->GetEntityForSyncId(kFolder1Id), NotNull());
2061   ASSERT_THAT(tracker->GetEntityForSyncId(kFolder2Id), NotNull());
2062 
2063   EXPECT_TRUE(tracker->GetEntityForSyncId(kFolder1Id)->IsUnsynced());
2064   EXPECT_FALSE(tracker->GetEntityForSyncId(kFolder2Id)->IsUnsynced());
2065 }
2066 
TEST(BookmarkModelMergerTest,ShouldRemoveDifferentTypeDuplicatesByGUID)2067 TEST(BookmarkModelMergerTest, ShouldRemoveDifferentTypeDuplicatesByGUID) {
2068   const std::string kTitle = "Title";
2069 
2070   const std::string kGUID = base::GenerateGUID();
2071 
2072   // The remote model has 2 duplicates, a folder and a URL.
2073   //
2074   // -------- The remote model --------
2075   // bookmark_bar
2076   //  |- folder1(GUID)
2077   //    |- folder11
2078   //  |- URL1(GUID)
2079   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
2080       bookmarks::TestBookmarkClient::CreateModel();
2081 
2082   syncer::UpdateResponseDataList updates;
2083   updates.push_back(CreateBookmarkBarNodeUpdateData());
2084 
2085   updates.push_back(CreateUpdateResponseData(
2086       /*server_id=*/"Id1", /*parent_id=*/kBookmarkBarId, kTitle,
2087       /*url=*/"",
2088       /*is_folder=*/true, MakeRandomPosition(), kGUID));
2089   updates.push_back(CreateUpdateResponseData(
2090       /*server_id=*/"Id11", /*parent_id=*/"Id1", "Some title",
2091       /*url=*/"", /*is_folder=*/true, MakeRandomPosition()));
2092 
2093   updates.push_back(CreateUpdateResponseData(
2094       /*server_id=*/"Id2", /*parent_id=*/kBookmarkBarId, kTitle,
2095       /*url=*/"http://url1.com", /*is_folder=*/false, MakeRandomPosition(),
2096       kGUID));
2097 
2098   base::HistogramTester histogram_tester;
2099   std::unique_ptr<SyncedBookmarkTracker> tracker =
2100       Merge(std::move(updates), bookmark_model.get());
2101   const bookmarks::BookmarkNode* bookmark_bar_node =
2102       bookmark_model->bookmark_bar_node();
2103   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(1u));
2104   histogram_tester.ExpectUniqueSample(
2105       "Sync.BookmarksGUIDDuplicates",
2106       /*sample=*/ExpectedBookmarksGUIDDuplicates::kDifferentTypes,
2107       /*count=*/1);
2108   EXPECT_THAT(tracker->GetEntityForSyncId("Id1"), NotNull());
2109   EXPECT_THAT(tracker->GetEntityForSyncId("Id2"), IsNull());
2110   EXPECT_EQ(bookmark_bar_node->children().front()->children().size(), 1u);
2111 }
2112 
2113 }  // namespace sync_bookmarks
2114