1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <string> 6 7 #include "base/format_macros.h" 8 #include "base/string_util.h" 9 #include "chrome/browser/sync/engine/apply_updates_command.h" 10 #include "chrome/browser/sync/engine/syncer.h" 11 #include "chrome/browser/sync/engine/syncer_util.h" 12 #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" 13 #include "chrome/browser/sync/sessions/sync_session.h" 14 #include "chrome/browser/sync/syncable/directory_manager.h" 15 #include "chrome/browser/sync/syncable/nigori_util.h" 16 #include "chrome/browser/sync/syncable/syncable.h" 17 #include "chrome/browser/sync/syncable/syncable_id.h" 18 #include "chrome/test/sync/engine/syncer_command_test.h" 19 #include "chrome/test/sync/engine/test_id_factory.h" 20 #include "testing/gtest/include/gtest/gtest.h" 21 22 namespace browser_sync { 23 24 using sessions::SyncSession; 25 using std::string; 26 using syncable::Entry; 27 using syncable::GetEncryptedDataTypes; 28 using syncable::Id; 29 using syncable::MutableEntry; 30 using syncable::ReadTransaction; 31 using syncable::ScopedDirLookup; 32 using syncable::UNITTEST; 33 using syncable::WriteTransaction; 34 35 // A test fixture for tests exercising ApplyUpdatesCommand. 36 class ApplyUpdatesCommandTest : public SyncerCommandTest { 37 public: 38 protected: 39 ApplyUpdatesCommandTest() : next_revision_(1) {} 40 virtual ~ApplyUpdatesCommandTest() {} 41 42 virtual void SetUp() { 43 workers()->clear(); 44 mutable_routing_info()->clear(); 45 // GROUP_PASSIVE worker. 46 workers()->push_back(make_scoped_refptr(new ModelSafeWorker())); 47 (*mutable_routing_info())[syncable::BOOKMARKS] = GROUP_PASSIVE; 48 (*mutable_routing_info())[syncable::PASSWORDS] = GROUP_PASSIVE; 49 (*mutable_routing_info())[syncable::NIGORI] = GROUP_PASSIVE; 50 SyncerCommandTest::SetUp(); 51 } 52 53 // Create a new unapplied bookmark node with a parent. 54 void CreateUnappliedNewItemWithParent(const string& item_id, 55 const string& parent_id) { 56 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 57 ASSERT_TRUE(dir.good()); 58 WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); 59 MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM, 60 Id::CreateFromServerId(item_id)); 61 ASSERT_TRUE(entry.good()); 62 entry.Put(syncable::SERVER_VERSION, next_revision_++); 63 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); 64 65 entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id); 66 entry.Put(syncable::SERVER_PARENT_ID, Id::CreateFromServerId(parent_id)); 67 entry.Put(syncable::SERVER_IS_DIR, true); 68 sync_pb::EntitySpecifics default_bookmark_specifics; 69 default_bookmark_specifics.MutableExtension(sync_pb::bookmark); 70 entry.Put(syncable::SERVER_SPECIFICS, default_bookmark_specifics); 71 } 72 73 // Create a new unapplied update without a parent. 74 void CreateUnappliedNewItem(const string& item_id, 75 const sync_pb::EntitySpecifics& specifics, 76 bool is_unique) { 77 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 78 ASSERT_TRUE(dir.good()); 79 WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); 80 MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM, 81 Id::CreateFromServerId(item_id)); 82 ASSERT_TRUE(entry.good()); 83 entry.Put(syncable::SERVER_VERSION, next_revision_++); 84 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); 85 entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id); 86 entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId); 87 entry.Put(syncable::SERVER_IS_DIR, false); 88 entry.Put(syncable::SERVER_SPECIFICS, specifics); 89 if (is_unique) // For top-level nodes. 90 entry.Put(syncable::UNIQUE_SERVER_TAG, item_id); 91 } 92 93 // Create an unsynced item in the database. If item_id is a local ID, it 94 // will be treated as a create-new. Otherwise, if it's a server ID, we'll 95 // fake the server data so that it looks like it exists on the server. 96 // Returns the methandle of the created item in |metahandle_out| if not NULL. 97 void CreateUnsyncedItem(const Id& item_id, 98 const Id& parent_id, 99 const string& name, 100 bool is_folder, 101 syncable::ModelType model_type, 102 int64* metahandle_out) { 103 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 104 ASSERT_TRUE(dir.good()); 105 WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); 106 Id predecessor_id = dir->GetLastChildId(&trans, parent_id); 107 MutableEntry entry(&trans, syncable::CREATE, parent_id, name); 108 ASSERT_TRUE(entry.good()); 109 entry.Put(syncable::ID, item_id); 110 entry.Put(syncable::BASE_VERSION, 111 item_id.ServerKnows() ? next_revision_++ : 0); 112 entry.Put(syncable::IS_UNSYNCED, true); 113 entry.Put(syncable::IS_DIR, is_folder); 114 entry.Put(syncable::IS_DEL, false); 115 entry.Put(syncable::PARENT_ID, parent_id); 116 entry.PutPredecessor(predecessor_id); 117 sync_pb::EntitySpecifics default_specifics; 118 syncable::AddDefaultExtensionValue(model_type, &default_specifics); 119 entry.Put(syncable::SPECIFICS, default_specifics); 120 if (item_id.ServerKnows()) { 121 entry.Put(syncable::SERVER_SPECIFICS, default_specifics); 122 entry.Put(syncable::SERVER_IS_DIR, is_folder); 123 entry.Put(syncable::SERVER_PARENT_ID, parent_id); 124 entry.Put(syncable::SERVER_IS_DEL, false); 125 } 126 if (metahandle_out) 127 *metahandle_out = entry.Get(syncable::META_HANDLE); 128 } 129 130 ApplyUpdatesCommand apply_updates_command_; 131 TestIdFactory id_factory_; 132 private: 133 int64 next_revision_; 134 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); 135 }; 136 137 TEST_F(ApplyUpdatesCommandTest, Simple) { 138 string root_server_id = syncable::kNullId.GetServerId(); 139 CreateUnappliedNewItemWithParent("parent", root_server_id); 140 CreateUnappliedNewItemWithParent("child", "parent"); 141 142 apply_updates_command_.ExecuteImpl(session()); 143 144 sessions::StatusController* status = session()->status_controller(); 145 146 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 147 EXPECT_EQ(2, status->update_progress().AppliedUpdatesSize()) 148 << "All updates should have been attempted"; 149 EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize()) 150 << "Simple update shouldn't result in conflicts"; 151 EXPECT_EQ(2, status->update_progress().SuccessfullyAppliedUpdateCount()) 152 << "All items should have been successfully applied"; 153 } 154 155 TEST_F(ApplyUpdatesCommandTest, UpdateWithChildrenBeforeParents) { 156 // Set a bunch of updates which are difficult to apply in the order 157 // they're received due to dependencies on other unseen items. 158 string root_server_id = syncable::kNullId.GetServerId(); 159 CreateUnappliedNewItemWithParent("a_child_created_first", "parent"); 160 CreateUnappliedNewItemWithParent("x_child_created_first", "parent"); 161 CreateUnappliedNewItemWithParent("parent", root_server_id); 162 CreateUnappliedNewItemWithParent("a_child_created_second", "parent"); 163 CreateUnappliedNewItemWithParent("x_child_created_second", "parent"); 164 165 apply_updates_command_.ExecuteImpl(session()); 166 167 sessions::StatusController* status = session()->status_controller(); 168 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 169 EXPECT_EQ(5, status->update_progress().AppliedUpdatesSize()) 170 << "All updates should have been attempted"; 171 EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize()) 172 << "Simple update shouldn't result in conflicts, even if out-of-order"; 173 EXPECT_EQ(5, status->update_progress().SuccessfullyAppliedUpdateCount()) 174 << "All updates should have been successfully applied"; 175 } 176 177 TEST_F(ApplyUpdatesCommandTest, NestedItemsWithUnknownParent) { 178 // We shouldn't be able to do anything with either of these items. 179 CreateUnappliedNewItemWithParent("some_item", "unknown_parent"); 180 CreateUnappliedNewItemWithParent("some_other_item", "some_item"); 181 182 apply_updates_command_.ExecuteImpl(session()); 183 184 sessions::StatusController* status = session()->status_controller(); 185 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 186 EXPECT_EQ(2, status->update_progress().AppliedUpdatesSize()) 187 << "All updates should have been attempted"; 188 EXPECT_EQ(2, status->conflict_progress().ConflictingItemsSize()) 189 << "All updates with an unknown ancestors should be in conflict"; 190 EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount()) 191 << "No item with an unknown ancestor should be applied"; 192 } 193 194 TEST_F(ApplyUpdatesCommandTest, ItemsBothKnownAndUnknown) { 195 // See what happens when there's a mixture of good and bad updates. 196 string root_server_id = syncable::kNullId.GetServerId(); 197 CreateUnappliedNewItemWithParent("first_unknown_item", "unknown_parent"); 198 CreateUnappliedNewItemWithParent("first_known_item", root_server_id); 199 CreateUnappliedNewItemWithParent("second_unknown_item", "unknown_parent"); 200 CreateUnappliedNewItemWithParent("second_known_item", "first_known_item"); 201 CreateUnappliedNewItemWithParent("third_known_item", "fourth_known_item"); 202 CreateUnappliedNewItemWithParent("fourth_known_item", root_server_id); 203 204 apply_updates_command_.ExecuteImpl(session()); 205 206 sessions::StatusController* status = session()->status_controller(); 207 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 208 EXPECT_EQ(6, status->update_progress().AppliedUpdatesSize()) 209 << "All updates should have been attempted"; 210 EXPECT_EQ(2, status->conflict_progress().ConflictingItemsSize()) 211 << "The updates with unknown ancestors should be in conflict"; 212 EXPECT_EQ(4, status->update_progress().SuccessfullyAppliedUpdateCount()) 213 << "The updates with known ancestors should be successfully applied"; 214 } 215 216 TEST_F(ApplyUpdatesCommandTest, DecryptablePassword) { 217 // Decryptable password updates should be applied. 218 Cryptographer* cryptographer; 219 { 220 // Storing the cryptographer separately is bad, but for this test we 221 // know it's safe. 222 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 223 ASSERT_TRUE(dir.good()); 224 ReadTransaction trans(dir, __FILE__, __LINE__); 225 cryptographer = 226 session()->context()->directory_manager()->GetCryptographer(&trans); 227 } 228 229 browser_sync::KeyParams params = {"localhost", "dummy", "foobar"}; 230 cryptographer->AddKey(params); 231 232 sync_pb::EntitySpecifics specifics; 233 sync_pb::PasswordSpecificsData data; 234 data.set_origin("http://example.com"); 235 236 cryptographer->Encrypt(data, 237 specifics.MutableExtension(sync_pb::password)->mutable_encrypted()); 238 CreateUnappliedNewItem("item", specifics, false); 239 240 apply_updates_command_.ExecuteImpl(session()); 241 242 sessions::StatusController* status = session()->status_controller(); 243 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 244 EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize()) 245 << "All updates should have been attempted"; 246 EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize()) 247 << "No update should be in conflict because they're all decryptable"; 248 EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount()) 249 << "The updates that can be decrypted should be applied"; 250 } 251 252 TEST_F(ApplyUpdatesCommandTest, UndecryptablePassword) { 253 // Undecryptable password updates should not be applied. 254 sync_pb::EntitySpecifics specifics; 255 specifics.MutableExtension(sync_pb::password); 256 CreateUnappliedNewItem("item", specifics, false); 257 258 apply_updates_command_.ExecuteImpl(session()); 259 260 sessions::StatusController* status = session()->status_controller(); 261 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 262 EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize()) 263 << "All updates should have been attempted"; 264 EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize()) 265 << "The updates that can't be decrypted should be in conflict"; 266 EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount()) 267 << "No update that can't be decrypted should be applied"; 268 } 269 270 TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) { 271 // Only decryptable password updates should be applied. 272 { 273 sync_pb::EntitySpecifics specifics; 274 sync_pb::PasswordSpecificsData data; 275 data.set_origin("http://example.com/1"); 276 { 277 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 278 ASSERT_TRUE(dir.good()); 279 ReadTransaction trans(dir, __FILE__, __LINE__); 280 Cryptographer* cryptographer = 281 session()->context()->directory_manager()->GetCryptographer(&trans); 282 283 KeyParams params = {"localhost", "dummy", "foobar"}; 284 cryptographer->AddKey(params); 285 286 cryptographer->Encrypt(data, 287 specifics.MutableExtension(sync_pb::password)->mutable_encrypted()); 288 } 289 CreateUnappliedNewItem("item1", specifics, false); 290 } 291 { 292 // Create a new cryptographer, independent of the one in the session. 293 Cryptographer cryptographer; 294 KeyParams params = {"localhost", "dummy", "bazqux"}; 295 cryptographer.AddKey(params); 296 297 sync_pb::EntitySpecifics specifics; 298 sync_pb::PasswordSpecificsData data; 299 data.set_origin("http://example.com/2"); 300 301 cryptographer.Encrypt(data, 302 specifics.MutableExtension(sync_pb::password)->mutable_encrypted()); 303 CreateUnappliedNewItem("item2", specifics, false); 304 } 305 306 apply_updates_command_.ExecuteImpl(session()); 307 308 sessions::StatusController* status = session()->status_controller(); 309 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 310 EXPECT_EQ(2, status->update_progress().AppliedUpdatesSize()) 311 << "All updates should have been attempted"; 312 EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize()) 313 << "The decryptable password update should be applied"; 314 EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount()) 315 << "The undecryptable password update shouldn't be applied"; 316 } 317 318 TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { 319 // Storing the cryptographer separately is bad, but for this test we 320 // know it's safe. 321 Cryptographer* cryptographer; 322 syncable::ModelTypeSet encrypted_types; 323 { 324 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 325 ASSERT_TRUE(dir.good()); 326 ReadTransaction trans(dir, __FILE__, __LINE__); 327 EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); 328 cryptographer = 329 session()->context()->directory_manager()->GetCryptographer(&trans); 330 } 331 332 // Nigori node updates should update the Cryptographer. 333 Cryptographer other_cryptographer; 334 KeyParams params = {"localhost", "dummy", "foobar"}; 335 other_cryptographer.AddKey(params); 336 337 sync_pb::EntitySpecifics specifics; 338 sync_pb::NigoriSpecifics* nigori = 339 specifics.MutableExtension(sync_pb::nigori); 340 other_cryptographer.GetKeys(nigori->mutable_encrypted()); 341 nigori->set_encrypt_bookmarks(true); 342 encrypted_types.insert(syncable::BOOKMARKS); 343 CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), 344 specifics, true); 345 EXPECT_FALSE(cryptographer->has_pending_keys()); 346 347 apply_updates_command_.ExecuteImpl(session()); 348 349 sessions::StatusController* status = session()->status_controller(); 350 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 351 EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize()) 352 << "All updates should have been attempted"; 353 EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize()) 354 << "The nigori update shouldn't be in conflict"; 355 EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount()) 356 << "The nigori update should be applied"; 357 358 EXPECT_FALSE(cryptographer->is_ready()); 359 EXPECT_TRUE(cryptographer->has_pending_keys()); 360 } 361 362 TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { 363 // Storing the cryptographer separately is bad, but for this test we 364 // know it's safe. 365 Cryptographer* cryptographer; 366 syncable::ModelTypeSet encrypted_types; 367 { 368 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 369 ASSERT_TRUE(dir.good()); 370 ReadTransaction trans(dir, __FILE__, __LINE__); 371 EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); 372 cryptographer = 373 session()->context()->directory_manager()->GetCryptographer(&trans); 374 375 // With empty encrypted_types, this should be true. 376 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); 377 378 Syncer::UnsyncedMetaHandles handles; 379 SyncerUtil::GetUnsyncedEntries(&trans, &handles); 380 EXPECT_TRUE(handles.empty()); 381 } 382 383 // Create unsynced bookmarks without encryption. 384 // First item is a folder 385 Id folder_id = id_factory_.NewLocalId(); 386 CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", 387 true, syncable::BOOKMARKS, NULL); 388 // Next five items are children of the folder 389 size_t i; 390 size_t batch_s = 5; 391 for (i = 0; i < batch_s; ++i) { 392 CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, 393 StringPrintf("Item %"PRIuS"", i), false, 394 syncable::BOOKMARKS, NULL); 395 } 396 // Next five items are children of the root. 397 for (; i < 2*batch_s; ++i) { 398 CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(), 399 StringPrintf("Item %"PRIuS"", i), false, 400 syncable::BOOKMARKS, NULL); 401 } 402 403 KeyParams params = {"localhost", "dummy", "foobar"}; 404 cryptographer->AddKey(params); 405 sync_pb::EntitySpecifics specifics; 406 sync_pb::NigoriSpecifics* nigori = 407 specifics.MutableExtension(sync_pb::nigori); 408 cryptographer->GetKeys(nigori->mutable_encrypted()); 409 nigori->set_encrypt_bookmarks(true); 410 encrypted_types.insert(syncable::BOOKMARKS); 411 CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), 412 specifics, true); 413 EXPECT_FALSE(cryptographer->has_pending_keys()); 414 EXPECT_TRUE(cryptographer->is_ready()); 415 416 { 417 // Ensure we have unsynced nodes that aren't properly encrypted. 418 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 419 ASSERT_TRUE(dir.good()); 420 ReadTransaction trans(dir, __FILE__, __LINE__); 421 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); 422 423 Syncer::UnsyncedMetaHandles handles; 424 SyncerUtil::GetUnsyncedEntries(&trans, &handles); 425 EXPECT_EQ(2*batch_s+1, handles.size()); 426 } 427 428 apply_updates_command_.ExecuteImpl(session()); 429 430 sessions::StatusController* status = session()->status_controller(); 431 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 432 EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize()) 433 << "All updates should have been attempted"; 434 EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize()) 435 << "The nigori update shouldn't be in conflict"; 436 EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount()) 437 << "The nigori update should be applied"; 438 EXPECT_FALSE(cryptographer->has_pending_keys()); 439 EXPECT_TRUE(cryptographer->is_ready()); 440 { 441 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 442 ASSERT_TRUE(dir.good()); 443 ReadTransaction trans(dir, __FILE__, __LINE__); 444 445 // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes 446 // should be encrypted now. 447 EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); 448 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); 449 450 Syncer::UnsyncedMetaHandles handles; 451 SyncerUtil::GetUnsyncedEntries(&trans, &handles); 452 EXPECT_EQ(2*batch_s+1, handles.size()); 453 } 454 } 455 456 TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) { 457 // Storing the cryptographer separately is bad, but for this test we 458 // know it's safe. 459 Cryptographer* cryptographer; 460 syncable::ModelTypeSet encrypted_types; 461 { 462 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 463 ASSERT_TRUE(dir.good()); 464 ReadTransaction trans(dir, __FILE__, __LINE__); 465 EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); 466 cryptographer = 467 session()->context()->directory_manager()->GetCryptographer(&trans); 468 469 // With empty encrypted_types, this should be true. 470 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); 471 472 Syncer::UnsyncedMetaHandles handles; 473 SyncerUtil::GetUnsyncedEntries(&trans, &handles); 474 EXPECT_TRUE(handles.empty()); 475 } 476 477 // Create unsynced bookmarks without encryption. 478 // First item is a folder 479 Id folder_id = id_factory_.NewLocalId(); 480 CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", true, 481 syncable::BOOKMARKS, NULL); 482 // Next five items are children of the folder 483 size_t i; 484 size_t batch_s = 5; 485 for (i = 0; i < batch_s; ++i) { 486 CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, 487 StringPrintf("Item %"PRIuS"", i), false, 488 syncable::BOOKMARKS, NULL); 489 } 490 // Next five items are children of the root. 491 for (; i < 2*batch_s; ++i) { 492 CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(), 493 StringPrintf("Item %"PRIuS"", i), false, 494 syncable::BOOKMARKS, NULL); 495 } 496 497 // We encrypt with new keys, triggering the local cryptographer to be unready 498 // and unable to decrypt data (once updated). 499 Cryptographer other_cryptographer; 500 KeyParams params = {"localhost", "dummy", "foobar"}; 501 other_cryptographer.AddKey(params); 502 sync_pb::EntitySpecifics specifics; 503 sync_pb::NigoriSpecifics* nigori = 504 specifics.MutableExtension(sync_pb::nigori); 505 other_cryptographer.GetKeys(nigori->mutable_encrypted()); 506 nigori->set_encrypt_bookmarks(true); 507 encrypted_types.insert(syncable::BOOKMARKS); 508 CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), 509 specifics, true); 510 EXPECT_FALSE(cryptographer->has_pending_keys()); 511 512 { 513 // Ensure we have unsynced nodes that aren't properly encrypted. 514 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 515 ASSERT_TRUE(dir.good()); 516 ReadTransaction trans(dir, __FILE__, __LINE__); 517 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); 518 Syncer::UnsyncedMetaHandles handles; 519 SyncerUtil::GetUnsyncedEntries(&trans, &handles); 520 EXPECT_EQ(2*batch_s+1, handles.size()); 521 } 522 523 apply_updates_command_.ExecuteImpl(session()); 524 525 sessions::StatusController* status = session()->status_controller(); 526 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); 527 EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize()) 528 << "All updates should have been attempted"; 529 EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize()) 530 << "The unsynced chnages trigger a conflict with the nigori update."; 531 EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount()) 532 << "The nigori update should not be applied"; 533 EXPECT_FALSE(cryptographer->is_ready()); 534 EXPECT_TRUE(cryptographer->has_pending_keys()); 535 { 536 // Ensure the unsynced nodes are still not encrypted. 537 ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); 538 ASSERT_TRUE(dir.good()); 539 ReadTransaction trans(dir, __FILE__, __LINE__); 540 541 // Since we're in conflict, the specifics don't reflect the unapplied 542 // changes. 543 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); 544 encrypted_types.clear(); 545 EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); 546 547 Syncer::UnsyncedMetaHandles handles; 548 SyncerUtil::GetUnsyncedEntries(&trans, &handles); 549 EXPECT_EQ(2*batch_s+1, handles.size()); 550 } 551 } 552 553 } // namespace browser_sync 554