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 // Unit tests for the SyncApi. Note that a lot of the underlying 6 // functionality is provided by the Syncable layer, which has its own 7 // unit tests. We'll test SyncApi specific things in this harness. 8 9 #include <map> 10 11 #include "base/basictypes.h" 12 #include "base/format_macros.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/memory/scoped_temp_dir.h" 15 #include "base/message_loop.h" 16 #include "base/string_number_conversions.h" 17 #include "base/string_util.h" 18 #include "base/utf_string_conversions.h" 19 #include "base/values.h" 20 #include "chrome/browser/sync/engine/http_post_provider_factory.h" 21 #include "chrome/browser/sync/engine/http_post_provider_interface.h" 22 #include "chrome/browser/sync/engine/model_safe_worker.h" 23 #include "chrome/browser/sync/engine/syncapi.h" 24 #include "chrome/browser/sync/js_arg_list.h" 25 #include "chrome/browser/sync/js_backend.h" 26 #include "chrome/browser/sync/js_event_handler.h" 27 #include "chrome/browser/sync/js_event_router.h" 28 #include "chrome/browser/sync/js_test_util.h" 29 #include "chrome/browser/sync/notifier/sync_notifier.h" 30 #include "chrome/browser/sync/notifier/sync_notifier_observer.h" 31 #include "chrome/browser/sync/protocol/password_specifics.pb.h" 32 #include "chrome/browser/sync/protocol/proto_value_conversions.h" 33 #include "chrome/browser/sync/sessions/sync_session.h" 34 #include "chrome/browser/sync/syncable/directory_manager.h" 35 #include "chrome/browser/sync/syncable/nigori_util.h" 36 #include "chrome/browser/sync/syncable/syncable.h" 37 #include "chrome/browser/sync/syncable/syncable_id.h" 38 #include "chrome/browser/sync/util/cryptographer.h" 39 #include "chrome/test/sync/engine/test_user_share.h" 40 #include "chrome/test/values_test_util.h" 41 #include "content/browser/browser_thread.h" 42 #include "testing/gmock/include/gmock/gmock.h" 43 #include "testing/gtest/include/gtest/gtest.h" 44 45 using browser_sync::Cryptographer; 46 using browser_sync::HasArgsAsList; 47 using browser_sync::KeyParams; 48 using browser_sync::JsArgList; 49 using browser_sync::MockJsEventHandler; 50 using browser_sync::MockJsEventRouter; 51 using browser_sync::ModelSafeRoutingInfo; 52 using browser_sync::ModelSafeWorker; 53 using browser_sync::ModelSafeWorkerRegistrar; 54 using browser_sync::sessions::SyncSessionSnapshot; 55 using syncable::ModelType; 56 using syncable::ModelTypeSet; 57 using test::ExpectDictionaryValue; 58 using test::ExpectStringValue; 59 using testing::_; 60 using testing::AtLeast; 61 using testing::Invoke; 62 using testing::SaveArg; 63 using testing::StrictMock; 64 65 namespace sync_api { 66 67 namespace { 68 69 void ExpectInt64Value(int64 expected_value, 70 const DictionaryValue& value, const std::string& key) { 71 std::string int64_str; 72 EXPECT_TRUE(value.GetString(key, &int64_str)); 73 int64 val = 0; 74 EXPECT_TRUE(base::StringToInt64(int64_str, &val)); 75 EXPECT_EQ(expected_value, val); 76 } 77 78 // Makes a non-folder child of the root node. Returns the id of the 79 // newly-created node. 80 int64 MakeNode(UserShare* share, 81 ModelType model_type, 82 const std::string& client_tag) { 83 WriteTransaction trans(share); 84 ReadNode root_node(&trans); 85 root_node.InitByRootLookup(); 86 WriteNode node(&trans); 87 EXPECT_TRUE(node.InitUniqueByCreation(model_type, root_node, client_tag)); 88 node.SetIsFolder(false); 89 return node.GetId(); 90 } 91 92 // Make a folder as a child of the root node. Returns the id of the 93 // newly-created node. 94 int64 MakeFolder(UserShare* share, 95 syncable::ModelType model_type, 96 const std::string& client_tag) { 97 WriteTransaction trans(share); 98 ReadNode root_node(&trans); 99 root_node.InitByRootLookup(); 100 WriteNode node(&trans); 101 EXPECT_TRUE(node.InitUniqueByCreation(model_type, root_node, client_tag)); 102 node.SetIsFolder(true); 103 return node.GetId(); 104 } 105 106 // Makes a non-folder child of a non-root node. Returns the id of the 107 // newly-created node. 108 int64 MakeNodeWithParent(UserShare* share, 109 ModelType model_type, 110 const std::string& client_tag, 111 int64 parent_id) { 112 WriteTransaction trans(share); 113 ReadNode parent_node(&trans); 114 parent_node.InitByIdLookup(parent_id); 115 WriteNode node(&trans); 116 EXPECT_TRUE(node.InitUniqueByCreation(model_type, parent_node, client_tag)); 117 node.SetIsFolder(false); 118 return node.GetId(); 119 } 120 121 // Makes a folder child of a non-root node. Returns the id of the 122 // newly-created node. 123 int64 MakeFolderWithParent(UserShare* share, 124 ModelType model_type, 125 int64 parent_id, 126 BaseNode* predecessor) { 127 WriteTransaction trans(share); 128 ReadNode parent_node(&trans); 129 parent_node.InitByIdLookup(parent_id); 130 WriteNode node(&trans); 131 EXPECT_TRUE(node.InitByCreation(model_type, parent_node, predecessor)); 132 node.SetIsFolder(true); 133 return node.GetId(); 134 } 135 136 // Creates the "synced" root node for a particular datatype. We use the syncable 137 // methods here so that the syncer treats these nodes as if they were already 138 // received from the server. 139 int64 MakeServerNodeForType(UserShare* share, 140 ModelType model_type) { 141 sync_pb::EntitySpecifics specifics; 142 syncable::AddDefaultExtensionValue(model_type, &specifics); 143 syncable::ScopedDirLookup dir(share->dir_manager.get(), share->name); 144 EXPECT_TRUE(dir.good()); 145 syncable::WriteTransaction trans(dir, syncable::UNITTEST, __FILE__, __LINE__); 146 // Attempt to lookup by nigori tag. 147 std::string type_tag = syncable::ModelTypeToRootTag(model_type); 148 syncable::Id node_id = syncable::Id::CreateFromServerId(type_tag); 149 syncable::MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM, 150 node_id); 151 EXPECT_TRUE(entry.good()); 152 entry.Put(syncable::BASE_VERSION, 1); 153 entry.Put(syncable::SERVER_VERSION, 1); 154 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); 155 entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId); 156 entry.Put(syncable::SERVER_IS_DIR, true); 157 entry.Put(syncable::IS_DIR, true); 158 entry.Put(syncable::SERVER_SPECIFICS, specifics); 159 entry.Put(syncable::UNIQUE_SERVER_TAG, type_tag); 160 entry.Put(syncable::NON_UNIQUE_NAME, type_tag); 161 entry.Put(syncable::IS_DEL, false); 162 entry.Put(syncable::SPECIFICS, specifics); 163 return entry.Get(syncable::META_HANDLE); 164 } 165 166 } // namespace 167 168 class SyncApiTest : public testing::Test { 169 public: 170 virtual void SetUp() { 171 test_user_share_.SetUp(); 172 } 173 174 virtual void TearDown() { 175 test_user_share_.TearDown(); 176 } 177 178 protected: 179 browser_sync::TestUserShare test_user_share_; 180 }; 181 182 TEST_F(SyncApiTest, SanityCheckTest) { 183 { 184 ReadTransaction trans(test_user_share_.user_share()); 185 EXPECT_TRUE(trans.GetWrappedTrans() != NULL); 186 } 187 { 188 WriteTransaction trans(test_user_share_.user_share()); 189 EXPECT_TRUE(trans.GetWrappedTrans() != NULL); 190 } 191 { 192 // No entries but root should exist 193 ReadTransaction trans(test_user_share_.user_share()); 194 ReadNode node(&trans); 195 // Metahandle 1 can be root, sanity check 2 196 EXPECT_FALSE(node.InitByIdLookup(2)); 197 } 198 } 199 200 TEST_F(SyncApiTest, BasicTagWrite) { 201 { 202 ReadTransaction trans(test_user_share_.user_share()); 203 ReadNode root_node(&trans); 204 root_node.InitByRootLookup(); 205 EXPECT_EQ(root_node.GetFirstChildId(), 0); 206 } 207 208 ignore_result(MakeNode(test_user_share_.user_share(), 209 syncable::BOOKMARKS, "testtag")); 210 211 { 212 ReadTransaction trans(test_user_share_.user_share()); 213 ReadNode node(&trans); 214 EXPECT_TRUE(node.InitByClientTagLookup(syncable::BOOKMARKS, 215 "testtag")); 216 217 ReadNode root_node(&trans); 218 root_node.InitByRootLookup(); 219 EXPECT_NE(node.GetId(), 0); 220 EXPECT_EQ(node.GetId(), root_node.GetFirstChildId()); 221 } 222 } 223 224 TEST_F(SyncApiTest, GenerateSyncableHash) { 225 EXPECT_EQ("OyaXV5mEzrPS4wbogmtKvRfekAI=", 226 BaseNode::GenerateSyncableHash(syncable::BOOKMARKS, "tag1")); 227 EXPECT_EQ("iNFQtRFQb+IZcn1kKUJEZDDkLs4=", 228 BaseNode::GenerateSyncableHash(syncable::PREFERENCES, "tag1")); 229 EXPECT_EQ("gO1cPZQXaM73sHOvSA+tKCKFs58=", 230 BaseNode::GenerateSyncableHash(syncable::AUTOFILL, "tag1")); 231 232 EXPECT_EQ("A0eYIHXM1/jVwKDDp12Up20IkKY=", 233 BaseNode::GenerateSyncableHash(syncable::BOOKMARKS, "tag2")); 234 EXPECT_EQ("XYxkF7bhS4eItStFgiOIAU23swI=", 235 BaseNode::GenerateSyncableHash(syncable::PREFERENCES, "tag2")); 236 EXPECT_EQ("GFiWzo5NGhjLlN+OyCfhy28DJTQ=", 237 BaseNode::GenerateSyncableHash(syncable::AUTOFILL, "tag2")); 238 } 239 240 TEST_F(SyncApiTest, ModelTypesSiloed) { 241 { 242 WriteTransaction trans(test_user_share_.user_share()); 243 ReadNode root_node(&trans); 244 root_node.InitByRootLookup(); 245 EXPECT_EQ(root_node.GetFirstChildId(), 0); 246 } 247 248 ignore_result(MakeNode(test_user_share_.user_share(), 249 syncable::BOOKMARKS, "collideme")); 250 ignore_result(MakeNode(test_user_share_.user_share(), 251 syncable::PREFERENCES, "collideme")); 252 ignore_result(MakeNode(test_user_share_.user_share(), 253 syncable::AUTOFILL, "collideme")); 254 255 { 256 ReadTransaction trans(test_user_share_.user_share()); 257 258 ReadNode bookmarknode(&trans); 259 EXPECT_TRUE(bookmarknode.InitByClientTagLookup(syncable::BOOKMARKS, 260 "collideme")); 261 262 ReadNode prefnode(&trans); 263 EXPECT_TRUE(prefnode.InitByClientTagLookup(syncable::PREFERENCES, 264 "collideme")); 265 266 ReadNode autofillnode(&trans); 267 EXPECT_TRUE(autofillnode.InitByClientTagLookup(syncable::AUTOFILL, 268 "collideme")); 269 270 EXPECT_NE(bookmarknode.GetId(), prefnode.GetId()); 271 EXPECT_NE(autofillnode.GetId(), prefnode.GetId()); 272 EXPECT_NE(bookmarknode.GetId(), autofillnode.GetId()); 273 } 274 } 275 276 TEST_F(SyncApiTest, ReadMissingTagsFails) { 277 { 278 ReadTransaction trans(test_user_share_.user_share()); 279 ReadNode node(&trans); 280 EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS, 281 "testtag")); 282 } 283 { 284 WriteTransaction trans(test_user_share_.user_share()); 285 WriteNode node(&trans); 286 EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS, 287 "testtag")); 288 } 289 } 290 291 // TODO(chron): Hook this all up to the server and write full integration tests 292 // for update->undelete behavior. 293 TEST_F(SyncApiTest, TestDeleteBehavior) { 294 int64 node_id; 295 int64 folder_id; 296 std::wstring test_title(L"test1"); 297 298 { 299 WriteTransaction trans(test_user_share_.user_share()); 300 ReadNode root_node(&trans); 301 root_node.InitByRootLookup(); 302 303 // we'll use this spare folder later 304 WriteNode folder_node(&trans); 305 EXPECT_TRUE(folder_node.InitByCreation(syncable::BOOKMARKS, 306 root_node, NULL)); 307 folder_id = folder_node.GetId(); 308 309 WriteNode wnode(&trans); 310 EXPECT_TRUE(wnode.InitUniqueByCreation(syncable::BOOKMARKS, 311 root_node, "testtag")); 312 wnode.SetIsFolder(false); 313 wnode.SetTitle(test_title); 314 315 node_id = wnode.GetId(); 316 } 317 318 // Ensure we can delete something with a tag. 319 { 320 WriteTransaction trans(test_user_share_.user_share()); 321 WriteNode wnode(&trans); 322 EXPECT_TRUE(wnode.InitByClientTagLookup(syncable::BOOKMARKS, 323 "testtag")); 324 EXPECT_FALSE(wnode.GetIsFolder()); 325 EXPECT_EQ(wnode.GetTitle(), test_title); 326 327 wnode.Remove(); 328 } 329 330 // Lookup of a node which was deleted should return failure, 331 // but have found some data about the node. 332 { 333 ReadTransaction trans(test_user_share_.user_share()); 334 ReadNode node(&trans); 335 EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS, 336 "testtag")); 337 // Note that for proper function of this API this doesn't need to be 338 // filled, we're checking just to make sure the DB worked in this test. 339 EXPECT_EQ(node.GetTitle(), test_title); 340 } 341 342 { 343 WriteTransaction trans(test_user_share_.user_share()); 344 ReadNode folder_node(&trans); 345 EXPECT_TRUE(folder_node.InitByIdLookup(folder_id)); 346 347 WriteNode wnode(&trans); 348 // This will undelete the tag. 349 EXPECT_TRUE(wnode.InitUniqueByCreation(syncable::BOOKMARKS, 350 folder_node, "testtag")); 351 EXPECT_EQ(wnode.GetIsFolder(), false); 352 EXPECT_EQ(wnode.GetParentId(), folder_node.GetId()); 353 EXPECT_EQ(wnode.GetId(), node_id); 354 EXPECT_NE(wnode.GetTitle(), test_title); // Title should be cleared 355 wnode.SetTitle(test_title); 356 } 357 358 // Now look up should work. 359 { 360 ReadTransaction trans(test_user_share_.user_share()); 361 ReadNode node(&trans); 362 EXPECT_TRUE(node.InitByClientTagLookup(syncable::BOOKMARKS, 363 "testtag")); 364 EXPECT_EQ(node.GetTitle(), test_title); 365 EXPECT_EQ(node.GetModelType(), syncable::BOOKMARKS); 366 } 367 } 368 369 TEST_F(SyncApiTest, WriteAndReadPassword) { 370 KeyParams params = {"localhost", "username", "passphrase"}; 371 { 372 ReadTransaction trans(test_user_share_.user_share()); 373 trans.GetCryptographer()->AddKey(params); 374 } 375 { 376 WriteTransaction trans(test_user_share_.user_share()); 377 ReadNode root_node(&trans); 378 root_node.InitByRootLookup(); 379 380 WriteNode password_node(&trans); 381 EXPECT_TRUE(password_node.InitUniqueByCreation(syncable::PASSWORDS, 382 root_node, "foo")); 383 sync_pb::PasswordSpecificsData data; 384 data.set_password_value("secret"); 385 password_node.SetPasswordSpecifics(data); 386 } 387 { 388 ReadTransaction trans(test_user_share_.user_share()); 389 ReadNode root_node(&trans); 390 root_node.InitByRootLookup(); 391 392 ReadNode password_node(&trans); 393 EXPECT_TRUE(password_node.InitByClientTagLookup(syncable::PASSWORDS, 394 "foo")); 395 const sync_pb::PasswordSpecificsData& data = 396 password_node.GetPasswordSpecifics(); 397 EXPECT_EQ("secret", data.password_value()); 398 } 399 } 400 401 namespace { 402 403 void CheckNodeValue(const BaseNode& node, const DictionaryValue& value) { 404 ExpectInt64Value(node.GetId(), value, "id"); 405 ExpectInt64Value(node.GetModificationTime(), value, "modificationTime"); 406 ExpectInt64Value(node.GetParentId(), value, "parentId"); 407 { 408 bool is_folder = false; 409 EXPECT_TRUE(value.GetBoolean("isFolder", &is_folder)); 410 EXPECT_EQ(node.GetIsFolder(), is_folder); 411 } 412 ExpectStringValue(WideToUTF8(node.GetTitle()), value, "title"); 413 { 414 ModelType expected_model_type = node.GetModelType(); 415 std::string type_str; 416 EXPECT_TRUE(value.GetString("type", &type_str)); 417 if (expected_model_type >= syncable::FIRST_REAL_MODEL_TYPE) { 418 ModelType model_type = 419 syncable::ModelTypeFromString(type_str); 420 EXPECT_EQ(expected_model_type, model_type); 421 } else if (expected_model_type == syncable::TOP_LEVEL_FOLDER) { 422 EXPECT_EQ("Top-level folder", type_str); 423 } else if (expected_model_type == syncable::UNSPECIFIED) { 424 EXPECT_EQ("Unspecified", type_str); 425 } else { 426 ADD_FAILURE(); 427 } 428 } 429 ExpectInt64Value(node.GetExternalId(), value, "externalId"); 430 ExpectInt64Value(node.GetPredecessorId(), value, "predecessorId"); 431 ExpectInt64Value(node.GetSuccessorId(), value, "successorId"); 432 ExpectInt64Value(node.GetFirstChildId(), value, "firstChildId"); 433 { 434 scoped_ptr<DictionaryValue> expected_entry(node.GetEntry()->ToValue()); 435 Value* entry = NULL; 436 EXPECT_TRUE(value.Get("entry", &entry)); 437 EXPECT_TRUE(Value::Equals(entry, expected_entry.get())); 438 } 439 EXPECT_EQ(11u, value.size()); 440 } 441 442 } // namespace 443 444 TEST_F(SyncApiTest, BaseNodeToValue) { 445 ReadTransaction trans(test_user_share_.user_share()); 446 ReadNode node(&trans); 447 node.InitByRootLookup(); 448 scoped_ptr<DictionaryValue> value(node.ToValue()); 449 if (value.get()) { 450 CheckNodeValue(node, *value); 451 } else { 452 ADD_FAILURE(); 453 } 454 } 455 456 namespace { 457 458 void ExpectChangeRecordActionValue(SyncManager::ChangeRecord::Action 459 expected_value, 460 const DictionaryValue& value, 461 const std::string& key) { 462 std::string str_value; 463 EXPECT_TRUE(value.GetString(key, &str_value)); 464 switch (expected_value) { 465 case SyncManager::ChangeRecord::ACTION_ADD: 466 EXPECT_EQ("Add", str_value); 467 break; 468 case SyncManager::ChangeRecord::ACTION_UPDATE: 469 EXPECT_EQ("Update", str_value); 470 break; 471 case SyncManager::ChangeRecord::ACTION_DELETE: 472 EXPECT_EQ("Delete", str_value); 473 break; 474 default: 475 NOTREACHED(); 476 break; 477 } 478 } 479 480 void CheckNonDeleteChangeRecordValue(const SyncManager::ChangeRecord& record, 481 const DictionaryValue& value, 482 BaseTransaction* trans) { 483 EXPECT_NE(SyncManager::ChangeRecord::ACTION_DELETE, record.action); 484 ExpectChangeRecordActionValue(record.action, value, "action"); 485 { 486 ReadNode node(trans); 487 EXPECT_TRUE(node.InitByIdLookup(record.id)); 488 scoped_ptr<DictionaryValue> expected_node_value(node.ToValue()); 489 ExpectDictionaryValue(*expected_node_value, value, "node"); 490 } 491 } 492 493 void CheckDeleteChangeRecordValue(const SyncManager::ChangeRecord& record, 494 const DictionaryValue& value) { 495 EXPECT_EQ(SyncManager::ChangeRecord::ACTION_DELETE, record.action); 496 ExpectChangeRecordActionValue(record.action, value, "action"); 497 DictionaryValue* node_value = NULL; 498 EXPECT_TRUE(value.GetDictionary("node", &node_value)); 499 if (node_value) { 500 ExpectInt64Value(record.id, *node_value, "id"); 501 scoped_ptr<DictionaryValue> expected_specifics_value( 502 browser_sync::EntitySpecificsToValue(record.specifics)); 503 ExpectDictionaryValue(*expected_specifics_value, 504 *node_value, "specifics"); 505 scoped_ptr<DictionaryValue> expected_extra_value; 506 if (record.extra.get()) { 507 expected_extra_value.reset(record.extra->ToValue()); 508 } 509 Value* extra_value = NULL; 510 EXPECT_EQ(record.extra.get() != NULL, 511 node_value->Get("extra", &extra_value)); 512 EXPECT_TRUE(Value::Equals(extra_value, expected_extra_value.get())); 513 } 514 } 515 516 class MockExtraChangeRecordData 517 : public SyncManager::ExtraPasswordChangeRecordData { 518 public: 519 MOCK_CONST_METHOD0(ToValue, DictionaryValue*()); 520 }; 521 522 } // namespace 523 524 TEST_F(SyncApiTest, ChangeRecordToValue) { 525 int64 child_id = MakeNode(test_user_share_.user_share(), 526 syncable::BOOKMARKS, "testtag"); 527 sync_pb::EntitySpecifics child_specifics; 528 { 529 ReadTransaction trans(test_user_share_.user_share()); 530 ReadNode node(&trans); 531 EXPECT_TRUE(node.InitByIdLookup(child_id)); 532 child_specifics = node.GetEntry()->Get(syncable::SPECIFICS); 533 } 534 535 // Add 536 { 537 ReadTransaction trans(test_user_share_.user_share()); 538 SyncManager::ChangeRecord record; 539 record.action = SyncManager::ChangeRecord::ACTION_ADD; 540 record.id = 1; 541 record.specifics = child_specifics; 542 record.extra.reset(new StrictMock<MockExtraChangeRecordData>()); 543 scoped_ptr<DictionaryValue> value(record.ToValue(&trans)); 544 CheckNonDeleteChangeRecordValue(record, *value, &trans); 545 } 546 547 // Update 548 { 549 ReadTransaction trans(test_user_share_.user_share()); 550 SyncManager::ChangeRecord record; 551 record.action = SyncManager::ChangeRecord::ACTION_UPDATE; 552 record.id = child_id; 553 record.specifics = child_specifics; 554 record.extra.reset(new StrictMock<MockExtraChangeRecordData>()); 555 scoped_ptr<DictionaryValue> value(record.ToValue(&trans)); 556 CheckNonDeleteChangeRecordValue(record, *value, &trans); 557 } 558 559 // Delete (no extra) 560 { 561 ReadTransaction trans(test_user_share_.user_share()); 562 SyncManager::ChangeRecord record; 563 record.action = SyncManager::ChangeRecord::ACTION_DELETE; 564 record.id = child_id + 1; 565 record.specifics = child_specifics; 566 scoped_ptr<DictionaryValue> value(record.ToValue(&trans)); 567 CheckDeleteChangeRecordValue(record, *value); 568 } 569 570 // Delete (with extra) 571 { 572 ReadTransaction trans(test_user_share_.user_share()); 573 SyncManager::ChangeRecord record; 574 record.action = SyncManager::ChangeRecord::ACTION_DELETE; 575 record.id = child_id + 1; 576 record.specifics = child_specifics; 577 578 DictionaryValue extra_value; 579 extra_value.SetString("foo", "bar"); 580 scoped_ptr<StrictMock<MockExtraChangeRecordData> > extra( 581 new StrictMock<MockExtraChangeRecordData>()); 582 EXPECT_CALL(*extra, ToValue()).Times(2).WillRepeatedly( 583 Invoke(&extra_value, &DictionaryValue::DeepCopy)); 584 585 record.extra.reset(extra.release()); 586 scoped_ptr<DictionaryValue> value(record.ToValue(&trans)); 587 CheckDeleteChangeRecordValue(record, *value); 588 } 589 } 590 591 namespace { 592 593 class TestHttpPostProviderFactory : public HttpPostProviderFactory { 594 public: 595 virtual ~TestHttpPostProviderFactory() {} 596 virtual HttpPostProviderInterface* Create() { 597 NOTREACHED(); 598 return NULL; 599 } 600 virtual void Destroy(HttpPostProviderInterface* http) { 601 NOTREACHED(); 602 } 603 }; 604 605 class SyncManagerObserverMock : public SyncManager::Observer { 606 public: 607 MOCK_METHOD4(OnChangesApplied, 608 void(ModelType, 609 const BaseTransaction*, 610 const SyncManager::ChangeRecord*, 611 int)); // NOLINT 612 MOCK_METHOD1(OnChangesComplete, void(ModelType)); // NOLINT 613 MOCK_METHOD1(OnSyncCycleCompleted, 614 void(const SyncSessionSnapshot*)); // NOLINT 615 MOCK_METHOD0(OnInitializationComplete, void()); // NOLINT 616 MOCK_METHOD1(OnAuthError, void(const GoogleServiceAuthError&)); // NOLINT 617 MOCK_METHOD1(OnPassphraseRequired, void(bool)); // NOLINT 618 MOCK_METHOD0(OnPassphraseFailed, void()); // NOLINT 619 MOCK_METHOD1(OnPassphraseAccepted, void(const std::string&)); // NOLINT 620 MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT 621 MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT 622 MOCK_METHOD1(OnMigrationNeededForTypes, void(const ModelTypeSet&)); 623 MOCK_METHOD0(OnClearServerDataFailed, void()); // NOLINT 624 MOCK_METHOD0(OnClearServerDataSucceeded, void()); // NOLINT 625 MOCK_METHOD1(OnEncryptionComplete, void(const ModelTypeSet&)); // NOLINT 626 }; 627 628 class SyncNotifierMock : public sync_notifier::SyncNotifier { 629 public: 630 MOCK_METHOD1(AddObserver, void(sync_notifier::SyncNotifierObserver*)); 631 MOCK_METHOD1(RemoveObserver, void(sync_notifier::SyncNotifierObserver*)); 632 MOCK_METHOD1(SetState, void(const std::string&)); 633 MOCK_METHOD2(UpdateCredentials, 634 void(const std::string&, const std::string&)); 635 MOCK_METHOD1(UpdateEnabledTypes, 636 void(const syncable::ModelTypeSet&)); 637 MOCK_METHOD0(SendNotification, void()); 638 }; 639 640 class SyncManagerTest : public testing::Test, 641 public ModelSafeWorkerRegistrar { 642 protected: 643 SyncManagerTest() 644 : ui_thread_(BrowserThread::UI, &ui_loop_), 645 sync_notifier_observer_(NULL), 646 update_enabled_types_call_count_(0) {} 647 648 // Test implementation. 649 void SetUp() { 650 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 651 652 SyncCredentials credentials; 653 credentials.email = "foo (at) bar.com"; 654 credentials.sync_token = "sometoken"; 655 656 sync_notifier_mock_.reset(new StrictMock<SyncNotifierMock>()); 657 EXPECT_CALL(*sync_notifier_mock_, AddObserver(_)). 658 WillOnce(Invoke(this, &SyncManagerTest::SyncNotifierAddObserver)); 659 EXPECT_CALL(*sync_notifier_mock_, SetState("")); 660 EXPECT_CALL(*sync_notifier_mock_, 661 UpdateCredentials(credentials.email, credentials.sync_token)); 662 EXPECT_CALL(*sync_notifier_mock_, UpdateEnabledTypes(_)). 663 Times(AtLeast(1)). 664 WillRepeatedly( 665 Invoke(this, &SyncManagerTest::SyncNotifierUpdateEnabledTypes)); 666 EXPECT_CALL(*sync_notifier_mock_, RemoveObserver(_)). 667 WillOnce(Invoke(this, &SyncManagerTest::SyncNotifierRemoveObserver)); 668 669 EXPECT_FALSE(sync_notifier_observer_); 670 671 sync_manager_.Init(temp_dir_.path(), "bogus", 0, false, 672 new TestHttpPostProviderFactory(), this, "bogus", 673 credentials, sync_notifier_mock_.get(), "", 674 true /* setup_for_test_mode */); 675 676 EXPECT_TRUE(sync_notifier_observer_); 677 sync_manager_.AddObserver(&observer_); 678 679 EXPECT_EQ(1, update_enabled_types_call_count_); 680 681 ModelSafeRoutingInfo routes; 682 GetModelSafeRoutingInfo(&routes); 683 for (ModelSafeRoutingInfo::iterator i = routes.begin(); i != routes.end(); 684 ++i) { 685 EXPECT_CALL(observer_, OnChangesApplied(i->first, _, _, 1)) 686 .RetiresOnSaturation(); 687 EXPECT_CALL(observer_, OnChangesComplete(i->first)) 688 .RetiresOnSaturation(); 689 type_roots_[i->first] = MakeServerNodeForType( 690 sync_manager_.GetUserShare(), i->first); 691 } 692 } 693 694 void TearDown() { 695 sync_manager_.RemoveObserver(&observer_); 696 sync_manager_.Shutdown(); 697 EXPECT_FALSE(sync_notifier_observer_); 698 } 699 700 // ModelSafeWorkerRegistrar implementation. 701 virtual void GetWorkers(std::vector<ModelSafeWorker*>* out) { 702 NOTIMPLEMENTED(); 703 out->clear(); 704 } 705 virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) { 706 (*out)[syncable::NIGORI] = browser_sync::GROUP_PASSIVE; 707 (*out)[syncable::BOOKMARKS] = browser_sync::GROUP_PASSIVE; 708 (*out)[syncable::THEMES] = browser_sync::GROUP_PASSIVE; 709 (*out)[syncable::SESSIONS] = browser_sync::GROUP_PASSIVE; 710 (*out)[syncable::PASSWORDS] = browser_sync::GROUP_PASSIVE; 711 } 712 713 // Helper methods. 714 bool SetUpEncryption() { 715 // We need to create the nigori node as if it were an applied server update. 716 UserShare* share = sync_manager_.GetUserShare(); 717 int64 nigori_id = GetIdForDataType(syncable::NIGORI); 718 if (nigori_id == kInvalidId) 719 return false; 720 721 // Set the nigori cryptographer information. 722 WriteTransaction trans(share); 723 Cryptographer* cryptographer = trans.GetCryptographer(); 724 if (!cryptographer) 725 return false; 726 KeyParams params = {"localhost", "dummy", "foobar"}; 727 cryptographer->AddKey(params); 728 sync_pb::NigoriSpecifics nigori; 729 cryptographer->GetKeys(nigori.mutable_encrypted()); 730 WriteNode node(&trans); 731 node.InitByIdLookup(nigori_id); 732 node.SetNigoriSpecifics(nigori); 733 return cryptographer->is_ready(); 734 } 735 736 int64 GetIdForDataType(ModelType type) { 737 if (type_roots_.count(type) == 0) 738 return 0; 739 return type_roots_[type]; 740 } 741 742 void SyncNotifierAddObserver( 743 sync_notifier::SyncNotifierObserver* sync_notifier_observer) { 744 EXPECT_EQ(NULL, sync_notifier_observer_); 745 sync_notifier_observer_ = sync_notifier_observer; 746 } 747 748 void SyncNotifierRemoveObserver( 749 sync_notifier::SyncNotifierObserver* sync_notifier_observer) { 750 EXPECT_EQ(sync_notifier_observer_, sync_notifier_observer); 751 sync_notifier_observer_ = NULL; 752 } 753 754 void SyncNotifierUpdateEnabledTypes( 755 const syncable::ModelTypeSet& types) { 756 ModelSafeRoutingInfo routes; 757 GetModelSafeRoutingInfo(&routes); 758 syncable::ModelTypeSet expected_types; 759 for (ModelSafeRoutingInfo::const_iterator it = routes.begin(); 760 it != routes.end(); ++it) { 761 expected_types.insert(it->first); 762 } 763 EXPECT_EQ(expected_types, types); 764 ++update_enabled_types_call_count_; 765 } 766 767 private: 768 // Needed by |ui_thread_|. 769 MessageLoopForUI ui_loop_; 770 // Needed by |sync_manager_|. 771 BrowserThread ui_thread_; 772 // Needed by |sync_manager_|. 773 ScopedTempDir temp_dir_; 774 // Sync Id's for the roots of the enabled datatypes. 775 std::map<ModelType, int64> type_roots_; 776 scoped_ptr<StrictMock<SyncNotifierMock> > sync_notifier_mock_; 777 778 protected: 779 SyncManager sync_manager_; 780 StrictMock<SyncManagerObserverMock> observer_; 781 sync_notifier::SyncNotifierObserver* sync_notifier_observer_; 782 int update_enabled_types_call_count_; 783 }; 784 785 TEST_F(SyncManagerTest, UpdateEnabledTypes) { 786 EXPECT_EQ(1, update_enabled_types_call_count_); 787 // Triggers SyncNotifierUpdateEnabledTypes. 788 sync_manager_.UpdateEnabledTypes(); 789 EXPECT_EQ(2, update_enabled_types_call_count_); 790 } 791 792 TEST_F(SyncManagerTest, ParentJsEventRouter) { 793 StrictMock<MockJsEventRouter> event_router; 794 browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); 795 EXPECT_EQ(NULL, js_backend->GetParentJsEventRouter()); 796 js_backend->SetParentJsEventRouter(&event_router); 797 EXPECT_EQ(&event_router, js_backend->GetParentJsEventRouter()); 798 js_backend->RemoveParentJsEventRouter(); 799 EXPECT_EQ(NULL, js_backend->GetParentJsEventRouter()); 800 } 801 802 TEST_F(SyncManagerTest, ProcessMessage) { 803 const JsArgList kNoArgs; 804 805 browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); 806 807 // Messages sent without any parent router should be dropped. 808 { 809 StrictMock<MockJsEventHandler> event_handler; 810 js_backend->ProcessMessage("unknownMessage", 811 kNoArgs, &event_handler); 812 js_backend->ProcessMessage("getNotificationState", 813 kNoArgs, &event_handler); 814 } 815 816 { 817 StrictMock<MockJsEventHandler> event_handler; 818 StrictMock<MockJsEventRouter> event_router; 819 820 ListValue false_args; 821 false_args.Append(Value::CreateBooleanValue(false)); 822 823 EXPECT_CALL(event_router, 824 RouteJsEvent("onGetNotificationStateFinished", 825 HasArgsAsList(false_args), &event_handler)); 826 827 js_backend->SetParentJsEventRouter(&event_router); 828 829 // This message should be dropped. 830 js_backend->ProcessMessage("unknownMessage", 831 kNoArgs, &event_handler); 832 833 // This should trigger the reply. 834 js_backend->ProcessMessage("getNotificationState", 835 kNoArgs, &event_handler); 836 837 js_backend->RemoveParentJsEventRouter(); 838 } 839 840 // Messages sent after a parent router has been removed should be 841 // dropped. 842 { 843 StrictMock<MockJsEventHandler> event_handler; 844 js_backend->ProcessMessage("unknownMessage", 845 kNoArgs, &event_handler); 846 js_backend->ProcessMessage("getNotificationState", 847 kNoArgs, &event_handler); 848 } 849 } 850 851 TEST_F(SyncManagerTest, ProcessMessageGetRootNode) { 852 const JsArgList kNoArgs; 853 854 browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); 855 856 StrictMock<MockJsEventHandler> event_handler; 857 StrictMock<MockJsEventRouter> event_router; 858 859 JsArgList return_args; 860 861 EXPECT_CALL(event_router, 862 RouteJsEvent("onGetRootNodeFinished", _, &event_handler)). 863 WillOnce(SaveArg<1>(&return_args)); 864 865 js_backend->SetParentJsEventRouter(&event_router); 866 867 // Should trigger the reply. 868 js_backend->ProcessMessage("getRootNode", kNoArgs, &event_handler); 869 870 EXPECT_EQ(1u, return_args.Get().GetSize()); 871 DictionaryValue* node_info = NULL; 872 EXPECT_TRUE(return_args.Get().GetDictionary(0, &node_info)); 873 if (node_info) { 874 ReadTransaction trans(sync_manager_.GetUserShare()); 875 ReadNode node(&trans); 876 node.InitByRootLookup(); 877 CheckNodeValue(node, *node_info); 878 } else { 879 ADD_FAILURE(); 880 } 881 882 js_backend->RemoveParentJsEventRouter(); 883 } 884 885 void CheckGetNodeByIdReturnArgs(const SyncManager& sync_manager, 886 const JsArgList& return_args, 887 int64 id) { 888 EXPECT_EQ(1u, return_args.Get().GetSize()); 889 DictionaryValue* node_info = NULL; 890 EXPECT_TRUE(return_args.Get().GetDictionary(0, &node_info)); 891 if (node_info) { 892 ReadTransaction trans(sync_manager.GetUserShare()); 893 ReadNode node(&trans); 894 node.InitByIdLookup(id); 895 CheckNodeValue(node, *node_info); 896 } else { 897 ADD_FAILURE(); 898 } 899 } 900 901 TEST_F(SyncManagerTest, ProcessMessageGetNodeById) { 902 int64 child_id = 903 MakeNode(sync_manager_.GetUserShare(), syncable::BOOKMARKS, "testtag"); 904 905 browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); 906 907 StrictMock<MockJsEventHandler> event_handler; 908 StrictMock<MockJsEventRouter> event_router; 909 910 JsArgList return_args; 911 912 EXPECT_CALL(event_router, 913 RouteJsEvent("onGetNodeByIdFinished", _, &event_handler)) 914 .Times(2).WillRepeatedly(SaveArg<1>(&return_args)); 915 916 js_backend->SetParentJsEventRouter(&event_router); 917 918 // Should trigger the reply. 919 { 920 ListValue args; 921 args.Append(Value::CreateStringValue("1")); 922 js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler); 923 } 924 925 CheckGetNodeByIdReturnArgs(sync_manager_, return_args, 1); 926 927 // Should trigger another reply. 928 { 929 ListValue args; 930 args.Append(Value::CreateStringValue(base::Int64ToString(child_id))); 931 js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler); 932 } 933 934 CheckGetNodeByIdReturnArgs(sync_manager_, return_args, child_id); 935 936 js_backend->RemoveParentJsEventRouter(); 937 } 938 939 TEST_F(SyncManagerTest, ProcessMessageGetNodeByIdFailure) { 940 browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); 941 942 StrictMock<MockJsEventHandler> event_handler; 943 StrictMock<MockJsEventRouter> event_router; 944 945 ListValue null_args; 946 null_args.Append(Value::CreateNullValue()); 947 948 EXPECT_CALL(event_router, 949 RouteJsEvent("onGetNodeByIdFinished", 950 HasArgsAsList(null_args), &event_handler)) 951 .Times(5); 952 953 js_backend->SetParentJsEventRouter(&event_router); 954 955 { 956 ListValue args; 957 js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler); 958 } 959 960 { 961 ListValue args; 962 args.Append(Value::CreateStringValue("")); 963 js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler); 964 } 965 966 { 967 ListValue args; 968 args.Append(Value::CreateStringValue("nonsense")); 969 js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler); 970 } 971 972 { 973 ListValue args; 974 args.Append(Value::CreateStringValue("nonsense")); 975 js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler); 976 } 977 978 { 979 ListValue args; 980 args.Append(Value::CreateStringValue("0")); 981 js_backend->ProcessMessage("getNodeById", JsArgList(args), &event_handler); 982 } 983 984 // TODO(akalin): Figure out how to test InitByIdLookup() failure. 985 986 js_backend->RemoveParentJsEventRouter(); 987 } 988 989 TEST_F(SyncManagerTest, OnNotificationStateChange) { 990 StrictMock<MockJsEventRouter> event_router; 991 992 ListValue true_args; 993 true_args.Append(Value::CreateBooleanValue(true)); 994 ListValue false_args; 995 false_args.Append(Value::CreateBooleanValue(false)); 996 997 EXPECT_CALL(event_router, 998 RouteJsEvent("onSyncNotificationStateChange", 999 HasArgsAsList(true_args), NULL)); 1000 EXPECT_CALL(event_router, 1001 RouteJsEvent("onSyncNotificationStateChange", 1002 HasArgsAsList(false_args), NULL)); 1003 1004 browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); 1005 1006 sync_manager_.TriggerOnNotificationStateChangeForTest(true); 1007 sync_manager_.TriggerOnNotificationStateChangeForTest(false); 1008 1009 js_backend->SetParentJsEventRouter(&event_router); 1010 sync_manager_.TriggerOnNotificationStateChangeForTest(true); 1011 sync_manager_.TriggerOnNotificationStateChangeForTest(false); 1012 js_backend->RemoveParentJsEventRouter(); 1013 1014 sync_manager_.TriggerOnNotificationStateChangeForTest(true); 1015 sync_manager_.TriggerOnNotificationStateChangeForTest(false); 1016 } 1017 1018 TEST_F(SyncManagerTest, OnIncomingNotification) { 1019 StrictMock<MockJsEventRouter> event_router; 1020 1021 const syncable::ModelTypeBitSet empty_model_types; 1022 syncable::ModelTypeBitSet model_types; 1023 model_types.set(syncable::BOOKMARKS); 1024 model_types.set(syncable::THEMES); 1025 1026 // Build expected_args to have a single argument with the string 1027 // equivalents of model_types. 1028 ListValue expected_args; 1029 { 1030 ListValue* model_type_list = new ListValue(); 1031 expected_args.Append(model_type_list); 1032 for (int i = syncable::FIRST_REAL_MODEL_TYPE; 1033 i < syncable::MODEL_TYPE_COUNT; ++i) { 1034 if (model_types[i]) { 1035 model_type_list->Append( 1036 Value::CreateStringValue( 1037 syncable::ModelTypeToString( 1038 syncable::ModelTypeFromInt(i)))); 1039 } 1040 } 1041 } 1042 1043 EXPECT_CALL(event_router, 1044 RouteJsEvent("onSyncIncomingNotification", 1045 HasArgsAsList(expected_args), NULL)); 1046 1047 browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); 1048 1049 sync_manager_.TriggerOnIncomingNotificationForTest(empty_model_types); 1050 sync_manager_.TriggerOnIncomingNotificationForTest(model_types); 1051 1052 js_backend->SetParentJsEventRouter(&event_router); 1053 sync_manager_.TriggerOnIncomingNotificationForTest(model_types); 1054 js_backend->RemoveParentJsEventRouter(); 1055 1056 sync_manager_.TriggerOnIncomingNotificationForTest(empty_model_types); 1057 sync_manager_.TriggerOnIncomingNotificationForTest(model_types); 1058 } 1059 1060 TEST_F(SyncManagerTest, EncryptDataTypesWithNoData) { 1061 EXPECT_TRUE(SetUpEncryption()); 1062 ModelTypeSet encrypted_types; 1063 encrypted_types.insert(syncable::BOOKMARKS); 1064 // Even though Passwords isn't marked for encryption, it's enabled, so it 1065 // should automatically be added to the response of OnEncryptionComplete. 1066 ModelTypeSet expected_types = encrypted_types; 1067 expected_types.insert(syncable::PASSWORDS); 1068 EXPECT_CALL(observer_, OnEncryptionComplete(expected_types)); 1069 sync_manager_.EncryptDataTypes(encrypted_types); 1070 { 1071 ReadTransaction trans(sync_manager_.GetUserShare()); 1072 EXPECT_EQ(encrypted_types, 1073 GetEncryptedDataTypes(trans.GetWrappedTrans())); 1074 } 1075 } 1076 1077 TEST_F(SyncManagerTest, EncryptDataTypesWithData) { 1078 size_t batch_size = 5; 1079 EXPECT_TRUE(SetUpEncryption()); 1080 1081 // Create some unencrypted unsynced data. 1082 int64 folder = MakeFolderWithParent(sync_manager_.GetUserShare(), 1083 syncable::BOOKMARKS, 1084 GetIdForDataType(syncable::BOOKMARKS), 1085 NULL); 1086 // First batch_size nodes are children of folder. 1087 size_t i; 1088 for (i = 0; i < batch_size; ++i) { 1089 MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::BOOKMARKS, 1090 StringPrintf("%"PRIuS"", i), folder); 1091 } 1092 // Next batch_size nodes are a different type and on their own. 1093 for (; i < 2*batch_size; ++i) { 1094 MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::SESSIONS, 1095 StringPrintf("%"PRIuS"", i), 1096 GetIdForDataType(syncable::SESSIONS)); 1097 } 1098 // Last batch_size nodes are a third type that will not need encryption. 1099 for (; i < 3*batch_size; ++i) { 1100 MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::THEMES, 1101 StringPrintf("%"PRIuS"", i), 1102 GetIdForDataType(syncable::THEMES)); 1103 } 1104 1105 { 1106 ReadTransaction trans(sync_manager_.GetUserShare()); 1107 EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), 1108 syncable::BOOKMARKS, 1109 false /* not encrypted */)); 1110 EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), 1111 syncable::SESSIONS, 1112 false /* not encrypted */)); 1113 EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), 1114 syncable::THEMES, 1115 false /* not encrypted */)); 1116 } 1117 1118 ModelTypeSet encrypted_types; 1119 encrypted_types.insert(syncable::BOOKMARKS); 1120 encrypted_types.insert(syncable::SESSIONS); 1121 encrypted_types.insert(syncable::PASSWORDS); 1122 EXPECT_CALL(observer_, OnEncryptionComplete(encrypted_types)); 1123 sync_manager_.EncryptDataTypes(encrypted_types); 1124 1125 { 1126 ReadTransaction trans(sync_manager_.GetUserShare()); 1127 encrypted_types.erase(syncable::PASSWORDS); // Not stored in nigori node. 1128 EXPECT_EQ(encrypted_types, 1129 GetEncryptedDataTypes(trans.GetWrappedTrans())); 1130 EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), 1131 syncable::BOOKMARKS, 1132 true /* is encrypted */)); 1133 EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), 1134 syncable::SESSIONS, 1135 true /* is encrypted */)); 1136 EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), 1137 syncable::THEMES, 1138 false /* not encrypted */)); 1139 } 1140 } 1141 1142 } // namespace 1143 1144 } // namespace browser_sync 1145