1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "sync/engine/directory_update_handler.h" 6 7#include "base/compiler_specific.h" 8#include "base/message_loop/message_loop.h" 9#include "base/stl_util.h" 10#include "sync/engine/syncer_proto_util.h" 11#include "sync/internal_api/public/base/model_type.h" 12#include "sync/internal_api/public/test/test_entry_factory.h" 13#include "sync/protocol/sync.pb.h" 14#include "sync/sessions/directory_type_debug_info_emitter.h" 15#include "sync/sessions/status_controller.h" 16#include "sync/syncable/directory.h" 17#include "sync/syncable/entry.h" 18#include "sync/syncable/mutable_entry.h" 19#include "sync/syncable/syncable_model_neutral_write_transaction.h" 20#include "sync/syncable/syncable_proto_util.h" 21#include "sync/syncable/syncable_read_transaction.h" 22#include "sync/syncable/syncable_write_transaction.h" 23#include "sync/test/engine/fake_model_worker.h" 24#include "sync/test/engine/test_directory_setter_upper.h" 25#include "sync/test/engine/test_id_factory.h" 26#include "sync/test/engine/test_syncable_utils.h" 27#include "testing/gtest/include/gtest/gtest.h" 28 29namespace syncer { 30 31using syncable::UNITTEST; 32 33static const int64 kDefaultVersion = 1000; 34 35// A test harness for tests that focus on processing updates. 36// 37// Update processing is what occurs when we first download updates. It converts 38// the received protobuf message into information in the syncable::Directory. 39// Any invalid or redundant updates will be dropped at this point. 40class DirectoryUpdateHandlerProcessUpdateTest : public ::testing::Test { 41 public: 42 DirectoryUpdateHandlerProcessUpdateTest() 43 : ui_worker_(new FakeModelWorker(GROUP_UI)) { 44 } 45 46 virtual ~DirectoryUpdateHandlerProcessUpdateTest() {} 47 48 virtual void SetUp() OVERRIDE { 49 dir_maker_.SetUp(); 50 } 51 52 virtual void TearDown() OVERRIDE { 53 dir_maker_.TearDown(); 54 } 55 56 syncable::Directory* dir() { 57 return dir_maker_.directory(); 58 } 59 60 protected: 61 scoped_ptr<sync_pb::SyncEntity> CreateUpdate( 62 const std::string& id, 63 const std::string& parent, 64 const ModelType& type); 65 66 // This exists mostly to give tests access to the protected member function. 67 // Warning: This takes the syncable directory lock. 68 void UpdateSyncEntities( 69 DirectoryUpdateHandler* handler, 70 const SyncEntityList& applicable_updates, 71 sessions::StatusController* status); 72 73 // Another function to access private member functions. 74 void UpdateProgressMarkers( 75 DirectoryUpdateHandler* handler, 76 const sync_pb::DataTypeProgressMarker& progress); 77 78 scoped_refptr<FakeModelWorker> ui_worker() { 79 return ui_worker_; 80 } 81 82 bool EntryExists(const std::string& id) { 83 syncable::ReadTransaction trans(FROM_HERE, dir()); 84 syncable::Entry e(&trans, syncable::GET_BY_ID, 85 syncable::Id::CreateFromServerId(id)); 86 return e.good() && !e.GetIsDel(); 87 } 88 89 protected: 90 // Used in the construction of DirectoryTypeDebugInfoEmitters. 91 ObserverList<TypeDebugInfoObserver> type_observers_; 92 93 private: 94 base::MessageLoop loop_; // Needed to initialize the directory. 95 TestDirectorySetterUpper dir_maker_; 96 scoped_refptr<FakeModelWorker> ui_worker_; 97}; 98 99scoped_ptr<sync_pb::SyncEntity> 100DirectoryUpdateHandlerProcessUpdateTest::CreateUpdate( 101 const std::string& id, 102 const std::string& parent, 103 const ModelType& type) { 104 scoped_ptr<sync_pb::SyncEntity> e(new sync_pb::SyncEntity()); 105 e->set_id_string(id); 106 e->set_parent_id_string(parent); 107 e->set_non_unique_name(id); 108 e->set_name(id); 109 e->set_version(kDefaultVersion); 110 AddDefaultFieldValue(type, e->mutable_specifics()); 111 return e.Pass(); 112} 113 114void DirectoryUpdateHandlerProcessUpdateTest::UpdateSyncEntities( 115 DirectoryUpdateHandler* handler, 116 const SyncEntityList& applicable_updates, 117 sessions::StatusController* status) { 118 syncable::ModelNeutralWriteTransaction trans(FROM_HERE, UNITTEST, dir()); 119 handler->UpdateSyncEntities(&trans, applicable_updates, status); 120} 121 122void DirectoryUpdateHandlerProcessUpdateTest::UpdateProgressMarkers( 123 DirectoryUpdateHandler* handler, 124 const sync_pb::DataTypeProgressMarker& progress) { 125 handler->UpdateProgressMarker(progress); 126} 127 128static const char kCacheGuid[] = "IrcjZ2jyzHDV9Io4+zKcXQ=="; 129 130// Test that the bookmark tag is set on newly downloaded items. 131TEST_F(DirectoryUpdateHandlerProcessUpdateTest, NewBookmarkTag) { 132 DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_); 133 DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter); 134 sync_pb::GetUpdatesResponse gu_response; 135 sessions::StatusController status; 136 137 // Add a bookmark item to the update message. 138 std::string root = syncable::GetNullId().GetServerId(); 139 syncable::Id server_id = syncable::Id::CreateFromServerId("b1"); 140 scoped_ptr<sync_pb::SyncEntity> e = 141 CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS); 142 e->set_originator_cache_guid( 143 std::string(kCacheGuid, arraysize(kCacheGuid)-1)); 144 syncable::Id client_id = syncable::Id::CreateFromClientString("-2"); 145 e->set_originator_client_item_id(client_id.GetServerId()); 146 e->set_position_in_parent(0); 147 148 // Add it to the applicable updates list. 149 SyncEntityList bookmark_updates; 150 bookmark_updates.push_back(e.get()); 151 152 // Process the update. 153 UpdateSyncEntities(&handler, bookmark_updates, &status); 154 155 syncable::ReadTransaction trans(FROM_HERE, dir()); 156 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id); 157 ASSERT_TRUE(entry.good()); 158 EXPECT_TRUE(UniquePosition::IsValidSuffix(entry.GetUniqueBookmarkTag())); 159 EXPECT_TRUE(entry.GetServerUniquePosition().IsValid()); 160 161 // If this assertion fails, that might indicate that the algorithm used to 162 // generate bookmark tags has been modified. This could have implications for 163 // bookmark ordering. Please make sure you know what you're doing if you 164 // intend to make such a change. 165 EXPECT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", entry.GetUniqueBookmarkTag()); 166} 167 168// Test the receipt of a type root node. 169TEST_F(DirectoryUpdateHandlerProcessUpdateTest, 170 ReceiveServerCreatedBookmarkFolders) { 171 DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_); 172 DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter); 173 sync_pb::GetUpdatesResponse gu_response; 174 sessions::StatusController status; 175 176 // Create an update that mimics the bookmark root. 177 syncable::Id server_id = syncable::Id::CreateFromServerId("xyz"); 178 std::string root = syncable::GetNullId().GetServerId(); 179 scoped_ptr<sync_pb::SyncEntity> e = 180 CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS); 181 e->set_server_defined_unique_tag("google_chrome_bookmarks"); 182 e->set_folder(true); 183 184 // Add it to the applicable updates list. 185 SyncEntityList bookmark_updates; 186 bookmark_updates.push_back(e.get()); 187 188 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e)); 189 190 // Process it. 191 UpdateSyncEntities(&handler, bookmark_updates, &status); 192 193 // Verify the results. 194 syncable::ReadTransaction trans(FROM_HERE, dir()); 195 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id); 196 ASSERT_TRUE(entry.good()); 197 198 EXPECT_FALSE(entry.ShouldMaintainPosition()); 199 EXPECT_FALSE(entry.GetUniquePosition().IsValid()); 200 EXPECT_FALSE(entry.GetServerUniquePosition().IsValid()); 201 EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty()); 202} 203 204// Test the receipt of a non-bookmark item. 205TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ReceiveNonBookmarkItem) { 206 DirectoryTypeDebugInfoEmitter emitter(AUTOFILL, &type_observers_); 207 DirectoryUpdateHandler handler(dir(), AUTOFILL, ui_worker(), &emitter); 208 sync_pb::GetUpdatesResponse gu_response; 209 sessions::StatusController status; 210 211 std::string root = syncable::GetNullId().GetServerId(); 212 syncable::Id server_id = syncable::Id::CreateFromServerId("xyz"); 213 scoped_ptr<sync_pb::SyncEntity> e = 214 CreateUpdate(SyncableIdToProto(server_id), root, AUTOFILL); 215 e->set_server_defined_unique_tag("9PGRuKdX5sHyGMB17CvYTXuC43I="); 216 217 // Add it to the applicable updates list. 218 SyncEntityList autofill_updates; 219 autofill_updates.push_back(e.get()); 220 221 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e)); 222 223 // Process it. 224 UpdateSyncEntities(&handler, autofill_updates, &status); 225 226 syncable::ReadTransaction trans(FROM_HERE, dir()); 227 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id); 228 ASSERT_TRUE(entry.good()); 229 230 EXPECT_FALSE(entry.ShouldMaintainPosition()); 231 EXPECT_FALSE(entry.GetUniquePosition().IsValid()); 232 EXPECT_FALSE(entry.GetServerUniquePosition().IsValid()); 233 EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty()); 234} 235 236// Tests the setting of progress markers. 237TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ProcessNewProgressMarkers) { 238 DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_); 239 DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter); 240 241 sync_pb::DataTypeProgressMarker progress; 242 progress.set_data_type_id(GetSpecificsFieldNumberFromModelType(BOOKMARKS)); 243 progress.set_token("token"); 244 245 UpdateProgressMarkers(&handler, progress); 246 247 sync_pb::DataTypeProgressMarker saved; 248 dir()->GetDownloadProgress(BOOKMARKS, &saved); 249 250 EXPECT_EQ(progress.token(), saved.token()); 251 EXPECT_EQ(progress.data_type_id(), saved.data_type_id()); 252} 253 254TEST_F(DirectoryUpdateHandlerProcessUpdateTest, GarbageCollectionByVersion) { 255 DirectoryTypeDebugInfoEmitter emitter(SYNCED_NOTIFICATIONS, &type_observers_); 256 DirectoryUpdateHandler handler(dir(), SYNCED_NOTIFICATIONS, 257 ui_worker(), &emitter); 258 sessions::StatusController status; 259 260 sync_pb::DataTypeProgressMarker progress; 261 progress.set_data_type_id( 262 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS)); 263 progress.set_token("token"); 264 progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 10); 265 266 sync_pb::DataTypeContext context; 267 context.set_data_type_id( 268 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS)); 269 context.set_context("context"); 270 context.set_version(1); 271 272 scoped_ptr<sync_pb::SyncEntity> type_root = 273 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")), 274 syncable::GetNullId().GetServerId(), 275 SYNCED_NOTIFICATIONS); 276 type_root->set_server_defined_unique_tag( 277 ModelTypeToRootTag(SYNCED_NOTIFICATIONS)); 278 type_root->set_folder(true); 279 280 scoped_ptr<sync_pb::SyncEntity> e1 = 281 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")), 282 type_root->id_string(), 283 SYNCED_NOTIFICATIONS); 284 285 scoped_ptr<sync_pb::SyncEntity> e2 = 286 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")), 287 type_root->id_string(), 288 SYNCED_NOTIFICATIONS); 289 e2->set_version(kDefaultVersion + 100); 290 291 // Add to the applicable updates list. 292 SyncEntityList updates; 293 updates.push_back(type_root.get()); 294 updates.push_back(e1.get()); 295 updates.push_back(e2.get()); 296 297 // Process and apply updates. 298 EXPECT_EQ( 299 SYNCER_OK, 300 handler.ProcessGetUpdatesResponse(progress, context, updates, &status)); 301 handler.ApplyUpdates(&status); 302 303 // Verify none is deleted because they are unapplied during GC. 304 EXPECT_TRUE(EntryExists(type_root->id_string())); 305 EXPECT_TRUE(EntryExists(e1->id_string())); 306 EXPECT_TRUE(EntryExists(e2->id_string())); 307 308 // Process and apply again. Old entry is deleted but not root. 309 progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 20); 310 EXPECT_EQ(SYNCER_OK, 311 handler.ProcessGetUpdatesResponse( 312 progress, context, SyncEntityList(), &status)); 313 handler.ApplyUpdates(&status); 314 EXPECT_TRUE(EntryExists(type_root->id_string())); 315 EXPECT_FALSE(EntryExists(e1->id_string())); 316 EXPECT_TRUE(EntryExists(e2->id_string())); 317} 318 319TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ContextVersion) { 320 DirectoryTypeDebugInfoEmitter emitter(SYNCED_NOTIFICATIONS, &type_observers_); 321 DirectoryUpdateHandler handler(dir(), SYNCED_NOTIFICATIONS, 322 ui_worker(), &emitter); 323 sessions::StatusController status; 324 int field_number = GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS); 325 326 sync_pb::DataTypeProgressMarker progress; 327 progress.set_data_type_id( 328 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS)); 329 progress.set_token("token"); 330 331 sync_pb::DataTypeContext old_context; 332 old_context.set_version(1); 333 old_context.set_context("data"); 334 old_context.set_data_type_id(field_number); 335 336 scoped_ptr<sync_pb::SyncEntity> type_root = 337 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")), 338 syncable::GetNullId().GetServerId(), 339 SYNCED_NOTIFICATIONS); 340 type_root->set_server_defined_unique_tag( 341 ModelTypeToRootTag(SYNCED_NOTIFICATIONS)); 342 type_root->set_folder(true); 343 scoped_ptr<sync_pb::SyncEntity> e1 = 344 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")), 345 type_root->id_string(), 346 SYNCED_NOTIFICATIONS); 347 348 SyncEntityList updates; 349 updates.push_back(type_root.get()); 350 updates.push_back(e1.get()); 351 352 // The first response should be processed fine. 353 EXPECT_EQ(SYNCER_OK, 354 handler.ProcessGetUpdatesResponse( 355 progress, old_context, updates, &status)); 356 handler.ApplyUpdates(&status); 357 358 EXPECT_TRUE(EntryExists(type_root->id_string())); 359 EXPECT_TRUE(EntryExists(e1->id_string())); 360 361 { 362 sync_pb::DataTypeContext dir_context; 363 syncable::ReadTransaction trans(FROM_HERE, dir()); 364 trans.directory()->GetDataTypeContext( 365 &trans, SYNCED_NOTIFICATIONS, &dir_context); 366 EXPECT_EQ(old_context.SerializeAsString(), dir_context.SerializeAsString()); 367 } 368 369 sync_pb::DataTypeContext new_context; 370 new_context.set_version(0); 371 new_context.set_context("old"); 372 new_context.set_data_type_id(field_number); 373 374 scoped_ptr<sync_pb::SyncEntity> e2 = 375 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")), 376 type_root->id_string(), 377 SYNCED_NOTIFICATIONS); 378 updates.clear(); 379 updates.push_back(e2.get()); 380 381 // The second response, with an old context version, should result in an 382 // error and the updates should be dropped. 383 EXPECT_EQ(DATATYPE_TRIGGERED_RETRY, 384 handler.ProcessGetUpdatesResponse( 385 progress, new_context, updates, &status)); 386 handler.ApplyUpdates(&status); 387 388 EXPECT_FALSE(EntryExists(e2->id_string())); 389 390 { 391 sync_pb::DataTypeContext dir_context; 392 syncable::ReadTransaction trans(FROM_HERE, dir()); 393 trans.directory()->GetDataTypeContext( 394 &trans, SYNCED_NOTIFICATIONS, &dir_context); 395 EXPECT_EQ(old_context.SerializeAsString(), dir_context.SerializeAsString()); 396 } 397} 398 399// A test harness for tests that focus on applying updates. 400// 401// Update application is performed when we want to take updates that were 402// previously downloaded, processed, and stored in our syncable::Directory 403// and use them to update our local state (both the Directory's local state 404// and the model's local state, though these tests focus only on the Directory's 405// local state). 406// 407// This is kept separate from the update processing test in part for historical 408// reasons, and in part because these tests may require a bit more infrastrcture 409// in the future. Update application should happen on a different thread a lot 410// of the time so these tests may end up requiring more infrastructure than the 411// update processing tests. Currently, we're bypassing most of those issues by 412// using FakeModelWorkers, so there's not much difference between the two test 413// harnesses. 414class DirectoryUpdateHandlerApplyUpdateTest : public ::testing::Test { 415 public: 416 DirectoryUpdateHandlerApplyUpdateTest() 417 : ui_worker_(new FakeModelWorker(GROUP_UI)), 418 password_worker_(new FakeModelWorker(GROUP_PASSWORD)), 419 passive_worker_(new FakeModelWorker(GROUP_PASSIVE)), 420 bookmarks_emitter_(BOOKMARKS, &type_observers_), 421 passwords_emitter_(PASSWORDS, &type_observers_), 422 update_handler_map_deleter_(&update_handler_map_) {} 423 424 virtual void SetUp() OVERRIDE { 425 dir_maker_.SetUp(); 426 entry_factory_.reset(new TestEntryFactory(directory())); 427 428 update_handler_map_.insert(std::make_pair( 429 BOOKMARKS, 430 new DirectoryUpdateHandler(directory(), BOOKMARKS, 431 ui_worker_, &bookmarks_emitter_))); 432 update_handler_map_.insert(std::make_pair( 433 PASSWORDS, 434 new DirectoryUpdateHandler(directory(), 435 PASSWORDS, 436 password_worker_, 437 &passwords_emitter_))); 438 } 439 440 virtual void TearDown() OVERRIDE { 441 dir_maker_.TearDown(); 442 } 443 444 const UpdateCounters& GetBookmarksUpdateCounters() { 445 return bookmarks_emitter_.GetUpdateCounters(); 446 } 447 448 const UpdateCounters& GetPasswordsUpdateCounters() { 449 return passwords_emitter_.GetUpdateCounters(); 450 } 451 452 protected: 453 void ApplyBookmarkUpdates(sessions::StatusController* status) { 454 update_handler_map_[BOOKMARKS]->ApplyUpdates(status); 455 } 456 457 void ApplyPasswordUpdates(sessions::StatusController* status) { 458 update_handler_map_[PASSWORDS]->ApplyUpdates(status); 459 } 460 461 TestEntryFactory* entry_factory() { 462 return entry_factory_.get(); 463 } 464 465 syncable::Directory* directory() { 466 return dir_maker_.directory(); 467 } 468 469 private: 470 typedef std::map<ModelType, UpdateHandler*> UpdateHandlerMap; 471 472 base::MessageLoop loop_; // Needed to initialize the directory. 473 TestDirectorySetterUpper dir_maker_; 474 scoped_ptr<TestEntryFactory> entry_factory_; 475 476 scoped_refptr<FakeModelWorker> ui_worker_; 477 scoped_refptr<FakeModelWorker> password_worker_; 478 scoped_refptr<FakeModelWorker> passive_worker_; 479 480 ObserverList<TypeDebugInfoObserver> type_observers_; 481 DirectoryTypeDebugInfoEmitter bookmarks_emitter_; 482 DirectoryTypeDebugInfoEmitter passwords_emitter_; 483 484 UpdateHandlerMap update_handler_map_; 485 STLValueDeleter<UpdateHandlerMap> update_handler_map_deleter_; 486}; 487 488namespace { 489sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { 490 sync_pb::EntitySpecifics result; 491 AddDefaultFieldValue(BOOKMARKS, &result); 492 return result; 493} 494} // namespace 495 496// Test update application for a few bookmark items. 497TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmark) { 498 sessions::StatusController status; 499 500 std::string root_server_id = syncable::GetNullId().GetServerId(); 501 int64 parent_handle = 502 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( 503 "parent", DefaultBookmarkSpecifics(), root_server_id); 504 int64 child_handle = 505 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( 506 "child", DefaultBookmarkSpecifics(), "parent"); 507 508 ApplyBookmarkUpdates(&status); 509 510 const UpdateCounters& counter = GetBookmarksUpdateCounters(); 511 EXPECT_EQ(0, counter.num_encryption_conflict_application_failures) 512 << "Simple update shouldn't result in conflicts"; 513 EXPECT_EQ(0, counter.num_hierarchy_conflict_application_failures) 514 << "Simple update shouldn't result in conflicts"; 515 EXPECT_EQ(2, counter.num_updates_applied) 516 << "All items should have been successfully applied"; 517 518 { 519 syncable::ReadTransaction trans(FROM_HERE, directory()); 520 521 syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle); 522 syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle); 523 524 ASSERT_TRUE(parent.good()); 525 ASSERT_TRUE(child.good()); 526 527 EXPECT_FALSE(parent.GetIsUnsynced()); 528 EXPECT_FALSE(parent.GetIsUnappliedUpdate()); 529 EXPECT_FALSE(child.GetIsUnsynced()); 530 EXPECT_FALSE(child.GetIsUnappliedUpdate()); 531 } 532} 533 534// Test that the applicator can handle updates delivered out of order. 535TEST_F(DirectoryUpdateHandlerApplyUpdateTest, 536 BookmarkChildrenBeforeParent) { 537 // Start with some bookmarks whose parents are unknown. 538 std::string root_server_id = syncable::GetNullId().GetServerId(); 539 int64 a_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent( 540 "a_child_created_first", DefaultBookmarkSpecifics(), "parent"); 541 int64 x_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent( 542 "x_child_created_first", DefaultBookmarkSpecifics(), "parent"); 543 544 // Update application will fail. 545 sessions::StatusController status1; 546 ApplyBookmarkUpdates(&status1); 547 EXPECT_EQ(0, status1.num_updates_applied()); 548 EXPECT_EQ(2, status1.num_hierarchy_conflicts()); 549 550 { 551 syncable::ReadTransaction trans(FROM_HERE, directory()); 552 553 syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle); 554 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle); 555 556 ASSERT_TRUE(a.good()); 557 ASSERT_TRUE(x.good()); 558 559 EXPECT_TRUE(a.GetIsUnappliedUpdate()); 560 EXPECT_TRUE(x.GetIsUnappliedUpdate()); 561 } 562 563 // Now add their parent and a few siblings. 564 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( 565 "parent", DefaultBookmarkSpecifics(), root_server_id); 566 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( 567 "a_child_created_second", DefaultBookmarkSpecifics(), "parent"); 568 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( 569 "x_child_created_second", DefaultBookmarkSpecifics(), "parent"); 570 571 // Update application will succeed. 572 sessions::StatusController status2; 573 ApplyBookmarkUpdates(&status2); 574 EXPECT_EQ(5, status2.num_updates_applied()) 575 << "All updates should have been successfully applied"; 576 577 { 578 syncable::ReadTransaction trans(FROM_HERE, directory()); 579 580 syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle); 581 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle); 582 583 ASSERT_TRUE(a.good()); 584 ASSERT_TRUE(x.good()); 585 586 EXPECT_FALSE(a.GetIsUnappliedUpdate()); 587 EXPECT_FALSE(x.GetIsUnappliedUpdate()); 588 } 589} 590 591// Try to apply changes on an item that is both IS_UNSYNCED and 592// IS_UNAPPLIED_UPDATE. Conflict resolution should be performed. 593TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmarkConflict) { 594 int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem("x"); 595 596 int original_server_version = -10; 597 { 598 syncable::ReadTransaction trans(FROM_HERE, directory()); 599 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); 600 original_server_version = e.GetServerVersion(); 601 ASSERT_NE(original_server_version, e.GetBaseVersion()); 602 EXPECT_TRUE(e.GetIsUnsynced()); 603 } 604 605 sessions::StatusController status; 606 ApplyBookmarkUpdates(&status); 607 608 const UpdateCounters& counters = GetBookmarksUpdateCounters(); 609 EXPECT_EQ(1, counters.num_server_overwrites) 610 << "Unsynced and unapplied item conflict should be resolved"; 611 EXPECT_EQ(0, counters.num_updates_applied) 612 << "Update should not be applied; we should override the server."; 613 614 { 615 syncable::ReadTransaction trans(FROM_HERE, directory()); 616 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); 617 ASSERT_TRUE(e.good()); 618 EXPECT_EQ(original_server_version, e.GetServerVersion()); 619 EXPECT_EQ(original_server_version, e.GetBaseVersion()); 620 EXPECT_FALSE(e.GetIsUnappliedUpdate()); 621 622 // The unsynced flag will remain set until we successfully commit the item. 623 EXPECT_TRUE(e.GetIsUnsynced()); 624 } 625} 626 627// Create a simple conflict that is also a hierarchy conflict. If we were to 628// follow the normal "server wins" logic, we'd end up violating hierarchy 629// constraints. The hierarchy conflict must take precedence. We can not allow 630// the update to be applied. The item must remain in the conflict state. 631TEST_F(DirectoryUpdateHandlerApplyUpdateTest, HierarchyAndSimpleConflict) { 632 // Create a simply-conflicting item. It will start with valid parent ids. 633 int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem( 634 "orphaned_by_server"); 635 { 636 // Manually set the SERVER_PARENT_ID to bad value. 637 // A bad parent indicates a hierarchy conflict. 638 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); 639 syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); 640 ASSERT_TRUE(entry.good()); 641 642 entry.PutServerParentId(TestIdFactory::MakeServer("bogus_parent")); 643 } 644 645 sessions::StatusController status; 646 ApplyBookmarkUpdates(&status); 647 648 const UpdateCounters& counters = GetBookmarksUpdateCounters(); 649 EXPECT_EQ(0, counters.num_updates_applied); 650 EXPECT_EQ(0, counters.num_server_overwrites); 651 EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures); 652 653 { 654 syncable::ReadTransaction trans(FROM_HERE, directory()); 655 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); 656 ASSERT_TRUE(e.good()); 657 EXPECT_TRUE(e.GetIsUnappliedUpdate()); 658 EXPECT_TRUE(e.GetIsUnsynced()); 659 } 660} 661 662// Attempt to apply an udpate that would create a bookmark folder loop. This 663// application should fail. 664TEST_F(DirectoryUpdateHandlerApplyUpdateTest, BookmarkFolderLoop) { 665 // Item 'X' locally has parent of 'root'. Server is updating it to have 666 // parent of 'Y'. 667 668 // Create it as a child of root node. 669 int64 handle = entry_factory()->CreateSyncedItem("X", BOOKMARKS, true); 670 671 { 672 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); 673 syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); 674 ASSERT_TRUE(entry.good()); 675 676 // Re-parent from root to "Y" 677 entry.PutServerVersion(entry_factory()->GetNextRevision()); 678 entry.PutIsUnappliedUpdate(true); 679 entry.PutServerParentId(TestIdFactory::MakeServer("Y")); 680 } 681 682 // Item 'Y' is child of 'X'. 683 entry_factory()->CreateUnsyncedItem( 684 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true, 685 BOOKMARKS, NULL); 686 687 // If the server's update were applied, we would have X be a child of Y, and Y 688 // as a child of X. That's a directory loop. The UpdateApplicator should 689 // prevent the update from being applied and note that this is a hierarchy 690 // conflict. 691 692 sessions::StatusController status; 693 ApplyBookmarkUpdates(&status); 694 695 // This should count as a hierarchy conflict. 696 const UpdateCounters& counters = GetBookmarksUpdateCounters(); 697 EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures); 698 699 { 700 syncable::ReadTransaction trans(FROM_HERE, directory()); 701 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); 702 ASSERT_TRUE(e.good()); 703 EXPECT_TRUE(e.GetIsUnappliedUpdate()); 704 EXPECT_FALSE(e.GetIsUnsynced()); 705 } 706} 707 708// Test update application where the update has been orphaned by a local folder 709// deletion. The update application attempt should fail. 710TEST_F(DirectoryUpdateHandlerApplyUpdateTest, 711 HierarchyConflictDeletedParent) { 712 // Create a locally deleted parent item. 713 int64 parent_handle; 714 entry_factory()->CreateUnsyncedItem( 715 syncable::Id::CreateFromServerId("parent"), TestIdFactory::root(), 716 "parent", true, BOOKMARKS, &parent_handle); 717 { 718 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); 719 syncable::MutableEntry entry(&trans, 720 syncable::GET_BY_HANDLE, 721 parent_handle); 722 entry.PutIsDel(true); 723 } 724 725 // Create an incoming child from the server. 726 int64 child_handle = entry_factory()->CreateUnappliedNewItemWithParent( 727 "child", DefaultBookmarkSpecifics(), "parent"); 728 729 // The server's update may seem valid to some other client, but on this client 730 // that new item's parent no longer exists. The update should not be applied 731 // and the update applicator should indicate this is a hierarchy conflict. 732 733 sessions::StatusController status; 734 ApplyBookmarkUpdates(&status); 735 const UpdateCounters& counters = GetBookmarksUpdateCounters(); 736 EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures); 737 738 { 739 syncable::ReadTransaction trans(FROM_HERE, directory()); 740 syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle); 741 ASSERT_TRUE(child.good()); 742 EXPECT_TRUE(child.GetIsUnappliedUpdate()); 743 EXPECT_FALSE(child.GetIsUnsynced()); 744 } 745} 746 747// Attempt to apply an update that deletes a folder where the folder has 748// locally-created children. The update application should fail. 749TEST_F(DirectoryUpdateHandlerApplyUpdateTest, 750 HierarchyConflictDeleteNonEmptyDirectory) { 751 // Create a server-deleted folder as a child of root node. 752 int64 parent_handle = 753 entry_factory()->CreateSyncedItem("parent", BOOKMARKS, true); 754 { 755 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); 756 syncable::MutableEntry entry(&trans, 757 syncable::GET_BY_HANDLE, 758 parent_handle); 759 ASSERT_TRUE(entry.good()); 760 761 // Delete it on the server. 762 entry.PutServerVersion(entry_factory()->GetNextRevision()); 763 entry.PutIsUnappliedUpdate(true); 764 entry.PutServerParentId(TestIdFactory::root()); 765 entry.PutServerIsDel(true); 766 } 767 768 // Create a local child of the server-deleted directory. 769 entry_factory()->CreateUnsyncedItem( 770 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"), 771 "child", false, BOOKMARKS, NULL); 772 773 // The server's request to delete the directory must be ignored, otherwise our 774 // unsynced new child would be orphaned. This is a hierarchy conflict. 775 776 sessions::StatusController status; 777 ApplyBookmarkUpdates(&status); 778 779 // This should count as a hierarchy conflict. 780 const UpdateCounters& counters = GetBookmarksUpdateCounters(); 781 EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures); 782 783 { 784 syncable::ReadTransaction trans(FROM_HERE, directory()); 785 syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle); 786 ASSERT_TRUE(parent.good()); 787 EXPECT_TRUE(parent.GetIsUnappliedUpdate()); 788 EXPECT_FALSE(parent.GetIsUnsynced()); 789 } 790} 791 792// Attempt to apply updates where the updated item's parent is not known to this 793// client. The update application attempt should fail. 794TEST_F(DirectoryUpdateHandlerApplyUpdateTest, 795 HierarchyConflictUnknownParent) { 796 // We shouldn't be able to do anything with either of these items. 797 int64 x_handle = entry_factory()->CreateUnappliedNewItemWithParent( 798 "some_item", DefaultBookmarkSpecifics(), "unknown_parent"); 799 int64 y_handle = entry_factory()->CreateUnappliedNewItemWithParent( 800 "some_other_item", DefaultBookmarkSpecifics(), "some_item"); 801 802 sessions::StatusController status; 803 ApplyBookmarkUpdates(&status); 804 805 const UpdateCounters& counters = GetBookmarksUpdateCounters(); 806 EXPECT_EQ(2, counters.num_hierarchy_conflict_application_failures) 807 << "All updates with an unknown ancestors should be in conflict"; 808 EXPECT_EQ(0, counters.num_updates_applied) 809 << "No item with an unknown ancestor should be applied"; 810 811 { 812 syncable::ReadTransaction trans(FROM_HERE, directory()); 813 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle); 814 syncable::Entry y(&trans, syncable::GET_BY_HANDLE, y_handle); 815 ASSERT_TRUE(x.good()); 816 ASSERT_TRUE(y.good()); 817 EXPECT_TRUE(x.GetIsUnappliedUpdate()); 818 EXPECT_TRUE(y.GetIsUnappliedUpdate()); 819 EXPECT_FALSE(x.GetIsUnsynced()); 820 EXPECT_FALSE(y.GetIsUnsynced()); 821 } 822} 823 824// Attempt application of a mix of items. Some update application attempts will 825// fail due to hierarchy conflicts. Others should succeed. 826TEST_F(DirectoryUpdateHandlerApplyUpdateTest, ItemsBothKnownAndUnknown) { 827 // See what happens when there's a mixture of good and bad updates. 828 std::string root_server_id = syncable::GetNullId().GetServerId(); 829 int64 u1_handle = entry_factory()->CreateUnappliedNewItemWithParent( 830 "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent"); 831 int64 k1_handle = entry_factory()->CreateUnappliedNewItemWithParent( 832 "first_known_item", DefaultBookmarkSpecifics(), root_server_id); 833 int64 u2_handle = entry_factory()->CreateUnappliedNewItemWithParent( 834 "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent"); 835 int64 k2_handle = entry_factory()->CreateUnappliedNewItemWithParent( 836 "second_known_item", DefaultBookmarkSpecifics(), "first_known_item"); 837 int64 k3_handle = entry_factory()->CreateUnappliedNewItemWithParent( 838 "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item"); 839 int64 k4_handle = entry_factory()->CreateUnappliedNewItemWithParent( 840 "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id); 841 842 sessions::StatusController status; 843 ApplyBookmarkUpdates(&status); 844 845 const UpdateCounters& counters = GetBookmarksUpdateCounters(); 846 EXPECT_EQ(2, counters.num_hierarchy_conflict_application_failures) 847 << "The updates with unknown ancestors should be in conflict"; 848 EXPECT_EQ(4, counters.num_updates_applied) 849 << "The updates with known ancestors should be successfully applied"; 850 851 { 852 syncable::ReadTransaction trans(FROM_HERE, directory()); 853 syncable::Entry u1(&trans, syncable::GET_BY_HANDLE, u1_handle); 854 syncable::Entry u2(&trans, syncable::GET_BY_HANDLE, u2_handle); 855 syncable::Entry k1(&trans, syncable::GET_BY_HANDLE, k1_handle); 856 syncable::Entry k2(&trans, syncable::GET_BY_HANDLE, k2_handle); 857 syncable::Entry k3(&trans, syncable::GET_BY_HANDLE, k3_handle); 858 syncable::Entry k4(&trans, syncable::GET_BY_HANDLE, k4_handle); 859 ASSERT_TRUE(u1.good()); 860 ASSERT_TRUE(u2.good()); 861 ASSERT_TRUE(k1.good()); 862 ASSERT_TRUE(k2.good()); 863 ASSERT_TRUE(k3.good()); 864 ASSERT_TRUE(k4.good()); 865 EXPECT_TRUE(u1.GetIsUnappliedUpdate()); 866 EXPECT_TRUE(u2.GetIsUnappliedUpdate()); 867 EXPECT_FALSE(k1.GetIsUnappliedUpdate()); 868 EXPECT_FALSE(k2.GetIsUnappliedUpdate()); 869 EXPECT_FALSE(k3.GetIsUnappliedUpdate()); 870 EXPECT_FALSE(k4.GetIsUnappliedUpdate()); 871 } 872} 873 874// Attempt application of password upates where the passphrase is known. 875TEST_F(DirectoryUpdateHandlerApplyUpdateTest, DecryptablePassword) { 876 // Decryptable password updates should be applied. 877 Cryptographer* cryptographer; 878 { 879 // Storing the cryptographer separately is bad, but for this test we 880 // know it's safe. 881 syncable::ReadTransaction trans(FROM_HERE, directory()); 882 cryptographer = directory()->GetCryptographer(&trans); 883 } 884 885 KeyParams params = {"localhost", "dummy", "foobar"}; 886 cryptographer->AddKey(params); 887 888 sync_pb::EntitySpecifics specifics; 889 sync_pb::PasswordSpecificsData data; 890 data.set_origin("http://example.com"); 891 892 cryptographer->Encrypt(data, 893 specifics.mutable_password()->mutable_encrypted()); 894 int64 handle = 895 entry_factory()->CreateUnappliedNewItem("item", specifics, false); 896 897 sessions::StatusController status; 898 ApplyPasswordUpdates(&status); 899 900 const UpdateCounters& counters = GetPasswordsUpdateCounters(); 901 EXPECT_EQ(1, counters.num_updates_applied) 902 << "The updates that can be decrypted should be applied"; 903 904 { 905 syncable::ReadTransaction trans(FROM_HERE, directory()); 906 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); 907 ASSERT_TRUE(e.good()); 908 EXPECT_FALSE(e.GetIsUnappliedUpdate()); 909 EXPECT_FALSE(e.GetIsUnsynced()); 910 } 911} 912 913// Attempt application of encrypted items when the passphrase is not known. 914TEST_F(DirectoryUpdateHandlerApplyUpdateTest, UndecryptableData) { 915 // Undecryptable updates should not be applied. 916 sync_pb::EntitySpecifics encrypted_bookmark; 917 encrypted_bookmark.mutable_encrypted(); 918 AddDefaultFieldValue(BOOKMARKS, &encrypted_bookmark); 919 std::string root_server_id = syncable::GetNullId().GetServerId(); 920 int64 folder_handle = entry_factory()->CreateUnappliedNewItemWithParent( 921 "folder", 922 encrypted_bookmark, 923 root_server_id); 924 int64 bookmark_handle = entry_factory()->CreateUnappliedNewItem( 925 "item2", 926 encrypted_bookmark, 927 false); 928 sync_pb::EntitySpecifics encrypted_password; 929 encrypted_password.mutable_password(); 930 int64 password_handle = entry_factory()->CreateUnappliedNewItem( 931 "item3", 932 encrypted_password, 933 false); 934 935 sessions::StatusController status; 936 ApplyBookmarkUpdates(&status); 937 ApplyPasswordUpdates(&status); 938 939 const UpdateCounters& bm_counters = GetBookmarksUpdateCounters(); 940 EXPECT_EQ(2, bm_counters.num_encryption_conflict_application_failures) 941 << "Updates that can't be decrypted should be in encryption conflict"; 942 EXPECT_EQ(0, bm_counters.num_updates_applied) 943 << "No update that can't be decrypted should be applied"; 944 945 const UpdateCounters& pw_counters = GetPasswordsUpdateCounters(); 946 EXPECT_EQ(1, pw_counters.num_encryption_conflict_application_failures) 947 << "Updates that can't be decrypted should be in encryption conflict"; 948 EXPECT_EQ(0, pw_counters.num_updates_applied) 949 << "No update that can't be decrypted should be applied"; 950 951 { 952 syncable::ReadTransaction trans(FROM_HERE, directory()); 953 syncable::Entry folder(&trans, syncable::GET_BY_HANDLE, folder_handle); 954 syncable::Entry bm(&trans, syncable::GET_BY_HANDLE, bookmark_handle); 955 syncable::Entry pw(&trans, syncable::GET_BY_HANDLE, password_handle); 956 ASSERT_TRUE(folder.good()); 957 ASSERT_TRUE(bm.good()); 958 ASSERT_TRUE(pw.good()); 959 EXPECT_TRUE(folder.GetIsUnappliedUpdate()); 960 EXPECT_TRUE(bm.GetIsUnappliedUpdate()); 961 EXPECT_TRUE(pw.GetIsUnappliedUpdate()); 962 } 963} 964 965// Test a mix of decryptable and undecryptable updates. 966TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SomeUndecryptablePassword) { 967 Cryptographer* cryptographer; 968 969 int64 decryptable_handle = -1; 970 int64 undecryptable_handle = -1; 971 972 // Only decryptable password updates should be applied. 973 { 974 sync_pb::EntitySpecifics specifics; 975 sync_pb::PasswordSpecificsData data; 976 data.set_origin("http://example.com/1"); 977 { 978 syncable::ReadTransaction trans(FROM_HERE, directory()); 979 cryptographer = directory()->GetCryptographer(&trans); 980 981 KeyParams params = {"localhost", "dummy", "foobar"}; 982 cryptographer->AddKey(params); 983 984 cryptographer->Encrypt(data, 985 specifics.mutable_password()->mutable_encrypted()); 986 } 987 decryptable_handle = 988 entry_factory()->CreateUnappliedNewItem("item1", specifics, false); 989 } 990 { 991 // Create a new cryptographer, independent of the one in the session. 992 Cryptographer other_cryptographer(cryptographer->encryptor()); 993 KeyParams params = {"localhost", "dummy", "bazqux"}; 994 other_cryptographer.AddKey(params); 995 996 sync_pb::EntitySpecifics specifics; 997 sync_pb::PasswordSpecificsData data; 998 data.set_origin("http://example.com/2"); 999 1000 other_cryptographer.Encrypt(data, 1001 specifics.mutable_password()->mutable_encrypted()); 1002 undecryptable_handle = 1003 entry_factory()->CreateUnappliedNewItem("item2", specifics, false); 1004 } 1005 1006 sessions::StatusController status; 1007 ApplyPasswordUpdates(&status); 1008 1009 const UpdateCounters& counters = GetPasswordsUpdateCounters(); 1010 EXPECT_EQ(1, counters.num_encryption_conflict_application_failures) 1011 << "The updates that can't be decrypted should be in encryption " 1012 << "conflict"; 1013 EXPECT_EQ(1, counters.num_updates_applied) 1014 << "The undecryptable password update shouldn't be applied"; 1015 1016 { 1017 syncable::ReadTransaction trans(FROM_HERE, directory()); 1018 syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, decryptable_handle); 1019 syncable::Entry e2(&trans, syncable::GET_BY_HANDLE, undecryptable_handle); 1020 ASSERT_TRUE(e1.good()); 1021 ASSERT_TRUE(e2.good()); 1022 EXPECT_FALSE(e1.GetIsUnappliedUpdate()); 1023 EXPECT_TRUE(e2.GetIsUnappliedUpdate()); 1024 } 1025} 1026 1027} // namespace syncer 1028