1 // Copyright (c) 2011 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 <stack> 10 #include <vector> 11 12 #include "base/file_path.h" 13 #include "base/file_util.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/message_loop.h" 16 #include "base/string16.h" 17 #include "base/string_number_conversions.h" 18 #include "base/string_util.h" 19 #include "base/utf_string_conversions.h" 20 #include "chrome/browser/bookmarks/bookmark_model.h" 21 #include "chrome/browser/sync/abstract_profile_sync_service_test.h" 22 #include "chrome/browser/sync/engine/syncapi.h" 23 #include "chrome/browser/sync/glue/bookmark_change_processor.h" 24 #include "chrome/browser/sync/glue/bookmark_model_associator.h" 25 #include "chrome/browser/sync/syncable/directory_manager.h" 26 #include "chrome/test/sync/engine/test_id_factory.h" 27 #include "chrome/test/sync/engine/test_user_share.h" 28 #include "chrome/test/testing_profile.h" 29 #include "content/browser/browser_thread.h" 30 #include "testing/gmock/include/gmock/gmock.h" 31 #include "testing/gtest/include/gtest/gtest.h" 32 33 namespace browser_sync { 34 35 namespace { 36 37 using testing::_; 38 using testing::InvokeWithoutArgs; 39 using testing::Mock; 40 using testing::StrictMock; 41 42 class TestBookmarkModelAssociator : public BookmarkModelAssociator { 43 public: 44 TestBookmarkModelAssociator( 45 BookmarkModel* bookmark_model, 46 sync_api::UserShare* user_share, 47 UnrecoverableErrorHandler* unrecoverable_error_handler) 48 : BookmarkModelAssociator(bookmark_model, user_share, 49 unrecoverable_error_handler), 50 user_share_(user_share) {} 51 52 // TODO(akalin): This logic lazily creates any tagged node that is 53 // requested. A better way would be to have utility functions to 54 // create sync nodes from some bookmark structure and to use that. 55 virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) { 56 std::wstring tag_wide; 57 if (!UTF8ToWide(tag.c_str(), tag.length(), &tag_wide)) { 58 NOTREACHED() << "Unable to convert UTF8 to wide for string: " << tag; 59 return false; 60 } 61 62 bool root_exists = false; 63 syncable::ModelType type = model_type(); 64 { 65 sync_api::WriteTransaction trans(user_share_); 66 sync_api::ReadNode uber_root(&trans); 67 uber_root.InitByRootLookup(); 68 69 sync_api::ReadNode root(&trans); 70 root_exists = root.InitByTagLookup( 71 ProfileSyncServiceTestHelper::GetTagForType(type)); 72 } 73 74 if (!root_exists) { 75 bool created = ProfileSyncServiceTestHelper::CreateRoot( 76 type, 77 user_share_, 78 &id_factory_); 79 if (!created) 80 return false; 81 } 82 83 sync_api::WriteTransaction trans(user_share_); 84 sync_api::ReadNode root(&trans); 85 EXPECT_TRUE(root.InitByTagLookup( 86 ProfileSyncServiceTestHelper::GetTagForType(type))); 87 88 // First, try to find a node with the title among the root's children. 89 // This will be the case if we are testing model persistence, and 90 // are reloading a sync repository created earlier in the test. 91 int64 last_child_id = sync_api::kInvalidId; 92 for (int64 id = root.GetFirstChildId(); id != sync_api::kInvalidId; /***/) { 93 sync_api::ReadNode child(&trans); 94 child.InitByIdLookup(id); 95 last_child_id = id; 96 if (tag_wide == child.GetTitle()) { 97 *sync_id = id; 98 return true; 99 } 100 id = child.GetSuccessorId(); 101 } 102 103 sync_api::ReadNode predecessor_node(&trans); 104 sync_api::ReadNode* predecessor = NULL; 105 if (last_child_id != sync_api::kInvalidId) { 106 predecessor_node.InitByIdLookup(last_child_id); 107 predecessor = &predecessor_node; 108 } 109 sync_api::WriteNode node(&trans); 110 // Create new fake tagged nodes at the end of the ordering. 111 node.InitByCreation(type, root, predecessor); 112 node.SetIsFolder(true); 113 node.SetTitle(tag_wide); 114 node.SetExternalId(0); 115 *sync_id = node.GetId(); 116 return true; 117 } 118 119 private: 120 sync_api::UserShare* user_share_; 121 browser_sync::TestIdFactory id_factory_; 122 }; 123 124 // FakeServerChange constructs a list of sync_api::ChangeRecords while modifying 125 // the sync model, and can pass the ChangeRecord list to a 126 // sync_api::SyncObserver (i.e., the ProfileSyncService) to test the client 127 // change-application behavior. 128 // Tests using FakeServerChange should be careful to avoid back-references, 129 // since FakeServerChange will send the edits in the order specified. 130 class FakeServerChange { 131 public: 132 explicit FakeServerChange(sync_api::WriteTransaction* trans) : trans_(trans) { 133 } 134 135 // Pretend that the server told the syncer to add a bookmark object. 136 int64 Add(const std::wstring& title, 137 const std::string& url, 138 bool is_folder, 139 int64 parent_id, 140 int64 predecessor_id) { 141 sync_api::ReadNode parent(trans_); 142 EXPECT_TRUE(parent.InitByIdLookup(parent_id)); 143 sync_api::WriteNode node(trans_); 144 if (predecessor_id == 0) { 145 EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent, NULL)); 146 } else { 147 sync_api::ReadNode predecessor(trans_); 148 EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id)); 149 EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); 150 EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent, 151 &predecessor)); 152 } 153 EXPECT_EQ(node.GetPredecessorId(), predecessor_id); 154 EXPECT_EQ(node.GetParentId(), parent_id); 155 node.SetIsFolder(is_folder); 156 node.SetTitle(title); 157 if (!is_folder) 158 node.SetURL(GURL(url)); 159 sync_api::SyncManager::ChangeRecord record; 160 record.action = sync_api::SyncManager::ChangeRecord::ACTION_ADD; 161 record.id = node.GetId(); 162 changes_.push_back(record); 163 return node.GetId(); 164 } 165 166 // Add a bookmark folder. 167 int64 AddFolder(const std::wstring& title, 168 int64 parent_id, 169 int64 predecessor_id) { 170 return Add(title, std::string(), true, parent_id, predecessor_id); 171 } 172 173 // Add a bookmark. 174 int64 AddURL(const std::wstring& title, 175 const std::string& url, 176 int64 parent_id, 177 int64 predecessor_id) { 178 return Add(title, url, false, parent_id, predecessor_id); 179 } 180 181 // Pretend that the server told the syncer to delete an object. 182 void Delete(int64 id) { 183 { 184 // Delete the sync node. 185 sync_api::WriteNode node(trans_); 186 EXPECT_TRUE(node.InitByIdLookup(id)); 187 EXPECT_FALSE(node.GetFirstChildId()); 188 node.Remove(); 189 } 190 { 191 // Verify the deletion. 192 sync_api::ReadNode node(trans_); 193 EXPECT_FALSE(node.InitByIdLookup(id)); 194 } 195 196 sync_api::SyncManager::ChangeRecord record; 197 record.action = sync_api::SyncManager::ChangeRecord::ACTION_DELETE; 198 record.id = id; 199 // Deletions are always first in the changelist, but we can't actually do 200 // WriteNode::Remove() on the node until its children are moved. So, as 201 // a practical matter, users of FakeServerChange must move or delete 202 // children before parents. Thus, we must insert the deletion record 203 // at the front of the vector. 204 changes_.insert(changes_.begin(), record); 205 } 206 207 // Set a new title value, and return the old value. 208 std::wstring ModifyTitle(int64 id, const std::wstring& new_title) { 209 sync_api::WriteNode node(trans_); 210 EXPECT_TRUE(node.InitByIdLookup(id)); 211 std::wstring old_title = node.GetTitle(); 212 node.SetTitle(new_title); 213 SetModified(id); 214 return old_title; 215 } 216 217 // Set a new parent and predecessor value. Return the old parent id. 218 // We could return the old predecessor id, but it turns out not to be 219 // very useful for assertions. 220 int64 ModifyPosition(int64 id, int64 parent_id, int64 predecessor_id) { 221 sync_api::ReadNode parent(trans_); 222 EXPECT_TRUE(parent.InitByIdLookup(parent_id)); 223 sync_api::WriteNode node(trans_); 224 EXPECT_TRUE(node.InitByIdLookup(id)); 225 int64 old_parent_id = node.GetParentId(); 226 if (predecessor_id == 0) { 227 EXPECT_TRUE(node.SetPosition(parent, NULL)); 228 } else { 229 sync_api::ReadNode predecessor(trans_); 230 EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id)); 231 EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); 232 EXPECT_TRUE(node.SetPosition(parent, &predecessor)); 233 } 234 SetModified(id); 235 return old_parent_id; 236 } 237 238 // Pass the fake change list to |service|. 239 void ApplyPendingChanges(ChangeProcessor* processor) { 240 processor->ApplyChangesFromSyncModel(trans_, 241 changes_.size() ? &changes_[0] : NULL, changes_.size()); 242 } 243 244 const std::vector<sync_api::SyncManager::ChangeRecord>& changes() { 245 return changes_; 246 } 247 248 private: 249 // Helper function to push an ACTION_UPDATE record onto the back 250 // of the changelist. 251 void SetModified(int64 id) { 252 // Coalesce multi-property edits. 253 if (!changes_.empty() && changes_.back().id == id && 254 changes_.back().action == 255 sync_api::SyncManager::ChangeRecord::ACTION_UPDATE) 256 return; 257 sync_api::SyncManager::ChangeRecord record; 258 record.action = sync_api::SyncManager::ChangeRecord::ACTION_UPDATE; 259 record.id = id; 260 changes_.push_back(record); 261 } 262 263 // The transaction on which everything happens. 264 sync_api::WriteTransaction *trans_; 265 266 // The change list we construct. 267 std::vector<sync_api::SyncManager::ChangeRecord> changes_; 268 }; 269 270 class MockUnrecoverableErrorHandler : public UnrecoverableErrorHandler { 271 public: 272 MOCK_METHOD2(OnUnrecoverableError, 273 void(const tracked_objects::Location&, const std::string&)); 274 }; 275 276 class ProfileSyncServiceBookmarkTest : public testing::Test { 277 protected: 278 enum LoadOption { LOAD_FROM_STORAGE, DELETE_EXISTING_STORAGE }; 279 enum SaveOption { SAVE_TO_STORAGE, DONT_SAVE_TO_STORAGE }; 280 281 ProfileSyncServiceBookmarkTest() 282 : ui_thread_(BrowserThread::UI, &message_loop_), 283 file_thread_(BrowserThread::FILE, &message_loop_), 284 model_(NULL) { 285 } 286 287 virtual ~ProfileSyncServiceBookmarkTest() { 288 StopSync(); 289 UnloadBookmarkModel(); 290 } 291 292 virtual void SetUp() { 293 test_user_share_.SetUp(); 294 } 295 296 virtual void TearDown() { 297 test_user_share_.TearDown(); 298 } 299 300 // Load (or re-load) the bookmark model. |load| controls use of the 301 // bookmarks file on disk. |save| controls whether the newly loaded 302 // bookmark model will write out a bookmark file as it goes. 303 void LoadBookmarkModel(LoadOption load, SaveOption save) { 304 bool delete_bookmarks = load == DELETE_EXISTING_STORAGE; 305 profile_.CreateBookmarkModel(delete_bookmarks); 306 model_ = profile_.GetBookmarkModel(); 307 // Wait for the bookmarks model to load. 308 profile_.BlockUntilBookmarkModelLoaded(); 309 // This noticeably speeds up the unit tests that request it. 310 if (save == DONT_SAVE_TO_STORAGE) 311 model_->ClearStore(); 312 message_loop_.RunAllPending(); 313 } 314 315 void StartSync() { 316 // Set up model associator. 317 model_associator_.reset(new TestBookmarkModelAssociator( 318 profile_.GetBookmarkModel(), 319 test_user_share_.user_share(), 320 &mock_unrecoverable_error_handler_)); 321 EXPECT_TRUE(model_associator_->AssociateModels()); 322 MessageLoop::current()->RunAllPending(); 323 324 // Set up change processor. 325 change_processor_.reset( 326 new BookmarkChangeProcessor(model_associator_.get(), 327 &mock_unrecoverable_error_handler_)); 328 change_processor_->Start(&profile_, test_user_share_.user_share()); 329 } 330 331 void StopSync() { 332 change_processor_->Stop(); 333 change_processor_.reset(); 334 335 EXPECT_TRUE(model_associator_->DisassociateModels()); 336 model_associator_.reset(); 337 338 message_loop_.RunAllPending(); 339 340 // TODO(akalin): Actually close the database and flush it to disk 341 // (and make StartSync reload from disk). This would require 342 // refactoring TestUserShare. 343 } 344 345 void UnloadBookmarkModel() { 346 profile_.CreateBookmarkModel(false /* delete_bookmarks */); 347 model_ = NULL; 348 message_loop_.RunAllPending(); 349 } 350 351 bool InitSyncNodeFromChromeNode(const BookmarkNode* bnode, 352 sync_api::BaseNode* sync_node) { 353 return model_associator_->InitSyncNodeFromChromeId(bnode->id(), 354 sync_node); 355 } 356 357 void ExpectSyncerNodeMatching(sync_api::BaseTransaction* trans, 358 const BookmarkNode* bnode) { 359 sync_api::ReadNode gnode(trans); 360 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnode, &gnode)); 361 // Non-root node titles and parents must match. 362 if (bnode != model_->GetBookmarkBarNode() && 363 bnode != model_->other_node()) { 364 EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(gnode.GetTitle())); 365 EXPECT_EQ( 366 model_associator_->GetChromeNodeFromSyncId(gnode.GetParentId()), 367 bnode->parent()); 368 } 369 EXPECT_EQ(bnode->is_folder(), gnode.GetIsFolder()); 370 if (bnode->is_url()) 371 EXPECT_EQ(bnode->GetURL(), gnode.GetURL()); 372 373 // Check for position matches. 374 int browser_index = bnode->parent()->GetIndexOf(bnode); 375 if (browser_index == 0) { 376 EXPECT_EQ(gnode.GetPredecessorId(), 0); 377 } else { 378 const BookmarkNode* bprev = 379 bnode->parent()->GetChild(browser_index - 1); 380 sync_api::ReadNode gprev(trans); 381 ASSERT_TRUE(InitSyncNodeFromChromeNode(bprev, &gprev)); 382 EXPECT_EQ(gnode.GetPredecessorId(), gprev.GetId()); 383 EXPECT_EQ(gnode.GetParentId(), gprev.GetParentId()); 384 } 385 if (browser_index == bnode->parent()->child_count() - 1) { 386 EXPECT_EQ(gnode.GetSuccessorId(), 0); 387 } else { 388 const BookmarkNode* bnext = 389 bnode->parent()->GetChild(browser_index + 1); 390 sync_api::ReadNode gnext(trans); 391 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnext, &gnext)); 392 EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId()); 393 EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId()); 394 } 395 if (bnode->child_count()) { 396 EXPECT_TRUE(gnode.GetFirstChildId()); 397 } 398 } 399 400 void ExpectSyncerNodeMatching(const BookmarkNode* bnode) { 401 sync_api::ReadTransaction trans(test_user_share_.user_share()); 402 ExpectSyncerNodeMatching(&trans, bnode); 403 } 404 405 void ExpectBrowserNodeMatching(sync_api::BaseTransaction* trans, 406 int64 sync_id) { 407 EXPECT_TRUE(sync_id); 408 const BookmarkNode* bnode = 409 model_associator_->GetChromeNodeFromSyncId(sync_id); 410 ASSERT_TRUE(bnode); 411 int64 id = model_associator_->GetSyncIdFromChromeId(bnode->id()); 412 EXPECT_EQ(id, sync_id); 413 ExpectSyncerNodeMatching(trans, bnode); 414 } 415 416 void ExpectBrowserNodeUnknown(int64 sync_id) { 417 EXPECT_FALSE(model_associator_->GetChromeNodeFromSyncId(sync_id)); 418 } 419 420 void ExpectBrowserNodeKnown(int64 sync_id) { 421 EXPECT_TRUE(model_associator_->GetChromeNodeFromSyncId(sync_id)); 422 } 423 424 void ExpectSyncerNodeKnown(const BookmarkNode* node) { 425 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id()); 426 EXPECT_NE(sync_id, sync_api::kInvalidId); 427 } 428 429 void ExpectSyncerNodeUnknown(const BookmarkNode* node) { 430 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id()); 431 EXPECT_EQ(sync_id, sync_api::kInvalidId); 432 } 433 434 void ExpectBrowserNodeTitle(int64 sync_id, const std::wstring& title) { 435 const BookmarkNode* bnode = 436 model_associator_->GetChromeNodeFromSyncId(sync_id); 437 ASSERT_TRUE(bnode); 438 EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(title)); 439 } 440 441 void ExpectBrowserNodeURL(int64 sync_id, const std::string& url) { 442 const BookmarkNode* bnode = 443 model_associator_->GetChromeNodeFromSyncId(sync_id); 444 ASSERT_TRUE(bnode); 445 EXPECT_EQ(GURL(url), bnode->GetURL()); 446 } 447 448 void ExpectBrowserNodeParent(int64 sync_id, int64 parent_sync_id) { 449 const BookmarkNode* node = 450 model_associator_->GetChromeNodeFromSyncId(sync_id); 451 ASSERT_TRUE(node); 452 const BookmarkNode* parent = 453 model_associator_->GetChromeNodeFromSyncId(parent_sync_id); 454 EXPECT_TRUE(parent); 455 EXPECT_EQ(node->parent(), parent); 456 } 457 458 void ExpectModelMatch(sync_api::BaseTransaction* trans) { 459 const BookmarkNode* root = model_->root_node(); 460 EXPECT_EQ(root->GetIndexOf(model_->GetBookmarkBarNode()), 0); 461 EXPECT_EQ(root->GetIndexOf(model_->other_node()), 1); 462 463 std::stack<int64> stack; 464 stack.push(bookmark_bar_id()); 465 while (!stack.empty()) { 466 int64 id = stack.top(); 467 stack.pop(); 468 if (!id) continue; 469 470 ExpectBrowserNodeMatching(trans, id); 471 472 sync_api::ReadNode gnode(trans); 473 ASSERT_TRUE(gnode.InitByIdLookup(id)); 474 stack.push(gnode.GetFirstChildId()); 475 stack.push(gnode.GetSuccessorId()); 476 } 477 } 478 479 void ExpectModelMatch() { 480 sync_api::ReadTransaction trans(test_user_share_.user_share()); 481 ExpectModelMatch(&trans); 482 } 483 484 int64 other_bookmarks_id() { 485 return 486 model_associator_->GetSyncIdFromChromeId(model_->other_node()->id()); 487 } 488 489 int64 bookmark_bar_id() { 490 return model_associator_->GetSyncIdFromChromeId( 491 model_->GetBookmarkBarNode()->id()); 492 } 493 494 private: 495 // Used by both |ui_thread_| and |file_thread_|. 496 MessageLoop message_loop_; 497 BrowserThread ui_thread_; 498 // Needed by |model_|. 499 BrowserThread file_thread_; 500 501 TestingProfile profile_; 502 scoped_ptr<TestBookmarkModelAssociator> model_associator_; 503 504 protected: 505 BookmarkModel* model_; 506 TestUserShare test_user_share_; 507 scoped_ptr<BookmarkChangeProcessor> change_processor_; 508 StrictMock<MockUnrecoverableErrorHandler> mock_unrecoverable_error_handler_; 509 }; 510 511 TEST_F(ProfileSyncServiceBookmarkTest, InitialState) { 512 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 513 StartSync(); 514 515 EXPECT_TRUE(other_bookmarks_id()); 516 EXPECT_TRUE(bookmark_bar_id()); 517 518 ExpectModelMatch(); 519 } 520 521 TEST_F(ProfileSyncServiceBookmarkTest, BookmarkModelOperations) { 522 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 523 StartSync(); 524 525 // Test addition. 526 const BookmarkNode* folder = 527 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("foobar")); 528 ExpectSyncerNodeMatching(folder); 529 ExpectModelMatch(); 530 const BookmarkNode* folder2 = 531 model_->AddFolder(folder, 0, ASCIIToUTF16("nested")); 532 ExpectSyncerNodeMatching(folder2); 533 ExpectModelMatch(); 534 const BookmarkNode* url1 = model_->AddURL( 535 folder, 0, ASCIIToUTF16("Internets #1 Pies Site"), 536 GURL("http://www.easypie.com/")); 537 ExpectSyncerNodeMatching(url1); 538 ExpectModelMatch(); 539 const BookmarkNode* url2 = model_->AddURL( 540 folder, 1, ASCIIToUTF16("Airplanes"), GURL("http://www.easyjet.com/")); 541 ExpectSyncerNodeMatching(url2); 542 ExpectModelMatch(); 543 544 // Test modification. 545 model_->SetTitle(url2, ASCIIToUTF16("EasyJet")); 546 ExpectModelMatch(); 547 model_->Move(url1, folder2, 0); 548 ExpectModelMatch(); 549 model_->Move(folder2, model_->GetBookmarkBarNode(), 0); 550 ExpectModelMatch(); 551 model_->SetTitle(folder2, ASCIIToUTF16("Not Nested")); 552 ExpectModelMatch(); 553 model_->Move(folder, folder2, 0); 554 ExpectModelMatch(); 555 model_->SetTitle(folder, ASCIIToUTF16("who's nested now?")); 556 ExpectModelMatch(); 557 model_->Copy(url2, model_->GetBookmarkBarNode(), 0); 558 ExpectModelMatch(); 559 560 // Test deletion. 561 // Delete a single item. 562 model_->Remove(url2->parent(), url2->parent()->GetIndexOf(url2)); 563 ExpectModelMatch(); 564 // Delete an item with several children. 565 model_->Remove(folder2->parent(), 566 folder2->parent()->GetIndexOf(folder2)); 567 ExpectModelMatch(); 568 } 569 570 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeProcessing) { 571 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 572 StartSync(); 573 574 sync_api::WriteTransaction trans(test_user_share_.user_share()); 575 576 FakeServerChange adds(&trans); 577 int64 f1 = adds.AddFolder(L"Server Folder B", bookmark_bar_id(), 0); 578 int64 f2 = adds.AddFolder(L"Server Folder A", bookmark_bar_id(), f1); 579 int64 u1 = adds.AddURL(L"Some old site", "ftp://nifty.andrew.cmu.edu/", 580 bookmark_bar_id(), f2); 581 int64 u2 = adds.AddURL(L"Nifty", "ftp://nifty.andrew.cmu.edu/", f1, 0); 582 // u3 is a duplicate URL 583 int64 u3 = adds.AddURL(L"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1, u2); 584 // u4 is a duplicate title, different URL. 585 adds.AddURL(L"Some old site", "http://slog.thestranger.com/", 586 bookmark_bar_id(), u1); 587 // u5 tests an empty-string title. 588 std::string javascript_url( 589 "javascript:(function(){var w=window.open(" \ 590 "'about:blank','gnotesWin','location=0,menubar=0," \ 591 "scrollbars=0,status=0,toolbar=0,width=300," \ 592 "height=300,resizable');});"); 593 adds.AddURL(L"", javascript_url, other_bookmarks_id(), 0); 594 595 std::vector<sync_api::SyncManager::ChangeRecord>::const_iterator it; 596 // The bookmark model shouldn't yet have seen any of the nodes of |adds|. 597 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 598 ExpectBrowserNodeUnknown(it->id); 599 600 adds.ApplyPendingChanges(change_processor_.get()); 601 602 // Make sure the bookmark model received all of the nodes in |adds|. 603 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 604 ExpectBrowserNodeMatching(&trans, it->id); 605 ExpectModelMatch(&trans); 606 607 // Part two: test modifications. 608 FakeServerChange mods(&trans); 609 // Mess with u2, and move it into empty folder f2 610 // TODO(ncarter): Determine if we allow ModifyURL ops or not. 611 /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */ 612 std::wstring u2_old_title = mods.ModifyTitle(u2, L"The Google"); 613 int64 u2_old_parent = mods.ModifyPosition(u2, f2, 0); 614 615 // Now move f1 after u2. 616 std::wstring f1_old_title = mods.ModifyTitle(f1, L"Server Folder C"); 617 int64 f1_old_parent = mods.ModifyPosition(f1, f2, u2); 618 619 // Then add u3 after f1. 620 int64 u3_old_parent = mods.ModifyPosition(u3, f2, f1); 621 622 // Test that the property changes have not yet taken effect. 623 ExpectBrowserNodeTitle(u2, u2_old_title); 624 /* ExpectBrowserNodeURL(u2, u2_old_url); */ 625 ExpectBrowserNodeParent(u2, u2_old_parent); 626 627 ExpectBrowserNodeTitle(f1, f1_old_title); 628 ExpectBrowserNodeParent(f1, f1_old_parent); 629 630 ExpectBrowserNodeParent(u3, u3_old_parent); 631 632 // Apply the changes. 633 mods.ApplyPendingChanges(change_processor_.get()); 634 635 // Check for successful application. 636 for (it = mods.changes().begin(); it != mods.changes().end(); ++it) 637 ExpectBrowserNodeMatching(&trans, it->id); 638 ExpectModelMatch(&trans); 639 640 // Part 3: Test URL deletion. 641 FakeServerChange dels(&trans); 642 dels.Delete(u2); 643 dels.Delete(u3); 644 645 ExpectBrowserNodeKnown(u2); 646 ExpectBrowserNodeKnown(u3); 647 648 dels.ApplyPendingChanges(change_processor_.get()); 649 650 ExpectBrowserNodeUnknown(u2); 651 ExpectBrowserNodeUnknown(u3); 652 ExpectModelMatch(&trans); 653 } 654 655 // Tests a specific case in ApplyModelChanges where we move the 656 // children out from under a parent, and then delete the parent 657 // in the same changelist. The delete shows up first in the changelist, 658 // requiring the children to be moved to a temporary location. 659 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeRequiringFosterParent) { 660 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 661 StartSync(); 662 663 sync_api::WriteTransaction trans(test_user_share_.user_share()); 664 665 // Stress the immediate children of other_node because that's where 666 // ApplyModelChanges puts a temporary foster parent node. 667 std::string url("http://dev.chromium.org/"); 668 FakeServerChange adds(&trans); 669 int64 f0 = other_bookmarks_id(); // + other_node 670 int64 f1 = adds.AddFolder(L"f1", f0, 0); // + f1 671 int64 f2 = adds.AddFolder(L"f2", f1, 0); // + f2 672 int64 u3 = adds.AddURL( L"u3", url, f2, 0); // + u3 NOLINT 673 int64 u4 = adds.AddURL( L"u4", url, f2, u3); // + u4 NOLINT 674 int64 u5 = adds.AddURL( L"u5", url, f1, f2); // + u5 NOLINT 675 int64 f6 = adds.AddFolder(L"f6", f1, u5); // + f6 676 int64 u7 = adds.AddURL( L"u7", url, f0, f1); // + u7 NOLINT 677 678 std::vector<sync_api::SyncManager::ChangeRecord>::const_iterator it; 679 // The bookmark model shouldn't yet have seen any of the nodes of |adds|. 680 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 681 ExpectBrowserNodeUnknown(it->id); 682 683 adds.ApplyPendingChanges(change_processor_.get()); 684 685 // Make sure the bookmark model received all of the nodes in |adds|. 686 for (it = adds.changes().begin(); it != adds.changes().end(); ++it) 687 ExpectBrowserNodeMatching(&trans, it->id); 688 ExpectModelMatch(&trans); 689 690 // We have to do the moves before the deletions, but FakeServerChange will 691 // put the deletion at the front of the changelist. 692 FakeServerChange ops(&trans); 693 ops.ModifyPosition(f6, other_bookmarks_id(), 0); 694 ops.ModifyPosition(u3, other_bookmarks_id(), f1); // Prev == f1 is OK here. 695 ops.ModifyPosition(f2, other_bookmarks_id(), u7); 696 ops.ModifyPosition(u7, f2, 0); 697 ops.ModifyPosition(u4, other_bookmarks_id(), f2); 698 ops.ModifyPosition(u5, f6, 0); 699 ops.Delete(f1); 700 701 ops.ApplyPendingChanges(change_processor_.get()); 702 703 ExpectModelMatch(&trans); 704 } 705 706 // Simulate a server change record containing a valid but non-canonical URL. 707 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeWithNonCanonicalURL) { 708 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 709 StartSync(); 710 711 { 712 sync_api::WriteTransaction trans(test_user_share_.user_share()); 713 714 FakeServerChange adds(&trans); 715 std::string url("http://dev.chromium.org"); 716 EXPECT_NE(GURL(url).spec(), url); 717 adds.AddURL(L"u1", url, other_bookmarks_id(), 0); 718 719 adds.ApplyPendingChanges(change_processor_.get()); 720 721 EXPECT_TRUE(model_->other_node()->child_count() == 1); 722 ExpectModelMatch(&trans); 723 } 724 725 // Now reboot the sync service, forcing a merge step. 726 StopSync(); 727 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); 728 StartSync(); 729 730 // There should still be just the one bookmark. 731 EXPECT_TRUE(model_->other_node()->child_count() == 1); 732 ExpectModelMatch(); 733 } 734 735 // Simulate a server change record containing an invalid URL (per GURL). 736 // TODO(ncarter): Disabled due to crashes. Fix bug 1677563. 737 TEST_F(ProfileSyncServiceBookmarkTest, DISABLED_ServerChangeWithInvalidURL) { 738 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 739 StartSync(); 740 741 int child_count = 0; 742 { 743 sync_api::WriteTransaction trans(test_user_share_.user_share()); 744 745 FakeServerChange adds(&trans); 746 std::string url("x"); 747 EXPECT_FALSE(GURL(url).is_valid()); 748 adds.AddURL(L"u1", url, other_bookmarks_id(), 0); 749 750 adds.ApplyPendingChanges(change_processor_.get()); 751 752 // We're lenient about what should happen -- the model could wind up with 753 // the node or without it; but things should be consistent, and we 754 // shouldn't crash. 755 child_count = model_->other_node()->child_count(); 756 EXPECT_TRUE(child_count == 0 || child_count == 1); 757 ExpectModelMatch(&trans); 758 } 759 760 // Now reboot the sync service, forcing a merge step. 761 StopSync(); 762 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); 763 StartSync(); 764 765 // Things ought not to have changed. 766 EXPECT_EQ(model_->other_node()->child_count(), child_count); 767 ExpectModelMatch(); 768 } 769 770 771 // Test strings that might pose a problem if the titles ever became used as 772 // file names in the sync backend. 773 TEST_F(ProfileSyncServiceBookmarkTest, CornerCaseNames) { 774 // TODO(ncarter): Bug 1570238 explains the failure of this test. 775 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 776 StartSync(); 777 778 const char* names[] = { 779 // The empty string. 780 "", 781 // Illegal Windows filenames. 782 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", 783 "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", 784 "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", 785 // Current/parent directory markers. 786 ".", "..", "...", 787 // Files created automatically by the Windows shell. 788 "Thumbs.db", ".DS_Store", 789 // Names including Win32-illegal characters, and path separators. 790 "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar", 791 "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar", 792 "foo[bar", 793 }; 794 // Create both folders and bookmarks using each name. 795 GURL url("http://www.doublemint.com"); 796 for (size_t i = 0; i < arraysize(names); ++i) { 797 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16(names[i])); 798 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16(names[i]), url); 799 } 800 801 // Verify that the browser model matches the sync model. 802 EXPECT_TRUE(model_->other_node()->child_count() == 2*arraysize(names)); 803 ExpectModelMatch(); 804 } 805 806 // Stress the internal representation of position by sparse numbers. We want 807 // to repeatedly bisect the range of available positions, to force the 808 // syncer code to renumber its ranges. Pick a number big enough so that it 809 // would exhaust 32bits of room between items a couple of times. 810 TEST_F(ProfileSyncServiceBookmarkTest, RepeatedMiddleInsertion) { 811 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 812 StartSync(); 813 814 static const int kTimesToInsert = 256; 815 816 // Create two book-end nodes to insert between. 817 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("Alpha")); 818 model_->AddFolder(model_->other_node(), 1, ASCIIToUTF16("Omega")); 819 int count = 2; 820 821 // Test insertion in first half of range by repeatedly inserting in second 822 // position. 823 for (int i = 0; i < kTimesToInsert; ++i) { 824 string16 title = ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i); 825 model_->AddFolder(model_->other_node(), 1, title); 826 count++; 827 } 828 829 // Test insertion in second half of range by repeatedly inserting in 830 // second-to-last position. 831 for (int i = 0; i < kTimesToInsert; ++i) { 832 string16 title = ASCIIToUTF16("Post-insertion ") + base::IntToString16(i); 833 model_->AddFolder(model_->other_node(), count - 1, title); 834 count++; 835 } 836 837 // Verify that the browser model matches the sync model. 838 EXPECT_EQ(model_->other_node()->child_count(), count); 839 ExpectModelMatch(); 840 } 841 842 // Introduce a consistency violation into the model, and see that it 843 // puts itself into a lame, error state. 844 TEST_F(ProfileSyncServiceBookmarkTest, UnrecoverableErrorSuspendsService) { 845 EXPECT_CALL(mock_unrecoverable_error_handler_, 846 OnUnrecoverableError(_, _)); 847 848 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 849 StartSync(); 850 851 // Add a node which will be the target of the consistency violation. 852 const BookmarkNode* node = 853 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("node")); 854 ExpectSyncerNodeMatching(node); 855 856 // Now destroy the syncer node as if we were the ProfileSyncService without 857 // updating the ProfileSyncService state. This should introduce 858 // inconsistency between the two models. 859 { 860 sync_api::WriteTransaction trans(test_user_share_.user_share()); 861 sync_api::WriteNode sync_node(&trans); 862 ASSERT_TRUE(InitSyncNodeFromChromeNode(node, &sync_node)); 863 sync_node.Remove(); 864 } 865 // The models don't match at this point, but the ProfileSyncService 866 // doesn't know it yet. 867 ExpectSyncerNodeKnown(node); 868 869 // Add a child to the inconsistent node. This should cause detection of the 870 // problem and the syncer should stop processing changes. 871 model_->AddFolder(node, 0, ASCIIToUTF16("nested")); 872 } 873 874 // See what happens if we run model association when there are two exact URL 875 // duplicate bookmarks. The BookmarkModelAssociator should not fall over when 876 // this happens. 877 TEST_F(ProfileSyncServiceBookmarkTest, MergeDuplicates) { 878 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 879 StartSync(); 880 881 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), 882 GURL("http://dup.com/")); 883 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), 884 GURL("http://dup.com/")); 885 886 EXPECT_EQ(2, model_->other_node()->child_count()); 887 888 // Restart the sync service to trigger model association. 889 StopSync(); 890 StartSync(); 891 892 EXPECT_EQ(2, model_->other_node()->child_count()); 893 ExpectModelMatch(); 894 } 895 896 struct TestData { 897 const wchar_t* title; 898 const char* url; 899 }; 900 901 // TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code 902 // in the bookmark model unittest, to make it simpler to set up test data 903 // here (and reduce the amount of duplication among tests), and to reduce the 904 // duplication. 905 class ProfileSyncServiceBookmarkTestWithData 906 : public ProfileSyncServiceBookmarkTest { 907 protected: 908 // Populates or compares children of the given bookmark node from/with the 909 // given test data array with the given size. 910 void PopulateFromTestData(const BookmarkNode* node, 911 const TestData* data, 912 int size); 913 void CompareWithTestData(const BookmarkNode* node, 914 const TestData* data, 915 int size); 916 917 void ExpectBookmarkModelMatchesTestData(); 918 void WriteTestDataToBookmarkModel(); 919 }; 920 921 namespace { 922 923 // Constants for bookmark model that looks like: 924 // |-- Bookmark bar 925 // | |-- u2, http://www.u2.com/ 926 // | |-- f1 927 // | | |-- f1u4, http://www.f1u4.com/ 928 // | | |-- f1u2, http://www.f1u2.com/ 929 // | | |-- f1u3, http://www.f1u3.com/ 930 // | | +-- f1u1, http://www.f1u1.com/ 931 // | |-- u1, http://www.u1.com/ 932 // | +-- f2 933 // | |-- f2u2, http://www.f2u2.com/ 934 // | |-- f2u4, http://www.f2u4.com/ 935 // | |-- f2u3, http://www.f2u3.com/ 936 // | +-- f2u1, http://www.f2u1.com/ 937 // +-- Other bookmarks 938 // |-- f3 939 // | |-- f3u4, http://www.f3u4.com/ 940 // | |-- f3u2, http://www.f3u2.com/ 941 // | |-- f3u3, http://www.f3u3.com/ 942 // | +-- f3u1, http://www.f3u1.com/ 943 // |-- u4, http://www.u4.com/ 944 // |-- u3, http://www.u3.com/ 945 // --- f4 946 // | |-- f4u1, http://www.f4u1.com/ 947 // | |-- f4u2, http://www.f4u2.com/ 948 // | |-- f4u3, http://www.f4u3.com/ 949 // | +-- f4u4, http://www.f4u4.com/ 950 // |-- dup 951 // | +-- dupu1, http://www.dupu1.com/ 952 // +-- dup 953 // +-- dupu2, http://www.dupu1.com/ 954 // 955 static TestData kBookmarkBarChildren[] = { 956 { L"u2", "http://www.u2.com/" }, 957 { L"f1", NULL }, 958 { L"u1", "http://www.u1.com/" }, 959 { L"f2", NULL }, 960 }; 961 static TestData kF1Children[] = { 962 { L"f1u4", "http://www.f1u4.com/" }, 963 { L"f1u2", "http://www.f1u2.com/" }, 964 { L"f1u3", "http://www.f1u3.com/" }, 965 { L"f1u1", "http://www.f1u1.com/" }, 966 }; 967 static TestData kF2Children[] = { 968 { L"f2u2", "http://www.f2u2.com/" }, 969 { L"f2u4", "http://www.f2u4.com/" }, 970 { L"f2u3", "http://www.f2u3.com/" }, 971 { L"f2u1", "http://www.f2u1.com/" }, 972 }; 973 974 static TestData kOtherBookmarkChildren[] = { 975 { L"f3", NULL }, 976 { L"u4", "http://www.u4.com/" }, 977 { L"u3", "http://www.u3.com/" }, 978 { L"f4", NULL }, 979 { L"dup", NULL }, 980 { L"dup", NULL }, 981 }; 982 static TestData kF3Children[] = { 983 { L"f3u4", "http://www.f3u4.com/" }, 984 { L"f3u2", "http://www.f3u2.com/" }, 985 { L"f3u3", "http://www.f3u3.com/" }, 986 { L"f3u1", "http://www.f3u1.com/" }, 987 }; 988 static TestData kF4Children[] = { 989 { L"f4u1", "http://www.f4u1.com/" }, 990 { L"f4u2", "http://www.f4u2.com/" }, 991 { L"f4u3", "http://www.f4u3.com/" }, 992 { L"f4u4", "http://www.f4u4.com/" }, 993 }; 994 static TestData kDup1Children[] = { 995 { L"dupu1", "http://www.dupu1.com/" }, 996 }; 997 static TestData kDup2Children[] = { 998 { L"dupu2", "http://www.dupu2.com/" }, 999 }; 1000 1001 } // anonymous namespace. 1002 1003 void ProfileSyncServiceBookmarkTestWithData::PopulateFromTestData( 1004 const BookmarkNode* node, const TestData* data, int size) { 1005 DCHECK(node); 1006 DCHECK(data); 1007 DCHECK(node->is_folder()); 1008 for (int i = 0; i < size; ++i) { 1009 const TestData& item = data[i]; 1010 if (item.url) { 1011 model_->AddURL(node, i, WideToUTF16Hack(item.title), GURL(item.url)); 1012 } else { 1013 model_->AddFolder(node, i, WideToUTF16Hack(item.title)); 1014 } 1015 } 1016 } 1017 1018 void ProfileSyncServiceBookmarkTestWithData::CompareWithTestData( 1019 const BookmarkNode* node, const TestData* data, int size) { 1020 DCHECK(node); 1021 DCHECK(data); 1022 DCHECK(node->is_folder()); 1023 ASSERT_EQ(size, node->child_count()); 1024 for (int i = 0; i < size; ++i) { 1025 const BookmarkNode* child_node = node->GetChild(i); 1026 const TestData& item = data[i]; 1027 EXPECT_EQ(child_node->GetTitle(), WideToUTF16Hack(item.title)); 1028 if (item.url) { 1029 EXPECT_FALSE(child_node->is_folder()); 1030 EXPECT_TRUE(child_node->is_url()); 1031 EXPECT_EQ(child_node->GetURL(), GURL(item.url)); 1032 } else { 1033 EXPECT_TRUE(child_node->is_folder()); 1034 EXPECT_FALSE(child_node->is_url()); 1035 } 1036 } 1037 } 1038 1039 // TODO(munjal): We should implement some way of generating random data and can 1040 // use the same seed to generate the same sequence. 1041 void ProfileSyncServiceBookmarkTestWithData::WriteTestDataToBookmarkModel() { 1042 const BookmarkNode* bookmarks_bar_node = model_->GetBookmarkBarNode(); 1043 PopulateFromTestData(bookmarks_bar_node, 1044 kBookmarkBarChildren, 1045 arraysize(kBookmarkBarChildren)); 1046 1047 ASSERT_GE(bookmarks_bar_node->child_count(), 4); 1048 const BookmarkNode* f1_node = bookmarks_bar_node->GetChild(1); 1049 PopulateFromTestData(f1_node, kF1Children, arraysize(kF1Children)); 1050 const BookmarkNode* f2_node = bookmarks_bar_node->GetChild(3); 1051 PopulateFromTestData(f2_node, kF2Children, arraysize(kF2Children)); 1052 1053 const BookmarkNode* other_bookmarks_node = model_->other_node(); 1054 PopulateFromTestData(other_bookmarks_node, 1055 kOtherBookmarkChildren, 1056 arraysize(kOtherBookmarkChildren)); 1057 1058 ASSERT_GE(other_bookmarks_node->child_count(), 6); 1059 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); 1060 PopulateFromTestData(f3_node, kF3Children, arraysize(kF3Children)); 1061 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); 1062 PopulateFromTestData(f4_node, kF4Children, arraysize(kF4Children)); 1063 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); 1064 PopulateFromTestData(dup_node, kDup1Children, arraysize(kDup1Children)); 1065 dup_node = other_bookmarks_node->GetChild(5); 1066 PopulateFromTestData(dup_node, kDup2Children, arraysize(kDup2Children)); 1067 1068 ExpectBookmarkModelMatchesTestData(); 1069 } 1070 1071 void ProfileSyncServiceBookmarkTestWithData:: 1072 ExpectBookmarkModelMatchesTestData() { 1073 const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); 1074 CompareWithTestData(bookmark_bar_node, 1075 kBookmarkBarChildren, 1076 arraysize(kBookmarkBarChildren)); 1077 1078 ASSERT_GE(bookmark_bar_node->child_count(), 4); 1079 const BookmarkNode* f1_node = bookmark_bar_node->GetChild(1); 1080 CompareWithTestData(f1_node, kF1Children, arraysize(kF1Children)); 1081 const BookmarkNode* f2_node = bookmark_bar_node->GetChild(3); 1082 CompareWithTestData(f2_node, kF2Children, arraysize(kF2Children)); 1083 1084 const BookmarkNode* other_bookmarks_node = model_->other_node(); 1085 CompareWithTestData(other_bookmarks_node, 1086 kOtherBookmarkChildren, 1087 arraysize(kOtherBookmarkChildren)); 1088 1089 ASSERT_GE(other_bookmarks_node->child_count(), 6); 1090 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); 1091 CompareWithTestData(f3_node, kF3Children, arraysize(kF3Children)); 1092 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); 1093 CompareWithTestData(f4_node, kF4Children, arraysize(kF4Children)); 1094 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); 1095 CompareWithTestData(dup_node, kDup1Children, arraysize(kDup1Children)); 1096 dup_node = other_bookmarks_node->GetChild(5); 1097 CompareWithTestData(dup_node, kDup2Children, arraysize(kDup2Children)); 1098 } 1099 1100 // Tests persistence of the profile sync service by unloading the 1101 // database and then reloading it from disk. 1102 TEST_F(ProfileSyncServiceBookmarkTestWithData, Persistence) { 1103 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1104 StartSync(); 1105 1106 WriteTestDataToBookmarkModel(); 1107 1108 ExpectModelMatch(); 1109 1110 // Force both models to discard their data and reload from disk. This 1111 // simulates what would happen if the browser were to shutdown normally, 1112 // and then relaunch. 1113 StopSync(); 1114 UnloadBookmarkModel(); 1115 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); 1116 StartSync(); 1117 1118 ExpectBookmarkModelMatchesTestData(); 1119 1120 // With the BookmarkModel contents verified, ExpectModelMatch will 1121 // verify the contents of the sync model. 1122 ExpectModelMatch(); 1123 } 1124 1125 // Tests the merge case when the BookmarkModel is non-empty but the 1126 // sync model is empty. This corresponds to uploading browser 1127 // bookmarks to an initially empty, new account. 1128 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptySyncModel) { 1129 // Don't start the sync service until we've populated the bookmark model. 1130 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1131 1132 WriteTestDataToBookmarkModel(); 1133 1134 // Restart sync. This should trigger a merge step during 1135 // initialization -- we expect the browser bookmarks to be written 1136 // to the sync service during this call. 1137 StartSync(); 1138 1139 // Verify that the bookmark model hasn't changed, and that the sync model 1140 // matches it exactly. 1141 ExpectBookmarkModelMatchesTestData(); 1142 ExpectModelMatch(); 1143 } 1144 1145 // Tests the merge case when the BookmarkModel is empty but the sync model is 1146 // non-empty. This corresponds (somewhat) to a clean install of the browser, 1147 // with no bookmarks, connecting to a sync account that has some bookmarks. 1148 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptyBookmarkModel) { 1149 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1150 StartSync(); 1151 1152 WriteTestDataToBookmarkModel(); 1153 1154 ExpectModelMatch(); 1155 1156 // Force the databse to unload and write itself to disk. 1157 StopSync(); 1158 1159 // Blow away the bookmark model -- it should be empty afterwards. 1160 UnloadBookmarkModel(); 1161 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1162 EXPECT_EQ(model_->GetBookmarkBarNode()->child_count(), 0); 1163 EXPECT_EQ(model_->other_node()->child_count(), 0); 1164 1165 // Now restart the sync service. Starting it should populate the bookmark 1166 // model -- test for consistency. 1167 StartSync(); 1168 ExpectBookmarkModelMatchesTestData(); 1169 ExpectModelMatch(); 1170 } 1171 1172 // Tests the merge cases when both the models are expected to be identical 1173 // after the merge. 1174 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeExpectedIdenticalModels) { 1175 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1176 StartSync(); 1177 WriteTestDataToBookmarkModel(); 1178 ExpectModelMatch(); 1179 StopSync(); 1180 UnloadBookmarkModel(); 1181 1182 // At this point both the bookmark model and the server should have the 1183 // exact same data and it should match the test data. 1184 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); 1185 StartSync(); 1186 ExpectBookmarkModelMatchesTestData(); 1187 ExpectModelMatch(); 1188 StopSync(); 1189 UnloadBookmarkModel(); 1190 1191 // Now reorder some bookmarks in the bookmark model and then merge. Make 1192 // sure we get the order of the server after merge. 1193 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); 1194 ExpectBookmarkModelMatchesTestData(); 1195 const BookmarkNode* bookmark_bar = model_->GetBookmarkBarNode(); 1196 ASSERT_TRUE(bookmark_bar); 1197 ASSERT_GT(bookmark_bar->child_count(), 1); 1198 model_->Move(bookmark_bar->GetChild(0), bookmark_bar, 1); 1199 StartSync(); 1200 ExpectModelMatch(); 1201 ExpectBookmarkModelMatchesTestData(); 1202 } 1203 1204 // Tests the merge cases when both the models are expected to be identical 1205 // after the merge. 1206 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeModelsWithSomeExtras) { 1207 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1208 WriteTestDataToBookmarkModel(); 1209 ExpectBookmarkModelMatchesTestData(); 1210 1211 // Remove some nodes and reorder some nodes. 1212 const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); 1213 int remove_index = 2; 1214 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1215 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index); 1216 ASSERT_TRUE(child_node); 1217 ASSERT_TRUE(child_node->is_url()); 1218 model_->Remove(bookmark_bar_node, remove_index); 1219 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1220 child_node = bookmark_bar_node->GetChild(remove_index); 1221 ASSERT_TRUE(child_node); 1222 ASSERT_TRUE(child_node->is_folder()); 1223 model_->Remove(bookmark_bar_node, remove_index); 1224 1225 const BookmarkNode* other_node = model_->other_node(); 1226 ASSERT_GE(other_node->child_count(), 1); 1227 const BookmarkNode* f3_node = other_node->GetChild(0); 1228 ASSERT_TRUE(f3_node); 1229 ASSERT_TRUE(f3_node->is_folder()); 1230 remove_index = 2; 1231 ASSERT_GT(f3_node->child_count(), remove_index); 1232 model_->Remove(f3_node, remove_index); 1233 ASSERT_GT(f3_node->child_count(), remove_index); 1234 model_->Remove(f3_node, remove_index); 1235 1236 StartSync(); 1237 ExpectModelMatch(); 1238 StopSync(); 1239 1240 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1241 WriteTestDataToBookmarkModel(); 1242 ExpectBookmarkModelMatchesTestData(); 1243 1244 // Remove some nodes and reorder some nodes. 1245 bookmark_bar_node = model_->GetBookmarkBarNode(); 1246 remove_index = 0; 1247 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1248 child_node = bookmark_bar_node->GetChild(remove_index); 1249 ASSERT_TRUE(child_node); 1250 ASSERT_TRUE(child_node->is_url()); 1251 model_->Remove(bookmark_bar_node, remove_index); 1252 ASSERT_GT(bookmark_bar_node->child_count(), remove_index); 1253 child_node = bookmark_bar_node->GetChild(remove_index); 1254 ASSERT_TRUE(child_node); 1255 ASSERT_TRUE(child_node->is_folder()); 1256 model_->Remove(bookmark_bar_node, remove_index); 1257 1258 ASSERT_GE(bookmark_bar_node->child_count(), 2); 1259 model_->Move(bookmark_bar_node->GetChild(0), bookmark_bar_node, 1); 1260 1261 other_node = model_->other_node(); 1262 ASSERT_GE(other_node->child_count(), 1); 1263 f3_node = other_node->GetChild(0); 1264 ASSERT_TRUE(f3_node); 1265 ASSERT_TRUE(f3_node->is_folder()); 1266 remove_index = 0; 1267 ASSERT_GT(f3_node->child_count(), remove_index); 1268 model_->Remove(f3_node, remove_index); 1269 ASSERT_GT(f3_node->child_count(), remove_index); 1270 model_->Remove(f3_node, remove_index); 1271 1272 ASSERT_GE(other_node->child_count(), 4); 1273 model_->Move(other_node->GetChild(0), other_node, 1); 1274 model_->Move(other_node->GetChild(2), other_node, 3); 1275 1276 StartSync(); 1277 ExpectModelMatch(); 1278 1279 // After the merge, the model should match the test data. 1280 ExpectBookmarkModelMatchesTestData(); 1281 } 1282 1283 // Tests that when persisted model associations are used, things work fine. 1284 TEST_F(ProfileSyncServiceBookmarkTestWithData, ModelAssociationPersistence) { 1285 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1286 WriteTestDataToBookmarkModel(); 1287 StartSync(); 1288 ExpectModelMatch(); 1289 // Force sync to shut down and write itself to disk. 1290 StopSync(); 1291 // Now restart sync. This time it should use the persistent 1292 // associations. 1293 StartSync(); 1294 ExpectModelMatch(); 1295 } 1296 1297 // Tests that when persisted model associations are used, things work fine. 1298 TEST_F(ProfileSyncServiceBookmarkTestWithData, 1299 ModelAssociationInvalidPersistence) { 1300 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1301 WriteTestDataToBookmarkModel(); 1302 StartSync(); 1303 ExpectModelMatch(); 1304 // Force sync to shut down and write itself to disk. 1305 StopSync(); 1306 // Change the bookmark model before restarting sync service to simulate 1307 // the situation where bookmark model is different from sync model and 1308 // make sure model associator correctly rebuilds associations. 1309 const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); 1310 model_->AddURL(bookmark_bar_node, 0, ASCIIToUTF16("xtra"), 1311 GURL("http://www.xtra.com")); 1312 // Now restart sync. This time it will try to use the persistent 1313 // associations and realize that they are invalid and hence will rebuild 1314 // associations. 1315 StartSync(); 1316 ExpectModelMatch(); 1317 } 1318 1319 TEST_F(ProfileSyncServiceBookmarkTestWithData, SortChildren) { 1320 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); 1321 StartSync(); 1322 1323 // Write test data to bookmark model and verify that the models match. 1324 WriteTestDataToBookmarkModel(); 1325 const BookmarkNode* folder_added = model_->other_node()->GetChild(0); 1326 ASSERT_TRUE(folder_added); 1327 ASSERT_TRUE(folder_added->is_folder()); 1328 1329 ExpectModelMatch(); 1330 1331 // Sort the other-bookmarks children and expect that hte models match. 1332 model_->SortChildren(folder_added); 1333 ExpectModelMatch(); 1334 } 1335 1336 // See what happens if we enable sync but then delete the "Sync Data" 1337 // folder. 1338 TEST_F(ProfileSyncServiceBookmarkTestWithData, 1339 RecoverAfterDeletingSyncDataDirectory) { 1340 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); 1341 StartSync(); 1342 1343 WriteTestDataToBookmarkModel(); 1344 1345 StopSync(); 1346 1347 // Nuke the sync DB and reload. 1348 test_user_share_.TearDown(); 1349 test_user_share_.SetUp(); 1350 1351 StartSync(); 1352 1353 // Make sure we're back in sync. In real life, the user would need 1354 // to reauthenticate before this happens, but in the test, authentication 1355 // is sidestepped. 1356 ExpectBookmarkModelMatchesTestData(); 1357 ExpectModelMatch(); 1358 } 1359 1360 } // namespace 1361 1362 } // namespace browser_sync 1363