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