Index: chrome/browser/sync/engine/syncapi_unittest.cc |
diff --git a/chrome/browser/sync/engine/syncapi_unittest.cc b/chrome/browser/sync/engine/syncapi_unittest.cc |
index c2e35845cd051ba00e87ce71a75def574a980515..ff6d02237529e2ee168a8453742b88c9dd85c2c0 100644 |
--- a/chrome/browser/sync/engine/syncapi_unittest.cc |
+++ b/chrome/browser/sync/engine/syncapi_unittest.cc |
@@ -6,14 +6,19 @@ |
// functionality is provided by the Syncable layer, which has its own |
// unit tests. We'll test SyncApi specific things in this harness. |
+#include <map> |
+ |
#include "base/basictypes.h" |
+#include "base/format_macros.h" |
#include "base/message_loop.h" |
#include "base/scoped_ptr.h" |
#include "base/scoped_temp_dir.h" |
#include "base/string_number_conversions.h" |
+#include "base/string_util.h" |
#include "base/utf_string_conversions.h" |
#include "base/values.h" |
#include "chrome/browser/browser_thread.h" |
+#include "chrome/browser/sync/engine/model_safe_worker.h" |
#include "chrome/browser/sync/engine/syncapi.h" |
#include "chrome/browser/sync/js_arg_list.h" |
#include "chrome/browser/sync/js_backend.h" |
@@ -22,19 +27,30 @@ |
#include "chrome/browser/sync/js_test_util.h" |
#include "chrome/browser/sync/protocol/password_specifics.pb.h" |
#include "chrome/browser/sync/protocol/proto_value_conversions.h" |
+#include "chrome/browser/sync/sessions/sync_session.h" |
#include "chrome/browser/sync/syncable/directory_manager.h" |
+#include "chrome/browser/sync/syncable/nigori_util.h" |
#include "chrome/browser/sync/syncable/syncable.h" |
+#include "chrome/browser/sync/syncable/syncable_id.h" |
+#include "chrome/browser/sync/util/cryptographer.h" |
#include "chrome/test/sync/engine/test_directory_setter_upper.h" |
#include "chrome/test/values_test_util.h" |
#include "jingle/notifier/base/notifier_options.h" |
#include "testing/gmock/include/gmock/gmock.h" |
#include "testing/gtest/include/gtest/gtest.h" |
+using browser_sync::Cryptographer; |
using browser_sync::HasArgsAsList; |
using browser_sync::KeyParams; |
using browser_sync::JsArgList; |
using browser_sync::MockJsEventHandler; |
using browser_sync::MockJsEventRouter; |
+using browser_sync::ModelSafeRoutingInfo; |
+using browser_sync::ModelSafeWorker; |
+using browser_sync::ModelSafeWorkerRegistrar; |
+using browser_sync::sessions::SyncSessionSnapshot; |
+using syncable::ModelType; |
+using syncable::ModelTypeSet; |
using test::ExpectDictionaryValue; |
using test::ExpectStringValue; |
using testing::_; |
@@ -58,7 +74,7 @@ void ExpectInt64Value(int64 expected_value, |
// Makes a non-folder child of the root node. Returns the id of the |
// newly-created node. |
int64 MakeNode(UserShare* share, |
- syncable::ModelType model_type, |
+ ModelType model_type, |
const std::string& client_tag) { |
WriteTransaction trans(share); |
ReadNode root_node(&trans); |
@@ -69,6 +85,80 @@ int64 MakeNode(UserShare* share, |
return node.GetId(); |
} |
+// Make a folder as a child of the root node. Returns the id of the |
+// newly-created node. |
+int64 MakeFolder(UserShare* share, |
+ syncable::ModelType model_type, |
+ const std::string& client_tag) { |
+ WriteTransaction trans(share); |
+ ReadNode root_node(&trans); |
+ root_node.InitByRootLookup(); |
+ WriteNode node(&trans); |
+ EXPECT_TRUE(node.InitUniqueByCreation(model_type, root_node, client_tag)); |
+ node.SetIsFolder(true); |
+ return node.GetId(); |
+} |
+ |
+// Makes a non-folder child of a non-root node. Returns the id of the |
+// newly-created node. |
+int64 MakeNodeWithParent(UserShare* share, |
+ ModelType model_type, |
+ const std::string& client_tag, |
+ int64 parent_id) { |
+ WriteTransaction trans(share); |
+ ReadNode parent_node(&trans); |
+ parent_node.InitByIdLookup(parent_id); |
+ WriteNode node(&trans); |
+ EXPECT_TRUE(node.InitUniqueByCreation(model_type, parent_node, client_tag)); |
+ node.SetIsFolder(false); |
+ return node.GetId(); |
+} |
+ |
+// Makes a folder child of a non-root node. Returns the id of the |
+// newly-created node. |
+int64 MakeFolderWithParent(UserShare* share, |
+ ModelType model_type, |
+ int64 parent_id, |
+ BaseNode* predecessor) { |
+ WriteTransaction trans(share); |
+ ReadNode parent_node(&trans); |
+ parent_node.InitByIdLookup(parent_id); |
+ WriteNode node(&trans); |
+ EXPECT_TRUE(node.InitByCreation(model_type, parent_node, predecessor)); |
+ node.SetIsFolder(true); |
+ return node.GetId(); |
+} |
+ |
+// Creates the "synced" root node for a particular datatype. We use the syncable |
+// methods here so that the syncer treats these nodes as if they were already |
+// received from the server. |
+int64 MakeServerNodeForType(UserShare* share, |
+ ModelType model_type) { |
+ sync_pb::EntitySpecifics specifics; |
+ syncable::AddDefaultExtensionValue(model_type, &specifics); |
+ syncable::ScopedDirLookup dir(share->dir_manager.get(), share->name); |
+ EXPECT_TRUE(dir.good()); |
+ syncable::WriteTransaction trans(dir, syncable::UNITTEST, __FILE__, __LINE__); |
+ // Attempt to lookup by nigori tag. |
+ std::string type_tag = syncable::ModelTypeToRootTag(model_type); |
+ syncable::Id node_id = syncable::Id::CreateFromServerId(type_tag); |
+ syncable::MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM, |
+ node_id); |
+ EXPECT_TRUE(entry.good()); |
+ entry.Put(syncable::BASE_VERSION, 1); |
+ entry.Put(syncable::SERVER_VERSION, 1); |
+ entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); |
+ entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId); |
+ entry.Put(syncable::SERVER_IS_DIR, true); |
+ entry.Put(syncable::IS_DIR, true); |
+ entry.Put(syncable::SERVER_SPECIFICS, specifics); |
+ entry.Put(syncable::UNIQUE_SERVER_TAG, type_tag); |
+ entry.Put(syncable::NON_UNIQUE_NAME, type_tag); |
+ entry.Put(syncable::IS_DEL, false); |
+ entry.Put(syncable::SPECIFICS, specifics); |
+ return entry.Get(syncable::META_HANDLE); |
+} |
+ |
} // namespace |
class SyncApiTest : public testing::Test { |
@@ -198,7 +288,6 @@ TEST_F(SyncApiTest, ReadMissingTagsFails) { |
// TODO(chron): Hook this all up to the server and write full integration tests |
// for update->undelete behavior. |
TEST_F(SyncApiTest, TestDeleteBehavior) { |
- |
int64 node_id; |
int64 folder_id; |
std::wstring test_title(L"test1"); |
@@ -316,11 +405,11 @@ void CheckNodeValue(const BaseNode& node, const DictionaryValue& value) { |
} |
ExpectStringValue(WideToUTF8(node.GetTitle()), value, "title"); |
{ |
- syncable::ModelType expected_model_type = node.GetModelType(); |
+ ModelType expected_model_type = node.GetModelType(); |
std::string type_str; |
EXPECT_TRUE(value.GetString("type", &type_str)); |
if (expected_model_type >= syncable::FIRST_REAL_MODEL_TYPE) { |
- syncable::ModelType model_type = |
+ ModelType model_type = |
syncable::ModelTypeFromString(type_str); |
EXPECT_EQ(expected_model_type, model_type); |
} else if (expected_model_type == syncable::TOP_LEVEL_FOLDER) { |
@@ -420,7 +509,8 @@ void CheckDeleteChangeRecordValue(const SyncManager::ChangeRecord& record, |
} |
} |
-class MockExtraChangeRecordData : public SyncManager::ExtraChangeRecordData { |
+class MockExtraChangeRecordData |
+ : public SyncManager::ExtraPasswordChangeRecordData { |
public: |
MOCK_CONST_METHOD0(ToValue, DictionaryValue*()); |
}; |
@@ -507,22 +597,102 @@ class TestHttpPostProviderFactory : public HttpPostProviderFactory { |
} |
}; |
-class SyncManagerTest : public testing::Test { |
+class SyncManagerObserverMock : public SyncManager::Observer { |
+ public: |
+ MOCK_METHOD4(OnChangesApplied, |
+ void(ModelType, |
+ const BaseTransaction*, |
+ const SyncManager::ChangeRecord*, |
+ int)); // NOLINT |
+ MOCK_METHOD1(OnChangesComplete, void(ModelType)); // NOLINT |
+ MOCK_METHOD1(OnSyncCycleCompleted, |
+ void(const SyncSessionSnapshot*)); // NOLINT |
+ MOCK_METHOD0(OnInitializationComplete, void()); // NOLINT |
+ MOCK_METHOD1(OnAuthError, void(const GoogleServiceAuthError&)); // NOLINT |
+ MOCK_METHOD1(OnPassphraseRequired, void(bool)); // NOLINT |
+ MOCK_METHOD1(OnPassphraseAccepted, void(const std::string&)); // NOLINT |
+ MOCK_METHOD0(OnPaused, void()); // NOLINT |
+ MOCK_METHOD0(OnResumed, void()); // NOLINT |
+ MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT |
+ MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT |
+ MOCK_METHOD0(OnClearServerDataFailed, void()); // NOLINT |
+ MOCK_METHOD0(OnClearServerDataSucceeded, void()); // NOLINT |
+ MOCK_METHOD1(OnEncryptionComplete, void(const ModelTypeSet&)); // NOLINT |
+}; |
+ |
+class SyncManagerTest : public testing::Test, |
+ public ModelSafeWorkerRegistrar { |
protected: |
SyncManagerTest() : ui_thread_(BrowserThread::UI, &ui_loop_) {} |
+ // Test implementation. |
void SetUp() { |
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
sync_manager_.Init(temp_dir_.path(), "bogus", 0, false, |
- new TestHttpPostProviderFactory(), NULL, "bogus", |
+ new TestHttpPostProviderFactory(), this, "bogus", |
SyncCredentials(), notifier::NotifierOptions(), |
"", true /* setup_for_test_mode */); |
+ sync_manager_.AddObserver(&observer_); |
+ ModelSafeRoutingInfo routes; |
+ GetModelSafeRoutingInfo(&routes); |
+ for (ModelSafeRoutingInfo::iterator i = routes.begin(); i != routes.end(); |
+ ++i) { |
+ EXPECT_CALL(observer_, OnChangesApplied(i->first, _, _, 1)) |
+ .RetiresOnSaturation(); |
+ EXPECT_CALL(observer_, OnChangesComplete(i->first)) |
+ .RetiresOnSaturation(); |
+ type_roots_[i->first] = MakeServerNodeForType( |
+ sync_manager_.GetUserShare(), i->first); |
+ } |
} |
void TearDown() { |
+ sync_manager_.RemoveObserver(&observer_); |
sync_manager_.Shutdown(); |
} |
+ // ModelSafeWorkerRegistrar implementation. |
+ virtual void GetWorkers(std::vector<ModelSafeWorker*>* out) { |
+ NOTIMPLEMENTED(); |
+ out->clear(); |
+ } |
+ virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) { |
+ (*out)[syncable::NIGORI] = browser_sync::GROUP_PASSIVE; |
+ (*out)[syncable::BOOKMARKS] = browser_sync::GROUP_PASSIVE; |
+ (*out)[syncable::THEMES] = browser_sync::GROUP_PASSIVE; |
+ (*out)[syncable::SESSIONS] = browser_sync::GROUP_PASSIVE; |
+ (*out)[syncable::PASSWORDS] = browser_sync::GROUP_PASSIVE; |
+ } |
+ |
+ // Helper methods. |
+ bool SetUpEncryption() { |
+ // We need to create the nigori node as if it were an applied server update. |
+ UserShare* share = sync_manager_.GetUserShare(); |
+ int64 nigori_id = GetIdForDataType(syncable::NIGORI); |
+ if (nigori_id == kInvalidId) |
+ return false; |
+ |
+ // Set the nigori cryptographer information. |
+ Cryptographer* cryptographer = share->dir_manager->cryptographer(); |
+ if (!cryptographer) |
+ return false; |
+ KeyParams params = {"localhost", "dummy", "foobar"}; |
+ cryptographer->AddKey(params); |
+ sync_pb::NigoriSpecifics nigori; |
+ cryptographer->GetKeys(nigori.mutable_encrypted()); |
+ WriteTransaction trans(share); |
+ WriteNode node(&trans); |
+ node.InitByIdLookup(nigori_id); |
+ node.SetNigoriSpecifics(nigori); |
+ return cryptographer->is_ready(); |
+ } |
+ |
+ int64 GetIdForDataType(ModelType type) { |
+ if (type_roots_.count(type) == 0) |
+ return 0; |
+ return type_roots_[type]; |
+ } |
+ |
private: |
// Needed by |ui_thread_|. |
MessageLoopForUI ui_loop_; |
@@ -530,9 +700,12 @@ class SyncManagerTest : public testing::Test { |
BrowserThread ui_thread_; |
// Needed by |sync_manager_|. |
ScopedTempDir temp_dir_; |
+ // Sync Id's for the roots of the enabled datatypes. |
+ std::map<ModelType, int64> type_roots_; |
protected: |
SyncManager sync_manager_; |
+ StrictMock<SyncManagerObserverMock> observer_; |
}; |
TEST_F(SyncManagerTest, ParentJsEventRouter) { |
@@ -803,6 +976,88 @@ TEST_F(SyncManagerTest, OnIncomingNotification) { |
sync_manager_.TriggerOnIncomingNotificationForTest(model_types); |
} |
+TEST_F(SyncManagerTest, EncryptDataTypesWithNoData) { |
+ EXPECT_TRUE(SetUpEncryption()); |
+ ModelTypeSet encrypted_types; |
+ encrypted_types.insert(syncable::BOOKMARKS); |
+ // Even though Passwords isn't marked for encryption, it's enabled, so it |
+ // should automatically be added to the response of OnEncryptionComplete. |
+ ModelTypeSet expected_types = encrypted_types; |
+ expected_types.insert(syncable::PASSWORDS); |
+ EXPECT_CALL(observer_, OnEncryptionComplete(expected_types)); |
+ sync_manager_.EncryptDataTypes(encrypted_types); |
+ { |
+ ReadTransaction trans(sync_manager_.GetUserShare()); |
+ EXPECT_EQ(encrypted_types, |
+ GetEncryptedDataTypes(trans.GetWrappedTrans())); |
+ } |
+} |
+ |
+TEST_F(SyncManagerTest, EncryptDataTypesWithData) { |
+ size_t batch_size = 5; |
+ EXPECT_TRUE(SetUpEncryption()); |
+ |
+ // Create some unencrypted unsynced data. |
+ int64 folder = MakeFolderWithParent(sync_manager_.GetUserShare(), |
+ syncable::BOOKMARKS, |
+ GetIdForDataType(syncable::BOOKMARKS), |
+ NULL); |
+ // First batch_size nodes are children of folder. |
+ size_t i; |
+ for (i = 0; i < batch_size; ++i) { |
+ MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::BOOKMARKS, |
+ StringPrintf("%"PRIuS"", i), folder); |
+ } |
+ // Next batch_size nodes are a different type and on their own. |
+ for (; i < 2*batch_size; ++i) { |
+ MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::SESSIONS, |
+ StringPrintf("%"PRIuS"", i), |
+ GetIdForDataType(syncable::SESSIONS)); |
+ } |
+ // Last batch_size nodes are a third type that will not need encryption. |
+ for (; i < 3*batch_size; ++i) { |
+ MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::THEMES, |
+ StringPrintf("%"PRIuS"", i), |
+ GetIdForDataType(syncable::THEMES)); |
+ } |
+ |
+ { |
+ ReadTransaction trans(sync_manager_.GetUserShare()); |
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), |
+ syncable::BOOKMARKS, |
+ false /* not encrypted */)); |
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), |
+ syncable::SESSIONS, |
+ false /* not encrypted */)); |
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), |
+ syncable::THEMES, |
+ false /* not encrypted */)); |
+ } |
+ |
+ ModelTypeSet encrypted_types; |
+ encrypted_types.insert(syncable::BOOKMARKS); |
+ encrypted_types.insert(syncable::SESSIONS); |
+ encrypted_types.insert(syncable::PASSWORDS); |
+ EXPECT_CALL(observer_, OnEncryptionComplete(encrypted_types)); |
+ sync_manager_.EncryptDataTypes(encrypted_types); |
+ |
+ { |
+ ReadTransaction trans(sync_manager_.GetUserShare()); |
+ encrypted_types.erase(syncable::PASSWORDS); // Not stored in nigori node. |
+ EXPECT_EQ(encrypted_types, |
+ GetEncryptedDataTypes(trans.GetWrappedTrans())); |
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), |
+ syncable::BOOKMARKS, |
+ true /* is encrypted */)); |
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), |
+ syncable::SESSIONS, |
+ true /* is encrypted */)); |
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(), |
+ syncable::THEMES, |
+ false /* not encrypted */)); |
+ } |
+} |
+ |
} // namespace |
} // namespace browser_sync |