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/utf_string_conversions.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "components/bookmarks/browser/bookmark_model.h"
16 #include "components/bookmarks/test/test_bookmark_client.h"
17 #include "components/favicon/core/test/mock_favicon_service.h"
18 #include "components/sync/base/unique_position.h"
19 #include "components/sync_bookmarks/bookmark_specifics_conversions.h"
20 #include "components/sync_bookmarks/switches.h"
21 #include "components/sync_bookmarks/synced_bookmark_tracker.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 
25 using testing::_;
26 using testing::Eq;
27 using testing::Ne;
28 using testing::NotNull;
29 using testing::UnorderedElementsAre;
30 
31 namespace sync_bookmarks {
32 
33 namespace {
34 
35 const char kBookmarkBarId[] = "bookmark_bar_id";
36 const char kBookmarkBarTag[] = "bookmark_bar";
37 
38 // |*arg| must be of type std::vector<std::unique_ptr<bookmarks::BookmarkNode>>.
39 MATCHER_P(ElementRawPointersAre, expected_raw_ptr, "") {
40   if (arg.size() != 1) {
41     return false;
42   }
43   return arg[0].get() == expected_raw_ptr;
44 }
45 
46 // |*arg| must be of type std::vector<std::unique_ptr<bookmarks::BookmarkNode>>.
47 MATCHER_P2(ElementRawPointersAre, expected_raw_ptr0, expected_raw_ptr1, "") {
48   if (arg.size() != 2) {
49     return false;
50   }
51   return arg[0].get() == expected_raw_ptr0 && arg[1].get() == expected_raw_ptr1;
52 }
53 
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 ())54 syncer::UpdateResponseData CreateUpdateResponseData(
55     const std::string& server_id,
56     const std::string& parent_id,
57     const std::string& title,
58     const std::string& url,
59     bool is_folder,
60     const syncer::UniquePosition& unique_position,
61     base::Optional<std::string> guid = base::nullopt,
62     const std::string& icon_url = std::string(),
63     const std::string& icon_data = std::string()) {
64   if (!guid)
65     guid = base::GenerateGUID();
66 
67   syncer::EntityData data;
68   data.id = server_id;
69   data.originator_client_item_id = *guid;
70   data.parent_id = parent_id;
71   data.unique_position = unique_position.ToProto();
72 
73   sync_pb::BookmarkSpecifics* bookmark_specifics =
74       data.specifics.mutable_bookmark();
75   bookmark_specifics->set_guid(*guid);
76   bookmark_specifics->set_legacy_canonicalized_title(title);
77   bookmark_specifics->set_url(url);
78   bookmark_specifics->set_icon_url(icon_url);
79   bookmark_specifics->set_favicon(icon_data);
80 
81   data.is_folder = is_folder;
82   syncer::UpdateResponseData response_data;
83   response_data.entity = std::move(data);
84   // Similar to what's done in the loopback_server.
85   response_data.response_version = 0;
86   return response_data;
87 }
88 
CreateBookmarkBarNodeUpdateData()89 syncer::UpdateResponseData CreateBookmarkBarNodeUpdateData() {
90   syncer::EntityData data;
91   data.id = kBookmarkBarId;
92   data.server_defined_unique_tag = kBookmarkBarTag;
93 
94   data.specifics.mutable_bookmark();
95 
96   syncer::UpdateResponseData response_data;
97   response_data.entity = std::move(data);
98   // Similar to what's done in the loopback_server.
99   response_data.response_version = 0;
100   return response_data;
101 }
102 
PositionOf(const bookmarks::BookmarkNode * node,const SyncedBookmarkTracker & tracker)103 syncer::UniquePosition PositionOf(const bookmarks::BookmarkNode* node,
104                                   const SyncedBookmarkTracker& tracker) {
105   const SyncedBookmarkTracker::Entity* entity =
106       tracker.GetEntityForBookmarkNode(node);
107   return syncer::UniquePosition::FromProto(
108       entity->metadata()->unique_position());
109 }
110 
PositionsInTrackerMatchModel(const bookmarks::BookmarkNode * node,const SyncedBookmarkTracker & tracker)111 bool PositionsInTrackerMatchModel(const bookmarks::BookmarkNode* node,
112                                   const SyncedBookmarkTracker& tracker) {
113   if (node->children().empty()) {
114     return true;
115   }
116   syncer::UniquePosition last_pos =
117       PositionOf(node->children().front().get(), tracker);
118   for (size_t i = 1; i < node->children().size(); ++i) {
119     syncer::UniquePosition pos = PositionOf(node->children()[i].get(), tracker);
120     if (pos.LessThan(last_pos)) {
121       DLOG(ERROR) << "Position of " << node->children()[i]->GetTitle()
122                   << " is less than position of "
123                   << node->children()[i - 1]->GetTitle();
124       return false;
125     }
126     last_pos = pos;
127   }
128   return std::all_of(node->children().cbegin(), node->children().cend(),
129                      [&tracker](const auto& child) {
130                        return PositionsInTrackerMatchModel(child.get(),
131                                                            tracker);
132                      });
133 }
134 
Merge(syncer::UpdateResponseDataList updates,bookmarks::BookmarkModel * bookmark_model)135 std::unique_ptr<SyncedBookmarkTracker> Merge(
136     syncer::UpdateResponseDataList updates,
137     bookmarks::BookmarkModel* bookmark_model) {
138   std::unique_ptr<SyncedBookmarkTracker> tracker =
139       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
140   testing::NiceMock<favicon::MockFaviconService> favicon_service;
141   BookmarkModelMerger(std::move(updates), bookmark_model, &favicon_service,
142                       tracker.get())
143       .Merge();
144   return tracker;
145 }
146 
MakeRandomPosition()147 static syncer::UniquePosition MakeRandomPosition() {
148   const std::string suffix = syncer::UniquePosition::RandomSuffix();
149   return syncer::UniquePosition::InitialPosition(suffix);
150 }
151 
152 }  // namespace
153 
154 // TODO(crbug.com/978430): Parameterize unit tests to account for both
155 // GUID-based and original merge algorithms.
156 
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteModels)157 TEST(BookmarkModelMergerTest, ShouldMergeLocalAndRemoteModels) {
158   const size_t kMaxEntries = 1000;
159 
160   const std::string kFolder1Title = "folder1";
161   const std::string kFolder2Title = "folder2";
162   const std::string kFolder3Title = "folder3";
163 
164   const std::string kUrl1Title = "url1";
165   const std::string kUrl2Title = "url2";
166   const std::string kUrl3Title = "url3";
167   const std::string kUrl4Title = "url4";
168 
169   const std::string kUrl1 = "http://www.url1.com";
170   const std::string kUrl2 = "http://www.url2.com";
171   const std::string kUrl3 = "http://www.url3.com";
172   const std::string kUrl4 = "http://www.url4.com";
173   const std::string kAnotherUrl2 = "http://www.another-url2.com";
174 
175   const std::string kFolder1Id = "Folder1Id";
176   const std::string kFolder3Id = "Folder3Id";
177   const std::string kUrl1Id = "Url1Id";
178   const std::string kUrl2Id = "Url2Id";
179   const std::string kUrl3Id = "Url3Id";
180   const std::string kUrl4Id = "Url4Id";
181 
182   // -------- The local model --------
183   // bookmark_bar
184   //  |- folder 1
185   //    |- url1(http://www.url1.com)
186   //    |- url2(http://www.url2.com)
187   //  |- folder 2
188   //    |- url3(http://www.url3.com)
189   //    |- url4(http://www.url4.com)
190 
191   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
192       bookmarks::TestBookmarkClient::CreateModel();
193 
194   const bookmarks::BookmarkNode* bookmark_bar_node =
195       bookmark_model->bookmark_bar_node();
196   const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder(
197       /*parent=*/bookmark_bar_node, /*index=*/0,
198       base::UTF8ToUTF16(kFolder1Title));
199 
200   const bookmarks::BookmarkNode* folder2 = bookmark_model->AddFolder(
201       /*parent=*/bookmark_bar_node, /*index=*/1,
202       base::UTF8ToUTF16(kFolder2Title));
203 
204   bookmark_model->AddURL(
205       /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kUrl1Title),
206       GURL(kUrl1));
207   bookmark_model->AddURL(
208       /*parent=*/folder1, /*index=*/1, base::UTF8ToUTF16(kUrl2Title),
209       GURL(kUrl2));
210   bookmark_model->AddURL(
211       /*parent=*/folder2, /*index=*/0, base::UTF8ToUTF16(kUrl3Title),
212       GURL(kUrl3));
213   bookmark_model->AddURL(
214       /*parent=*/folder2, /*index=*/1, base::UTF8ToUTF16(kUrl4Title),
215       GURL(kUrl4));
216 
217   // -------- The remote model --------
218   // bookmark_bar
219   //  |- folder 1
220   //    |- url1(http://www.url1.com)
221   //    |- url2(http://www.another-url2.com)
222   //  |- folder 3
223   //    |- url3(http://www.url3.com)
224   //    |- url4(http://www.url4.com)
225 
226   const std::string suffix = syncer::UniquePosition::RandomSuffix();
227   syncer::UniquePosition posFolder1 =
228       syncer::UniquePosition::InitialPosition(suffix);
229   syncer::UniquePosition posFolder3 =
230       syncer::UniquePosition::After(posFolder1, suffix);
231 
232   syncer::UniquePosition posUrl1 =
233       syncer::UniquePosition::InitialPosition(suffix);
234   syncer::UniquePosition posUrl2 =
235       syncer::UniquePosition::After(posUrl1, suffix);
236 
237   syncer::UniquePosition posUrl3 =
238       syncer::UniquePosition::InitialPosition(suffix);
239   syncer::UniquePosition posUrl4 =
240       syncer::UniquePosition::After(posUrl3, suffix);
241 
242   syncer::UpdateResponseDataList updates;
243   updates.push_back(CreateBookmarkBarNodeUpdateData());
244   updates.push_back(CreateUpdateResponseData(
245       /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
246       /*url=*/std::string(),
247       /*is_folder=*/true, /*unique_position=*/posFolder1));
248   updates.push_back(CreateUpdateResponseData(
249       /*server_id=*/kUrl1Id, /*parent_id=*/kFolder1Id, kUrl1Title, kUrl1,
250       /*is_folder=*/false, /*unique_position=*/posUrl1));
251   updates.push_back(CreateUpdateResponseData(
252       /*server_id=*/kUrl2Id, /*parent_id=*/kFolder1Id, kUrl2Title, kAnotherUrl2,
253       /*is_folder=*/false, /*unique_position=*/posUrl2));
254   updates.push_back(CreateUpdateResponseData(
255       /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title,
256       /*url=*/std::string(),
257       /*is_folder=*/true, /*unique_position=*/posFolder3));
258   updates.push_back(CreateUpdateResponseData(
259       /*server_id=*/kUrl3Id, /*parent_id=*/kFolder3Id, kUrl3Title, kUrl3,
260       /*is_folder=*/false, /*unique_position=*/posUrl3));
261   updates.push_back(CreateUpdateResponseData(
262       /*server_id=*/kUrl4Id, /*parent_id=*/kFolder3Id, kUrl4Title, kUrl4,
263       /*is_folder=*/false, /*unique_position=*/posUrl4));
264 
265   // -------- The expected merge outcome --------
266   // bookmark_bar
267   //  |- folder 1
268   //    |- url1(http://www.url1.com)
269   //    |- url2(http://www.another-url2.com)
270   //    |- url2(http://www.url2.com)
271   //  |- folder 3
272   //    |- url3(http://www.url3.com)
273   //    |- url4(http://www.url4.com)
274   //  |- folder 2
275   //    |- url3(http://www.url3.com)
276   //    |- url4(http://www.url4.com)
277 
278   std::unique_ptr<SyncedBookmarkTracker> tracker =
279       Merge(std::move(updates), bookmark_model.get());
280   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(3u));
281 
282   // Verify Folder 1.
283   EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(),
284               Eq(base::ASCIIToUTF16(kFolder1Title)));
285   ASSERT_THAT(bookmark_bar_node->children()[0]->children().size(), Eq(3u));
286 
287   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->GetTitle(),
288               Eq(base::ASCIIToUTF16(kUrl1Title)));
289   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->url(),
290               Eq(GURL(kUrl1)));
291 
292   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[1]->GetTitle(),
293               Eq(base::ASCIIToUTF16(kUrl2Title)));
294   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[1]->url(),
295               Eq(GURL(kAnotherUrl2)));
296 
297   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[2]->GetTitle(),
298               Eq(base::ASCIIToUTF16(kUrl2Title)));
299   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[2]->url(),
300               Eq(GURL(kUrl2)));
301 
302   // Verify Folder 3.
303   EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(),
304               Eq(base::ASCIIToUTF16(kFolder3Title)));
305   ASSERT_THAT(bookmark_bar_node->children()[1]->children().size(), Eq(2u));
306 
307   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->GetTitle(),
308               Eq(base::ASCIIToUTF16(kUrl3Title)));
309   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->url(),
310               Eq(GURL(kUrl3)));
311   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[1]->GetTitle(),
312               Eq(base::ASCIIToUTF16(kUrl4Title)));
313   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[1]->url(),
314               Eq(GURL(kUrl4)));
315 
316   // Verify Folder 2.
317   EXPECT_THAT(bookmark_bar_node->children()[2]->GetTitle(),
318               Eq(base::ASCIIToUTF16(kFolder2Title)));
319   ASSERT_THAT(bookmark_bar_node->children()[2]->children().size(), Eq(2u));
320 
321   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[0]->GetTitle(),
322               Eq(base::ASCIIToUTF16(kUrl3Title)));
323   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[0]->url(),
324               Eq(GURL(kUrl3)));
325   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[1]->GetTitle(),
326               Eq(base::ASCIIToUTF16(kUrl4Title)));
327   EXPECT_THAT(bookmark_bar_node->children()[2]->children()[1]->url(),
328               Eq(GURL(kUrl4)));
329 
330   // Verify the tracker contents.
331   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(11U));
332   std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
333       tracker->GetEntitiesWithLocalChanges(kMaxEntries);
334 
335   EXPECT_THAT(local_changes.size(), Eq(4U));
336   std::vector<const bookmarks::BookmarkNode*> nodes_with_local_changes;
337   for (const SyncedBookmarkTracker::Entity* local_change : local_changes) {
338     nodes_with_local_changes.push_back(local_change->bookmark_node());
339   }
340   // Verify that url2(http://www.url2.com), Folder 2 and children have
341   // corresponding update.
342   EXPECT_THAT(nodes_with_local_changes,
343               UnorderedElementsAre(
344                   bookmark_bar_node->children()[0]->children()[2].get(),
345                   bookmark_bar_node->children()[2].get(),
346                   bookmark_bar_node->children()[2]->children()[0].get(),
347                   bookmark_bar_node->children()[2]->children()[1].get()));
348 
349   // Verify positions in tracker.
350   EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker));
351 }
352 
TEST(BookmarkModelMergerTest,ShouldMergeRemoteReorderToLocalModel)353 TEST(BookmarkModelMergerTest, ShouldMergeRemoteReorderToLocalModel) {
354   const size_t kMaxEntries = 1000;
355 
356   const std::string kFolder1Title = "folder1";
357   const std::string kFolder2Title = "folder2";
358   const std::string kFolder3Title = "folder3";
359 
360   const std::string kFolder1Id = "Folder1Id";
361   const std::string kFolder2Id = "Folder2Id";
362   const std::string kFolder3Id = "Folder3Id";
363 
364   // -------- The local model --------
365   // bookmark_bar
366   //  |- folder 1
367   //  |- folder 2
368   //  |- folder 3
369 
370   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
371       bookmarks::TestBookmarkClient::CreateModel();
372 
373   const bookmarks::BookmarkNode* bookmark_bar_node =
374       bookmark_model->bookmark_bar_node();
375   bookmark_model->AddFolder(
376       /*parent=*/bookmark_bar_node, /*index=*/0,
377       base::UTF8ToUTF16(kFolder1Title));
378 
379   bookmark_model->AddFolder(
380       /*parent=*/bookmark_bar_node, /*index=*/1,
381       base::UTF8ToUTF16(kFolder2Title));
382 
383   bookmark_model->AddFolder(
384       /*parent=*/bookmark_bar_node, /*index=*/2,
385       base::UTF8ToUTF16(kFolder3Title));
386 
387   // -------- The remote model --------
388   // bookmark_bar
389   //  |- folder 1
390   //  |- folder 3
391   //  |- folder 2
392 
393   const std::string suffix = syncer::UniquePosition::RandomSuffix();
394   syncer::UniquePosition posFolder1 =
395       syncer::UniquePosition::InitialPosition(suffix);
396   syncer::UniquePosition posFolder3 =
397       syncer::UniquePosition::After(posFolder1, suffix);
398   syncer::UniquePosition posFolder2 =
399       syncer::UniquePosition::After(posFolder3, suffix);
400 
401   syncer::UpdateResponseDataList updates;
402   updates.push_back(CreateBookmarkBarNodeUpdateData());
403   updates.push_back(CreateUpdateResponseData(
404       /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
405       /*url=*/std::string(),
406       /*is_folder=*/true, /*unique_position=*/posFolder1));
407   updates.push_back(CreateUpdateResponseData(
408       /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title,
409       /*url=*/std::string(),
410       /*is_folder=*/true, /*unique_position=*/posFolder2));
411   updates.push_back(CreateUpdateResponseData(
412       /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title,
413       /*url=*/std::string(),
414       /*is_folder=*/true, /*unique_position=*/posFolder3));
415 
416   // -------- The expected merge outcome --------
417   // bookmark_bar
418   //  |- folder 1
419   //  |- folder 3
420   //  |- folder 2
421 
422   std::unique_ptr<SyncedBookmarkTracker> tracker =
423       Merge(std::move(updates), bookmark_model.get());
424   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(3u));
425 
426   EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(),
427               Eq(base::ASCIIToUTF16(kFolder1Title)));
428   EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(),
429               Eq(base::ASCIIToUTF16(kFolder3Title)));
430   EXPECT_THAT(bookmark_bar_node->children()[2]->GetTitle(),
431               Eq(base::ASCIIToUTF16(kFolder2Title)));
432 
433   // Verify the tracker contents.
434   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(4U));
435 
436   // There should be no local changes.
437   std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
438       tracker->GetEntitiesWithLocalChanges(kMaxEntries);
439   EXPECT_THAT(local_changes.size(), Eq(0U));
440 
441   // Verify positions in tracker.
442   EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker));
443 }
444 
TEST(BookmarkModelMergerTest,ShouldMergeFaviconsForRemoteNodesOnly)445 TEST(BookmarkModelMergerTest, ShouldMergeFaviconsForRemoteNodesOnly) {
446   const std::string kTitle1 = "title1";
447   const GURL kUrl1("http://www.url1.com");
448   // -------- The local model --------
449   // bookmark_bar
450   //  |- title 1
451 
452   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
453       bookmarks::TestBookmarkClient::CreateModel();
454 
455   const bookmarks::BookmarkNode* bookmark_bar_node =
456       bookmark_model->bookmark_bar_node();
457   bookmark_model->AddURL(
458       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle1),
459       kUrl1);
460 
461   // -------- The remote model --------
462   // bookmark_bar
463   //  |- title 2
464 
465   const std::string kTitle2 = "title2";
466   const std::string kId2 = "Id2";
467   const GURL kUrl2("http://www.url2.com");
468   const GURL kIcon2Url("http://www.icon-url.com");
469   syncer::UniquePosition pos2 = syncer::UniquePosition::InitialPosition(
470       syncer::UniquePosition::RandomSuffix());
471 
472   syncer::UpdateResponseDataList updates;
473   updates.push_back(CreateBookmarkBarNodeUpdateData());
474   updates.push_back(CreateUpdateResponseData(
475       /*server_id=*/kId2, /*parent_id=*/kBookmarkBarId, kTitle2, kUrl2.spec(),
476       /*is_folder=*/false, /*unique_position=*/pos2, base::GenerateGUID(),
477       kIcon2Url.spec(),
478       /*icon_data=*/"PNG"));
479 
480   // -------- The expected merge outcome --------
481   // bookmark_bar
482   //  |- title 2
483   //  |- title 1
484 
485   std::unique_ptr<SyncedBookmarkTracker> tracker =
486       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
487   testing::NiceMock<favicon::MockFaviconService> favicon_service;
488 
489   // Favicon should be set for the remote node.
490   EXPECT_CALL(favicon_service,
491               AddPageNoVisitForBookmark(kUrl2, base::UTF8ToUTF16(kTitle2)));
492   EXPECT_CALL(favicon_service, MergeFavicon(kUrl2, _, _, _, _));
493 
494   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
495                       &favicon_service, tracker.get())
496       .Merge();
497 }
498 
499 // This tests that canonical titles produced by legacy clients are properly
500 // matched. Legacy clients append blank space to empty titles.
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyCanonicalTitle)501 TEST(BookmarkModelMergerTest,
502      ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyCanonicalTitle) {
503   const std::string kLocalTitle = "";
504   const std::string kRemoteTitle = " ";
505   const std::string kId = "Id";
506 
507   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
508       bookmarks::TestBookmarkClient::CreateModel();
509 
510   // -------- The local model --------
511   const bookmarks::BookmarkNode* bookmark_bar_node =
512       bookmark_model->bookmark_bar_node();
513   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
514       /*parent=*/bookmark_bar_node, /*index=*/0,
515       base::UTF8ToUTF16(kLocalTitle));
516   ASSERT_TRUE(folder);
517 
518   // -------- The remote model --------
519   const std::string suffix = syncer::UniquePosition::RandomSuffix();
520   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
521 
522   syncer::UpdateResponseDataList updates;
523   updates.push_back(CreateBookmarkBarNodeUpdateData());
524   updates.push_back(CreateUpdateResponseData(
525       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTitle,
526       /*url=*/std::string(),
527       /*is_folder=*/true, /*unique_position=*/pos));
528 
529   std::unique_ptr<SyncedBookmarkTracker> tracker =
530       Merge(std::move(updates), bookmark_model.get());
531 
532   // Both titles should have matched against each other and only node is in the
533   // model and the tracker.
534   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
535   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
536 }
537 
538 // This tests that truncated titles produced by legacy clients are properly
539 // matched.
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyTruncatedTitle)540 TEST(BookmarkModelMergerTest,
541      ShouldMergeLocalAndRemoteNodesWhenRemoteHasLegacyTruncatedTitle) {
542   const std::string kLocalLongTitle =
543       "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst"
544       "uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"
545       "OPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
546       "ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAB"
547       "CDEFGHIJKLMNOPQRSTUVWXYZ";
548   const std::string kRemoteTruncatedTitle =
549       "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst"
550       "uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"
551       "OPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
552       "ijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU";
553   const std::string kId = "Id";
554 
555   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
556       bookmarks::TestBookmarkClient::CreateModel();
557 
558   // -------- The local model --------
559   const bookmarks::BookmarkNode* bookmark_bar_node =
560       bookmark_model->bookmark_bar_node();
561   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
562       /*parent=*/bookmark_bar_node, /*index=*/0,
563       base::UTF8ToUTF16(kLocalLongTitle));
564   ASSERT_TRUE(folder);
565 
566   // -------- The remote model --------
567   const std::string suffix = syncer::UniquePosition::RandomSuffix();
568   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
569 
570   syncer::UpdateResponseDataList updates;
571   updates.push_back(CreateBookmarkBarNodeUpdateData());
572   updates.push_back(CreateUpdateResponseData(
573       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTruncatedTitle,
574       /*url=*/std::string(),
575       /*is_folder=*/true, /*unique_position=*/pos));
576 
577   std::unique_ptr<SyncedBookmarkTracker> tracker =
578       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
579   testing::NiceMock<favicon::MockFaviconService> favicon_service;
580   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
581                       &favicon_service, tracker.get())
582       .Merge();
583 
584   // Both titles should have matched against each other and only node is in the
585   // model and the tracker.
586   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
587   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
588 }
589 
TEST(BookmarkModelMergerTest,ShouldMergeNodesWhenRemoteHasLegacyTruncatedTitleInFullTitle)590 TEST(BookmarkModelMergerTest,
591      ShouldMergeNodesWhenRemoteHasLegacyTruncatedTitleInFullTitle) {
592   const std::string kLocalLongTitle(300, 'A');
593   const std::string kRemoteTruncatedFullTitle(255, 'A');
594   const std::string kId = "Id";
595 
596   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
597       bookmarks::TestBookmarkClient::CreateModel();
598 
599   // -------- The local model --------
600   const bookmarks::BookmarkNode* bookmark_bar_node =
601       bookmark_model->bookmark_bar_node();
602   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
603       /*parent=*/bookmark_bar_node, /*index=*/0,
604       base::UTF8ToUTF16(kLocalLongTitle));
605   ASSERT_TRUE(folder);
606 
607   // -------- The remote model --------
608   const std::string suffix = syncer::UniquePosition::RandomSuffix();
609   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
610 
611   syncer::UpdateResponseDataList updates;
612   updates.push_back(CreateBookmarkBarNodeUpdateData());
613   updates.push_back(CreateUpdateResponseData(
614       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId,
615       kRemoteTruncatedFullTitle,
616       /*url=*/std::string(),
617       /*is_folder=*/true, /*unique_position=*/pos));
618 
619   updates.back().entity.specifics.mutable_bookmark()->set_full_title(
620       kRemoteTruncatedFullTitle);
621 
622   std::unique_ptr<SyncedBookmarkTracker> tracker =
623       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
624   testing::NiceMock<favicon::MockFaviconService> favicon_service;
625   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
626                       &favicon_service, tracker.get())
627       .Merge();
628 
629   // Both titles should have matched against each other and only node is in the
630   // model and the tracker.
631   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
632   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
633 }
634 
635 // This test checks that local node with truncated title will merge with remote
636 // node which has full title.
TEST(BookmarkModelMergerTest,ShouldMergeLocalAndRemoteNodesWhenLocalHasLegacyTruncatedTitle)637 TEST(BookmarkModelMergerTest,
638      ShouldMergeLocalAndRemoteNodesWhenLocalHasLegacyTruncatedTitle) {
639   const std::string kRemoteFullTitle(300, 'A');
640   const std::string kLocalTruncatedTitle(255, 'A');
641   const std::string kId = "Id";
642 
643   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
644       bookmarks::TestBookmarkClient::CreateModel();
645 
646   // -------- The local model --------
647   const bookmarks::BookmarkNode* bookmark_bar_node =
648       bookmark_model->bookmark_bar_node();
649   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
650       /*parent=*/bookmark_bar_node, /*index=*/0,
651       base::UTF8ToUTF16(kLocalTruncatedTitle));
652   ASSERT_TRUE(folder);
653 
654   // -------- The remote model --------
655   const std::string suffix = syncer::UniquePosition::RandomSuffix();
656   syncer::UniquePosition pos = syncer::UniquePosition::InitialPosition(suffix);
657 
658   syncer::UpdateResponseDataList updates;
659   updates.push_back(CreateBookmarkBarNodeUpdateData());
660   updates.push_back(CreateUpdateResponseData(
661       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId,
662       sync_bookmarks::FullTitleToLegacyCanonicalizedTitle(kRemoteFullTitle),
663       /*url=*/std::string(),
664       /*is_folder=*/true, /*unique_position=*/pos));
665   ASSERT_EQ(
666       kLocalTruncatedTitle,
667       updates.back().entity.specifics.bookmark().legacy_canonicalized_title());
668 
669   updates.back().entity.specifics.mutable_bookmark()->set_full_title(
670       kRemoteFullTitle);
671 
672   std::unique_ptr<SyncedBookmarkTracker> tracker =
673       SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
674   testing::NiceMock<favicon::MockFaviconService> favicon_service;
675   BookmarkModelMerger(std::move(updates), bookmark_model.get(),
676                       &favicon_service, tracker.get())
677       .Merge();
678 
679   // Both titles should have matched against each other and only node is in the
680   // model and the tracker.
681   EXPECT_THAT(bookmark_bar_node->children().size(), Eq(1u));
682   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(2U));
683 }
684 
TEST(BookmarkModelMergerTest,ShouldMergeAndUseRemoteGUID)685 TEST(BookmarkModelMergerTest, ShouldMergeAndUseRemoteGUID) {
686   const std::string kId = "Id";
687   const std::string kTitle = "Title";
688   const std::string kRemoteGuid = base::GenerateGUID();
689 
690   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
691       bookmarks::TestBookmarkClient::CreateModel();
692 
693   // -------- The local model --------
694   const bookmarks::BookmarkNode* bookmark_bar_node =
695       bookmark_model->bookmark_bar_node();
696   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
697       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle));
698   ASSERT_TRUE(folder);
699 
700   // -------- The remote model --------
701   syncer::UpdateResponseDataList updates;
702   updates.push_back(CreateBookmarkBarNodeUpdateData());
703   updates.push_back(CreateUpdateResponseData(
704       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle,
705       /*url=*/std::string(),
706       /*is_folder=*/true, /*unique_position=*/MakeRandomPosition(),
707       /*guid=*/kRemoteGuid));
708 
709   std::unique_ptr<SyncedBookmarkTracker> tracker =
710       Merge(std::move(updates), bookmark_model.get());
711 
712   // Node should have been replaced and GUID should be set to that stored in the
713   // specifics.
714   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
715   const bookmarks::BookmarkNode* bookmark =
716       bookmark_model->bookmark_bar_node()->children()[0].get();
717   EXPECT_EQ(bookmark->guid(), kRemoteGuid);
718   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
719 }
720 
TEST(BookmarkModelMergerTest,ShouldMergeAndKeepOldGUIDWhenRemoteGUIDIsInvalid)721 TEST(BookmarkModelMergerTest,
722      ShouldMergeAndKeepOldGUIDWhenRemoteGUIDIsInvalid) {
723   const std::string kId = "Id";
724   const std::string kTitle = "Title";
725 
726   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
727       bookmarks::TestBookmarkClient::CreateModel();
728 
729   // -------- The local model --------
730   const bookmarks::BookmarkNode* bookmark_bar_node =
731       bookmark_model->bookmark_bar_node();
732   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
733       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle));
734   ASSERT_TRUE(folder);
735   const std::string old_guid = folder->guid();
736 
737   // -------- The remote model --------
738   syncer::UpdateResponseDataList updates;
739   updates.push_back(CreateBookmarkBarNodeUpdateData());
740   updates.push_back(CreateUpdateResponseData(
741       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle,
742       /*url=*/std::string(),
743       /*is_folder=*/true,
744       /*unique_position=*/MakeRandomPosition(),
745       /*guid=*/""));
746 
747   std::unique_ptr<SyncedBookmarkTracker> tracker =
748       Merge(std::move(updates), bookmark_model.get());
749 
750   // Node should not have been replaced and GUID should not have been set to
751   // that stored in the specifics, as it was invalid.
752   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
753   const bookmarks::BookmarkNode* bookmark =
754       bookmark_model->bookmark_bar_node()->children()[0].get();
755   EXPECT_EQ(bookmark->guid(), old_guid);
756   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
757 }
758 
TEST(BookmarkModelMergerTest,ShouldMergeBookmarkByGUID)759 TEST(BookmarkModelMergerTest, ShouldMergeBookmarkByGUID) {
760   base::test::ScopedFeatureList override_features;
761   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
762 
763   const std::string kId = "Id";
764   const std::string kLocalTitle = "Title 1";
765   const std::string kRemoteTitle = "Title 2";
766   const std::string kUrl = "http://www.foo.com/";
767   const std::string kGuid = base::GenerateGUID();
768 
769   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
770       bookmarks::TestBookmarkClient::CreateModel();
771 
772   // -------- The local model --------
773   // bookmark_bar
774   //  | - bookmark(kGuid/kLocalTitle)
775 
776   const bookmarks::BookmarkNode* bookmark_bar_node =
777       bookmark_model->bookmark_bar_node();
778   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
779       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kLocalTitle),
780       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
781   ASSERT_TRUE(bookmark);
782   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
783 
784   // -------- The remote model --------
785   // bookmark_bar
786   //  | - bookmark(kGuid/kRemoteTitle)
787 
788   syncer::UpdateResponseDataList updates;
789   updates.push_back(CreateBookmarkBarNodeUpdateData());
790   updates.push_back(CreateUpdateResponseData(
791       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTitle,
792       /*url=*/kUrl,
793       /*is_folder=*/false,
794       /*unique_position=*/MakeRandomPosition(),
795       /*guid=*/kGuid));
796 
797   std::unique_ptr<SyncedBookmarkTracker> tracker =
798       Merge(std::move(updates), bookmark_model.get());
799 
800   // -------- The merged model --------
801   // bookmark_bar
802   //  |- bookmark(kGuid/kRemoteTitle)
803 
804   // Node should have been merged.
805   EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
806   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kRemoteTitle));
807   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
808 }
809 
TEST(BookmarkModelMergerTest,ShouldMergeBookmarkByGUIDAndReparent)810 TEST(BookmarkModelMergerTest, ShouldMergeBookmarkByGUIDAndReparent) {
811   base::test::ScopedFeatureList override_features;
812   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
813 
814   const std::string kId = "Id";
815   const std::string kLocalTitle = "Title 1";
816   const std::string kRemoteTitle = "Title 2";
817   const std::string kUrl = "http://www.foo.com/";
818   const std::string kGuid = base::GenerateGUID();
819 
820   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
821       bookmarks::TestBookmarkClient::CreateModel();
822 
823   // -------- The local model --------
824   // bookmark_bar
825   //  | - folder
826   //    | - bookmark(kGuid)
827 
828   const bookmarks::BookmarkNode* bookmark_bar_node =
829       bookmark_model->bookmark_bar_node();
830   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
831       /*parent=*/bookmark_bar_node, /*index=*/0,
832       base::UTF8ToUTF16("Folder Title"), nullptr, base::GenerateGUID());
833   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
834       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16(kLocalTitle),
835       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
836   ASSERT_TRUE(folder);
837   ASSERT_TRUE(bookmark);
838   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
839   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
840 
841   // -------- The remote model --------
842   // bookmark_bar
843   //  |- bookmark(kGuid)
844 
845   syncer::UpdateResponseDataList updates;
846   updates.push_back(CreateBookmarkBarNodeUpdateData());
847   updates.push_back(CreateUpdateResponseData(
848       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kRemoteTitle,
849       /*url=*/kUrl,
850       /*is_folder=*/false,
851       /*unique_position=*/MakeRandomPosition(),
852       /*guid=*/kGuid));
853 
854   std::unique_ptr<SyncedBookmarkTracker> tracker =
855       Merge(std::move(updates), bookmark_model.get());
856 
857   // -------- The merged model --------
858   // bookmark_bar
859   //  | - bookmark(kGuid/kRemoteTitle)
860   //  | - folder
861 
862   // Node should have been merged and the local node should have been
863   // reparented.
864   EXPECT_THAT(bookmark_bar_node->children(),
865               ElementRawPointersAre(bookmark, folder));
866   EXPECT_EQ(folder->children().size(), 0u);
867   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kRemoteTitle));
868   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
869   EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder), NotNull());
870 }
871 
TEST(BookmarkModelMergerTest,ShouldMergeFolderByGUIDAndNotSemantics)872 TEST(BookmarkModelMergerTest, ShouldMergeFolderByGUIDAndNotSemantics) {
873   base::test::ScopedFeatureList override_features;
874   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
875 
876   const std::string kFolderId = "Folder Id";
877   const std::string kTitle1 = "Title 1";
878   const std::string kTitle2 = "Title 2";
879   const std::string kUrl = "http://www.foo.com/";
880   const std::string kGuid1 = base::GenerateGUID();
881   const std::string kGuid2 = base::GenerateGUID();
882 
883   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
884       bookmarks::TestBookmarkClient::CreateModel();
885 
886   // -------- The local model --------
887   // bookmark_bar
888   //  | - folder 1 (kGuid1/kTitle1)
889   //    | - folder 2 (kGuid2/kTitle2)
890 
891   const bookmarks::BookmarkNode* bookmark_bar_node =
892       bookmark_model->bookmark_bar_node();
893   const bookmarks::BookmarkNode* folder1 =
894       bookmark_model->AddFolder(/*parent=*/bookmark_bar_node, /*index=*/0,
895                                 base::UTF8ToUTF16(kTitle1), nullptr, kGuid1);
896   const bookmarks::BookmarkNode* folder2 =
897       bookmark_model->AddFolder(/*parent=*/folder1, /*index=*/0,
898                                 base::UTF8ToUTF16(kTitle2), nullptr, kGuid2);
899   ASSERT_TRUE(folder1);
900   ASSERT_TRUE(folder2);
901   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder1));
902   ASSERT_THAT(folder1->children(), ElementRawPointersAre(folder2));
903 
904   // -------- The remote model --------
905   // bookmark_bar
906   //  | - folder (kGuid2/kTitle1)
907 
908   syncer::UpdateResponseDataList updates;
909   updates.push_back(CreateBookmarkBarNodeUpdateData());
910 
911   // Add a remote folder to correspond to the local folder by GUID and
912   // semantics.
913   updates.push_back(CreateUpdateResponseData(
914       /*server_id=*/kFolderId, /*parent_id=*/kBookmarkBarId, kTitle1,
915       /*url=*/"",
916       /*is_folder=*/true,
917       /*unique_position=*/MakeRandomPosition(),
918       /*guid=*/kGuid2));
919 
920   std::unique_ptr<SyncedBookmarkTracker> tracker =
921       Merge(std::move(updates), bookmark_model.get());
922 
923   // -------- The merged model --------
924   // bookmark_bar
925   //  | - folder 2 (kGuid2/kTitle1)
926   //  | - folder 1 (kGuid1/kTitle1)
927 
928   // Node should have been merged with its GUID match.
929   EXPECT_THAT(bookmark_bar_node->children(),
930               ElementRawPointersAre(folder2, folder1));
931   EXPECT_EQ(folder1->guid(), kGuid1);
932   EXPECT_EQ(folder1->GetTitle(), base::UTF8ToUTF16(kTitle1));
933   EXPECT_EQ(folder1->children().size(), 0u);
934   EXPECT_EQ(folder2->guid(), kGuid2);
935   EXPECT_EQ(folder2->GetTitle(), base::UTF8ToUTF16(kTitle1));
936   EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder1), NotNull());
937   EXPECT_THAT(tracker->GetEntityForBookmarkNode(folder2), NotNull());
938 }
939 
TEST(BookmarkModelMergerTest,ShouldIgnoreChildrenForNonFolderNodes)940 TEST(BookmarkModelMergerTest, ShouldIgnoreChildrenForNonFolderNodes) {
941   const std::string kParentId = "parent_id";
942   const std::string kChildId = "child_id";
943   const std::string kParentTitle = "Parent Title";
944   const std::string kChildTitle = "Child Title";
945   const std::string kGuid1 = base::GenerateGUID();
946   const std::string kGuid2 = base::GenerateGUID();
947   const std::string kUrl1 = "http://www.foo.com/";
948   const std::string kUrl2 = "http://www.bar.com/";
949 
950   // -------- The remote model --------
951   // bookmark_bar
952   //  | - bookmark (kGuid1/kParentTitle, not a folder)
953   //    | - bookmark
954 
955   syncer::UpdateResponseDataList updates;
956   updates.push_back(CreateBookmarkBarNodeUpdateData());
957 
958   const std::string suffix = syncer::UniquePosition::RandomSuffix();
959   const syncer::UniquePosition pos1 =
960       syncer::UniquePosition::InitialPosition(suffix);
961   const syncer::UniquePosition pos2 =
962       syncer::UniquePosition::After(pos1, suffix);
963 
964   updates.push_back(CreateUpdateResponseData(
965       /*server_id=*/kParentId, /*parent_id=*/kBookmarkBarId, kParentTitle,
966       /*url=*/kUrl1,
967       /*is_folder=*/false,
968       /*unique_position=*/pos1,
969       /*guid=*/kGuid1));
970 
971   updates.push_back(CreateUpdateResponseData(
972       /*server_id=*/kChildId, /*parent_id=*/kParentId, kChildTitle,
973       /*url=*/kUrl2,
974       /*is_folder=*/false,
975       /*unique_position=*/pos2,
976       /*guid=*/kGuid2));
977 
978   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
979       bookmarks::TestBookmarkClient::CreateModel();
980   std::unique_ptr<SyncedBookmarkTracker> tracker =
981       Merge(std::move(updates), bookmark_model.get());
982 
983   // -------- The merged model --------
984   // bookmark_bar
985   //  | - bookmark (kGuid1/kParentTitle)
986 
987   const bookmarks::BookmarkNode* bookmark_bar_node =
988       bookmark_model->bookmark_bar_node();
989 
990   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
991   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid1);
992   EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(),
993             base::UTF8ToUTF16(kParentTitle));
994   EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 0u);
995   EXPECT_EQ(tracker->TrackedEntitiesCountForTest(), 2U);
996 }
997 
TEST(BookmarkModelMergerTest,ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithSemanticsNodeFirst)998 TEST(
999     BookmarkModelMergerTest,
1000     ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithSemanticsNodeFirst) {
1001   base::test::ScopedFeatureList override_features;
1002   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1003 
1004   const std::string kFolderId1 = "Folder Id 1";
1005   const std::string kFolderId2 = "Folder Id 2";
1006   const std::string kOriginalTitle = "Original Title";
1007   const std::string kNewTitle = "New Title";
1008   const std::string kGuid1 = base::GenerateGUID();
1009   const std::string kGuid2 = base::GenerateGUID();
1010 
1011   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1012       bookmarks::TestBookmarkClient::CreateModel();
1013 
1014   // -------- The local model --------
1015   // bookmark_bar
1016   //  | - folder (kGuid1/kOriginalTitle)
1017   //    | - bookmark
1018 
1019   const bookmarks::BookmarkNode* bookmark_bar_node =
1020       bookmark_model->bookmark_bar_node();
1021   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
1022       /*parent=*/bookmark_bar_node, /*index=*/0,
1023       base::UTF8ToUTF16(kOriginalTitle), nullptr, kGuid1);
1024   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1025       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16("Bookmark Title"),
1026       GURL("http://foo.com/"), nullptr, base::Time::Now(),
1027       base::GenerateGUID());
1028   ASSERT_TRUE(folder);
1029   ASSERT_TRUE(bookmark);
1030   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
1031   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
1032 
1033   // -------- The remote model --------
1034   // bookmark_bar
1035   //  | - folder (kGuid2/kOriginalTitle)
1036   //  | - folder (kGuid1/kNewTitle)
1037 
1038   syncer::UpdateResponseDataList updates;
1039   updates.push_back(CreateBookmarkBarNodeUpdateData());
1040 
1041   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1042   syncer::UniquePosition pos1 = syncer::UniquePosition::InitialPosition(suffix);
1043   syncer::UniquePosition pos2 = syncer::UniquePosition::After(pos1, suffix);
1044 
1045   // Add a remote folder to correspond to the local folder by semantics and not
1046   // GUID.
1047   updates.push_back(CreateUpdateResponseData(
1048       /*server_id=*/kFolderId1, /*parent_id=*/kBookmarkBarId, kOriginalTitle,
1049       /*url=*/"",
1050       /*is_folder=*/true,
1051       /*unique_position=*/pos1,
1052       /*guid=*/kGuid2));
1053 
1054   // Add a remote folder to correspond to the local folder by GUID and not
1055   // semantics.
1056   updates.push_back(CreateUpdateResponseData(
1057       /*server_id=*/kFolderId2, /*parent_id=*/kBookmarkBarId, kNewTitle,
1058       /*url=*/"",
1059       /*is_folder=*/true,
1060       /*unique_position=*/pos2,
1061       /*guid=*/kGuid1));
1062 
1063   std::unique_ptr<SyncedBookmarkTracker> tracker =
1064       Merge(std::move(updates), bookmark_model.get());
1065 
1066   // -------- The merged model --------
1067   // bookmark_bar
1068   //  | - folder (kGuid2/kOriginalTitle)
1069   //  | - folder (kGuid1/kNewTitle)
1070   //    | - bookmark
1071 
1072   // Node should have been merged with its GUID match.
1073   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1074   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid2);
1075   EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(),
1076             base::UTF8ToUTF16(kOriginalTitle));
1077   EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 0u);
1078   EXPECT_EQ(bookmark_bar_node->children()[1]->guid(), kGuid1);
1079   EXPECT_EQ(bookmark_bar_node->children()[1]->GetTitle(),
1080             base::UTF8ToUTF16(kNewTitle));
1081   EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 1u);
1082   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(4U));
1083 }
1084 
TEST(BookmarkModelMergerTest,ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithGUIDNodeFirst)1085 TEST(BookmarkModelMergerTest,
1086      ShouldIgnoreFolderSemanticsMatchAndLaterMatchByGUIDWithGUIDNodeFirst) {
1087   base::test::ScopedFeatureList override_features;
1088   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1089 
1090   const std::string kFolderId1 = "Folder Id 1";
1091   const std::string kFolderId2 = "Folder Id 2";
1092   const std::string kOriginalTitle = "Original Title";
1093   const std::string kNewTitle = "New Title";
1094   const std::string kGuid1 = base::GenerateGUID();
1095   const std::string kGuid2 = base::GenerateGUID();
1096 
1097   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1098       bookmarks::TestBookmarkClient::CreateModel();
1099 
1100   // -------- The local model --------
1101   // bookmark_bar
1102   //  | - folder (kGuid1/kOriginalTitle)
1103   //    | - bookmark
1104 
1105   const bookmarks::BookmarkNode* bookmark_bar_node =
1106       bookmark_model->bookmark_bar_node();
1107   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
1108       /*parent=*/bookmark_bar_node, /*index=*/0,
1109       base::UTF8ToUTF16(kOriginalTitle), nullptr, kGuid1);
1110   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1111       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16("Bookmark Title"),
1112       GURL("http://foo.com/"), nullptr, base::Time::Now(),
1113       base::GenerateGUID());
1114   ASSERT_TRUE(folder);
1115   ASSERT_TRUE(bookmark);
1116   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
1117   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
1118 
1119   // -------- The remote model --------
1120   // bookmark_bar
1121   //  | - folder (kGuid1/kNewTitle)
1122   //  | - folder (kGuid2/kOriginalTitle)
1123 
1124   syncer::UpdateResponseDataList updates;
1125   updates.push_back(CreateBookmarkBarNodeUpdateData());
1126 
1127   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1128   syncer::UniquePosition pos1 = syncer::UniquePosition::InitialPosition(suffix);
1129   syncer::UniquePosition pos2 = syncer::UniquePosition::After(pos1, suffix);
1130 
1131   // Add a remote folder to correspond to the local folder by GUID and not
1132   // semantics.
1133   updates.push_back(CreateUpdateResponseData(
1134       /*server_id=*/kFolderId2, /*parent_id=*/kBookmarkBarId, kNewTitle,
1135       /*url=*/"",
1136       /*is_folder=*/true,
1137       /*unique_position=*/pos1,
1138       /*guid=*/kGuid1));
1139 
1140   // Add a remote folder to correspond to the local folder by
1141   // semantics and not GUID.
1142   updates.push_back(CreateUpdateResponseData(
1143       /*server_id=*/kFolderId1, /*parent_id=*/kBookmarkBarId, kOriginalTitle,
1144       /*url=*/"",
1145       /*is_folder=*/true,
1146       /*unique_position=*/pos2,
1147       /*guid=*/kGuid2));
1148 
1149   Merge(std::move(updates), bookmark_model.get());
1150 
1151   // -------- The merged model --------
1152   // bookmark_bar
1153   //  | - folder (kGuid1/kNewTitle)
1154   //  | - folder (kGuid2/kOriginalTitle)
1155 
1156   // Node should have been merged with its GUID match.
1157   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1158   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid1);
1159   EXPECT_EQ(bookmark_bar_node->children()[0]->GetTitle(),
1160             base::UTF8ToUTF16(kNewTitle));
1161   EXPECT_EQ(bookmark_bar_node->children()[0]->children().size(), 1u);
1162   EXPECT_EQ(bookmark_bar_node->children()[1]->guid(), kGuid2);
1163   EXPECT_EQ(bookmark_bar_node->children()[1]->GetTitle(),
1164             base::UTF8ToUTF16(kOriginalTitle));
1165   EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 0u);
1166 }
1167 
TEST(BookmarkModelMergerTest,ShouldReplaceBookmarkGUIDWithConflictingURLs)1168 TEST(BookmarkModelMergerTest, ShouldReplaceBookmarkGUIDWithConflictingURLs) {
1169   base::test::ScopedFeatureList override_features;
1170   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1171 
1172   const std::string kTitle = "Title";
1173   const std::string kUrl1 = "http://www.foo.com/";
1174   const std::string kUrl2 = "http://www.bar.com/";
1175   const std::string kGuid = base::GenerateGUID();
1176 
1177   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1178       bookmarks::TestBookmarkClient::CreateModel();
1179 
1180   // -------- The local model --------
1181   // bookmark_bar
1182   //  | - bookmark (kGuid/kUril1)
1183 
1184   const bookmarks::BookmarkNode* bookmark_bar_node =
1185       bookmark_model->bookmark_bar_node();
1186   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1187       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1188       GURL(kUrl1), nullptr, base::Time::Now(), kGuid);
1189   ASSERT_TRUE(bookmark);
1190   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1191 
1192   // -------- The remote model --------
1193   // bookmark_bar
1194   //  | - bookmark (kGuid/kUrl2)
1195 
1196   syncer::UpdateResponseDataList updates;
1197   updates.push_back(CreateBookmarkBarNodeUpdateData());
1198 
1199   updates.push_back(CreateUpdateResponseData(  // Remote B
1200       /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, kTitle,
1201       /*url=*/kUrl2,
1202       /*is_folder=*/false,
1203       /*unique_position=*/MakeRandomPosition(),
1204       /*guid=*/kGuid));
1205 
1206   Merge(std::move(updates), bookmark_model.get());
1207 
1208   // -------- The merged model --------
1209   // bookmark_bar
1210   //  | - bookmark (kGuid/kUrl2)
1211   //  | - bookmark ([new GUID]/kUrl1)
1212 
1213   // Conflicting node GUID should have been replaced.
1214   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1215   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid);
1216   EXPECT_EQ(bookmark_bar_node->children()[0]->url(), kUrl2);
1217   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid);
1218   EXPECT_TRUE(
1219       base::IsValidGUIDOutputString(bookmark_bar_node->children()[1]->guid()));
1220   EXPECT_EQ(bookmark_bar_node->children()[1]->url(), kUrl1);
1221 }
1222 
TEST(BookmarkModelMergerTest,ShouldReplaceBookmarkGUIDWithConflictingTypes)1223 TEST(BookmarkModelMergerTest, ShouldReplaceBookmarkGUIDWithConflictingTypes) {
1224   base::test::ScopedFeatureList override_features;
1225   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1226 
1227   const std::string kTitle = "Title";
1228   const std::string kGuid = base::GenerateGUID();
1229 
1230   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1231       bookmarks::TestBookmarkClient::CreateModel();
1232 
1233   // -------- The local model --------
1234   // bookmark_bar
1235   //  | - bookmark (kGuid)
1236 
1237   const bookmarks::BookmarkNode* bookmark_bar_node =
1238       bookmark_model->bookmark_bar_node();
1239   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1240       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1241       GURL("http://www.foo.com/"), nullptr, base::Time::Now(), kGuid);
1242   ASSERT_TRUE(bookmark);
1243   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1244 
1245   // -------- The remote model --------
1246   // bookmark_bar
1247   //  | - folder(kGuid)
1248 
1249   syncer::UpdateResponseDataList updates;
1250   updates.push_back(CreateBookmarkBarNodeUpdateData());
1251 
1252   updates.push_back(CreateUpdateResponseData(  // Remote B
1253       /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, kTitle,
1254       /*url=*/"",
1255       /*is_folder=*/true,
1256       /*unique_position=*/MakeRandomPosition(),
1257       /*guid=*/kGuid));
1258 
1259   Merge(std::move(updates), bookmark_model.get());
1260 
1261   // -------- The merged model --------
1262   // bookmark_bar
1263   //  | - folder (kGuid)
1264   //  | - bookmark ([new GUID])
1265 
1266   // Conflicting node GUID should have been replaced.
1267   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1268   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid);
1269   EXPECT_TRUE(bookmark_bar_node->children()[0]->is_folder());
1270   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid);
1271   EXPECT_TRUE(
1272       base::IsValidGUIDOutputString(bookmark_bar_node->children()[1]->guid()));
1273   EXPECT_FALSE(bookmark_bar_node->children()[1]->is_folder());
1274 }
1275 
TEST(BookmarkModelMergerTest,ShouldReplaceBookmarkGUIDWithConflictingTypesAndLocalChildren)1276 TEST(BookmarkModelMergerTest,
1277      ShouldReplaceBookmarkGUIDWithConflictingTypesAndLocalChildren) {
1278   base::test::ScopedFeatureList override_features;
1279   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1280 
1281   const std::string kGuid = base::GenerateGUID();
1282   const std::string kGuid2 = base::GenerateGUID();
1283 
1284   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1285       bookmarks::TestBookmarkClient::CreateModel();
1286 
1287   // -------- The local model --------
1288   // bookmark_bar
1289   //  | - folder (kGuid)
1290   //    | - bookmark (kGuid2)
1291 
1292   const bookmarks::BookmarkNode* bookmark_bar_node =
1293       bookmark_model->bookmark_bar_node();
1294   const bookmarks::BookmarkNode* folder = bookmark_model->AddFolder(
1295       /*parent=*/bookmark_bar_node, /*index=*/0,
1296       base::UTF8ToUTF16("Folder Title"), nullptr, kGuid);
1297   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1298       /*parent=*/folder, /*index=*/0, base::UTF8ToUTF16("Foo's title"),
1299       GURL("http://foo.com"), nullptr, base::Time::Now(), kGuid2);
1300   ASSERT_TRUE(folder);
1301   ASSERT_TRUE(bookmark);
1302   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(folder));
1303   ASSERT_THAT(folder->children(), ElementRawPointersAre(bookmark));
1304 
1305   // -------- The remote model --------
1306   // bookmark_bar
1307   //  | - bookmark (kGuid)
1308 
1309   syncer::UpdateResponseDataList updates;
1310   updates.push_back(CreateBookmarkBarNodeUpdateData());
1311 
1312   updates.push_back(CreateUpdateResponseData(
1313       /*server_id=*/"Id", /*parent_id=*/kBookmarkBarId, "Bar's title",
1314       "http://bar.com/",
1315       /*is_folder=*/false,
1316       /*unique_position=*/MakeRandomPosition(),
1317       /*guid=*/kGuid));
1318 
1319   Merge(std::move(updates), bookmark_model.get());
1320 
1321   // -------- The merged model --------
1322   // bookmark_bar
1323   //  | - bookmark (kGuid)
1324   //  | - folder ([new GUID])
1325   //    | - bookmark (kGuid2)
1326 
1327   // Conflicting node GUID should have been replaced.
1328   ASSERT_EQ(bookmark_bar_node->children().size(), 2u);
1329   EXPECT_EQ(bookmark_bar_node->children()[0]->guid(), kGuid);
1330   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid);
1331   EXPECT_NE(bookmark_bar_node->children()[1]->guid(), kGuid2);
1332   EXPECT_FALSE(bookmark_bar_node->children()[0]->is_folder());
1333   EXPECT_TRUE(bookmark_bar_node->children()[1]->is_folder());
1334   EXPECT_EQ(bookmark_bar_node->children()[1]->children().size(), 1u);
1335   EXPECT_FALSE(bookmark_bar_node->children()[1]->children()[0]->is_folder());
1336   EXPECT_EQ(bookmark_bar_node->children()[1]->children()[0]->guid(), kGuid2);
1337 }
1338 
1339 // Tests that the GUID-based matching algorithm handles well the case where a
1340 // local bookmark matches a remote bookmark that is orphan. In this case the
1341 // remote node should be ignored and the local bookmark included in the merged
1342 // tree.
TEST(BookmarkModelMergerTest,ShouldIgnoreRemoteGUIDIfOrphanNode)1343 TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteGUIDIfOrphanNode) {
1344   base::test::ScopedFeatureList override_features;
1345   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1346 
1347   const std::string kId = "Id";
1348   const std::string kInexistentParentId = "InexistentParentId";
1349   const std::string kTitle = "Title";
1350   const std::string kUrl = "http://www.foo.com/";
1351   const std::string kGuid = base::GenerateGUID();
1352 
1353   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1354       bookmarks::TestBookmarkClient::CreateModel();
1355 
1356   // -------- The local model --------
1357   // bookmark_bar
1358   //  | - bookmark(kGuid/kTitle)
1359 
1360   const bookmarks::BookmarkNode* bookmark_bar_node =
1361       bookmark_model->bookmark_bar_node();
1362   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1363       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1364       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
1365   ASSERT_TRUE(bookmark);
1366   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1367 
1368   // -------- The remote model --------
1369   // bookmark_bar
1370   // Orphan node: bookmark(kGuid/kTitle)
1371 
1372   syncer::UpdateResponseDataList updates;
1373   updates.push_back(CreateBookmarkBarNodeUpdateData());
1374   updates.push_back(CreateUpdateResponseData(
1375       /*server_id=*/kId, /*parent_id=*/kInexistentParentId, kTitle,
1376       /*url=*/kUrl,
1377       /*is_folder=*/false,
1378       /*unique_position=*/MakeRandomPosition(),
1379       /*guid=*/kGuid));
1380 
1381   std::unique_ptr<SyncedBookmarkTracker> tracker =
1382       Merge(std::move(updates), bookmark_model.get());
1383 
1384   // -------- The merged model --------
1385   // bookmark_bar
1386   //  |- bookmark(kGuid/kTitle)
1387 
1388   // The local node should have been tracked.
1389   EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1390   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kTitle));
1391   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
1392 }
1393 
1394 // Tests that the GUID-based matching algorithm handles well the case where a
1395 // local bookmark matches a remote bookmark that contains invalid specifics
1396 // (e.g. invalid URL). In this case the remote node should be ignored and the
1397 // local bookmark included in the merged tree.
TEST(BookmarkModelMergerTest,ShouldIgnoreRemoteGUIDIfInvalidSpecifics)1398 TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteGUIDIfInvalidSpecifics) {
1399   base::test::ScopedFeatureList override_features;
1400   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1401 
1402   const std::string kId = "Id";
1403   const std::string kTitle = "Title";
1404   const std::string kLocalUrl = "http://www.foo.com/";
1405   const std::string kInvalidUrl = "invalidurl";
1406   const std::string kGuid = base::GenerateGUID();
1407 
1408   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1409       bookmarks::TestBookmarkClient::CreateModel();
1410 
1411   // -------- The local model --------
1412   // bookmark_bar
1413   //  | - bookmark(kGuid/kLocalUrl/kTitle)
1414 
1415   const bookmarks::BookmarkNode* bookmark_bar_node =
1416       bookmark_model->bookmark_bar_node();
1417   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1418       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
1419       GURL(kLocalUrl), nullptr, base::Time::Now(), kGuid);
1420   ASSERT_TRUE(bookmark);
1421   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1422 
1423   // -------- The remote model --------
1424   // bookmark_bar
1425   //  | - bookmark (kGuid/kInvalidURL/kTitle)
1426 
1427   syncer::UpdateResponseDataList updates;
1428   updates.push_back(CreateBookmarkBarNodeUpdateData());
1429   updates.push_back(CreateUpdateResponseData(
1430       /*server_id=*/kId, /*parent_id=*/kBookmarkBarId, kTitle,
1431       /*url=*/kInvalidUrl,
1432       /*is_folder=*/false,
1433       /*unique_position=*/MakeRandomPosition(),
1434       /*guid=*/kGuid));
1435 
1436   std::unique_ptr<SyncedBookmarkTracker> tracker =
1437       Merge(std::move(updates), bookmark_model.get());
1438 
1439   // -------- The merged model --------
1440   // bookmark_bar
1441   //  |- bookmark(kGuid/kLocalUrl/kTitle)
1442 
1443   // The local node should have been tracked.
1444   EXPECT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1445   EXPECT_EQ(bookmark->url(), GURL(kLocalUrl));
1446   EXPECT_EQ(bookmark->GetTitle(), base::UTF8ToUTF16(kTitle));
1447   EXPECT_THAT(tracker->GetEntityForBookmarkNode(bookmark), NotNull());
1448 }
1449 
1450 // Tests that updates with a GUID that is different to originator client item ID
1451 // are ignored.
TEST(BookmarkModelMergerTest,ShouldIgnoreRemoteUpdateWithInvalidGUID)1452 TEST(BookmarkModelMergerTest, ShouldIgnoreRemoteUpdateWithInvalidGUID) {
1453   base::test::ScopedFeatureList override_features;
1454   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1455 
1456   const std::string kId1 = "Id1";
1457   const std::string kId2 = "Id2";
1458   const std::string kTitle1 = "Title1";
1459   const std::string kTitle2 = "Title2";
1460   const std::string kLocalTitle = "LocalTitle";
1461   const std::string kUrl = "http://www.foo.com/";
1462   const std::string kGuid = base::GenerateGUID();
1463 
1464   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1465       bookmarks::TestBookmarkClient::CreateModel();
1466 
1467   // -------- The local model --------
1468   //  | - bookmark(kGuid/kUrl/kLocalTitle)
1469   const bookmarks::BookmarkNode* bookmark_bar_node =
1470       bookmark_model->bookmark_bar_node();
1471   const bookmarks::BookmarkNode* bookmark = bookmark_model->AddURL(
1472       /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kLocalTitle),
1473       GURL(kUrl), nullptr, base::Time::Now(), kGuid);
1474   ASSERT_TRUE(bookmark);
1475   ASSERT_THAT(bookmark_bar_node->children(), ElementRawPointersAre(bookmark));
1476 
1477   // -------- The remote model --------
1478   // bookmark_bar
1479   //  | - bookmark (kGuid/kUrl/kTitle1)
1480   //  | - bookmark (kGuid/kUrl/kTitle2)
1481   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1482   syncer::UniquePosition position1 =
1483       syncer::UniquePosition::InitialPosition(suffix);
1484   syncer::UniquePosition position2 =
1485       syncer::UniquePosition::After(position1, suffix);
1486 
1487   syncer::UpdateResponseDataList updates;
1488   updates.push_back(CreateBookmarkBarNodeUpdateData());
1489   updates.push_back(CreateUpdateResponseData(
1490       /*server_id=*/kId1, /*parent_id=*/kBookmarkBarId, kTitle1,
1491       /*url=*/kUrl,
1492       /*is_folder=*/false, /*unique_position=*/position1,
1493       /*guid=*/kGuid));
1494   updates.push_back(CreateUpdateResponseData(
1495       /*server_id=*/kId2, /*parent_id=*/kBookmarkBarId, kTitle2,
1496       /*url=*/kUrl,
1497       /*is_folder=*/false, /*unique_position=*/position2,
1498       /*guid=*/kGuid));
1499 
1500   // |originator_client_item_id| cannot itself be duplicated because
1501   // ModelTypeWorker guarantees otherwise.
1502   updates.back().entity.originator_client_item_id = base::GenerateGUID();
1503 
1504   std::unique_ptr<SyncedBookmarkTracker> tracker =
1505       Merge(std::move(updates), bookmark_model.get());
1506 
1507   // -------- The merged model --------
1508   //  | - bookmark (kGuid/kUrl/kTitle1)
1509 
1510   // The second remote node should have been filtered out.
1511   ASSERT_EQ(bookmark_bar_node->children().size(), 1u);
1512   const bookmarks::BookmarkNode* merged_bookmark =
1513       bookmark_model->bookmark_bar_node()->children()[0].get();
1514   EXPECT_THAT(merged_bookmark->guid(), Eq(kGuid));
1515   EXPECT_THAT(tracker->GetEntityForBookmarkNode(merged_bookmark), NotNull());
1516 }
1517 
1518 // Regression test for crbug.com/1050776. Verifies that computing the unique
1519 // position does not crash when processing local creation of bookmark during
1520 // initial merge.
TEST(BookmarkModelMergerTest,ShouldProcessLocalCreationWithUntrackedPredecessorNode)1521 TEST(BookmarkModelMergerTest,
1522      ShouldProcessLocalCreationWithUntrackedPredecessorNode) {
1523   base::test::ScopedFeatureList override_features;
1524   override_features.InitAndEnableFeature(switches::kMergeBookmarksUsingGUIDs);
1525 
1526   const std::string kFolder1Title = "folder1";
1527   const std::string kFolder2Title = "folder2";
1528 
1529   const std::string kUrl1Title = "url1";
1530   const std::string kUrl2Title = "url2";
1531 
1532   const std::string kUrl1 = "http://www.url1.com/";
1533   const std::string kUrl2 = "http://www.url2.com/";
1534 
1535   const std::string kFolder1Id = "Folder1Id";
1536   const std::string kFolder2Id = "Folder2Id";
1537   const std::string kUrl1Id = "Url1Id";
1538 
1539   // It is needed to use at least two folders to reproduce the crash. It is
1540   // needed because the bookmarks are processed in the order of remote entities
1541   // on the same level of the tree. To start processing of locally created
1542   // bookmarks while other remote bookmarks are not processed we need to use at
1543   // least one local folder with several urls.
1544   //
1545   // -------- The local model --------
1546   // bookmark_bar
1547   //  |- folder 1
1548   //    |- url1(http://www.url1.com)
1549   //    |- url2(http://www.url2.com)
1550 
1551   std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
1552       bookmarks::TestBookmarkClient::CreateModel();
1553 
1554   const bookmarks::BookmarkNode* bookmark_bar_node =
1555       bookmark_model->bookmark_bar_node();
1556   const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder(
1557       /*parent=*/bookmark_bar_node, /*index=*/0,
1558       base::UTF8ToUTF16(kFolder1Title));
1559   const bookmarks::BookmarkNode* folder1_url1_node = bookmark_model->AddURL(
1560       /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kUrl1Title),
1561       GURL(kUrl1));
1562   bookmark_model->AddURL(
1563       /*parent=*/folder1, /*index=*/1, base::UTF8ToUTF16(kUrl2Title),
1564       GURL(kUrl2));
1565 
1566   // The remote model contains two folders. The first one is the same as in
1567   // local model, but it does not contain any urls. The second one has the url1
1568   // from first folder with same GUID. This will cause skip local creation for
1569   // |url1| while processing |folder1|.
1570   //
1571   // -------- The remote model --------
1572   // bookmark_bar
1573   //  |- folder 1
1574   //  |- folder 2
1575   //    |- url1(http://www.url1.com)
1576 
1577   const std::string suffix = syncer::UniquePosition::RandomSuffix();
1578   syncer::UniquePosition posFolder1 =
1579       syncer::UniquePosition::InitialPosition(suffix);
1580   syncer::UniquePosition posFolder2 =
1581       syncer::UniquePosition::After(posFolder1, suffix);
1582 
1583   syncer::UniquePosition posUrl1 =
1584       syncer::UniquePosition::InitialPosition(suffix);
1585 
1586   syncer::UpdateResponseDataList updates;
1587   updates.push_back(CreateBookmarkBarNodeUpdateData());
1588   updates.push_back(CreateUpdateResponseData(
1589       /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
1590       /*url=*/std::string(),
1591       /*is_folder=*/true, /*unique_position=*/posFolder1));
1592   updates.push_back(CreateUpdateResponseData(
1593       /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title,
1594       /*url=*/std::string(),
1595       /*is_folder=*/true, /*unique_position=*/posFolder2));
1596   updates.push_back(CreateUpdateResponseData(
1597       /*server_id=*/kUrl1Id, /*parent_id=*/kFolder2Id, kUrl1Title, kUrl1,
1598       /*is_folder=*/false, /*unique_position=*/posUrl1,
1599       folder1_url1_node->guid()));
1600 
1601   // -------- The expected merge outcome --------
1602   // bookmark_bar
1603   //  |- folder 1
1604   //    |- url2(http://www.url2.com)
1605   //  |- folder 2
1606   //    |- url1(http://www.url1.com)
1607 
1608   std::unique_ptr<SyncedBookmarkTracker> tracker =
1609       Merge(std::move(updates), bookmark_model.get());
1610   ASSERT_THAT(bookmark_bar_node->children().size(), Eq(2u));
1611 
1612   // Verify Folder 1.
1613   EXPECT_THAT(bookmark_bar_node->children()[0]->GetTitle(),
1614               Eq(base::ASCIIToUTF16(kFolder1Title)));
1615   ASSERT_THAT(bookmark_bar_node->children()[0]->children().size(), Eq(1u));
1616 
1617   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->GetTitle(),
1618               Eq(base::ASCIIToUTF16(kUrl2Title)));
1619   EXPECT_THAT(bookmark_bar_node->children()[0]->children()[0]->url(),
1620               Eq(GURL(kUrl2)));
1621 
1622   // Verify Folder 2.
1623   EXPECT_THAT(bookmark_bar_node->children()[1]->GetTitle(),
1624               Eq(base::ASCIIToUTF16(kFolder2Title)));
1625   ASSERT_THAT(bookmark_bar_node->children()[1]->children().size(), Eq(1u));
1626 
1627   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->GetTitle(),
1628               Eq(base::ASCIIToUTF16(kUrl1Title)));
1629   EXPECT_THAT(bookmark_bar_node->children()[1]->children()[0]->url(),
1630               Eq(GURL(kUrl1)));
1631 
1632   // Verify the tracker contents.
1633   EXPECT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(5U));
1634 
1635   const size_t kMaxEntries = 1000;
1636   std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
1637       tracker->GetEntitiesWithLocalChanges(kMaxEntries);
1638 
1639   ASSERT_THAT(local_changes.size(), Eq(1U));
1640   EXPECT_THAT(local_changes[0]->bookmark_node(),
1641               Eq(bookmark_bar_node->children()[0]->children()[0].get()));
1642 
1643   // Verify positions in tracker.
1644   EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, *tracker));
1645 }
1646 
1647 }  // namespace sync_bookmarks
1648