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