| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <string> | 5 #include <string> |
| 6 | 6 |
| 7 #include "base/format_macros.h" | |
| 8 #include "base/location.h" | 7 #include "base/location.h" |
| 9 #include "base/memory/scoped_ptr.h" | 8 #include "base/memory/scoped_ptr.h" |
| 10 #include "base/stringprintf.h" | 9 #include "base/stringprintf.h" |
| 11 #include "sync/engine/apply_updates_command.h" | 10 #include "sync/engine/apply_updates_command.h" |
| 12 #include "sync/engine/syncer.h" | 11 #include "sync/engine/syncer.h" |
| 13 #include "sync/internal_api/public/test/test_entry_factory.h" | 12 #include "sync/internal_api/public/test/test_entry_factory.h" |
| 14 #include "sync/protocol/bookmark_specifics.pb.h" | 13 #include "sync/protocol/bookmark_specifics.pb.h" |
| 15 #include "sync/protocol/password_specifics.pb.h" | 14 #include "sync/protocol/password_specifics.pb.h" |
| 16 #include "sync/sessions/sync_session.h" | |
| 17 #include "sync/syncable/mutable_entry.h" | 15 #include "sync/syncable/mutable_entry.h" |
| 18 #include "sync/syncable/nigori_util.h" | 16 #include "sync/syncable/nigori_util.h" |
| 19 #include "sync/syncable/read_transaction.h" | 17 #include "sync/syncable/read_transaction.h" |
| 20 #include "sync/syncable/syncable_id.h" | 18 #include "sync/syncable/syncable_id.h" |
| 21 #include "sync/syncable/syncable_util.h" | 19 #include "sync/syncable/syncable_util.h" |
| 22 #include "sync/syncable/write_transaction.h" | 20 #include "sync/syncable/write_transaction.h" |
| 23 #include "sync/test/engine/fake_model_worker.h" | 21 #include "sync/test/engine/fake_model_worker.h" |
| 24 #include "sync/test/engine/syncer_command_test.h" | 22 #include "sync/test/engine/syncer_command_test.h" |
| 25 #include "sync/test/engine/test_id_factory.h" | 23 #include "sync/test/engine/test_id_factory.h" |
| 26 #include "sync/test/fake_encryptor.h" | 24 #include "sync/test/fake_encryptor.h" |
| 27 #include "sync/util/cryptographer.h" | 25 #include "sync/util/cryptographer.h" |
| 28 #include "testing/gtest/include/gtest/gtest.h" | 26 #include "testing/gtest/include/gtest/gtest.h" |
| 29 | 27 |
| 30 namespace syncer { | 28 namespace syncer { |
| 31 | 29 |
| 32 using sessions::SyncSession; | |
| 33 using std::string; | 30 using std::string; |
| 34 using syncable::Id; | 31 using syncable::Id; |
| 35 using syncable::MutableEntry; | 32 using syncable::MutableEntry; |
| 36 using syncable::UNITTEST; | 33 using syncable::UNITTEST; |
| 37 using syncable::WriteTransaction; | 34 using syncable::WriteTransaction; |
| 38 | 35 |
| 39 namespace { | 36 namespace { |
| 40 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { | 37 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { |
| 41 sync_pb::EntitySpecifics result; | 38 sync_pb::EntitySpecifics result; |
| 42 AddDefaultFieldValue(BOOKMARKS, &result); | 39 AddDefaultFieldValue(BOOKMARKS, &result); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 53 | 50 |
| 54 virtual void SetUp() { | 51 virtual void SetUp() { |
| 55 workers()->clear(); | 52 workers()->clear(); |
| 56 mutable_routing_info()->clear(); | 53 mutable_routing_info()->clear(); |
| 57 workers()->push_back( | 54 workers()->push_back( |
| 58 make_scoped_refptr(new FakeModelWorker(GROUP_UI))); | 55 make_scoped_refptr(new FakeModelWorker(GROUP_UI))); |
| 59 workers()->push_back( | 56 workers()->push_back( |
| 60 make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD))); | 57 make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD))); |
| 61 (*mutable_routing_info())[BOOKMARKS] = GROUP_UI; | 58 (*mutable_routing_info())[BOOKMARKS] = GROUP_UI; |
| 62 (*mutable_routing_info())[PASSWORDS] = GROUP_PASSWORD; | 59 (*mutable_routing_info())[PASSWORDS] = GROUP_PASSWORD; |
| 63 (*mutable_routing_info())[NIGORI] = GROUP_PASSIVE; | 60 (*mutable_routing_info())[NIGORI] = GROUP_CONTROL; |
| 64 SyncerCommandTest::SetUp(); | 61 SyncerCommandTest::SetUp(); |
| 65 entry_factory_.reset(new TestEntryFactory(directory())); | 62 entry_factory_.reset(new TestEntryFactory(directory())); |
| 66 ExpectNoGroupsToChange(apply_updates_command_); | 63 ExpectNoGroupsToChange(apply_updates_command_); |
| 67 } | 64 } |
| 68 | 65 |
| 69 ApplyUpdatesCommand apply_updates_command_; | 66 ApplyUpdatesCommand apply_updates_command_; |
| 70 FakeEncryptor encryptor_; | 67 FakeEncryptor encryptor_; |
| 71 TestIdFactory id_factory_; | |
| 72 scoped_ptr<TestEntryFactory> entry_factory_; | 68 scoped_ptr<TestEntryFactory> entry_factory_; |
| 73 private: | 69 private: |
| 74 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); | 70 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); |
| 75 }; | 71 }; |
| 76 | 72 |
| 77 TEST_F(ApplyUpdatesCommandTest, Simple) { | 73 TEST_F(ApplyUpdatesCommandTest, Simple) { |
| 78 string root_server_id = syncable::GetNullId().GetServerId(); | 74 string root_server_id = syncable::GetNullId().GetServerId(); |
| 79 entry_factory_->CreateUnappliedNewItemWithParent("parent", | 75 entry_factory_->CreateUnappliedNewItemWithParent("parent", |
| 80 DefaultBookmarkSpecifics(), | 76 DefaultBookmarkSpecifics(), |
| 81 root_server_id); | 77 root_server_id); |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 160 int64 handle = entry_factory_->CreateUnappliedAndUnsyncedItem( | 156 int64 handle = entry_factory_->CreateUnappliedAndUnsyncedItem( |
| 161 "orphaned_by_server", BOOKMARKS); | 157 "orphaned_by_server", BOOKMARKS); |
| 162 { | 158 { |
| 163 // Manually set the SERVER_PARENT_ID to bad value. | 159 // Manually set the SERVER_PARENT_ID to bad value. |
| 164 // A bad parent indicates a hierarchy conflict. | 160 // A bad parent indicates a hierarchy conflict. |
| 165 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 161 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 166 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | 162 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); |
| 167 ASSERT_TRUE(entry.good()); | 163 ASSERT_TRUE(entry.good()); |
| 168 | 164 |
| 169 entry.Put(syncable::SERVER_PARENT_ID, | 165 entry.Put(syncable::SERVER_PARENT_ID, |
| 170 id_factory_.MakeServer("bogus_parent")); | 166 TestIdFactory::MakeServer("bogus_parent")); |
| 171 } | 167 } |
| 172 | 168 |
| 173 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | 169 ExpectGroupToChange(apply_updates_command_, GROUP_UI); |
| 174 apply_updates_command_.ExecuteImpl(session()); | 170 apply_updates_command_.ExecuteImpl(session()); |
| 175 | 171 |
| 176 sessions::StatusController* status = session()->mutable_status_controller(); | 172 sessions::StatusController* status = session()->mutable_status_controller(); |
| 177 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | 173 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); |
| 178 | 174 |
| 179 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); | 175 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); |
| 180 | 176 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 197 // Create it as a child of root node. | 193 // Create it as a child of root node. |
| 198 int64 handle = entry_factory_->CreateSyncedItem("X", BOOKMARKS, true); | 194 int64 handle = entry_factory_->CreateSyncedItem("X", BOOKMARKS, true); |
| 199 | 195 |
| 200 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 196 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 201 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | 197 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); |
| 202 ASSERT_TRUE(entry.good()); | 198 ASSERT_TRUE(entry.good()); |
| 203 | 199 |
| 204 // Re-parent from root to "Y" | 200 // Re-parent from root to "Y" |
| 205 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); | 201 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); |
| 206 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | 202 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); |
| 207 entry.Put(syncable::SERVER_PARENT_ID, id_factory_.MakeServer("Y")); | 203 entry.Put(syncable::SERVER_PARENT_ID, TestIdFactory::MakeServer("Y")); |
| 208 } | 204 } |
| 209 | 205 |
| 210 // Item 'Y' is child of 'X'. | 206 // Item 'Y' is child of 'X'. |
| 211 entry_factory_->CreateUnsyncedItem( | 207 entry_factory_->CreateUnsyncedItem( |
| 212 id_factory_.MakeServer("Y"), id_factory_.MakeServer("X"), "Y", true, | 208 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true, |
| 213 BOOKMARKS, NULL); | 209 BOOKMARKS, NULL); |
| 214 | 210 |
| 215 // If the server's update were applied, we would have X be a child of Y, and Y | 211 // If the server's update were applied, we would have X be a child of Y, and Y |
| 216 // as a child of X. That's a directory loop. The UpdateApplicator should | 212 // as a child of X. That's a directory loop. The UpdateApplicator should |
| 217 // prevent the update from being applied and note that this is a hierarchy | 213 // prevent the update from being applied and note that this is a hierarchy |
| 218 // conflict. | 214 // conflict. |
| 219 | 215 |
| 220 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | 216 ExpectGroupToChange(apply_updates_command_, GROUP_UI); |
| 221 apply_updates_command_.ExecuteImpl(session()); | 217 apply_updates_command_.ExecuteImpl(session()); |
| 222 | 218 |
| 223 sessions::StatusController* status = session()->mutable_status_controller(); | 219 sessions::StatusController* status = session()->mutable_status_controller(); |
| 224 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | 220 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); |
| 225 | 221 |
| 226 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); | 222 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); |
| 227 | 223 |
| 228 // This should count as a hierarchy conflict. | 224 // This should count as a hierarchy conflict. |
| 229 ASSERT_TRUE(status->conflict_progress()); | 225 ASSERT_TRUE(status->conflict_progress()); |
| 230 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); | 226 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); |
| 231 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); | 227 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); |
| 232 } | 228 } |
| 233 | 229 |
| 234 // Runs the ApplyUpdatesCommand on a directory where the server sent us an | 230 // Runs the ApplyUpdatesCommand on a directory where the server sent us an |
| 235 // update to add a child to a locally deleted (and unsynced) parent. We expect | 231 // update to add a child to a locally deleted (and unsynced) parent. We expect |
| 236 // the command to not apply the update and to indicate the update is in a | 232 // the command to not apply the update and to indicate the update is in a |
| 237 // CONFLICT_HIERARCHY state. | 233 // CONFLICT_HIERARCHY state. |
| 238 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeletedParent) { | 234 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeletedParent) { |
| 239 // Create a locally deleted parent item. | 235 // Create a locally deleted parent item. |
| 240 int64 parent_handle; | 236 int64 parent_handle; |
| 241 entry_factory_->CreateUnsyncedItem( | 237 entry_factory_->CreateUnsyncedItem( |
| 242 Id::CreateFromServerId("parent"), id_factory_.root(), | 238 Id::CreateFromServerId("parent"), TestIdFactory::root(), |
| 243 "parent", true, BOOKMARKS, &parent_handle); | 239 "parent", true, BOOKMARKS, &parent_handle); |
| 244 { | 240 { |
| 245 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 241 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 246 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, parent_handle); | 242 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, parent_handle); |
| 247 entry.Put(syncable::IS_DEL, true); | 243 entry.Put(syncable::IS_DEL, true); |
| 248 } | 244 } |
| 249 | 245 |
| 250 // Create an incoming child from the server. | 246 // Create an incoming child from the server. |
| 251 entry_factory_->CreateUnappliedNewItemWithParent( | 247 entry_factory_->CreateUnappliedNewItemWithParent( |
| 252 "child", DefaultBookmarkSpecifics(), "parent"); | 248 "child", DefaultBookmarkSpecifics(), "parent"); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 278 int64 handle = | 274 int64 handle = |
| 279 entry_factory_->CreateSyncedItem("parent", BOOKMARKS, true); | 275 entry_factory_->CreateSyncedItem("parent", BOOKMARKS, true); |
| 280 | 276 |
| 281 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 277 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 282 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | 278 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); |
| 283 ASSERT_TRUE(entry.good()); | 279 ASSERT_TRUE(entry.good()); |
| 284 | 280 |
| 285 // Delete it on the server. | 281 // Delete it on the server. |
| 286 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); | 282 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); |
| 287 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | 283 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); |
| 288 entry.Put(syncable::SERVER_PARENT_ID, id_factory_.root()); | 284 entry.Put(syncable::SERVER_PARENT_ID, TestIdFactory::root()); |
| 289 entry.Put(syncable::SERVER_IS_DEL, true); | 285 entry.Put(syncable::SERVER_IS_DEL, true); |
| 290 } | 286 } |
| 291 | 287 |
| 292 // Create a local child of the server-deleted directory. | 288 // Create a local child of the server-deleted directory. |
| 293 entry_factory_->CreateUnsyncedItem( | 289 entry_factory_->CreateUnsyncedItem( |
| 294 id_factory_.MakeServer("child"), id_factory_.MakeServer("parent"), | 290 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"), |
| 295 "child", false, BOOKMARKS, NULL); | 291 "child", false, BOOKMARKS, NULL); |
| 296 | 292 |
| 297 // The server's request to delete the directory must be ignored, otherwise our | 293 // The server's request to delete the directory must be ignored, otherwise our |
| 298 // unsynced new child would be orphaned. This is a hierarchy conflict. | 294 // unsynced new child would be orphaned. This is a hierarchy conflict. |
| 299 | 295 |
| 300 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | 296 ExpectGroupToChange(apply_updates_command_, GROUP_UI); |
| 301 apply_updates_command_.ExecuteImpl(session()); | 297 apply_updates_command_.ExecuteImpl(session()); |
| 302 | 298 |
| 303 sessions::StatusController* status = session()->mutable_status_controller(); | 299 sessions::StatusController* status = session()->mutable_status_controller(); |
| 304 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | 300 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); |
| (...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 506 << "The updates that can't be decrypted should not be in regular " | 502 << "The updates that can't be decrypted should not be in regular " |
| 507 << "conflict"; | 503 << "conflict"; |
| 508 EXPECT_EQ(1, status->conflict_progress()->EncryptionConflictingItemsSize()) | 504 EXPECT_EQ(1, status->conflict_progress()->EncryptionConflictingItemsSize()) |
| 509 << "The updates that can't be decrypted should be in encryption " | 505 << "The updates that can't be decrypted should be in encryption " |
| 510 << "conflict"; | 506 << "conflict"; |
| 511 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | 507 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) |
| 512 << "The undecryptable password update shouldn't be applied"; | 508 << "The undecryptable password update shouldn't be applied"; |
| 513 } | 509 } |
| 514 } | 510 } |
| 515 | 511 |
| 516 TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { | |
| 517 // Storing the cryptographer separately is bad, but for this test we | |
| 518 // know it's safe. | |
| 519 Cryptographer* cryptographer; | |
| 520 ModelTypeSet encrypted_types; | |
| 521 encrypted_types.Put(PASSWORDS); | |
| 522 encrypted_types.Put(NIGORI); | |
| 523 { | |
| 524 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 525 cryptographer = directory()->GetCryptographer(&trans); | |
| 526 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 527 } | |
| 528 | |
| 529 // Nigori node updates should update the Cryptographer. | |
| 530 Cryptographer other_cryptographer(&encryptor_); | |
| 531 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 532 other_cryptographer.AddKey(params); | |
| 533 | |
| 534 sync_pb::EntitySpecifics specifics; | |
| 535 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 536 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
| 537 nigori->set_encrypt_bookmarks(true); | |
| 538 encrypted_types.Put(BOOKMARKS); | |
| 539 entry_factory_->CreateUnappliedNewItem( | |
| 540 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 541 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 542 | |
| 543 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 544 apply_updates_command_.ExecuteImpl(session()); | |
| 545 | |
| 546 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 547 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 548 ASSERT_TRUE(status->update_progress()); | |
| 549 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 550 << "All updates should have been attempted"; | |
| 551 ASSERT_TRUE(status->conflict_progress()); | |
| 552 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 553 << "The nigori update shouldn't be in conflict"; | |
| 554 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 555 << "The nigori update should be applied"; | |
| 556 | |
| 557 EXPECT_FALSE(cryptographer->is_ready()); | |
| 558 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 559 EXPECT_TRUE( | |
| 560 cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); | |
| 561 } | |
| 562 | |
| 563 TEST_F(ApplyUpdatesCommandTest, NigoriUpdateForDisabledTypes) { | |
| 564 // Storing the cryptographer separately is bad, but for this test we | |
| 565 // know it's safe. | |
| 566 Cryptographer* cryptographer; | |
| 567 ModelTypeSet encrypted_types; | |
| 568 encrypted_types.Put(PASSWORDS); | |
| 569 encrypted_types.Put(NIGORI); | |
| 570 { | |
| 571 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 572 cryptographer = directory()->GetCryptographer(&trans); | |
| 573 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 574 } | |
| 575 | |
| 576 // Nigori node updates should update the Cryptographer. | |
| 577 Cryptographer other_cryptographer(&encryptor_); | |
| 578 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 579 other_cryptographer.AddKey(params); | |
| 580 | |
| 581 sync_pb::EntitySpecifics specifics; | |
| 582 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 583 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
| 584 nigori->set_encrypt_sessions(true); | |
| 585 nigori->set_encrypt_themes(true); | |
| 586 encrypted_types.Put(SESSIONS); | |
| 587 encrypted_types.Put(THEMES); | |
| 588 entry_factory_->CreateUnappliedNewItem( | |
| 589 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 590 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 591 | |
| 592 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 593 apply_updates_command_.ExecuteImpl(session()); | |
| 594 | |
| 595 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 596 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 597 ASSERT_TRUE(status->update_progress()); | |
| 598 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 599 << "All updates should have been attempted"; | |
| 600 ASSERT_TRUE(status->conflict_progress()); | |
| 601 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 602 << "The nigori update shouldn't be in conflict"; | |
| 603 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 604 << "The nigori update should be applied"; | |
| 605 | |
| 606 EXPECT_FALSE(cryptographer->is_ready()); | |
| 607 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 608 EXPECT_TRUE( | |
| 609 cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); | |
| 610 } | |
| 611 | |
| 612 // Create some local unsynced and unencrypted data. Apply a nigori update that | |
| 613 // turns on encryption for the unsynced data. Ensure we properly encrypt the | |
| 614 // data as part of the nigori update. Apply another nigori update with no | |
| 615 // changes. Ensure we ignore already-encrypted unsynced data and that nothing | |
| 616 // breaks. | |
| 617 TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { | |
| 618 // Storing the cryptographer separately is bad, but for this test we | |
| 619 // know it's safe. | |
| 620 Cryptographer* cryptographer; | |
| 621 ModelTypeSet encrypted_types; | |
| 622 encrypted_types.Put(PASSWORDS); | |
| 623 encrypted_types.Put(NIGORI); | |
| 624 { | |
| 625 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 626 cryptographer = directory()->GetCryptographer(&trans); | |
| 627 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 628 | |
| 629 // With default encrypted_types, this should be true. | |
| 630 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 631 | |
| 632 Syncer::UnsyncedMetaHandles handles; | |
| 633 GetUnsyncedEntries(&trans, &handles); | |
| 634 EXPECT_TRUE(handles.empty()); | |
| 635 } | |
| 636 | |
| 637 // Create unsynced bookmarks without encryption. | |
| 638 // First item is a folder | |
| 639 Id folder_id = id_factory_.NewLocalId(); | |
| 640 entry_factory_->CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", | |
| 641 true, BOOKMARKS, NULL); | |
| 642 // Next five items are children of the folder | |
| 643 size_t i; | |
| 644 size_t batch_s = 5; | |
| 645 for (i = 0; i < batch_s; ++i) { | |
| 646 entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, | |
| 647 base::StringPrintf("Item %"PRIuS"", i), | |
| 648 false, BOOKMARKS, NULL); | |
| 649 } | |
| 650 // Next five items are children of the root. | |
| 651 for (; i < 2*batch_s; ++i) { | |
| 652 entry_factory_->CreateUnsyncedItem( | |
| 653 id_factory_.NewLocalId(), id_factory_.root(), | |
| 654 base::StringPrintf("Item %"PRIuS"", i), false, | |
| 655 BOOKMARKS, NULL); | |
| 656 } | |
| 657 | |
| 658 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 659 cryptographer->AddKey(params); | |
| 660 sync_pb::EntitySpecifics specifics; | |
| 661 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 662 cryptographer->GetKeys(nigori->mutable_encrypted()); | |
| 663 nigori->set_encrypt_bookmarks(true); | |
| 664 encrypted_types.Put(BOOKMARKS); | |
| 665 entry_factory_->CreateUnappliedNewItem( | |
| 666 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 667 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 668 EXPECT_TRUE(cryptographer->is_ready()); | |
| 669 | |
| 670 { | |
| 671 // Ensure we have unsynced nodes that aren't properly encrypted. | |
| 672 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 673 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 674 | |
| 675 Syncer::UnsyncedMetaHandles handles; | |
| 676 GetUnsyncedEntries(&trans, &handles); | |
| 677 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 678 } | |
| 679 | |
| 680 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 681 apply_updates_command_.ExecuteImpl(session()); | |
| 682 | |
| 683 { | |
| 684 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 685 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 686 ASSERT_TRUE(status->update_progress()); | |
| 687 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 688 << "All updates should have been attempted"; | |
| 689 ASSERT_TRUE(status->conflict_progress()); | |
| 690 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 691 << "No updates should be in conflict"; | |
| 692 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
| 693 << "No updates should be in conflict"; | |
| 694 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 695 << "The nigori update should be applied"; | |
| 696 } | |
| 697 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 698 EXPECT_TRUE(cryptographer->is_ready()); | |
| 699 { | |
| 700 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 701 | |
| 702 // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes | |
| 703 // should be encrypted now. | |
| 704 EXPECT_TRUE(ModelTypeSet::All().Equals( | |
| 705 cryptographer->GetEncryptedTypes())); | |
| 706 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 707 | |
| 708 Syncer::UnsyncedMetaHandles handles; | |
| 709 GetUnsyncedEntries(&trans, &handles); | |
| 710 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 711 } | |
| 712 | |
| 713 // Simulate another nigori update that doesn't change anything. | |
| 714 { | |
| 715 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
| 716 MutableEntry entry(&trans, syncable::GET_BY_SERVER_TAG, | |
| 717 ModelTypeToRootTag(NIGORI)); | |
| 718 ASSERT_TRUE(entry.good()); | |
| 719 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); | |
| 720 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
| 721 } | |
| 722 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 723 apply_updates_command_.ExecuteImpl(session()); | |
| 724 { | |
| 725 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 726 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 727 ASSERT_TRUE(status->update_progress()); | |
| 728 EXPECT_EQ(2, status->update_progress()->AppliedUpdatesSize()) | |
| 729 << "All updates should have been attempted"; | |
| 730 ASSERT_TRUE(status->conflict_progress()); | |
| 731 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 732 << "No updates should be in conflict"; | |
| 733 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
| 734 << "No updates should be in conflict"; | |
| 735 EXPECT_EQ(2, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 736 << "The nigori update should be applied"; | |
| 737 } | |
| 738 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 739 EXPECT_TRUE(cryptographer->is_ready()); | |
| 740 { | |
| 741 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 742 | |
| 743 // All our changes should still be encrypted. | |
| 744 EXPECT_TRUE(ModelTypeSet::All().Equals( | |
| 745 cryptographer->GetEncryptedTypes())); | |
| 746 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 747 | |
| 748 Syncer::UnsyncedMetaHandles handles; | |
| 749 GetUnsyncedEntries(&trans, &handles); | |
| 750 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 751 } | |
| 752 } | |
| 753 | |
| 754 TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) { | |
| 755 // Storing the cryptographer separately is bad, but for this test we | |
| 756 // know it's safe. | |
| 757 Cryptographer* cryptographer; | |
| 758 ModelTypeSet encrypted_types; | |
| 759 encrypted_types.Put(PASSWORDS); | |
| 760 encrypted_types.Put(NIGORI); | |
| 761 { | |
| 762 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 763 cryptographer = directory()->GetCryptographer(&trans); | |
| 764 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 765 | |
| 766 // With default encrypted_types, this should be true. | |
| 767 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 768 | |
| 769 Syncer::UnsyncedMetaHandles handles; | |
| 770 GetUnsyncedEntries(&trans, &handles); | |
| 771 EXPECT_TRUE(handles.empty()); | |
| 772 } | |
| 773 | |
| 774 // Create unsynced bookmarks without encryption. | |
| 775 // First item is a folder | |
| 776 Id folder_id = id_factory_.NewLocalId(); | |
| 777 entry_factory_->CreateUnsyncedItem( | |
| 778 folder_id, id_factory_.root(), "folder", true, | |
| 779 BOOKMARKS, NULL); | |
| 780 // Next five items are children of the folder | |
| 781 size_t i; | |
| 782 size_t batch_s = 5; | |
| 783 for (i = 0; i < batch_s; ++i) { | |
| 784 entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, | |
| 785 base::StringPrintf("Item %"PRIuS"", i), | |
| 786 false, BOOKMARKS, NULL); | |
| 787 } | |
| 788 // Next five items are children of the root. | |
| 789 for (; i < 2*batch_s; ++i) { | |
| 790 entry_factory_->CreateUnsyncedItem( | |
| 791 id_factory_.NewLocalId(), id_factory_.root(), | |
| 792 base::StringPrintf("Item %"PRIuS"", i), false, | |
| 793 BOOKMARKS, NULL); | |
| 794 } | |
| 795 | |
| 796 // We encrypt with new keys, triggering the local cryptographer to be unready | |
| 797 // and unable to decrypt data (once updated). | |
| 798 Cryptographer other_cryptographer(&encryptor_); | |
| 799 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 800 other_cryptographer.AddKey(params); | |
| 801 sync_pb::EntitySpecifics specifics; | |
| 802 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 803 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
| 804 nigori->set_encrypt_bookmarks(true); | |
| 805 encrypted_types.Put(BOOKMARKS); | |
| 806 entry_factory_->CreateUnappliedNewItem( | |
| 807 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 808 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 809 | |
| 810 { | |
| 811 // Ensure we have unsynced nodes that aren't properly encrypted. | |
| 812 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 813 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 814 Syncer::UnsyncedMetaHandles handles; | |
| 815 GetUnsyncedEntries(&trans, &handles); | |
| 816 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 817 } | |
| 818 | |
| 819 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 820 apply_updates_command_.ExecuteImpl(session()); | |
| 821 | |
| 822 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 823 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 824 ASSERT_TRUE(status->update_progress()); | |
| 825 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 826 << "All updates should have been attempted"; | |
| 827 ASSERT_TRUE(status->conflict_progress()); | |
| 828 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 829 << "The unsynced changes don't trigger a blocking conflict with the " | |
| 830 << "nigori update."; | |
| 831 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
| 832 << "The unsynced changes don't trigger an encryption conflict with the " | |
| 833 << "nigori update."; | |
| 834 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 835 << "The nigori update should be applied"; | |
| 836 EXPECT_FALSE(cryptographer->is_ready()); | |
| 837 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 838 { | |
| 839 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 840 | |
| 841 // Since we have pending keys, we would have failed to encrypt, but the | |
| 842 // cryptographer should be updated. | |
| 843 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 844 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals( | |
| 845 ModelTypeSet().All())); | |
| 846 EXPECT_FALSE(cryptographer->is_ready()); | |
| 847 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 848 | |
| 849 Syncer::UnsyncedMetaHandles handles; | |
| 850 GetUnsyncedEntries(&trans, &handles); | |
| 851 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 852 } | |
| 853 } | |
| 854 | |
| 855 } // namespace syncer | 512 } // namespace syncer |
| OLD | NEW |