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