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 f4a05a5f99781e044f2bb1a4f105f70b70ab85cb..fb5537160a19874e8a7afb36e8dc76c92e2435ec 100644 |
--- a/chrome/browser/sync/engine/syncapi_unittest.cc |
+++ b/chrome/browser/sync/engine/syncapi_unittest.cc |
@@ -6,14 +6,18 @@ |
// 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/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,24 +26,110 @@ |
#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 "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 testing::_; |
using testing::SaveArg; |
using testing::StrictMock; |
namespace sync_api { |
+namespace { |
+ |
+// Utility methods for creating nodes through the syncapi. |
+int64 MakeNode(UserShare* share, |
+ 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(false); |
+ return node.GetId(); |
+} |
+ |
+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(); |
+} |
+ |
+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); |
+ if (!dir.good()) |
+ return 0; |
+ 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); |
+ if (!entry.good()) |
+ return 0; |
+ 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, false); |
+ 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 { |
public: |
virtual void SetUp() { |
@@ -310,11 +400,11 @@ void CheckNodeValue(const BaseNode& node, const DictionaryValue& value) { |
EXPECT_EQ(node.GetTitle(), UTF8ToWide(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) { |
@@ -368,22 +458,101 @@ 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)); |
+ MOCK_METHOD1(OnChangesComplete, void(ModelType)); |
+ MOCK_METHOD1(OnSyncCycleCompleted, |
+ void(const SyncSessionSnapshot*)); |
+ MOCK_METHOD0(OnInitializationComplete, void()); |
+ MOCK_METHOD1(OnAuthError, void(const GoogleServiceAuthError&)); |
+ MOCK_METHOD1(OnPassphraseRequired, void(bool)); |
+ MOCK_METHOD1(OnPassphraseAccepted, void(const std::string&)); |
+ MOCK_METHOD0(OnPaused, void()); |
+ MOCK_METHOD0(OnResumed, void()); |
+ MOCK_METHOD0(OnStopSyncingPermanently, void()); |
+ MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); |
+ MOCK_METHOD0(OnClearServerDataFailed, void()); |
+ MOCK_METHOD0(OnClearServerDataSucceeded, void()); |
+ MOCK_METHOD1(OnEncryptionComplete, void(const ModelTypeSet&)); |
+}; |
+ |
+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_.SetObserver(&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(); |
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; |
+ } |
+ |
+ // 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_; |
@@ -391,9 +560,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) { |
@@ -675,6 +847,82 @@ TEST_F(SyncManagerTest, OnIncomingNotification) { |
sync_manager_.TriggerOnIncomingNotificationForTest(model_types); |
} |
+TEST_F(SyncManagerTest, EncryptDataTypesWithNoData) { |
+ EXPECT_TRUE(SetUpEncryption()); |
+ ModelTypeSet encrypted_types; |
+ encrypted_types.insert(syncable::BOOKMARKS); |
+ EXPECT_CALL(observer_, OnEncryptionComplete(encrypted_types)); |
+ sync_manager_.EncryptDataTypes(encrypted_types); |
+ { |
+ ReadTransaction trans(sync_manager_.GetUserShare()); |
+ EXPECT_EQ(encrypted_types, |
+ GetEncryptedDataTypesFromSyncer(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("%zu", 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("%zu", 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("%zu", 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); |
+ EXPECT_CALL(observer_, OnEncryptionComplete(encrypted_types)); |
+ sync_manager_.EncryptDataTypes(encrypted_types); |
+ |
+ { |
+ ReadTransaction trans(sync_manager_.GetUserShare()); |
+ EXPECT_EQ(encrypted_types, |
+ GetEncryptedDataTypesFromSyncer(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 |