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