// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/sync_driver/generic_change_processor.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "base/strings/stringprintf.h" #include "components/sync_driver/data_type_error_handler_mock.h" #include "components/sync_driver/sync_api_component_factory.h" #include "sync/api/attachments/fake_attachment_store.h" #include "sync/api/fake_syncable_service.h" #include "sync/api/sync_change.h" #include "sync/api/sync_merge_result.h" #include "sync/internal_api/public/attachments/attachment_service_impl.h" #include "sync/internal_api/public/attachments/fake_attachment_downloader.h" #include "sync/internal_api/public/attachments/fake_attachment_uploader.h" #include "sync/internal_api/public/base/model_type.h" #include "sync/internal_api/public/read_node.h" #include "sync/internal_api/public/read_transaction.h" #include "sync/internal_api/public/sync_encryption_handler.h" #include "sync/internal_api/public/test/test_user_share.h" #include "sync/internal_api/public/user_share.h" #include "sync/internal_api/public/write_node.h" #include "sync/internal_api/public/write_transaction.h" #include "testing/gtest/include/gtest/gtest.h" namespace sync_driver { namespace { const char kTestData[] = "some data"; // A mock that keeps track of attachments passed to StoreAttachments. class MockAttachmentService : public syncer::AttachmentServiceImpl { public: MockAttachmentService(); virtual ~MockAttachmentService(); virtual void StoreAttachments(const syncer::AttachmentList& attachments, const StoreCallback& callback) OVERRIDE; std::vector* attachment_lists(); private: std::vector attachment_lists_; }; MockAttachmentService::MockAttachmentService() : AttachmentServiceImpl( scoped_ptr(new syncer::FakeAttachmentStore( base::MessageLoopProxy::current())), scoped_ptr( new syncer::FakeAttachmentUploader), scoped_ptr( new syncer::FakeAttachmentDownloader), NULL) { } MockAttachmentService::~MockAttachmentService() { } void MockAttachmentService::StoreAttachments( const syncer::AttachmentList& attachments, const StoreCallback& callback) { attachment_lists_.push_back(attachments); AttachmentServiceImpl::StoreAttachments(attachments, callback); } std::vector* MockAttachmentService::attachment_lists() { return &attachment_lists_; } // MockSyncApiComponentFactory needed to initialize GenericChangeProcessor and // pass MockAttachmentService to it. class MockSyncApiComponentFactory : public SyncApiComponentFactory { public: MockSyncApiComponentFactory( scoped_ptr attachment_service) : attachment_service_(attachment_service.Pass()) {} virtual base::WeakPtr GetSyncableServiceForType( syncer::ModelType type) OVERRIDE { // Shouldn't be called for this test. NOTREACHED(); return base::WeakPtr(); } virtual scoped_ptr CreateAttachmentService( const syncer::UserShare& user_share, syncer::AttachmentService::Delegate* delegate) OVERRIDE { EXPECT_TRUE(attachment_service_ != NULL); return attachment_service_.Pass(); } private: scoped_ptr attachment_service_; }; class SyncGenericChangeProcessorTest : public testing::Test { public: // It doesn't matter which type we use. Just pick one. static const syncer::ModelType kType = syncer::PREFERENCES; SyncGenericChangeProcessorTest() : sync_merge_result_(kType), merge_result_ptr_factory_(&sync_merge_result_), syncable_service_ptr_factory_(&fake_syncable_service_), mock_attachment_service_(NULL) {} virtual void SetUp() OVERRIDE { test_user_share_.SetUp(); syncer::ModelTypeSet types = syncer::ProtocolTypes(); for (syncer::ModelTypeSet::Iterator iter = types.First(); iter.Good(); iter.Inc()) { syncer::TestUserShare::CreateRoot(iter.Get(), test_user_share_.user_share()); } test_user_share_.encryption_handler()->Init(); scoped_ptr mock_attachment_service( new MockAttachmentService); // GenericChangeProcessor takes ownership of the AttachmentService, but we // need to have a pointer to it so we can see that it was used properly. // Take a pointer and trust that GenericChangeProcessor does not prematurely // destroy it. mock_attachment_service_ = mock_attachment_service.get(); sync_factory_.reset(new MockSyncApiComponentFactory( mock_attachment_service.PassAs())); change_processor_.reset( new GenericChangeProcessor(&data_type_error_handler_, syncable_service_ptr_factory_.GetWeakPtr(), merge_result_ptr_factory_.GetWeakPtr(), test_user_share_.user_share(), sync_factory_.get())); } virtual void TearDown() OVERRIDE { mock_attachment_service_ = NULL; test_user_share_.TearDown(); } void BuildChildNodes(int n) { syncer::WriteTransaction trans(FROM_HERE, user_share()); syncer::ReadNode root(&trans); ASSERT_EQ(syncer::BaseNode::INIT_OK, root.InitTypeRoot(kType)); for (int i = 0; i < n; ++i) { syncer::WriteNode node(&trans); node.InitUniqueByCreation(kType, root, base::StringPrintf("node%05d", i)); } } GenericChangeProcessor* change_processor() { return change_processor_.get(); } syncer::UserShare* user_share() { return test_user_share_.user_share(); } MockAttachmentService* mock_attachment_service() { return mock_attachment_service_; } private: base::MessageLoopForUI loop_; syncer::SyncMergeResult sync_merge_result_; base::WeakPtrFactory merge_result_ptr_factory_; syncer::FakeSyncableService fake_syncable_service_; base::WeakPtrFactory syncable_service_ptr_factory_; DataTypeErrorHandlerMock data_type_error_handler_; syncer::TestUserShare test_user_share_; MockAttachmentService* mock_attachment_service_; scoped_ptr sync_factory_; scoped_ptr change_processor_; }; // Similar to above, but focused on the method that implements sync/api // interfaces and is hence exposed to datatypes directly. TEST_F(SyncGenericChangeProcessorTest, StressGetAllSyncData) { const int kNumChildNodes = 1000; const int kRepeatCount = 1; ASSERT_NO_FATAL_FAILURE(BuildChildNodes(kNumChildNodes)); for (int i = 0; i < kRepeatCount; ++i) { syncer::SyncDataList sync_data = change_processor()->GetAllSyncData(kType); // Start with a simple test. We can add more in-depth testing later. EXPECT_EQ(static_cast(kNumChildNodes), sync_data.size()); } } TEST_F(SyncGenericChangeProcessorTest, SetGetPasswords) { const int kNumPasswords = 10; sync_pb::PasswordSpecificsData password_data; password_data.set_username_value("user"); sync_pb::EntitySpecifics password_holder; syncer::SyncChangeList change_list; for (int i = 0; i < kNumPasswords; ++i) { password_data.set_password_value( base::StringPrintf("password%i", i)); password_holder.mutable_password()->mutable_client_only_encrypted_data()-> CopyFrom(password_data); change_list.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, syncer::SyncData::CreateLocalData( base::StringPrintf("tag%i", i), base::StringPrintf("title%i", i), password_holder))); } ASSERT_FALSE( change_processor()->ProcessSyncChanges(FROM_HERE, change_list).IsSet()); syncer::SyncDataList password_list( change_processor()->GetAllSyncData(syncer::PASSWORDS)); ASSERT_EQ(password_list.size(), change_list.size()); for (int i = 0; i < kNumPasswords; ++i) { // Verify the password is returned properly. ASSERT_TRUE(password_list[i].GetSpecifics().has_password()); ASSERT_TRUE(password_list[i].GetSpecifics().password(). has_client_only_encrypted_data()); ASSERT_FALSE(password_list[i].GetSpecifics().password().has_encrypted()); const sync_pb::PasswordSpecificsData& sync_password = password_list[i].GetSpecifics().password().client_only_encrypted_data(); const sync_pb::PasswordSpecificsData& change_password = change_list[i].sync_data().GetSpecifics().password(). client_only_encrypted_data(); ASSERT_EQ(sync_password.password_value(), change_password.password_value()); ASSERT_EQ(sync_password.username_value(), change_password.username_value()); // Verify the raw sync data was stored securely. syncer::ReadTransaction read_transaction(FROM_HERE, user_share()); syncer::ReadNode node(&read_transaction); ASSERT_EQ(node.InitByClientTagLookup(syncer::PASSWORDS, base::StringPrintf("tag%i", i)), syncer::BaseNode::INIT_OK); ASSERT_EQ(node.GetTitle(), "encrypted"); const sync_pb::EntitySpecifics& raw_specifics = node.GetEntitySpecifics(); ASSERT_TRUE(raw_specifics.has_password()); ASSERT_TRUE(raw_specifics.password().has_encrypted()); ASSERT_FALSE(raw_specifics.password().has_client_only_encrypted_data()); } } TEST_F(SyncGenericChangeProcessorTest, UpdatePasswords) { const int kNumPasswords = 10; sync_pb::PasswordSpecificsData password_data; password_data.set_username_value("user"); sync_pb::EntitySpecifics password_holder; syncer::SyncChangeList change_list; syncer::SyncChangeList change_list2; for (int i = 0; i < kNumPasswords; ++i) { password_data.set_password_value( base::StringPrintf("password%i", i)); password_holder.mutable_password()->mutable_client_only_encrypted_data()-> CopyFrom(password_data); change_list.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, syncer::SyncData::CreateLocalData( base::StringPrintf("tag%i", i), base::StringPrintf("title%i", i), password_holder))); password_data.set_password_value( base::StringPrintf("password_m%i", i)); password_holder.mutable_password()->mutable_client_only_encrypted_data()-> CopyFrom(password_data); change_list2.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, syncer::SyncData::CreateLocalData( base::StringPrintf("tag%i", i), base::StringPrintf("title_m%i", i), password_holder))); } ASSERT_FALSE( change_processor()->ProcessSyncChanges(FROM_HERE, change_list).IsSet()); ASSERT_FALSE( change_processor()->ProcessSyncChanges(FROM_HERE, change_list2).IsSet()); syncer::SyncDataList password_list( change_processor()->GetAllSyncData(syncer::PASSWORDS)); ASSERT_EQ(password_list.size(), change_list2.size()); for (int i = 0; i < kNumPasswords; ++i) { // Verify the password is returned properly. ASSERT_TRUE(password_list[i].GetSpecifics().has_password()); ASSERT_TRUE(password_list[i].GetSpecifics().password(). has_client_only_encrypted_data()); ASSERT_FALSE(password_list[i].GetSpecifics().password().has_encrypted()); const sync_pb::PasswordSpecificsData& sync_password = password_list[i].GetSpecifics().password().client_only_encrypted_data(); const sync_pb::PasswordSpecificsData& change_password = change_list2[i].sync_data().GetSpecifics().password(). client_only_encrypted_data(); ASSERT_EQ(sync_password.password_value(), change_password.password_value()); ASSERT_EQ(sync_password.username_value(), change_password.username_value()); // Verify the raw sync data was stored securely. syncer::ReadTransaction read_transaction(FROM_HERE, user_share()); syncer::ReadNode node(&read_transaction); ASSERT_EQ(node.InitByClientTagLookup(syncer::PASSWORDS, base::StringPrintf("tag%i", i)), syncer::BaseNode::INIT_OK); ASSERT_EQ(node.GetTitle(), "encrypted"); const sync_pb::EntitySpecifics& raw_specifics = node.GetEntitySpecifics(); ASSERT_TRUE(raw_specifics.has_password()); ASSERT_TRUE(raw_specifics.password().has_encrypted()); ASSERT_FALSE(raw_specifics.password().has_client_only_encrypted_data()); } } // Verify that attachments on newly added or updated SyncData are passed to the // AttachmentService. TEST_F(SyncGenericChangeProcessorTest, ProcessSyncChanges_AddUpdateWithAttachment) { std::string tag = "client_tag"; std::string title = "client_title"; sync_pb::EntitySpecifics specifics; sync_pb::PreferenceSpecifics* pref_specifics = specifics.mutable_preference(); pref_specifics->set_name("test"); syncer::AttachmentList attachments; scoped_refptr attachment_data = new base::RefCountedString; attachment_data->data() = kTestData; attachments.push_back(syncer::Attachment::Create(attachment_data)); attachments.push_back(syncer::Attachment::Create(attachment_data)); // Add a SyncData with two attachments. syncer::SyncChangeList change_list; change_list.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, syncer::SyncData::CreateLocalDataWithAttachments( tag, title, specifics, attachments))); ASSERT_FALSE( change_processor()->ProcessSyncChanges(FROM_HERE, change_list).IsSet()); // Check that the AttachmentService received the new attachments. ASSERT_EQ(mock_attachment_service()->attachment_lists()->size(), 1U); const syncer::AttachmentList& attachments_added = mock_attachment_service()->attachment_lists()->front(); ASSERT_EQ(attachments_added.size(), 2U); ASSERT_EQ(attachments_added[0].GetId(), attachments[0].GetId()); ASSERT_EQ(attachments_added[1].GetId(), attachments[1].GetId()); // Update the SyncData, replacing its two attachments with one new attachment. syncer::AttachmentList new_attachments; new_attachments.push_back(syncer::Attachment::Create(attachment_data)); mock_attachment_service()->attachment_lists()->clear(); change_list.clear(); change_list.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, syncer::SyncData::CreateLocalDataWithAttachments( tag, title, specifics, new_attachments))); ASSERT_FALSE( change_processor()->ProcessSyncChanges(FROM_HERE, change_list).IsSet()); // Check that the AttachmentService received it. ASSERT_EQ(mock_attachment_service()->attachment_lists()->size(), 1U); const syncer::AttachmentList& new_attachments_added = mock_attachment_service()->attachment_lists()->front(); ASSERT_EQ(new_attachments_added.size(), 1U); ASSERT_EQ(new_attachments_added[0].GetId(), new_attachments[0].GetId()); } // Verify that after attachment is uploaded GenericChangeProcessor updates // corresponding entries TEST_F(SyncGenericChangeProcessorTest, AttachmentUploaded) { std::string tag = "client_tag"; std::string title = "client_title"; sync_pb::EntitySpecifics specifics; sync_pb::PreferenceSpecifics* pref_specifics = specifics.mutable_preference(); pref_specifics->set_name("test"); syncer::AttachmentList attachments; scoped_refptr attachment_data = new base::RefCountedString; attachment_data->data() = kTestData; attachments.push_back(syncer::Attachment::Create(attachment_data)); // Add a SyncData with two attachments. syncer::SyncChangeList change_list; change_list.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, syncer::SyncData::CreateLocalDataWithAttachments( tag, title, specifics, attachments))); ASSERT_FALSE( change_processor()->ProcessSyncChanges(FROM_HERE, change_list).IsSet()); sync_pb::AttachmentIdProto attachment_id_proto = attachments[0].GetId().GetProto(); syncer::AttachmentId attachment_id = syncer::AttachmentId::CreateFromProto(attachment_id_proto); change_processor()->OnAttachmentUploaded(attachment_id); syncer::ReadTransaction read_transaction(FROM_HERE, user_share()); syncer::ReadNode node(&read_transaction); ASSERT_EQ(node.InitByClientTagLookup(syncer::PREFERENCES, tag), syncer::BaseNode::INIT_OK); syncer::AttachmentIdList attachment_ids = node.GetAttachmentIds(); EXPECT_EQ(1U, attachment_ids.size()); } } // namespace } // namespace sync_driver