| // Copyright 2015 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/device_info_service.h" |
| |
| #include <map> |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "components/sync_driver/local_device_info_provider_mock.h" |
| #include "sync/api/data_batch.h" |
| #include "sync/api/entity_data.h" |
| #include "sync/api/metadata_batch.h" |
| #include "sync/api/model_type_store.h" |
| #include "sync/internal_api/public/test/model_type_store_test_util.h" |
| #include "sync/protocol/data_type_state.pb.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace sync_driver_v2 { |
| |
| using syncer::SyncError; |
| using syncer_v2::DataBatch; |
| using syncer_v2::EntityChange; |
| using syncer_v2::EntityChangeList; |
| using syncer_v2::EntityData; |
| using syncer_v2::EntityDataMap; |
| using syncer_v2::EntityDataPtr; |
| using syncer_v2::MetadataBatch; |
| using syncer_v2::MetadataChangeList; |
| using syncer_v2::ModelTypeChangeProcessor; |
| using syncer_v2::ModelTypeService; |
| using syncer_v2::ModelTypeStore; |
| using syncer_v2::ModelTypeStoreTestUtil; |
| using syncer_v2::TagAndData; |
| using sync_driver::DeviceInfo; |
| using sync_driver::DeviceInfoTracker; |
| using sync_driver::LocalDeviceInfoProviderMock; |
| using sync_pb::DataTypeState; |
| using sync_pb::DeviceInfoSpecifics; |
| using sync_pb::EntitySpecifics; |
| |
| using ClientTagList = ModelTypeService::ClientTagList; |
| using RecordList = ModelTypeStore::RecordList; |
| using Result = ModelTypeStore::Result; |
| using WriteBatch = ModelTypeStore::WriteBatch; |
| |
| namespace { |
| |
| void AssertResultIsSuccess(Result result) { |
| ASSERT_EQ(Result::SUCCESS, result); |
| } |
| |
| void AssertEqual(const DeviceInfoSpecifics& s1, const DeviceInfoSpecifics& s2) { |
| ASSERT_EQ(s1.cache_guid(), s2.cache_guid()); |
| ASSERT_EQ(s1.client_name(), s2.client_name()); |
| ASSERT_EQ(s1.device_type(), s2.device_type()); |
| ASSERT_EQ(s1.sync_user_agent(), s2.sync_user_agent()); |
| ASSERT_EQ(s1.chrome_version(), s2.chrome_version()); |
| ASSERT_EQ(s1.signin_scoped_device_id(), s2.signin_scoped_device_id()); |
| } |
| |
| void AssertEqual(const DeviceInfoSpecifics& specifics, |
| const DeviceInfo& model) { |
| ASSERT_EQ(specifics.cache_guid(), model.guid()); |
| ASSERT_EQ(specifics.client_name(), model.client_name()); |
| ASSERT_EQ(specifics.device_type(), model.device_type()); |
| ASSERT_EQ(specifics.sync_user_agent(), model.sync_user_agent()); |
| ASSERT_EQ(specifics.chrome_version(), model.chrome_version()); |
| ASSERT_EQ(specifics.signin_scoped_device_id(), |
| model.signin_scoped_device_id()); |
| } |
| |
| void AssertErrorFromDataBatch(SyncError error, scoped_ptr<DataBatch> batch) { |
| ASSERT_TRUE(error.IsSet()); |
| } |
| |
| void AssertExpectedFromDataBatch( |
| std::map<std::string, DeviceInfoSpecifics> expected, |
| SyncError error, |
| scoped_ptr<DataBatch> batch) { |
| ASSERT_FALSE(error.IsSet()); |
| while (batch->HasNext()) { |
| const TagAndData& pair = batch->Next(); |
| std::map<std::string, DeviceInfoSpecifics>::iterator iter = |
| expected.find(pair.first); |
| ASSERT_NE(iter, expected.end()); |
| AssertEqual(iter->second, pair.second->specifics.device_info()); |
| // Removing allows us to verify we don't see the same item multiple times, |
| // and that we saw everything we expected. |
| expected.erase(iter); |
| } |
| ASSERT_TRUE(expected.empty()); |
| } |
| |
| // Creats an EntityData/EntityDataPtr around a copy of the given specifics. |
| EntityDataPtr SpecificsToEntity(const DeviceInfoSpecifics& specifics) { |
| EntityData data; |
| // These tests do not care about the tag hash, but EntityData and friends |
| // cannot differentiate between the default EntityData object if the hash |
| // is unset, which causes pass/copy operations to no-op and things start to |
| // break, so we throw in a junk value and forget about it. |
| data.client_tag_hash = "junk"; |
| *data.specifics.mutable_device_info() = specifics; |
| return data.PassToPtr(); |
| } |
| |
| // Instead of actually processing anything, simply accumulates all instructions |
| // in members that can then be accessed. TODO(skym): If this ends up being |
| // useful for other model type unittests it should be moved out to a shared |
| // location. |
| class FakeModelTypeChangeProcessor : public ModelTypeChangeProcessor { |
| public: |
| FakeModelTypeChangeProcessor() {} |
| ~FakeModelTypeChangeProcessor() override {} |
| |
| void Put(const std::string& client_tag, |
| scoped_ptr<EntityData> entity_data, |
| MetadataChangeList* metadata_changes) override { |
| put_map_.insert(std::make_pair(client_tag, std::move(entity_data))); |
| } |
| |
| void Delete(const std::string& client_tag, |
| MetadataChangeList* metadata_changes) override { |
| delete_set_.insert(client_tag); |
| } |
| |
| void OnMetadataLoaded(scoped_ptr<MetadataBatch> batch) override { |
| std::swap(metadata_, batch); |
| } |
| |
| const std::map<std::string, scoped_ptr<EntityData>>& put_map() const { |
| return put_map_; |
| } |
| const std::set<std::string>& delete_set() const { return delete_set_; } |
| const MetadataBatch* metadata() const { return metadata_.get(); } |
| |
| private: |
| std::map<std::string, scoped_ptr<EntityData>> put_map_; |
| std::set<std::string> delete_set_; |
| scoped_ptr<MetadataBatch> metadata_; |
| }; |
| |
| } // namespace |
| |
| class DeviceInfoServiceTest : public testing::Test, |
| public DeviceInfoTracker::Observer { |
| protected: |
| ~DeviceInfoServiceTest() override { |
| // Some tests may never initialize the service. |
| if (service_) |
| service_->RemoveObserver(this); |
| |
| // Force all remaining (store) tasks to execute so we don't leak memory. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void OnDeviceInfoChange() override { change_count_++; } |
| |
| protected: |
| DeviceInfoServiceTest() |
| : change_count_(0), |
| store_(ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()), |
| local_device_(new LocalDeviceInfoProviderMock( |
| "guid_1", |
| "client_1", |
| "Chromium 10k", |
| "Chrome 10k", |
| sync_pb::SyncEnums_DeviceType_TYPE_LINUX, |
| "device_id")) {} |
| |
| // Initialized the service based on the current local device and store. Can |
| // only be called once per run, as it passes |store_|. |
| void InitializeService() { |
| ASSERT_TRUE(store_); |
| service_.reset(new DeviceInfoService( |
| local_device_.get(), |
| base::Bind(&ModelTypeStoreTestUtil::MoveStoreToCallback, |
| base::Passed(&store_)))); |
| service_->AddObserver(this); |
| } |
| |
| // Creates the service and runs any outstanding tasks. This will typically |
| // cause all initialization callbacks between the sevice and store to fire. |
| void InitializeAndPump() { |
| InitializeService(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Creates a new processor and sets it on the service. We typically need to |
| // pump in this scenario because metadata is going to need to be loading from |
| // the store and given to the processor, which is async. |
| void SetProcessorAndPump() { |
| processor_ = new FakeModelTypeChangeProcessor(); |
| service()->set_change_processor(make_scoped_ptr(processor_)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Generates a specifics object with slightly differing values. Will generate |
| // the same values on each run of a test because a simple counter is used to |
| // vary field values. |
| DeviceInfoSpecifics GenerateTestSpecifics() { |
| int label = ++generated_count_; |
| DeviceInfoSpecifics specifics; |
| specifics.set_cache_guid(base::StringPrintf("cache guid %d", label)); |
| specifics.set_client_name(base::StringPrintf("client name %d", label)); |
| specifics.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX); |
| specifics.set_sync_user_agent( |
| base::StringPrintf("sync user agent %d", label)); |
| specifics.set_chrome_version( |
| base::StringPrintf("chrome version %d", label)); |
| specifics.set_signin_scoped_device_id( |
| base::StringPrintf("signin scoped device id %d", label)); |
| return specifics; |
| } |
| |
| // Override to allow specific cache guids. |
| DeviceInfoSpecifics GenerateTestSpecifics(const std::string& guid) { |
| DeviceInfoSpecifics specifics(GenerateTestSpecifics()); |
| specifics.set_cache_guid(guid); |
| return specifics; |
| } |
| |
| // Helper method to reduce duplicated code between tests. Wraps the given |
| // specifics object in an EntityData and EntityChange of type ACTION_ADD, and |
| // pushes them onto the given change list. The corresponding client tag the |
| // service determines is returned. Instance method because we need access to |
| // service to generate the tag. |
| std::string PushBackEntityChangeAdd(const DeviceInfoSpecifics& specifics, |
| EntityChangeList* changes) { |
| EntityDataPtr ptr = SpecificsToEntity(specifics); |
| const std::string tag = service()->GetClientTag(ptr.value()); |
| changes->push_back(EntityChange::CreateAdd(tag, ptr)); |
| return tag; |
| } |
| |
| // Allows access to the store before that will ultimately be used to |
| // initialize the service. |
| ModelTypeStore* store() { |
| EXPECT_TRUE(store_); |
| return store_.get(); |
| } |
| |
| // Get the number of times the service notifies observers of changes. |
| int change_count() { return change_count_; } |
| |
| // Allows overriding the provider before the service is initialized. |
| void set_local_device(scoped_ptr<LocalDeviceInfoProviderMock> provider) { |
| ASSERT_FALSE(service_); |
| std::swap(local_device_, provider); |
| } |
| LocalDeviceInfoProviderMock* local_device() { return local_device_.get(); } |
| |
| // Allows access to the service after InitializeService() is called. |
| DeviceInfoService* service() { |
| EXPECT_TRUE(service_); |
| return service_.get(); |
| } |
| |
| FakeModelTypeChangeProcessor* processor() { |
| EXPECT_TRUE(processor_); |
| return processor_; |
| } |
| |
| // Should only be called after the service has been initialized. Will first |
| // recover the service's store, so another can be initialized later, and then |
| // deletes the service. |
| void ShutdownService() { |
| ASSERT_TRUE(service_); |
| std::swap(store_, service_->store_); |
| service_->RemoveObserver(this); |
| service_.reset(); |
| } |
| |
| private: |
| int change_count_; |
| |
| // Although we never use this in this class, the in memory model type store |
| // grabs the current task runner from a static accessor which point at this |
| // message loop. Must be declared/initilized before we call the synchronous |
| // CreateInMemoryStoreForTest. |
| base::MessageLoop message_loop_; |
| |
| // Holds the store while the service is not initialized. |
| scoped_ptr<ModelTypeStore> store_; |
| |
| scoped_ptr<LocalDeviceInfoProviderMock> local_device_; |
| |
| // Not initialized immediately (upon test's constructor). This allows each |
| // test case to modify the dependencies the service will be constructed with. |
| scoped_ptr<DeviceInfoService> service_; |
| |
| // A non-owning pointer to the processor given to the service. Will be nullptr |
| // before being given to the service, to make ownership easier. |
| FakeModelTypeChangeProcessor* processor_ = nullptr; |
| |
| // A monotonically increasing label for generated specifics objects with data |
| // that is slightly different from eachother. |
| int generated_count_ = 0; |
| }; |
| |
| namespace { |
| |
| TEST_F(DeviceInfoServiceTest, EmptyDataReconciliation) { |
| InitializeService(); |
| ASSERT_EQ(0u, service()->GetAllDeviceInfo().size()); |
| base::RunLoop().RunUntilIdle(); |
| // TODO(skym): crbug.com/582460: Verify reconciliation has happened. |
| } |
| |
| TEST_F(DeviceInfoServiceTest, LocalProviderSubscription) { |
| set_local_device(make_scoped_ptr(new LocalDeviceInfoProviderMock())); |
| InitializeAndPump(); |
| ASSERT_EQ(0u, service()->GetAllDeviceInfo().size()); |
| local_device()->Initialize(make_scoped_ptr( |
| new DeviceInfo("guid_1", "client_1", "Chromium 10k", "Chrome 10k", |
| sync_pb::SyncEnums_DeviceType_TYPE_LINUX, "device_id"))); |
| // TODO(skym): crbug.com/582460: Verify reconciliation has happened. |
| } |
| |
| TEST_F(DeviceInfoServiceTest, NonEmptyStoreLoad) { |
| // Override the provider so that reconciliation never happens. |
| set_local_device(make_scoped_ptr(new LocalDeviceInfoProviderMock())); |
| |
| scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| DeviceInfoSpecifics specifics(GenerateTestSpecifics()); |
| store()->WriteData(batch.get(), "tag", specifics.SerializeAsString()); |
| store()->CommitWriteBatch(std::move(batch), |
| base::Bind(&AssertResultIsSuccess)); |
| |
| InitializeAndPump(); |
| |
| ScopedVector<DeviceInfo> all_device_info(service()->GetAllDeviceInfo()); |
| ASSERT_EQ(1u, all_device_info.size()); |
| AssertEqual(specifics, *all_device_info[0]); |
| AssertEqual(specifics, *service()->GetDeviceInfo("tag").get()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, GetClientTagNormal) { |
| InitializeAndPump(); |
| const std::string guid = "abc"; |
| EntitySpecifics entity_specifics; |
| entity_specifics.mutable_device_info()->set_cache_guid(guid); |
| EntityData entity_data; |
| entity_data.specifics = entity_specifics; |
| EXPECT_EQ(guid, service()->GetClientTag(entity_data)); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, GetClientTagEmpty) { |
| InitializeAndPump(); |
| EntitySpecifics entity_specifics; |
| entity_specifics.mutable_device_info(); |
| EntityData entity_data; |
| entity_data.specifics = entity_specifics; |
| EXPECT_EQ("", service()->GetClientTag(entity_data)); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, TestInitStoreThenProc) { |
| scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| DeviceInfoSpecifics specifics(GenerateTestSpecifics()); |
| store()->WriteData(batch.get(), "tag", specifics.SerializeAsString()); |
| DataTypeState state; |
| state.set_encryption_key_name("ekn"); |
| store()->WriteGlobalMetadata(batch.get(), state.SerializeAsString()); |
| store()->CommitWriteBatch(std::move(batch), |
| base::Bind(&AssertResultIsSuccess)); |
| |
| InitializeAndPump(); |
| |
| // Verify that we have data. We do this because we're testing that the service |
| // may sometimes come up after our store init is fully completed. |
| ScopedVector<DeviceInfo> all_device_info(service()->GetAllDeviceInfo()); |
| ASSERT_EQ(1u, all_device_info.size()); |
| AssertEqual(specifics, *all_device_info[0]); |
| AssertEqual(specifics, *service()->GetDeviceInfo("tag").get()); |
| |
| SetProcessorAndPump(); |
| ASSERT_TRUE(processor()->metadata()); |
| ASSERT_EQ(state.encryption_key_name(), |
| processor()->metadata()->GetDataTypeState().encryption_key_name()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, TestInitProcBeforeStoreFinishes) { |
| scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| DeviceInfoSpecifics specifics(GenerateTestSpecifics()); |
| store()->WriteData(batch.get(), "tag", specifics.SerializeAsString()); |
| DataTypeState state; |
| state.set_encryption_key_name("ekn"); |
| store()->WriteGlobalMetadata(batch.get(), state.SerializeAsString()); |
| store()->CommitWriteBatch(std::move(batch), |
| base::Bind(&AssertResultIsSuccess)); |
| |
| InitializeService(); |
| // Verify we have _NO_ data yet, to verify that we're testing when the |
| // processor is attached and ready before our store init is fully completed. |
| ASSERT_EQ(0u, service()->GetAllDeviceInfo().size()); |
| |
| SetProcessorAndPump(); |
| ASSERT_TRUE(processor()->metadata()); |
| ASSERT_EQ(state.encryption_key_name(), |
| processor()->metadata()->GetDataTypeState().encryption_key_name()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, GetData) { |
| scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| DeviceInfoSpecifics specifics1(GenerateTestSpecifics()); |
| DeviceInfoSpecifics specifics3(GenerateTestSpecifics()); |
| store()->WriteData(batch.get(), "tag1", specifics1.SerializeAsString()); |
| store()->WriteData(batch.get(), "tag2", |
| GenerateTestSpecifics().SerializeAsString()); |
| store()->WriteData(batch.get(), "tag3", specifics3.SerializeAsString()); |
| store()->CommitWriteBatch(std::move(batch), |
| base::Bind(&AssertResultIsSuccess)); |
| |
| InitializeAndPump(); |
| |
| std::map<std::string, DeviceInfoSpecifics> expected; |
| expected["tag1"] = specifics1; |
| expected["tag3"] = specifics3; |
| ClientTagList client_tags; |
| client_tags.push_back("tag1"); |
| client_tags.push_back("tag3"); |
| service()->GetData(client_tags, |
| base::Bind(&AssertExpectedFromDataBatch, expected)); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, GetDataMissing) { |
| InitializeAndPump(); |
| std::map<std::string, DeviceInfoSpecifics> expected; |
| ClientTagList client_tags; |
| client_tags.push_back("tag1"); |
| service()->GetData(client_tags, |
| base::Bind(&AssertExpectedFromDataBatch, expected)); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, GetDataNotInitialized) { |
| InitializeService(); |
| ClientTagList client_tags; |
| service()->GetData(client_tags, base::Bind(&AssertErrorFromDataBatch)); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, GetAllData) { |
| scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| DeviceInfoSpecifics specifics1(GenerateTestSpecifics()); |
| DeviceInfoSpecifics specifics2(GenerateTestSpecifics()); |
| store()->WriteData(batch.get(), "tag1", specifics1.SerializeAsString()); |
| store()->WriteData(batch.get(), "tag2", specifics2.SerializeAsString()); |
| store()->CommitWriteBatch(std::move(batch), |
| base::Bind(&AssertResultIsSuccess)); |
| |
| InitializeAndPump(); |
| |
| std::map<std::string, DeviceInfoSpecifics> expected; |
| expected["tag1"] = specifics1; |
| expected["tag2"] = specifics2; |
| ClientTagList client_tags; |
| client_tags.push_back("tag1"); |
| client_tags.push_back("tag2"); |
| service()->GetData(client_tags, |
| base::Bind(&AssertExpectedFromDataBatch, expected)); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, GetAllDataNotInitialized) { |
| InitializeService(); |
| service()->GetAllData(base::Bind(&AssertErrorFromDataBatch)); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, ApplySyncChangesBeforeInit) { |
| InitializeService(); |
| const SyncError error = service()->ApplySyncChanges( |
| service()->CreateMetadataChangeList(), EntityChangeList()); |
| EXPECT_TRUE(error.IsSet()); |
| EXPECT_EQ(0, change_count()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, ApplySyncChangesEmpty) { |
| InitializeAndPump(); |
| const SyncError error = service()->ApplySyncChanges( |
| service()->CreateMetadataChangeList(), EntityChangeList()); |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_EQ(0, change_count()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, ApplySyncChangesInMemory) { |
| InitializeAndPump(); |
| |
| DeviceInfoSpecifics specifics = GenerateTestSpecifics(); |
| EntityChangeList add_changes; |
| const std::string tag = PushBackEntityChangeAdd(specifics, &add_changes); |
| SyncError error = service()->ApplySyncChanges( |
| service()->CreateMetadataChangeList(), add_changes); |
| |
| EXPECT_FALSE(error.IsSet()); |
| scoped_ptr<DeviceInfo> info = service()->GetDeviceInfo(tag); |
| ASSERT_TRUE(info); |
| AssertEqual(specifics, *info.get()); |
| EXPECT_EQ(1, change_count()); |
| |
| EntityChangeList delete_changes; |
| delete_changes.push_back(EntityChange::CreateDelete(tag)); |
| error = service()->ApplySyncChanges(service()->CreateMetadataChangeList(), |
| delete_changes); |
| |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_FALSE(service()->GetDeviceInfo(tag)); |
| EXPECT_EQ(2, change_count()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, ApplySyncChangesStore) { |
| InitializeAndPump(); |
| |
| DeviceInfoSpecifics specifics = GenerateTestSpecifics(); |
| EntityChangeList data_changes; |
| const std::string tag = PushBackEntityChangeAdd(specifics, &data_changes); |
| |
| DataTypeState state; |
| state.set_encryption_key_name("ekn"); |
| scoped_ptr<MetadataChangeList> metadata_changes( |
| service()->CreateMetadataChangeList()); |
| metadata_changes->UpdateDataTypeState(state); |
| |
| const SyncError error = |
| service()->ApplySyncChanges(std::move(metadata_changes), data_changes); |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_EQ(1, change_count()); |
| |
| // Force write/commit tasks to finish before shutdown. |
| base::RunLoop().RunUntilIdle(); |
| ShutdownService(); |
| InitializeAndPump(); |
| SetProcessorAndPump(); |
| |
| scoped_ptr<DeviceInfo> info = service()->GetDeviceInfo(tag); |
| ASSERT_TRUE(info); |
| AssertEqual(specifics, *info.get()); |
| EXPECT_TRUE(processor()->metadata()); |
| // TODO(skym): Uncomment once SimpleMetadataChangeList::TransferChanges is |
| // implemented. |
| // EXPECT_EQ(state.encryption_key_name(), |
| // processor()->metadata()->GetDataTypeState().encryption_key_name()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, ApplySyncChangesWithLocalGuid) { |
| InitializeAndPump(); |
| |
| // The point of this test is to try to apply remote changes that have the same |
| // cache guid as the local device. The service should ignore these changes |
| // since only it should be performing writes on its data. |
| DeviceInfoSpecifics specifics = |
| GenerateTestSpecifics(local_device()->GetLocalDeviceInfo()->guid()); |
| |
| EntityChangeList data_changes; |
| const std::string tag = PushBackEntityChangeAdd(specifics, &data_changes); |
| const SyncError error = service()->ApplySyncChanges( |
| service()->CreateMetadataChangeList(), data_changes); |
| |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_FALSE(service()->GetDeviceInfo(tag)); |
| EXPECT_EQ(0, change_count()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, ApplyDeleteNonexistent) { |
| InitializeAndPump(); |
| EntityChangeList delete_changes; |
| delete_changes.push_back(EntityChange::CreateDelete("tag")); |
| const SyncError error = service()->ApplySyncChanges( |
| service()->CreateMetadataChangeList(), delete_changes); |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_EQ(0, change_count()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, MergeBeforeInit) { |
| InitializeService(); |
| const SyncError error = service()->MergeSyncData( |
| service()->CreateMetadataChangeList(), EntityDataMap()); |
| EXPECT_TRUE(error.IsSet()); |
| EXPECT_EQ(0, change_count()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, MergeEmpty) { |
| InitializeAndPump(); |
| SetProcessorAndPump(); |
| const SyncError error = service()->MergeSyncData( |
| service()->CreateMetadataChangeList(), EntityDataMap()); |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_EQ(0, change_count()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, MergeWithData) { |
| const DeviceInfoSpecifics unique_local(GenerateTestSpecifics("unique_local")); |
| const DeviceInfoSpecifics conflict_local(GenerateTestSpecifics("conflict")); |
| DeviceInfoSpecifics conflict_remote(GenerateTestSpecifics("conflict")); |
| DeviceInfoSpecifics unique_remote(GenerateTestSpecifics("unique_remote")); |
| |
| scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| store()->WriteData(batch.get(), unique_local.cache_guid(), |
| unique_local.SerializeAsString()); |
| store()->WriteData(batch.get(), conflict_local.cache_guid(), |
| conflict_local.SerializeAsString()); |
| store()->CommitWriteBatch(std::move(batch), |
| base::Bind(&AssertResultIsSuccess)); |
| |
| InitializeAndPump(); |
| SetProcessorAndPump(); |
| |
| EntityDataMap remote_input; |
| remote_input[conflict_remote.cache_guid()] = |
| SpecificsToEntity(conflict_remote); |
| remote_input[unique_remote.cache_guid()] = SpecificsToEntity(unique_remote); |
| |
| DataTypeState state; |
| state.set_encryption_key_name("ekn"); |
| scoped_ptr<MetadataChangeList> metadata_changes( |
| service()->CreateMetadataChangeList()); |
| metadata_changes->UpdateDataTypeState(state); |
| |
| const SyncError error = |
| service()->MergeSyncData(std::move(metadata_changes), remote_input); |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_EQ(1, change_count()); |
| |
| // The remote should beat the local in conflict. |
| EXPECT_EQ(3u, service()->GetAllDeviceInfo().size()); |
| AssertEqual(unique_local, *service()->GetDeviceInfo("unique_local").get()); |
| AssertEqual(unique_remote, *service()->GetDeviceInfo("unique_remote").get()); |
| AssertEqual(conflict_remote, *service()->GetDeviceInfo("conflict").get()); |
| |
| // Service should have told the processor about the existance of unique_local. |
| EXPECT_TRUE(processor()->delete_set().empty()); |
| EXPECT_EQ(1u, processor()->put_map().size()); |
| const auto& it = processor()->put_map().find("unique_local"); |
| ASSERT_NE(processor()->put_map().end(), it); |
| AssertEqual(unique_local, it->second->specifics.device_info()); |
| |
| // TODO(skym): Uncomment once SimpleMetadataChangeList::TransferChanges is |
| // implemented. |
| // ASSERT_EQ(state.encryption_key_name(), |
| // processor()->metadata()->GetDataTypeState().encryption_key_name()); |
| } |
| |
| TEST_F(DeviceInfoServiceTest, MergeLocalGuid) { |
| InitializeAndPump(); |
| SetProcessorAndPump(); |
| |
| // Service should ignore this because it uses the local device's guid. |
| DeviceInfoSpecifics specifics( |
| GenerateTestSpecifics(local_device()->GetLocalDeviceInfo()->guid())); |
| EntityDataMap remote_input; |
| remote_input[specifics.cache_guid()] = SpecificsToEntity(specifics); |
| |
| const SyncError error = service()->MergeSyncData( |
| service()->CreateMetadataChangeList(), remote_input); |
| EXPECT_FALSE(error.IsSet()); |
| EXPECT_EQ(0, change_count()); |
| EXPECT_TRUE(service()->GetAllDeviceInfo().empty()); |
| EXPECT_TRUE(processor()->delete_set().empty()); |
| EXPECT_TRUE(processor()->put_map().empty()); |
| } |
| |
| } // namespace |
| |
| } // namespace sync_driver_v2 |