1 // Copyright (c) 2012 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 // TODO(akalin): This file is basically just a unit test for 6 // BookmarkChangeProcessor. Write unit tests for 7 // BookmarkModelAssociator separately. 8 9 #include <map> 10 #include <queue> 11 #include <stack> 12 #include <vector> 13 14 #include "base/command_line.h" 15 #include "base/files/file_path.h" 16 #include "base/location.h" 17 #include "base/memory/scoped_ptr.h" 18 #include "base/message_loop/message_loop.h" 19 #include "base/strings/string16.h" 20 #include "base/strings/string_number_conversions.h" 21 #include "base/strings/string_util.h" 22 #include "base/strings/stringprintf.h" 23 #include "base/strings/utf_string_conversions.h" 24 #include "base/time/time.h" 25 #include "chrome/browser/bookmarks/base_bookmark_model_observer.h" 26 #include "chrome/browser/bookmarks/bookmark_model.h" 27 #include "chrome/browser/bookmarks/bookmark_model_factory.h" 28 #include "chrome/browser/sync/glue/bookmark_change_processor.h" 29 #include "chrome/browser/sync/glue/bookmark_model_associator.h" 30 #include "chrome/browser/sync/glue/data_type_error_handler.h" 31 #include "chrome/browser/sync/glue/data_type_error_handler_mock.h" 32 #include "chrome/common/chrome_switches.h" 33 #include "chrome/test/base/testing_profile.h" 34 #include "chrome/test/base/ui_test_utils.h" 35 #include "content/public/test/test_browser_thread.h" 36 #include "sync/api/sync_error.h" 37 #include "sync/internal_api/public/change_record.h" 38 #include "sync/internal_api/public/read_node.h" 39 #include "sync/internal_api/public/read_transaction.h" 40 #include "sync/internal_api/public/test/test_user_share.h" 41 #include "sync/internal_api/public/write_node.h" 42 #include "sync/internal_api/public/write_transaction.h" 43 #include "sync/syncable/mutable_entry.h" // TODO(tim): Remove. Bug 131130. 44 #include "testing/gmock/include/gmock/gmock.h" 45 #include "testing/gtest/include/gtest/gtest.h" 46 47 namespace browser_sync { 48 49 using content::BrowserThread; 50 using syncer::BaseNode; 51 using testing::_; 52 using testing::InvokeWithoutArgs; 53 using testing::Mock; 54 using testing::StrictMock; 55 56 #if defined(OS_ANDROID) 57 static const bool kExpectMobileBookmarks = true; 58 #else 59 static const bool kExpectMobileBookmarks = false; 60 #endif // defined(OS_ANDROID) 61 62 namespace { 63 64 // FakeServerChange constructs a list of syncer::ChangeRecords while modifying 65 // the sync model, and can pass the ChangeRecord list to a 66 // syncer::SyncObserver (i.e., the ProfileSyncService) to test the client 67 // change-application behavior. 68 // Tests using FakeServerChange should be careful to avoid back-references, 69 // since FakeServerChange will send the edits in the order specified. 70 class FakeServerChange { 71 public: 72 explicit FakeServerChange(syncer::WriteTransaction* trans) : trans_(trans) { 73 } 74 75 // Pretend that the server told the syncer to add a bookmark object. 76 int64 Add(const std::wstring& title, 77 const std::string& url, 78 bool is_folder, 79 int64 parent_id, 80 int64 predecessor_id) { 81 syncer::ReadNode parent(trans_); 82 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id)); 83 syncer::WriteNode node(trans_); 84 if (predecessor_id == 0) { 85 EXPECT_TRUE(node.InitBookmarkByCreation(parent, NULL)); 86 } else { 87 syncer::ReadNode predecessor(trans_); 88 EXPECT_EQ(BaseNode::INIT_OK, predecessor.InitByIdLookup(predecessor_id)); 89 EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); 90 EXPECT_TRUE(node.InitBookmarkByCreation(parent, &predecessor)); 91 } 92 EXPECT_EQ(node.GetPredecessorId(), predecessor_id); 93 EXPECT_EQ(node.GetParentId(), parent_id); 94 node.SetIsFolder(is_folder); 95 node.SetTitle(title); 96 if (!is_folder) { 97 sync_pb::BookmarkSpecifics specifics(node.GetBookmarkSpecifics()); 98 specifics.set_url(url); 99 node.SetBookmarkSpecifics(specifics); 100 } 101 syncer::ChangeRecord record; 102 record.action = syncer::ChangeRecord::ACTION_ADD; 103 record.id = node.GetId(); 104 changes_.push_back(record); 105 return node.GetId(); 106 } 107 108 // Add a bookmark folder. 109 int64 AddFolder(const std::wstring& title, 110 int64 parent_id, 111 int64 predecessor_id) { 112 return Add(title, std::string(), true, parent_id, predecessor_id); 113 } 114 115 // Add a bookmark. 116 int64 AddURL(const std::wstring& title, 117 const std::string& url, 118 int64 parent_id, 119 int64 predecessor_id) { 120 return Add(title, url, false, parent_id, predecessor_id); 121 } 122 123 // Pretend that the server told the syncer to delete an object. 124 void Delete(int64 id) { 125 { 126 // Delete the sync node. 127 syncer::WriteNode node(trans_); 128 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id)); 129 if (node.GetIsFolder()) 130 EXPECT_FALSE(node.GetFirstChildId()); 131 node.GetMutableEntryForTest()->Put(syncer::syncable::SERVER_IS_DEL, 132 true); 133 node.Tombstone(); 134 } 135 { 136 // Verify the deletion. 137 syncer::ReadNode node(trans_); 138 EXPECT_EQ(BaseNode::INIT_FAILED_ENTRY_IS_DEL, node.InitByIdLookup(id)); 139 } 140 141 syncer::ChangeRecord record; 142 record.action = syncer::ChangeRecord::ACTION_DELETE; 143 record.id = id; 144 // Deletions are always first in the changelist, but we can't actually do 145 // WriteNode::Remove() on the node until its children are moved. So, as 146 // a practical matter, users of FakeServerChange must move or delete 147 // children before parents. Thus, we must insert the deletion record 148 // at the front of the vector. 149 changes_.insert(changes_.begin(), record); 150 } 151 152 // Set a new title value, and return the old value. 153 std::wstring ModifyTitle(int64 id, const std::wstring& new_title) { 154 syncer::WriteNode node(trans_); 155 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id)); 156 std::string old_title = node.GetTitle(); 157 node.SetTitle(new_title); 158 SetModified(id); 159 return UTF8ToWide(old_title); 160 } 161 162 // Set a new parent and predecessor value. Return the old parent id. 163 // We could return the old predecessor id, but it turns out not to be 164 // very useful for assertions. 165 int64 ModifyPosition(int64 id, int64 parent_id, int64 predecessor_id) { 166 syncer::ReadNode parent(trans_); 167 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id)); 168 syncer::WriteNode node(trans_); 169 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id)); 170 int64 old_parent_id = node.GetParentId(); 171 if (predecessor_id == 0) { 172 EXPECT_TRUE(node.SetPosition(parent, NULL)); 173 } else { 174 syncer::ReadNode predecessor(trans_); 175 EXPECT_EQ(BaseNode::INIT_OK, predecessor.InitByIdLookup(predecessor_id)); 176 EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); 177 EXPECT_TRUE(node.SetPosition(parent, &predecessor)); 178 } 179 SetModified(id); 180 return old_parent_id; 181 } 182 183 void ModifyCreationTime(int64 id, int64 creation_time_us) { 184 syncer::WriteNode node(trans_); 185 ASSERT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id)); 186 sync_pb::BookmarkSpecifics specifics = node.GetBookmarkSpecifics(); 187 specifics.set_creation_time_us(creation_time_us); 188 node.SetBookmarkSpecifics(specifics); 189 SetModified(id); 190 } 191 192 // Pass the fake change list to |service|. 193 void ApplyPendingChanges(ChangeProcessor* processor) { 194 processor->ApplyChangesFromSyncModel( 195 trans_, 0, syncer::ImmutableChangeRecordList(&changes_)); 196 } 197 198 const syncer::ChangeRecordList& changes() { 199 return changes_; 200 } 201 202 private: 203 // Helper function to push an ACTION_UPDATE record onto the back 204 // of the changelist. 205 void SetModified(int64 id) { 206 // Coalesce multi-property edits. 207 if (!changes_.empty() && changes_.back().id == id && 208 changes_.back().action == 209 syncer::ChangeRecord::ACTION_UPDATE) 210 return; 211 syncer::ChangeRecord record; 212 record.action = syncer::ChangeRecord::ACTION_UPDATE; 213 record.id = id; 214 changes_.push_back(record); 215 } 216 217 // The transaction on which everything happens. 218 syncer::WriteTransaction *trans_; 219 220 // The change list we construct. 221 syncer::ChangeRecordList changes_; 222 }; 223 224 class ExtensiveChangesBookmarkModelObserver : public BaseBookmarkModelObserver { 225 public: 226 explicit ExtensiveChangesBookmarkModelObserver() 227 : started_count_(0), 228 completed_count_at_started_(0), 229 completed_count_(0) {} 230 231 virtual void ExtensiveBookmarkChangesBeginning( 232 BookmarkModel* model) OVERRIDE { 233 ++started_count_; 234 completed_count_at_started_ = completed_count_; 235 } 236 237 virtual void ExtensiveBookmarkChangesEnded(BookmarkModel* model) OVERRIDE { 238 ++completed_count_; 239 } 240 241 virtual void BookmarkModelChanged() OVERRIDE {} 242 243 int get_started() const { 244 return started_count_; 245 } 246 247 int get_completed_count_at_started() const { 248 return completed_count_at_started_; 249 } 250 251 int get_completed() const { 252 return completed_count_; 253 } 254 255 private: 256 int started_count_; 257 int completed_count_at_started_; 258 int completed_count_; 259 260 DISALLOW_COPY_AND_ASSIGN(ExtensiveChangesBookmarkModelObserver); 261 }; 262 263 264 class ProfileSyncServiceBookmarkTest : public testing::Test { 265 protected: 266 enum LoadOption { LOAD_FROM_STORAGE, DELETE_EXISTING_STORAGE }; 267 enum SaveOption { SAVE_TO_STORAGE, DONT_SAVE_TO_STORAGE }; 268 269 ProfileSyncServiceBookmarkTest() 270 : model_(NULL), 271 ui_thread_(BrowserThread::UI, &message_loop_), 272 file_thread_(BrowserThread::FILE, &message_loop_), 273 local_merge_result_(syncer::BOOKMARKS), 274 syncer_merge_result_(syncer::BOOKMARKS) { 275 } 276 277 virtual ~ProfileSyncServiceBookmarkTest() { 278 StopSync(); 279 UnloadBookmarkModel(); 280 } 281 282 virtual void SetUp() { 283 test_user_share_.SetUp(); 284 } 285 286 virtual void TearDown() { 287 test_user_share_.TearDown(); 288 } 289 290 // Inserts a folder directly to the share. 291 // Do not use this after model association is complete. 292 // 293 // This function differs from the AddFolder() function declared elsewhere in 294 // this file in that it only affects the sync model. It would be invalid to 295 // change the sync model directly after ModelAssociation. This function can 296 // be invoked prior to model association to set up first-time sync model 297 // association scenarios. 298 int64 AddFolderToShare(syncer::WriteTransaction* trans, std::string title) { 299 EXPECT_FALSE(model_associator_); 300 301 // Be sure to call CreatePermanentBookmarkNodes(), otherwise this will fail. 302 syncer::ReadNode bookmark_bar(trans); 303 EXPECT_EQ(BaseNode::INIT_OK, bookmark_bar.InitByTagLookup("bookmark_bar")); 304 305 syncer::WriteNode node(trans); 306 EXPECT_TRUE(node.InitBookmarkByCreation(bookmark_bar, NULL)); 307 node.SetIsFolder(true); 308 node.SetTitle(ASCIIToWide(title)); 309 310 return node.GetId(); 311 } 312 313 // Inserts a bookmark directly to the share. 314 // Do not use this after model association is complete. 315 // 316 // This function differs from the AddURL() function declared elsewhere in this 317 // file in that it only affects the sync model. It would be invalid to change 318 // the sync model directly after ModelAssociation. This function can be 319 // invoked prior to model association to set up first-time sync model 320 // association scenarios. 321 int64 AddBookmarkToShare(syncer::WriteTransaction *trans, 322 int64 parent_id, 323 std::string title) { 324 EXPECT_FALSE(model_associator_); 325 326 syncer::ReadNode parent(trans); 327 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id)); 328 329 sync_pb::BookmarkSpecifics specifics; 330 specifics.set_url("http://www.google.com/search?q=" + title); 331 specifics.set_title(title); 332 333 syncer::WriteNode node(trans); 334 EXPECT_TRUE(node.InitBookmarkByCreation(parent, NULL)); 335 node.SetIsFolder(false); 336 node.SetTitle(ASCIIToWide(title)); 337 node.SetBookmarkSpecifics(specifics); 338 339 return node.GetId(); 340 } 341 342 // Load (or re-load) the bookmark model. |load| controls use of the 343 // bookmarks file on disk. |save| controls whether the newly loaded 344 // bookmark model will write out a bookmark file as it goes. 345 void LoadBookmarkModel(LoadOption load, SaveOption save) { 346 bool delete_bookmarks = load == DELETE_EXISTING_STORAGE; 347 profile_.CreateBookmarkModel(delete_bookmarks); 348 model_ = BookmarkModelFactory::GetForProfile(&profile_); 349 ui_test_utils::WaitForBookmarkModelToLoad(model_); 350 // This noticeably speeds up the unit tests that request it. 351 if (save == DONT_SAVE_TO_STORAGE) 352 model_->ClearStore(); 353 message_loop_.RunUntilIdle(); 354 } 355 356 int GetSyncBookmarkCount() { 357 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); 358 syncer::ReadNode node(&trans); 359 if (node.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::BOOKMARKS)) != 360 syncer::BaseNode::INIT_OK) 361 return 0; 362 return node.GetTotalNodeCount(); 363 } 364 365 // Creates the bookmark root node and the permanent nodes if they don't 366 // already exist. 367 bool CreatePermanentBookmarkNodes() { 368 bool root_exists = false; 369 syncer::ModelType type = syncer::BOOKMARKS; 370 { 371 syncer::WriteTransaction trans(FROM_HERE, 372 test_user_share_.user_share()); 373 syncer::ReadNode uber_root(&trans); 374 uber_root.InitByRootLookup(); 375 376 syncer::ReadNode root(&trans); 377 root_exists = (root.InitByTagLookup(syncer::ModelTypeToRootTag(type)) == 378 BaseNode::INIT_OK); 379 } 380 381 if (!root_exists) { 382 if (!syncer::TestUserShare::CreateRoot(type, 383 test_user_share_.user_share())) 384 return false; 385 } 386 387 const int kNumPermanentNodes = 3; 388 const std::string permanent_tags[kNumPermanentNodes] = { 389 "bookmark_bar", "other_bookmarks", "synced_bookmarks" 390 }; 391 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 392 syncer::ReadNode root(&trans); 393 EXPECT_EQ(BaseNode::INIT_OK, root.InitByTagLookup( 394 syncer::ModelTypeToRootTag(type))); 395 396 // Loop through creating permanent nodes as necessary. 397 int64 last_child_id = syncer::kInvalidId; 398 for (int i = 0; i < kNumPermanentNodes; ++i) { 399 // First check if the node already exists. This is for tests that involve 400 // persistence and set up sync more than once. 401 syncer::ReadNode lookup(&trans); 402 if (lookup.InitByTagLookup(permanent_tags[i]) == 403 syncer::ReadNode::INIT_OK) { 404 last_child_id = lookup.GetId(); 405 continue; 406 } 407 408 // If it doesn't exist, create the permanent node at the end of the 409 // ordering. 410 syncer::ReadNode predecessor_node(&trans); 411 syncer::ReadNode* predecessor = NULL; 412 if (last_child_id != syncer::kInvalidId) { 413 EXPECT_EQ(BaseNode::INIT_OK, 414 predecessor_node.InitByIdLookup(last_child_id)); 415 predecessor = &predecessor_node; 416 } 417 syncer::WriteNode node(&trans); 418 if (!node.InitBookmarkByCreation(root, predecessor)) 419 return false; 420 node.SetIsFolder(true); 421 node.GetMutableEntryForTest()->Put( 422 syncer::syncable::UNIQUE_SERVER_TAG, permanent_tags[i]); 423 node.SetTitle(UTF8ToWide(permanent_tags[i])); 424 node.SetExternalId(0); 425 last_child_id = node.GetId(); 426 } 427 return true; 428 } 429 430 bool AssociateModels() { 431 DCHECK(!model_associator_); 432 433 // Set up model associator. 434 model_associator_.reset(new BookmarkModelAssociator( 435 BookmarkModelFactory::GetForProfile(&profile_), 436 &profile_, 437 test_user_share_.user_share(), 438 &mock_error_handler_, 439 kExpectMobileBookmarks)); 440 441 local_merge_result_ = syncer::SyncMergeResult(syncer::BOOKMARKS); 442 syncer_merge_result_ = syncer::SyncMergeResult(syncer::BOOKMARKS); 443 int local_count_before = model_->root_node()->GetTotalNodeCount(); 444 int syncer_count_before = GetSyncBookmarkCount(); 445 446 syncer::SyncError error = model_associator_->AssociateModels( 447 &local_merge_result_, 448 &syncer_merge_result_); 449 if (error.IsSet()) 450 return false; 451 452 base::MessageLoop::current()->RunUntilIdle(); 453 454 // Verify the merge results were calculated properly. 455 EXPECT_EQ(local_count_before, 456 local_merge_result_.num_items_before_association()); 457 EXPECT_EQ(syncer_count_before, 458 syncer_merge_result_.num_items_before_association()); 459 EXPECT_EQ(local_merge_result_.num_items_after_association(), 460 local_merge_result_.num_items_before_association() + 461 local_merge_result_.num_items_added() - 462 local_merge_result_.num_items_deleted()); 463 EXPECT_EQ(syncer_merge_result_.num_items_after_association(), 464 syncer_merge_result_.num_items_before_association() + 465 syncer_merge_result_.num_items_added() - 466 syncer_merge_result_.num_items_deleted()); 467 EXPECT_EQ(model_->root_node()->GetTotalNodeCount(), 468 local_merge_result_.num_items_after_association()); 469 EXPECT_EQ(GetSyncBookmarkCount(), 470 syncer_merge_result_.num_items_after_association()); 471 return true; 472 } 473 474 void StartSync() { 475 test_user_share_.Reload(); 476 477 ASSERT_TRUE(CreatePermanentBookmarkNodes()); 478 ASSERT_TRUE(AssociateModels()); 479 480 // Set up change processor. 481 change_processor_.reset( 482 new BookmarkChangeProcessor(model_associator_.get(), 483 &mock_error_handler_)); 484 change_processor_->Start(&profile_, test_user_share_.user_share()); 485 } 486 487 void StopSync() { 488 change_processor_.reset(); 489 if (model_associator_) { 490 syncer::SyncError error = model_associator_->DisassociateModels(); 491 EXPECT_FALSE(error.IsSet()); 492 } 493 model_associator_.reset(); 494 495 message_loop_.RunUntilIdle(); 496 497 // TODO(akalin): Actually close the database and flush it to disk 498 // (and make StartSync reload from disk). This would require 499 // refactoring TestUserShare. 500 } 501 502 void UnloadBookmarkModel() { 503 profile_.CreateBookmarkModel(false /* delete_bookmarks */); 504 model_ = NULL; 505 message_loop_.RunUntilIdle(); 506 } 507 508 bool InitSyncNodeFromChromeNode(const BookmarkNode* bnode, 509 syncer::BaseNode* sync_node) { 510 return model_associator_->InitSyncNodeFromChromeId(bnode->id(), 511 sync_node); 512 } 513 514 void ExpectSyncerNodeMatching(syncer::BaseTransaction* trans, 515 const BookmarkNode* bnode) { 516 syncer::ReadNode gnode(trans); 517 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnode, &gnode)); 518 // Non-root node titles and parents must match. 519 if (!model_->is_permanent_node(bnode)) { 520 EXPECT_EQ(bnode->GetTitle(), UTF8ToUTF16(gnode.GetTitle())); 521 EXPECT_EQ( 522 model_associator_->GetChromeNodeFromSyncId(gnode.GetParentId()), 523 bnode->parent()); 524 } 525 EXPECT_EQ(bnode->is_folder(), gnode.GetIsFolder()); 526 if (bnode->is_url()) 527 EXPECT_EQ(bnode->url(), GURL(gnode.GetBookmarkSpecifics().url())); 528 529 // Check for position matches. 530 int browser_index = bnode->parent()->GetIndexOf(bnode); 531 if (browser_index == 0) { 532 EXPECT_EQ(gnode.GetPredecessorId(), 0); 533 } else { 534 const BookmarkNode* bprev = 535 bnode->parent()->GetChild(browser_index - 1); 536 syncer::ReadNode gprev(trans); 537 ASSERT_TRUE(InitSyncNodeFromChromeNode(bprev, &gprev)); 538 EXPECT_EQ(gnode.GetPredecessorId(), gprev.GetId()); 539 EXPECT_EQ(gnode.GetParentId(), gprev.GetParentId()); 540 } 541 if (browser_index == bnode->parent()->child_count() - 1) { 542 EXPECT_EQ(gnode.GetSuccessorId(), 0); 543 } else { 544 const BookmarkNode* bnext = 545 bnode->parent()->GetChild(browser_index + 1); 546 syncer::ReadNode gnext(trans); 547 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnext, &gnext)); 548 EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId()); 549 EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId()); 550 } 551 if (!bnode->empty()) 552 EXPECT_TRUE(gnode.GetFirstChildId()); 553 } 554 555 void ExpectSyncerNodeMatching(const BookmarkNode* bnode) { 556 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); 557 ExpectSyncerNodeMatching(&trans, bnode); 558 } 559 560 void ExpectBrowserNodeMatching(syncer::BaseTransaction* trans, 561 int64 sync_id) { 562 EXPECT_TRUE(sync_id); 563 const BookmarkNode* bnode = 564 model_associator_->GetChromeNodeFromSyncId(sync_id); 565 ASSERT_TRUE(bnode); 566 int64 id = model_associator_->GetSyncIdFromChromeId(bnode->id()); 567 EXPECT_EQ(id, sync_id); 568 ExpectSyncerNodeMatching(trans, bnode); 569 } 570 571 void ExpectBrowserNodeUnknown(int64 sync_id) { 572 EXPECT_FALSE(model_associator_->GetChromeNodeFromSyncId(sync_id)); 573 } 574 575 void ExpectBrowserNodeKnown(int64 sync_id) { 576 EXPECT_TRUE(model_associator_->GetChromeNodeFromSyncId(sync_id)); 577 } 578 579 void ExpectSyncerNodeKnown(const BookmarkNode* node) { 580 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id()); 581 EXPECT_NE(sync_id, syncer::kInvalidId); 582 } 583 584 void ExpectSyncerNodeUnknown(const BookmarkNode* node) { 585 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id()); 586 EXPECT_EQ(sync_id, syncer::kInvalidId); 587 } 588 589 void ExpectBrowserNodeTitle(int64 sync_id, const std::wstring& title) { 590 const BookmarkNode* bnode = 591 model_associator_->GetChromeNodeFromSyncId(sync_id); 592 ASSERT_TRUE(bnode); 593 EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(title)); 594 } 595 596 void ExpectBrowserNodeURL(int64 sync_id, const std::string& url) { 597 const BookmarkNode* bnode = 598 model_associator_->GetChromeNodeFromSyncId(sync_id); 599 ASSERT_TRUE(bnode); 600 EXPECT_EQ(GURL(url), bnode->url()); 601 } 602 603 void ExpectBrowserNodeParent(int64 sync_id, int64 parent_sync_id) { 604 const BookmarkNode* node = 605 model_associator_->GetChromeNodeFromSyncId(sync_id); 606 ASSERT_TRUE(node); 607 const BookmarkNode* parent = 608 model_associator_->GetChromeNodeFromSyncId(parent_sync_id); 609 EXPECT_TRUE(parent); 610 EXPECT_EQ(node->parent(), parent); 611 } 612 613 void ExpectModelMatch(syncer::BaseTransaction* trans) { 614 const BookmarkNode* root = model_->root_node(); 615 EXPECT_EQ(root->GetIndexOf(model_->bookmark_bar_node()), 0); 616 EXPECT_EQ(root->GetIndexOf(model_->other_node()), 1); 617 EXPECT_EQ(root->GetIndexOf(model_->mobile_node()), 2); 618 619 std::stack<int64> stack; 620 stack.push(bookmark_bar_id()); 621 while (!stack.empty()) { 622 int64 id = stack.top(); 623 stack.pop(); 624 if (!id) continue; 625 626 ExpectBrowserNodeMatching(trans, id); 627 628 syncer::ReadNode gnode(trans); 629 ASSERT_EQ(BaseNode::INIT_OK, gnode.InitByIdLookup(id)); 630 stack.push(gnode.GetSuccessorId()); 631 if (gnode.GetIsFolder()) 632 stack.push(gnode.GetFirstChildId()); 633 } 634 } 635 636 void ExpectModelMatch() { 637 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); 638 ExpectModelMatch(&trans); 639 } 640 641 int64 mobile_bookmarks_id() { 642 return 643 model_associator_->GetSyncIdFromChromeId(model_->mobile_node()->id()); 644 } 645 646 int64 other_bookmarks_id() { 647 return 648 model_associator_->GetSyncIdFromChromeId(model_->other_node()->id()); 649 } 650 651 int64 bookmark_bar_id() { 652 return model_associator_->GetSyncIdFromChromeId( 653 model_->bookmark_bar_node()->id()); 654 } 655 656 protected: 657 BookmarkModel* model_; 658 syncer::TestUserShare test_user_share_; 659 scoped_ptr<BookmarkChangeProcessor> change_processor_; 660 StrictMock<DataTypeErrorHandlerMock> mock_error_handler_; 661 scoped_ptr<BookmarkModelAssociator> model_associator_; 662 663 private: 664 // Used by both |ui_thread_| and |file_thread_|. 665 base::MessageLoop message_loop_; 666 content::TestBrowserThread ui_thread_; 667 // Needed by |model_|. 668 content::TestBrowserThread file_thread_; 669 670 syncer::SyncMergeResult local_merge_result_; 671 syncer::SyncMergeResult syncer_merge_result_; 672 673 TestingProfile profile_; 674 }; 675 676 TEST_F(ProfileSyncServiceBookmarkTest, InitialState) { 677 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 678 StartSync(); 679 680 EXPECT_TRUE(other_bookmarks_id()); 681 EXPECT_TRUE(bookmark_bar_id()); 682 EXPECT_TRUE(mobile_bookmarks_id()); 683 684 ExpectModelMatch(); 685 } 686 687 // Populate the sync database then start model association. Sync's bookmarks 688 // should end up being copied into the native model, resulting in a successful 689 // "ExpectModelMatch()". 690 // 691 // This code has some use for verifying correctness. It's also a very useful 692 // for profiling bookmark ModelAssociation, an important part of some first-time 693 // sync scenarios. Simply increase the kNumFolders and kNumBookmarksPerFolder 694 // as desired, then run the test under a profiler to find hot spots in the model 695 // association code. 696 TEST_F(ProfileSyncServiceBookmarkTest, InitialModelAssociate) { 697 const int kNumBookmarksPerFolder = 10; 698 const int kNumFolders = 10; 699 700 CreatePermanentBookmarkNodes(); 701 702 { 703 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 704 for (int i = 0; i < kNumFolders; ++i) { 705 int64 folder_id = AddFolderToShare(&trans, 706 base::StringPrintf("folder%05d", i)); 707 for (int j = 0; j < kNumBookmarksPerFolder; ++j) { 708 AddBookmarkToShare(&trans, 709 folder_id, 710 base::StringPrintf("bookmark%05d", j)); 711 } 712 } 713 } 714 715 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 716 StartSync(); 717 718 ExpectModelMatch(); 719 } 720 721 722 TEST_F(ProfileSyncServiceBookmarkTest, BookmarkModelOperations) { 723 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 724 StartSync(); 725 726 // Test addition. 727 const BookmarkNode* folder = 728 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("foobar")); 729 ExpectSyncerNodeMatching(folder); 730 ExpectModelMatch(); 731 const BookmarkNode* folder2 = 732 model_->AddFolder(folder, 0, ASCIIToUTF16("nested")); 733 ExpectSyncerNodeMatching(folder2); 734 ExpectModelMatch(); 735 const BookmarkNode* url1 = model_->AddURL( 736 folder, 0, ASCIIToUTF16("Internets #1 Pies Site"), 737 GURL("http://www.easypie.com/")); 738 ExpectSyncerNodeMatching(url1); 739 ExpectModelMatch(); 740 const BookmarkNode* url2 = model_->AddURL( 741 folder, 1, ASCIIToUTF16("Airplanes"), GURL("http://www.easyjet.com/")); 742 ExpectSyncerNodeMatching(url2); 743 ExpectModelMatch(); 744 // Test addition. 745 const BookmarkNode* mobile_folder = 746 model_->AddFolder(model_->mobile_node(), 0, ASCIIToUTF16("pie")); 747 ExpectSyncerNodeMatching(mobile_folder); 748 ExpectModelMatch(); 749 750 // Test modification. 751 model_->SetTitle(url2, ASCIIToUTF16("EasyJet")); 752 ExpectModelMatch(); 753 model_->Move(url1, folder2, 0); 754 ExpectModelMatch(); 755 model_->Move(folder2, model_->bookmark_bar_node(), 0); 756 ExpectModelMatch(); 757 model_->SetTitle(folder2, ASCIIToUTF16("Not Nested")); 758 ExpectModelMatch(); 759 model_->Move(folder, folder2, 0); 760 ExpectModelMatch(); 761 model_->SetTitle(folder, ASCIIToUTF16("who's nested now?")); 762 ExpectModelMatch(); 763 model_->Copy(url2, model_->bookmark_bar_node(), 0); 764 ExpectModelMatch(); 765 model_->SetTitle(mobile_folder, ASCIIToUTF16("strawberry")); 766 ExpectModelMatch(); 767 768 // Test deletion. 769 // Delete a single item. 770 model_->Remove(url2->parent(), url2->parent()->GetIndexOf(url2)); 771 ExpectModelMatch(); 772 // Delete an item with several children. 773 model_->Remove(folder2->parent(), 774 folder2->parent()->GetIndexOf(folder2)); 775 ExpectModelMatch(); 776 model_->Remove(model_->mobile_node(), 0); 777 ExpectModelMatch(); 778 } 779 780 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeProcessing) { 781 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 782 StartSync(); 783 784 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 785 786 FakeServerChange adds(&trans); 787 int64 f1 = adds.AddFolder(L"Server Folder B", bookmark_bar_id(), 0); 788 int64 f2 = adds.AddFolder(L"Server Folder A", bookmark_bar_id(), f1); 789 int64 u1 = adds.AddURL(L"Some old site", "ftp://nifty.andrew.cmu.edu/", 790 bookmark_bar_id(), f2); 791 int64 u2 = adds.AddURL(L"Nifty", "ftp://nifty.andrew.cmu.edu/", f1, 0); 792 // u3 is a duplicate URL 793 int64 u3 = adds.AddURL(L"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1, u2); 794 // u4 is a duplicate title, different URL. 795 adds.AddURL(L"Some old site", "http://slog.thestranger.com/", 796 bookmark_bar_id(), u1); 797 // u5 tests an empty-string title. 798 std::string javascript_url( 799 "javascript:(function(){var w=window.open(" \ 800 "'about:blank','gnotesWin','location=0,menubar=0," \ 801 "scrollbars=0,status=0,toolbar=0,width=300," \ 802 "height=300,resizable');});"); 803 adds.AddURL(std::wstring(), javascript_url, other_bookmarks_id(), 0); 804 int64 u6 = adds.AddURL( 805 L"Sync1", "http://www.syncable.edu/", mobile_bookmarks_id(), 0); 806 807 syncer::ChangeRecordList::const_iterator it; 808 // The bookmark model shouldn't yet have seen any of the nodes of |adds|. 809 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 810 ExpectBrowserNodeUnknown(it->id); 811 812 adds.ApplyPendingChanges(change_processor_.get()); 813 814 // Make sure the bookmark model received all of the nodes in |adds|. 815 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 816 ExpectBrowserNodeMatching(&trans, it->id); 817 ExpectModelMatch(&trans); 818 819 // Part two: test modifications. 820 FakeServerChange mods(&trans); 821 // Mess with u2, and move it into empty folder f2 822 // TODO(ncarter): Determine if we allow ModifyURL ops or not. 823 /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */ 824 std::wstring u2_old_title = mods.ModifyTitle(u2, L"The Google"); 825 int64 u2_old_parent = mods.ModifyPosition(u2, f2, 0); 826 827 // Now move f1 after u2. 828 std::wstring f1_old_title = mods.ModifyTitle(f1, L"Server Folder C"); 829 int64 f1_old_parent = mods.ModifyPosition(f1, f2, u2); 830 831 // Then add u3 after f1. 832 int64 u3_old_parent = mods.ModifyPosition(u3, f2, f1); 833 834 std::wstring u6_old_title = mods.ModifyTitle(u6, L"Mobile Folder A"); 835 836 // Test that the property changes have not yet taken effect. 837 ExpectBrowserNodeTitle(u2, u2_old_title); 838 /* ExpectBrowserNodeURL(u2, u2_old_url); */ 839 ExpectBrowserNodeParent(u2, u2_old_parent); 840 841 ExpectBrowserNodeTitle(f1, f1_old_title); 842 ExpectBrowserNodeParent(f1, f1_old_parent); 843 844 ExpectBrowserNodeParent(u3, u3_old_parent); 845 846 ExpectBrowserNodeTitle(u6, u6_old_title); 847 848 // Apply the changes. 849 mods.ApplyPendingChanges(change_processor_.get()); 850 851 // Check for successful application. 852 for (it = mods.changes().begin(); it != mods.changes().end(); ++it) 853 ExpectBrowserNodeMatching(&trans, it->id); 854 ExpectModelMatch(&trans); 855 856 // Part 3: Test URL deletion. 857 FakeServerChange dels(&trans); 858 dels.Delete(u2); 859 dels.Delete(u3); 860 dels.Delete(u6); 861 862 ExpectBrowserNodeKnown(u2); 863 ExpectBrowserNodeKnown(u3); 864 865 dels.ApplyPendingChanges(change_processor_.get()); 866 867 ExpectBrowserNodeUnknown(u2); 868 ExpectBrowserNodeUnknown(u3); 869 ExpectBrowserNodeUnknown(u6); 870 ExpectModelMatch(&trans); 871 } 872 873 // Tests a specific case in ApplyModelChanges where we move the 874 // children out from under a parent, and then delete the parent 875 // in the same changelist. The delete shows up first in the changelist, 876 // requiring the children to be moved to a temporary location. 877 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeRequiringFosterParent) { 878 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 879 StartSync(); 880 881 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 882 883 // Stress the immediate children of other_node because that's where 884 // ApplyModelChanges puts a temporary foster parent node. 885 std::string url("http://dev.chromium.org/"); 886 FakeServerChange adds(&trans); 887 int64 f0 = other_bookmarks_id(); // + other_node 888 int64 f1 = adds.AddFolder(L"f1", f0, 0); // + f1 889 int64 f2 = adds.AddFolder(L"f2", f1, 0); // + f2 890 int64 u3 = adds.AddURL( L"u3", url, f2, 0); // + u3 NOLINT 891 int64 u4 = adds.AddURL( L"u4", url, f2, u3); // + u4 NOLINT 892 int64 u5 = adds.AddURL( L"u5", url, f1, f2); // + u5 NOLINT 893 int64 f6 = adds.AddFolder(L"f6", f1, u5); // + f6 894 int64 u7 = adds.AddURL( L"u7", url, f0, f1); // + u7 NOLINT 895 896 syncer::ChangeRecordList::const_iterator it; 897 // The bookmark model shouldn't yet have seen any of the nodes of |adds|. 898 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 899 ExpectBrowserNodeUnknown(it->id); 900 901 adds.ApplyPendingChanges(change_processor_.get()); 902 903 // Make sure the bookmark model received all of the nodes in |adds|. 904 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 905 ExpectBrowserNodeMatching(&trans, it->id); 906 ExpectModelMatch(&trans); 907 908 // We have to do the moves before the deletions, but FakeServerChange will 909 // put the deletion at the front of the changelist. 910 FakeServerChange ops(&trans); 911 ops.ModifyPosition(f6, other_bookmarks_id(), 0); 912 ops.ModifyPosition(u3, other_bookmarks_id(), f1); // Prev == f1 is OK here. 913 ops.ModifyPosition(f2, other_bookmarks_id(), u7); 914 ops.ModifyPosition(u7, f2, 0); 915 ops.ModifyPosition(u4, other_bookmarks_id(), f2); 916 ops.ModifyPosition(u5, f6, 0); 917 ops.Delete(f1); 918 919 ops.ApplyPendingChanges(change_processor_.get()); 920 921 ExpectModelMatch(&trans); 922 } 923 924 // Simulate a server change record containing a valid but non-canonical URL. 925 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeWithNonCanonicalURL) { 926 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 927 StartSync(); 928 929 { 930 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 931 932 FakeServerChange adds(&trans); 933 std::string url("http://dev.chromium.org"); 934 EXPECT_NE(GURL(url).spec(), url); 935 adds.AddURL(L"u1", url, other_bookmarks_id(), 0); 936 937 adds.ApplyPendingChanges(change_processor_.get()); 938 939 EXPECT_EQ(1, model_->other_node()->child_count()); 940 ExpectModelMatch(&trans); 941 } 942 943 // Now reboot the sync service, forcing a merge step. 944 StopSync(); 945 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); 946 StartSync(); 947 948 // There should still be just the one bookmark. 949 EXPECT_EQ(1, model_->other_node()->child_count()); 950 ExpectModelMatch(); 951 } 952 953 // Simulate a server change record containing an invalid URL (per GURL). 954 // TODO(ncarter): Disabled due to crashes. Fix bug 1677563. 955 TEST_F(ProfileSyncServiceBookmarkTest, DISABLED_ServerChangeWithInvalidURL) { 956 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 957 StartSync(); 958 959 int child_count = 0; 960 { 961 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 962 963 FakeServerChange adds(&trans); 964 std::string url("x"); 965 EXPECT_FALSE(GURL(url).is_valid()); 966 adds.AddURL(L"u1", url, other_bookmarks_id(), 0); 967 968 adds.ApplyPendingChanges(change_processor_.get()); 969 970 // We're lenient about what should happen -- the model could wind up with 971 // the node or without it; but things should be consistent, and we 972 // shouldn't crash. 973 child_count = model_->other_node()->child_count(); 974 EXPECT_TRUE(child_count == 0 || child_count == 1); 975 ExpectModelMatch(&trans); 976 } 977 978 // Now reboot the sync service, forcing a merge step. 979 StopSync(); 980 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); 981 StartSync(); 982 983 // Things ought not to have changed. 984 EXPECT_EQ(model_->other_node()->child_count(), child_count); 985 ExpectModelMatch(); 986 } 987 988 989 // Test strings that might pose a problem if the titles ever became used as 990 // file names in the sync backend. 991 TEST_F(ProfileSyncServiceBookmarkTest, CornerCaseNames) { 992 // TODO(ncarter): Bug 1570238 explains the failure of this test. 993 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 994 StartSync(); 995 996 const char* names[] = { 997 // The empty string. 998 "", 999 // Illegal Windows filenames. 1000 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", 1001 "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", 1002 "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", 1003 // Current/parent directory markers. 1004 ".", "..", "...", 1005 // Files created automatically by the Windows shell. 1006 "Thumbs.db", ".DS_Store", 1007 // Names including Win32-illegal characters, and path separators. 1008 "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar", 1009 "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar", 1010 "foo[bar", 1011 }; 1012 // Create both folders and bookmarks using each name. 1013 GURL url("http://www.doublemint.com"); 1014 for (size_t i = 0; i < arraysize(names); ++i) { 1015 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16(names[i])); 1016 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16(names[i]), url); 1017 } 1018 1019 // Verify that the browser model matches the sync model. 1020 EXPECT_TRUE(model_->other_node()->child_count() == 2*arraysize(names)); 1021 ExpectModelMatch(); 1022 } 1023 1024 // Stress the internal representation of position by sparse numbers. We want 1025 // to repeatedly bisect the range of available positions, to force the 1026 // syncer code to renumber its ranges. Pick a number big enough so that it 1027 // would exhaust 32bits of room between items a couple of times. 1028 TEST_F(ProfileSyncServiceBookmarkTest, RepeatedMiddleInsertion) { 1029 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1030 StartSync(); 1031 1032 static const int kTimesToInsert = 256; 1033 1034 // Create two book-end nodes to insert between. 1035 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("Alpha")); 1036 model_->AddFolder(model_->other_node(), 1, ASCIIToUTF16("Omega")); 1037 int count = 2; 1038 1039 // Test insertion in first half of range by repeatedly inserting in second 1040 // position. 1041 for (int i = 0; i < kTimesToInsert; ++i) { 1042 string16 title = ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i); 1043 model_->AddFolder(model_->other_node(), 1, title); 1044 count++; 1045 } 1046 1047 // Test insertion in second half of range by repeatedly inserting in 1048 // second-to-last position. 1049 for (int i = 0; i < kTimesToInsert; ++i) { 1050 string16 title = ASCIIToUTF16("Post-insertion ") + base::IntToString16(i); 1051 model_->AddFolder(model_->other_node(), count - 1, title); 1052 count++; 1053 } 1054 1055 // Verify that the browser model matches the sync model. 1056 EXPECT_EQ(model_->other_node()->child_count(), count); 1057 ExpectModelMatch(); 1058 } 1059 1060 // Introduce a consistency violation into the model, and see that it 1061 // puts itself into a lame, error state. 1062 TEST_F(ProfileSyncServiceBookmarkTest, UnrecoverableErrorSuspendsService) { 1063 EXPECT_CALL(mock_error_handler_, 1064 OnSingleDatatypeUnrecoverableError(_, _)); 1065 1066 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1067 StartSync(); 1068 1069 // Add a node which will be the target of the consistency violation. 1070 const BookmarkNode* node = 1071 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("node")); 1072 ExpectSyncerNodeMatching(node); 1073 1074 // Now destroy the syncer node as if we were the ProfileSyncService without 1075 // updating the ProfileSyncService state. This should introduce 1076 // inconsistency between the two models. 1077 { 1078 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 1079 syncer::WriteNode sync_node(&trans); 1080 ASSERT_TRUE(InitSyncNodeFromChromeNode(node, &sync_node)); 1081 sync_node.Tombstone(); 1082 } 1083 // The models don't match at this point, but the ProfileSyncService 1084 // doesn't know it yet. 1085 ExpectSyncerNodeKnown(node); 1086 1087 // Add a child to the inconsistent node. This should cause detection of the 1088 // problem and the syncer should stop processing changes. 1089 model_->AddFolder(node, 0, ASCIIToUTF16("nested")); 1090 } 1091 1092 // See what happens if we run model association when there are two exact URL 1093 // duplicate bookmarks. The BookmarkModelAssociator should not fall over when 1094 // this happens. 1095 TEST_F(ProfileSyncServiceBookmarkTest, MergeDuplicates) { 1096 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1097 StartSync(); 1098 1099 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), 1100 GURL("http://dup.com/")); 1101 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), 1102 GURL("http://dup.com/")); 1103 1104 EXPECT_EQ(2, model_->other_node()->child_count()); 1105 1106 // Restart the sync service to trigger model association. 1107 StopSync(); 1108 StartSync(); 1109 1110 EXPECT_EQ(2, model_->other_node()->child_count()); 1111 ExpectModelMatch(); 1112 } 1113 1114 TEST_F(ProfileSyncServiceBookmarkTest, ApplySyncDeletesFromJournal) { 1115 // Initialize sync model and bookmark model as: 1116 // URL 0 1117 // Folder 1 1118 // |-- URL 1 1119 // +-- Folder 2 1120 // +-- URL 2 1121 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1122 int64 u0 = 0; 1123 int64 f1 = 0; 1124 int64 u1 = 0; 1125 int64 f2 = 0; 1126 int64 u2 = 0; 1127 StartSync(); 1128 int fixed_sync_bk_count = GetSyncBookmarkCount(); 1129 { 1130 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 1131 FakeServerChange adds(&trans); 1132 u0 = adds.AddURL(L"URL 0", "http://plus.google.com/", bookmark_bar_id(), 0); 1133 f1 = adds.AddFolder(L"Folder 1", bookmark_bar_id(), u0); 1134 u1 = adds.AddURL(L"URL 1", "http://www.google.com/", f1, 0); 1135 f2 = adds.AddFolder(L"Folder 2", f1, u1); 1136 u2 = adds.AddURL(L"URL 2", "http://mail.google.com/", f2, 0); 1137 adds.ApplyPendingChanges(change_processor_.get()); 1138 } 1139 StopSync(); 1140 1141 // Reload bookmark model and disable model saving to make sync changes not 1142 // persisted. 1143 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); 1144 EXPECT_EQ(6, model_->bookmark_bar_node()->GetTotalNodeCount()); 1145 EXPECT_EQ(fixed_sync_bk_count + 5, GetSyncBookmarkCount()); 1146 StartSync(); 1147 { 1148 // Remove all folders/bookmarks except u3 added above. 1149 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 1150 FakeServerChange dels(&trans); 1151 dels.Delete(u2); 1152 dels.Delete(f2); 1153 dels.Delete(u1); 1154 dels.Delete(f1); 1155 dels.ApplyPendingChanges(change_processor_.get()); 1156 } 1157 StopSync(); 1158 // Bookmark bar itself and u0 remain. 1159 EXPECT_EQ(2, model_->bookmark_bar_node()->GetTotalNodeCount()); 1160 1161 // Reload bookmarks including ones deleted in sync model from storage. 1162 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); 1163 EXPECT_EQ(6, model_->bookmark_bar_node()->GetTotalNodeCount()); 1164 // Add a bookmark under f1 when sync is off so that f1 will not be 1165 // deleted even when f1 matches delete journal because it's not empty. 1166 model_->AddURL(model_->bookmark_bar_node()->GetChild(1), 1167 0, UTF8ToUTF16("local"), GURL("http://www.youtube.com")); 1168 // Sync model has fixed bookmarks nodes and u3. 1169 EXPECT_EQ(fixed_sync_bk_count + 1, GetSyncBookmarkCount()); 1170 StartSync(); 1171 // Expect 4 bookmarks after model association because u2, f2, u1 are removed 1172 // by delete journal, f1 is not removed by delete journal because it's 1173 // not empty due to www.youtube.com added above. 1174 EXPECT_EQ(4, model_->bookmark_bar_node()->GetTotalNodeCount()); 1175 EXPECT_EQ(UTF8ToUTF16("URL 0"), 1176 model_->bookmark_bar_node()->GetChild(0)->GetTitle()); 1177 EXPECT_EQ(UTF8ToUTF16("Folder 1"), 1178 model_->bookmark_bar_node()->GetChild(1)->GetTitle()); 1179 EXPECT_EQ(UTF8ToUTF16("local"), 1180 model_->bookmark_bar_node()->GetChild(1)->GetChild(0)->GetTitle()); 1181 StopSync(); 1182 1183 // Verify purging of delete journals. 1184 // Delete journals for u2, f2, u1 remains because they are used in last 1185 // association. 1186 EXPECT_EQ(3u, test_user_share_.GetDeleteJournalSize()); 1187 StartSync(); 1188 StopSync(); 1189 // Reload again and all delete journals should be gone because none is used 1190 // in last association. 1191 ASSERT_TRUE(test_user_share_.Reload()); 1192 EXPECT_EQ(0u, test_user_share_.GetDeleteJournalSize()); 1193 } 1194 1195 struct TestData { 1196 const wchar_t* title; 1197 const char* url; 1198 }; 1199 1200 // Map from bookmark node ID to its version. 1201 typedef std::map<int64, int64> BookmarkNodeVersionMap; 1202 1203 // TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code 1204 // in the bookmark model unittest, to make it simpler to set up test data 1205 // here (and reduce the amount of duplication among tests), and to reduce the 1206 // duplication. 1207 class ProfileSyncServiceBookmarkTestWithData 1208 : public ProfileSyncServiceBookmarkTest { 1209 public: 1210 ProfileSyncServiceBookmarkTestWithData(); 1211 1212 protected: 1213 // Populates or compares children of the given bookmark node from/with the 1214 // given test data array with the given size. |running_count| is updated as 1215 // urls are added. It is used to set the creation date (or test the creation 1216 // date for CompareWithTestData()). 1217 void PopulateFromTestData(const BookmarkNode* node, 1218 const TestData* data, 1219 int size, 1220 int* running_count); 1221 void CompareWithTestData(const BookmarkNode* node, 1222 const TestData* data, 1223 int size, 1224 int* running_count); 1225 1226 void ExpectBookmarkModelMatchesTestData(); 1227 void WriteTestDataToBookmarkModel(); 1228 1229 // Verify transaction versions of bookmark nodes and sync nodes are equal 1230 // recursively. If node is in |version_expected|, versions should match 1231 // there, too. 1232 void ExpectTransactionVersionMatch( 1233 const BookmarkNode* node, 1234 const BookmarkNodeVersionMap& version_expected); 1235 1236 private: 1237 const base::Time start_time_; 1238 1239 DISALLOW_COPY_AND_ASSIGN(ProfileSyncServiceBookmarkTestWithData); 1240 }; 1241 1242 namespace { 1243 1244 // Constants for bookmark model that looks like: 1245 // |-- Bookmark bar 1246 // | |-- u2, http://www.u2.com/ 1247 // | |-- f1 1248 // | | |-- f1u4, http://www.f1u4.com/ 1249 // | | |-- f1u2, http://www.f1u2.com/ 1250 // | | |-- f1u3, http://www.f1u3.com/ 1251 // | | +-- f1u1, http://www.f1u1.com/ 1252 // | |-- u1, http://www.u1.com/ 1253 // | +-- f2 1254 // | |-- f2u2, http://www.f2u2.com/ 1255 // | |-- f2u4, http://www.f2u4.com/ 1256 // | |-- f2u3, http://www.f2u3.com/ 1257 // | +-- f2u1, http://www.f2u1.com/ 1258 // +-- Other bookmarks 1259 // | |-- f3 1260 // | | |-- f3u4, http://www.f3u4.com/ 1261 // | | |-- f3u2, http://www.f3u2.com/ 1262 // | | |-- f3u3, http://www.f3u3.com/ 1263 // | | +-- f3u1, http://www.f3u1.com/ 1264 // | |-- u4, http://www.u4.com/ 1265 // | |-- u3, http://www.u3.com/ 1266 // | --- f4 1267 // | | |-- f4u1, http://www.f4u1.com/ 1268 // | | |-- f4u2, http://www.f4u2.com/ 1269 // | | |-- f4u3, http://www.f4u3.com/ 1270 // | | +-- f4u4, http://www.f4u4.com/ 1271 // | |-- dup 1272 // | | +-- dupu1, http://www.dupu1.com/ 1273 // | +-- dup 1274 // | | +-- dupu2, http://www.dupu1.com/ 1275 // | +-- ls , http://www.ls.com/ 1276 // | 1277 // +-- Mobile bookmarks 1278 // |-- f5 1279 // | |-- f5u1, http://www.f5u1.com/ 1280 // |-- f6 1281 // | |-- f6u1, http://www.f6u1.com/ 1282 // | |-- f6u2, http://www.f6u2.com/ 1283 // +-- u5, http://www.u5.com/ 1284 1285 static TestData kBookmarkBarChildren[] = { 1286 { L"u2", "http://www.u2.com/" }, 1287 { L"f1", NULL }, 1288 { L"u1", "http://www.u1.com/" }, 1289 { L"f2", NULL }, 1290 }; 1291 static TestData kF1Children[] = { 1292 { L"f1u4", "http://www.f1u4.com/" }, 1293 { L"f1u2", "http://www.f1u2.com/" }, 1294 { L"f1u3", "http://www.f1u3.com/" }, 1295 { L"f1u1", "http://www.f1u1.com/" }, 1296 }; 1297 static TestData kF2Children[] = { 1298 { L"f2u2", "http://www.f2u2.com/" }, 1299 { L"f2u4", "http://www.f2u4.com/" }, 1300 { L"f2u3", "http://www.f2u3.com/" }, 1301 { L"f2u1", "http://www.f2u1.com/" }, 1302 }; 1303 1304 static TestData kOtherBookmarkChildren[] = { 1305 { L"f3", NULL }, 1306 { L"u4", "http://www.u4.com/" }, 1307 { L"u3", "http://www.u3.com/" }, 1308 { L"f4", NULL }, 1309 { L"dup", NULL }, 1310 { L"dup", NULL }, 1311 { L" ls ", "http://www.ls.com/" } 1312 }; 1313 static TestData kF3Children[] = { 1314 { L"f3u4", "http://www.f3u4.com/" }, 1315 { L"f3u2", "http://www.f3u2.com/" }, 1316 { L"f3u3", "http://www.f3u3.com/" }, 1317 { L"f3u1", "http://www.f3u1.com/" }, 1318 }; 1319 static TestData kF4Children[] = { 1320 { L"f4u1", "http://www.f4u1.com/" }, 1321 { L"f4u2", "http://www.f4u2.com/" }, 1322 { L"f4u3", "http://www.f4u3.com/" }, 1323 { L"f4u4", "http://www.f4u4.com/" }, 1324 }; 1325 static TestData kDup1Children[] = { 1326 { L"dupu1", "http://www.dupu1.com/" }, 1327 }; 1328 static TestData kDup2Children[] = { 1329 { L"dupu2", "http://www.dupu2.com/" }, 1330 }; 1331 1332 static TestData kMobileBookmarkChildren[] = { 1333 { L"f5", NULL }, 1334 { L"f6", NULL }, 1335 { L"u5", "http://www.u5.com/" }, 1336 }; 1337 static TestData kF5Children[] = { 1338 { L"f5u1", "http://www.f5u1.com/" }, 1339 { L"f5u2", "http://www.f5u2.com/" }, 1340 }; 1341 static TestData kF6Children[] = { 1342 { L"f6u1", "http://www.f6u1.com/" }, 1343 { L"f6u2", "http://www.f6u2.com/" }, 1344 }; 1345 1346 } // anonymous namespace. 1347 1348 ProfileSyncServiceBookmarkTestWithData:: 1349 ProfileSyncServiceBookmarkTestWithData() 1350 : start_time_(base::Time::Now()) { 1351 } 1352 1353 void ProfileSyncServiceBookmarkTestWithData::PopulateFromTestData( 1354 const BookmarkNode* node, 1355 const TestData* data, 1356 int size, 1357 int* running_count) { 1358 DCHECK(node); 1359 DCHECK(data); 1360 DCHECK(node->is_folder()); 1361 for (int i = 0; i < size; ++i) { 1362 const TestData& item = data[i]; 1363 if (item.url) { 1364 const base::Time add_time = 1365 start_time_ + base::TimeDelta::FromMinutes(*running_count); 1366 model_->AddURLWithCreationTime(node, i, WideToUTF16Hack(item.title), 1367 GURL(item.url), add_time); 1368 } else { 1369 model_->AddFolder(node, i, WideToUTF16Hack(item.title)); 1370 } 1371 (*running_count)++; 1372 } 1373 } 1374 1375 void ProfileSyncServiceBookmarkTestWithData::CompareWithTestData( 1376 const BookmarkNode* node, 1377 const TestData* data, 1378 int size, 1379 int* running_count) { 1380 DCHECK(node); 1381 DCHECK(data); 1382 DCHECK(node->is_folder()); 1383 ASSERT_EQ(size, node->child_count()); 1384 for (int i = 0; i < size; ++i) { 1385 const BookmarkNode* child_node = node->GetChild(i); 1386 const TestData& item = data[i]; 1387 GURL url = GURL(item.url == NULL ? "" : item.url); 1388 BookmarkNode test_node(url); 1389 test_node.SetTitle(WideToUTF16Hack(item.title)); 1390 EXPECT_EQ(child_node->GetTitle(), test_node.GetTitle()); 1391 if (item.url) { 1392 EXPECT_FALSE(child_node->is_folder()); 1393 EXPECT_TRUE(child_node->is_url()); 1394 EXPECT_EQ(child_node->url(), test_node.url()); 1395 const base::Time expected_time = 1396 start_time_ + base::TimeDelta::FromMinutes(*running_count); 1397 EXPECT_EQ(expected_time.ToInternalValue(), 1398 child_node->date_added().ToInternalValue()); 1399 } else { 1400 EXPECT_TRUE(child_node->is_folder()); 1401 EXPECT_FALSE(child_node->is_url()); 1402 } 1403 (*running_count)++; 1404 } 1405 } 1406 1407 // TODO(munjal): We should implement some way of generating random data and can 1408 // use the same seed to generate the same sequence. 1409 void ProfileSyncServiceBookmarkTestWithData::WriteTestDataToBookmarkModel() { 1410 const BookmarkNode* bookmarks_bar_node = model_->bookmark_bar_node(); 1411 int count = 0; 1412 PopulateFromTestData(bookmarks_bar_node, 1413 kBookmarkBarChildren, 1414 arraysize(kBookmarkBarChildren), 1415 &count); 1416 1417 ASSERT_GE(bookmarks_bar_node->child_count(), 4); 1418 const BookmarkNode* f1_node = bookmarks_bar_node->GetChild(1); 1419 PopulateFromTestData(f1_node, kF1Children, arraysize(kF1Children), &count); 1420 const BookmarkNode* f2_node = bookmarks_bar_node->GetChild(3); 1421 PopulateFromTestData(f2_node, kF2Children, arraysize(kF2Children), &count); 1422 1423 const BookmarkNode* other_bookmarks_node = model_->other_node(); 1424 PopulateFromTestData(other_bookmarks_node, 1425 kOtherBookmarkChildren, 1426 arraysize(kOtherBookmarkChildren), 1427 &count); 1428 1429 ASSERT_GE(other_bookmarks_node->child_count(), 6); 1430 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); 1431 PopulateFromTestData(f3_node, kF3Children, arraysize(kF3Children), &count); 1432 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); 1433 PopulateFromTestData(f4_node, kF4Children, arraysize(kF4Children), &count); 1434 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); 1435 PopulateFromTestData(dup_node, kDup1Children, arraysize(kDup1Children), 1436 &count); 1437 dup_node = other_bookmarks_node->GetChild(5); 1438 PopulateFromTestData(dup_node, kDup2Children, arraysize(kDup2Children), 1439 &count); 1440 1441 const BookmarkNode* mobile_bookmarks_node = model_->mobile_node(); 1442 PopulateFromTestData(mobile_bookmarks_node, 1443 kMobileBookmarkChildren, 1444 arraysize(kMobileBookmarkChildren), 1445 &count); 1446 1447 ASSERT_GE(mobile_bookmarks_node->child_count(), 3); 1448 const BookmarkNode* f5_node = mobile_bookmarks_node->GetChild(0); 1449 PopulateFromTestData(f5_node, kF5Children, arraysize(kF5Children), &count); 1450 const BookmarkNode* f6_node = mobile_bookmarks_node->GetChild(1); 1451 PopulateFromTestData(f6_node, kF6Children, arraysize(kF6Children), &count); 1452 1453 ExpectBookmarkModelMatchesTestData(); 1454 } 1455 1456 void ProfileSyncServiceBookmarkTestWithData:: 1457 ExpectBookmarkModelMatchesTestData() { 1458 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node(); 1459 int count = 0; 1460 CompareWithTestData(bookmark_bar_node, 1461 kBookmarkBarChildren, 1462 arraysize(kBookmarkBarChildren), 1463 &count); 1464 1465 ASSERT_GE(bookmark_bar_node->child_count(), 4); 1466 const BookmarkNode* f1_node = bookmark_bar_node->GetChild(1); 1467 CompareWithTestData(f1_node, kF1Children, arraysize(kF1Children), &count); 1468 const BookmarkNode* f2_node = bookmark_bar_node->GetChild(3); 1469 CompareWithTestData(f2_node, kF2Children, arraysize(kF2Children), &count); 1470 1471 const BookmarkNode* other_bookmarks_node = model_->other_node(); 1472 CompareWithTestData(other_bookmarks_node, 1473 kOtherBookmarkChildren, 1474 arraysize(kOtherBookmarkChildren), 1475 &count); 1476 1477 ASSERT_GE(other_bookmarks_node->child_count(), 6); 1478 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); 1479 CompareWithTestData(f3_node, kF3Children, arraysize(kF3Children), &count); 1480 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); 1481 CompareWithTestData(f4_node, kF4Children, arraysize(kF4Children), &count); 1482 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); 1483 CompareWithTestData(dup_node, kDup1Children, arraysize(kDup1Children), 1484 &count); 1485 dup_node = other_bookmarks_node->GetChild(5); 1486 CompareWithTestData(dup_node, kDup2Children, arraysize(kDup2Children), 1487 &count); 1488 1489 const BookmarkNode* mobile_bookmarks_node = model_->mobile_node(); 1490 CompareWithTestData(mobile_bookmarks_node, 1491 kMobileBookmarkChildren, 1492 arraysize(kMobileBookmarkChildren), 1493 &count); 1494 1495 ASSERT_GE(mobile_bookmarks_node->child_count(), 3); 1496 const BookmarkNode* f5_node = mobile_bookmarks_node->GetChild(0); 1497 CompareWithTestData(f5_node, kF5Children, arraysize(kF5Children), &count); 1498 const BookmarkNode* f6_node = mobile_bookmarks_node->GetChild(1); 1499 CompareWithTestData(f6_node, kF6Children, arraysize(kF6Children), &count); 1500 } 1501 1502 // Tests persistence of the profile sync service by unloading the 1503 // database and then reloading it from disk. 1504 TEST_F(ProfileSyncServiceBookmarkTestWithData, Persistence) { 1505 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1506 StartSync(); 1507 1508 WriteTestDataToBookmarkModel(); 1509 1510 ExpectModelMatch(); 1511 1512 // Force both models to discard their data and reload from disk. This 1513 // simulates what would happen if the browser were to shutdown normally, 1514 // and then relaunch. 1515 StopSync(); 1516 UnloadBookmarkModel(); 1517 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); 1518 StartSync(); 1519 1520 ExpectBookmarkModelMatchesTestData(); 1521 1522 // With the BookmarkModel contents verified, ExpectModelMatch will 1523 // verify the contents of the sync model. 1524 ExpectModelMatch(); 1525 } 1526 1527 // Tests the merge case when the BookmarkModel is non-empty but the 1528 // sync model is empty. This corresponds to uploading browser 1529 // bookmarks to an initially empty, new account. 1530 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptySyncModel) { 1531 // Don't start the sync service until we've populated the bookmark model. 1532 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1533 1534 WriteTestDataToBookmarkModel(); 1535 1536 // Restart sync. This should trigger a merge step during 1537 // initialization -- we expect the browser bookmarks to be written 1538 // to the sync service during this call. 1539 StartSync(); 1540 1541 // Verify that the bookmark model hasn't changed, and that the sync model 1542 // matches it exactly. 1543 ExpectBookmarkModelMatchesTestData(); 1544 ExpectModelMatch(); 1545 } 1546 1547 // Tests the merge case when the BookmarkModel is empty but the sync model is 1548 // non-empty. This corresponds (somewhat) to a clean install of the browser, 1549 // with no bookmarks, connecting to a sync account that has some bookmarks. 1550 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptyBookmarkModel) { 1551 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1552 StartSync(); 1553 1554 WriteTestDataToBookmarkModel(); 1555 1556 ExpectModelMatch(); 1557 1558 // Force the databse to unload and write itself to disk. 1559 StopSync(); 1560 1561 // Blow away the bookmark model -- it should be empty afterwards. 1562 UnloadBookmarkModel(); 1563 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1564 EXPECT_EQ(model_->bookmark_bar_node()->child_count(), 0); 1565 EXPECT_EQ(model_->other_node()->child_count(), 0); 1566 EXPECT_EQ(model_->mobile_node()->child_count(), 0); 1567 1568 // Now restart the sync service. Starting it should populate the bookmark 1569 // model -- test for consistency. 1570 StartSync(); 1571 ExpectBookmarkModelMatchesTestData(); 1572 ExpectModelMatch(); 1573 } 1574 1575 // Tests the merge cases when both the models are expected to be identical 1576 // after the merge. 1577 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeExpectedIdenticalModels) { 1578 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1579 StartSync(); 1580 WriteTestDataToBookmarkModel(); 1581 ExpectModelMatch(); 1582 StopSync(); 1583 UnloadBookmarkModel(); 1584 1585 // At this point both the bookmark model and the server should have the 1586 // exact same data and it should match the test data. 1587 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); 1588 StartSync(); 1589 ExpectBookmarkModelMatchesTestData(); 1590 ExpectModelMatch(); 1591 StopSync(); 1592 UnloadBookmarkModel(); 1593 1594 // Now reorder some bookmarks in the bookmark model and then merge. Make 1595 // sure we get the order of the server after merge. 1596 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); 1597 ExpectBookmarkModelMatchesTestData(); 1598 const BookmarkNode* bookmark_bar = model_->bookmark_bar_node(); 1599 ASSERT_TRUE(bookmark_bar); 1600 ASSERT_GT(bookmark_bar->child_count(), 1); 1601 model_->Move(bookmark_bar->GetChild(0), bookmark_bar, 1); 1602 StartSync(); 1603 ExpectModelMatch(); 1604 ExpectBookmarkModelMatchesTestData(); 1605 } 1606 1607 // Tests the merge cases when both the models are expected to be identical 1608 // after the merge. 1609 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeModelsWithSomeExtras) { 1610 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1611 WriteTestDataToBookmarkModel(); 1612 ExpectBookmarkModelMatchesTestData(); 1613 1614 // Remove some nodes and reorder some nodes. 1615 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node(); 1616 int remove_index = 2; 1617 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1618 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index); 1619 ASSERT_TRUE(child_node); 1620 ASSERT_TRUE(child_node->is_url()); 1621 model_->Remove(bookmark_bar_node, remove_index); 1622 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1623 child_node = bookmark_bar_node->GetChild(remove_index); 1624 ASSERT_TRUE(child_node); 1625 ASSERT_TRUE(child_node->is_folder()); 1626 model_->Remove(bookmark_bar_node, remove_index); 1627 1628 const BookmarkNode* other_node = model_->other_node(); 1629 ASSERT_GE(other_node->child_count(), 1); 1630 const BookmarkNode* f3_node = other_node->GetChild(0); 1631 ASSERT_TRUE(f3_node); 1632 ASSERT_TRUE(f3_node->is_folder()); 1633 remove_index = 2; 1634 ASSERT_GT(f3_node->child_count(), remove_index); 1635 model_->Remove(f3_node, remove_index); 1636 ASSERT_GT(f3_node->child_count(), remove_index); 1637 model_->Remove(f3_node, remove_index); 1638 1639 StartSync(); 1640 ExpectModelMatch(); 1641 StopSync(); 1642 1643 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1644 WriteTestDataToBookmarkModel(); 1645 ExpectBookmarkModelMatchesTestData(); 1646 1647 // Remove some nodes and reorder some nodes. 1648 bookmark_bar_node = model_->bookmark_bar_node(); 1649 remove_index = 0; 1650 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1651 child_node = bookmark_bar_node->GetChild(remove_index); 1652 ASSERT_TRUE(child_node); 1653 ASSERT_TRUE(child_node->is_url()); 1654 model_->Remove(bookmark_bar_node, remove_index); 1655 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1656 child_node = bookmark_bar_node->GetChild(remove_index); 1657 ASSERT_TRUE(child_node); 1658 ASSERT_TRUE(child_node->is_folder()); 1659 model_->Remove(bookmark_bar_node, remove_index); 1660 1661 ASSERT_GE(bookmark_bar_node->child_count(), 2); 1662 model_->Move(bookmark_bar_node->GetChild(0), bookmark_bar_node, 1); 1663 1664 other_node = model_->other_node(); 1665 ASSERT_GE(other_node->child_count(), 1); 1666 f3_node = other_node->GetChild(0); 1667 ASSERT_TRUE(f3_node); 1668 ASSERT_TRUE(f3_node->is_folder()); 1669 remove_index = 0; 1670 ASSERT_GT(f3_node->child_count(), remove_index); 1671 model_->Remove(f3_node, remove_index); 1672 ASSERT_GT(f3_node->child_count(), remove_index); 1673 model_->Remove(f3_node, remove_index); 1674 1675 ASSERT_GE(other_node->child_count(), 4); 1676 model_->Move(other_node->GetChild(0), other_node, 1); 1677 model_->Move(other_node->GetChild(2), other_node, 3); 1678 1679 StartSync(); 1680 ExpectModelMatch(); 1681 1682 // After the merge, the model should match the test data. 1683 ExpectBookmarkModelMatchesTestData(); 1684 } 1685 1686 // Tests that when persisted model associations are used, things work fine. 1687 TEST_F(ProfileSyncServiceBookmarkTestWithData, ModelAssociationPersistence) { 1688 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1689 WriteTestDataToBookmarkModel(); 1690 StartSync(); 1691 ExpectModelMatch(); 1692 // Force sync to shut down and write itself to disk. 1693 StopSync(); 1694 // Now restart sync. This time it should use the persistent 1695 // associations. 1696 StartSync(); 1697 ExpectModelMatch(); 1698 } 1699 1700 // Tests that when persisted model associations are used, things work fine. 1701 TEST_F(ProfileSyncServiceBookmarkTestWithData, 1702 ModelAssociationInvalidPersistence) { 1703 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1704 WriteTestDataToBookmarkModel(); 1705 StartSync(); 1706 ExpectModelMatch(); 1707 // Force sync to shut down and write itself to disk. 1708 StopSync(); 1709 // Change the bookmark model before restarting sync service to simulate 1710 // the situation where bookmark model is different from sync model and 1711 // make sure model associator correctly rebuilds associations. 1712 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node(); 1713 model_->AddURL(bookmark_bar_node, 0, ASCIIToUTF16("xtra"), 1714 GURL("http://www.xtra.com")); 1715 // Now restart sync. This time it will try to use the persistent 1716 // associations and realize that they are invalid and hence will rebuild 1717 // associations. 1718 StartSync(); 1719 ExpectModelMatch(); 1720 } 1721 1722 TEST_F(ProfileSyncServiceBookmarkTestWithData, SortChildren) { 1723 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1724 StartSync(); 1725 1726 // Write test data to bookmark model and verify that the models match. 1727 WriteTestDataToBookmarkModel(); 1728 const BookmarkNode* folder_added = model_->other_node()->GetChild(0); 1729 ASSERT_TRUE(folder_added); 1730 ASSERT_TRUE(folder_added->is_folder()); 1731 1732 ExpectModelMatch(); 1733 1734 // Sort the other-bookmarks children and expect that hte models match. 1735 model_->SortChildren(folder_added); 1736 ExpectModelMatch(); 1737 } 1738 1739 // See what happens if we enable sync but then delete the "Sync Data" 1740 // folder. 1741 TEST_F(ProfileSyncServiceBookmarkTestWithData, 1742 RecoverAfterDeletingSyncDataDirectory) { 1743 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1744 StartSync(); 1745 1746 WriteTestDataToBookmarkModel(); 1747 1748 StopSync(); 1749 1750 // Nuke the sync DB and reload. 1751 TearDown(); 1752 SetUp(); 1753 1754 // First attempt fails due to a persistence error. 1755 EXPECT_TRUE(CreatePermanentBookmarkNodes()); 1756 EXPECT_FALSE(AssociateModels()); 1757 1758 // Second attempt succeeds due to the previous error resetting the native 1759 // transaction version. 1760 model_associator_.reset(); 1761 EXPECT_TRUE(CreatePermanentBookmarkNodes()); 1762 EXPECT_TRUE(AssociateModels()); 1763 1764 // Make sure we're back in sync. In real life, the user would need 1765 // to reauthenticate before this happens, but in the test, authentication 1766 // is sidestepped. 1767 ExpectBookmarkModelMatchesTestData(); 1768 ExpectModelMatch(); 1769 } 1770 1771 // Verify that the bookmark model is updated about whether the 1772 // associator is currently running. 1773 TEST_F(ProfileSyncServiceBookmarkTest, AssociationState) { 1774 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1775 1776 ExtensiveChangesBookmarkModelObserver observer; 1777 model_->AddObserver(&observer); 1778 1779 StartSync(); 1780 1781 EXPECT_EQ(1, observer.get_started()); 1782 EXPECT_EQ(0, observer.get_completed_count_at_started()); 1783 EXPECT_EQ(1, observer.get_completed()); 1784 1785 model_->RemoveObserver(&observer); 1786 } 1787 1788 // Verify that the creation_time_us changes are applied in the local model at 1789 // association time and update time. 1790 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateDateAdded) { 1791 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1792 WriteTestDataToBookmarkModel(); 1793 1794 // Start and stop sync in order to create bookmark nodes in the sync db. 1795 StartSync(); 1796 StopSync(); 1797 1798 // Modify the date_added field of a bookmark so it doesn't match with 1799 // the sync data. 1800 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node(); 1801 int remove_index = 2; 1802 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1803 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index); 1804 ASSERT_TRUE(child_node); 1805 EXPECT_TRUE(child_node->is_url()); 1806 model_->SetDateAdded(child_node, base::Time::FromInternalValue(10)); 1807 1808 StartSync(); 1809 1810 // Everything should be back in sync after model association. 1811 ExpectBookmarkModelMatchesTestData(); 1812 ExpectModelMatch(); 1813 1814 // Now trigger a change while syncing. We add a new bookmark, sync it, then 1815 // updates it's creation time. 1816 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); 1817 FakeServerChange adds(&trans); 1818 const std::wstring kTitle = L"Some site"; 1819 const std::string kUrl = "http://www.whatwhat.yeah/"; 1820 const int kCreationTime = 30; 1821 int64 id = adds.AddURL(kTitle, kUrl, 1822 bookmark_bar_id(), 0); 1823 adds.ApplyPendingChanges(change_processor_.get()); 1824 FakeServerChange updates(&trans); 1825 updates.ModifyCreationTime(id, kCreationTime); 1826 updates.ApplyPendingChanges(change_processor_.get()); 1827 1828 const BookmarkNode* node = model_->bookmark_bar_node()->GetChild(0); 1829 ASSERT_TRUE(node); 1830 EXPECT_TRUE(node->is_url()); 1831 EXPECT_EQ(WideToUTF16Hack(kTitle), node->GetTitle()); 1832 EXPECT_EQ(kUrl, node->url().possibly_invalid_spec()); 1833 EXPECT_EQ(node->date_added(), base::Time::FromInternalValue(30)); 1834 } 1835 1836 // Output transaction versions of |node| and nodes under it to |node_versions|. 1837 void GetTransactionVersions( 1838 const BookmarkNode* root, 1839 BookmarkNodeVersionMap* node_versions) { 1840 node_versions->clear(); 1841 std::queue<const BookmarkNode*> nodes; 1842 nodes.push(root); 1843 while (!nodes.empty()) { 1844 const BookmarkNode* n = nodes.front(); 1845 nodes.pop(); 1846 1847 std::string version_str; 1848 int64 version; 1849 EXPECT_TRUE(n->GetMetaInfo(kBookmarkTransactionVersionKey, &version_str)); 1850 EXPECT_TRUE(base::StringToInt64(version_str, &version)); 1851 1852 (*node_versions)[n->id()] = version; 1853 for (int i = 0; i < n->child_count(); ++i) 1854 nodes.push(n->GetChild(i)); 1855 } 1856 } 1857 1858 void ProfileSyncServiceBookmarkTestWithData::ExpectTransactionVersionMatch( 1859 const BookmarkNode* node, 1860 const BookmarkNodeVersionMap& version_expected) { 1861 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); 1862 1863 BookmarkNodeVersionMap bnodes_versions; 1864 GetTransactionVersions(node, &bnodes_versions); 1865 for (BookmarkNodeVersionMap::const_iterator it = bnodes_versions.begin(); 1866 it != bnodes_versions.end(); ++it) { 1867 syncer::ReadNode sync_node(&trans); 1868 ASSERT_TRUE(model_associator_->InitSyncNodeFromChromeId(it->first, 1869 &sync_node)); 1870 EXPECT_EQ(sync_node.GetEntry()->Get(syncer::syncable::TRANSACTION_VERSION), 1871 it->second); 1872 BookmarkNodeVersionMap::const_iterator expected_ver_it = 1873 version_expected.find(it->first); 1874 if (expected_ver_it != version_expected.end()) 1875 EXPECT_EQ(expected_ver_it->second, it->second); 1876 } 1877 } 1878 1879 // Test transaction versions of model and nodes are incremented after changes 1880 // are applied. 1881 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateTransactionVersion) { 1882 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1883 StartSync(); 1884 WriteTestDataToBookmarkModel(); 1885 base::MessageLoop::current()->RunUntilIdle(); 1886 1887 BookmarkNodeVersionMap initial_versions; 1888 1889 // Verify transaction versions in sync model and bookmark model (saved as 1890 // transaction version of root node) are equal after 1891 // WriteTestDataToBookmarkModel() created bookmarks. 1892 { 1893 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); 1894 EXPECT_GT(trans.GetModelVersion(syncer::BOOKMARKS), 0); 1895 GetTransactionVersions(model_->root_node(), &initial_versions); 1896 EXPECT_EQ(trans.GetModelVersion(syncer::BOOKMARKS), 1897 initial_versions[model_->root_node()->id()]); 1898 } 1899 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), 1900 BookmarkNodeVersionMap()); 1901 ExpectTransactionVersionMatch(model_->other_node(), 1902 BookmarkNodeVersionMap()); 1903 ExpectTransactionVersionMatch(model_->mobile_node(), 1904 BookmarkNodeVersionMap()); 1905 1906 // Verify model version is incremented and bookmark node versions remain 1907 // the same. 1908 const BookmarkNode* bookmark_bar = model_->bookmark_bar_node(); 1909 model_->Remove(bookmark_bar, 0); 1910 base::MessageLoop::current()->RunUntilIdle(); 1911 BookmarkNodeVersionMap new_versions; 1912 GetTransactionVersions(model_->root_node(), &new_versions); 1913 EXPECT_EQ(initial_versions[model_->root_node()->id()] + 1, 1914 new_versions[model_->root_node()->id()]); 1915 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), initial_versions); 1916 ExpectTransactionVersionMatch(model_->other_node(), initial_versions); 1917 ExpectTransactionVersionMatch(model_->mobile_node(), initial_versions); 1918 1919 // Verify model version and version of changed bookmark are incremented and 1920 // versions of others remain same. 1921 const BookmarkNode* changed_bookmark = 1922 model_->bookmark_bar_node()->GetChild(0); 1923 model_->SetTitle(changed_bookmark, WideToUTF16Hack(L"test")); 1924 base::MessageLoop::current()->RunUntilIdle(); 1925 GetTransactionVersions(model_->root_node(), &new_versions); 1926 EXPECT_EQ(initial_versions[model_->root_node()->id()] + 2, 1927 new_versions[model_->root_node()->id()]); 1928 EXPECT_LT(initial_versions[changed_bookmark->id()], 1929 new_versions[changed_bookmark->id()]); 1930 initial_versions.erase(changed_bookmark->id()); 1931 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), initial_versions); 1932 ExpectTransactionVersionMatch(model_->other_node(), initial_versions); 1933 ExpectTransactionVersionMatch(model_->mobile_node(), initial_versions); 1934 } 1935 1936 // Test that sync persistence errors are detected and trigger a failed 1937 // association. 1938 TEST_F(ProfileSyncServiceBookmarkTestWithData, PersistenceError) { 1939 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1940 StartSync(); 1941 WriteTestDataToBookmarkModel(); 1942 base::MessageLoop::current()->RunUntilIdle(); 1943 1944 BookmarkNodeVersionMap initial_versions; 1945 1946 // Verify transaction versions in sync model and bookmark model (saved as 1947 // transaction version of root node) are equal after 1948 // WriteTestDataToBookmarkModel() created bookmarks. 1949 { 1950 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); 1951 EXPECT_GT(trans.GetModelVersion(syncer::BOOKMARKS), 0); 1952 GetTransactionVersions(model_->root_node(), &initial_versions); 1953 EXPECT_EQ(trans.GetModelVersion(syncer::BOOKMARKS), 1954 initial_versions[model_->root_node()->id()]); 1955 } 1956 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), 1957 BookmarkNodeVersionMap()); 1958 ExpectTransactionVersionMatch(model_->other_node(), 1959 BookmarkNodeVersionMap()); 1960 ExpectTransactionVersionMatch(model_->mobile_node(), 1961 BookmarkNodeVersionMap()); 1962 1963 // Now shut down sync and artificially increment the native model's version. 1964 StopSync(); 1965 int64 root_version = initial_versions[model_->root_node()->id()]; 1966 model_->SetNodeMetaInfo(model_->root_node(), kBookmarkTransactionVersionKey, 1967 base::Int64ToString(root_version+1)); 1968 1969 // Upon association, bookmarks should fail to associate. 1970 EXPECT_FALSE(AssociateModels()); 1971 } 1972 1973 } // namespace 1974 1975 } // namespace browser_sync 1976