| // 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_device_info/device_info_sync_bridge.h" |
| |
| #include <algorithm> |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "components/sync/base/time.h" |
| #include "components/sync/model/data_batch.h" |
| #include "components/sync/model/data_type_activation_request.h" |
| #include "components/sync/model/data_type_error_handler_mock.h" |
| #include "components/sync/model/entity_data.h" |
| #include "components/sync/model/metadata_batch.h" |
| #include "components/sync/model/mock_model_type_change_processor.h" |
| #include "components/sync/model/model_type_store_test_util.h" |
| #include "components/sync/protocol/model_type_state.pb.h" |
| #include "components/sync/test/test_matchers.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace syncer { |
| namespace { |
| |
| using base::OneShotTimer; |
| using sync_pb::DeviceInfoSpecifics; |
| using sync_pb::EntitySpecifics; |
| using sync_pb::ModelTypeState; |
| using testing::_; |
| using testing::IsEmpty; |
| using testing::Matcher; |
| using testing::NotNull; |
| using testing::Pair; |
| using testing::Return; |
| using testing::SizeIs; |
| using testing::UnorderedElementsAre; |
| |
| using DeviceInfoList = std::vector<std::unique_ptr<DeviceInfo>>; |
| using StorageKeyList = ModelTypeSyncBridge::StorageKeyList; |
| using RecordList = ModelTypeStore::RecordList; |
| using StartCallback = ModelTypeControllerDelegate::StartCallback; |
| using WriteBatch = ModelTypeStore::WriteBatch; |
| |
| const int kLocalSuffix = 0; |
| |
| MATCHER_P(HasDeviceInfo, expected, "") { |
| return arg.device_info().SerializeAsString() == expected.SerializeAsString(); |
| } |
| |
| MATCHER_P(EqualsProto, expected, "") { |
| return arg.SerializeAsString() == expected.SerializeAsString(); |
| } |
| |
| MATCHER_P(ModelEqualsSpecifics, expected_specifics, "") { |
| // Note that we ignore the device name here to avoid having to inject the |
| // local device's. |
| return expected_specifics.cache_guid() == arg.guid() && |
| expected_specifics.device_type() == arg.device_type() && |
| expected_specifics.sync_user_agent() == arg.sync_user_agent() && |
| expected_specifics.chrome_version() == arg.chrome_version() && |
| expected_specifics.signin_scoped_device_id() == |
| arg.signin_scoped_device_id() && |
| expected_specifics.feature_fields() |
| .send_tab_to_self_receiving_enabled() == |
| arg.send_tab_to_self_receiving_enabled(); |
| } |
| |
| Matcher<std::unique_ptr<EntityData>> HasSpecifics( |
| const Matcher<sync_pb::EntitySpecifics>& m) { |
| return testing::Pointee(testing::Field(&EntityData::specifics, m)); |
| } |
| |
| MATCHER(HasLastUpdatedAboutNow, "") { |
| const sync_pb::DeviceInfoSpecifics& specifics = arg.device_info(); |
| const base::Time now = base::Time::Now(); |
| const base::TimeDelta tolerance = base::TimeDelta::FromMinutes(1); |
| const base::Time actual_last_updated = |
| ProtoTimeToTime(specifics.last_updated_timestamp()); |
| |
| if (actual_last_updated < now - tolerance) { |
| *result_listener << "which is too far in the past"; |
| return false; |
| } |
| if (actual_last_updated > now + tolerance) { |
| *result_listener << "which is too far in the future"; |
| return false; |
| } |
| return true; |
| } |
| |
| std::string CacheGuidForSuffix(int suffix) { |
| return base::StringPrintf("cache guid %d", suffix); |
| } |
| |
| std::string ClientNameForSuffix(int suffix) { |
| return base::StringPrintf("client name %d", suffix); |
| } |
| |
| std::string SyncUserAgentForSuffix(int suffix) { |
| return base::StringPrintf("sync user agent %d", suffix); |
| } |
| |
| std::string ChromeVersionForSuffix(int suffix) { |
| return base::StringPrintf("chrome version %d", suffix); |
| } |
| |
| std::string SigninScopedDeviceIdForSuffix(int suffix) { |
| return base::StringPrintf("signin scoped device id %d", suffix); |
| } |
| |
| DataTypeActivationRequest TestDataTypeActivationRequest() { |
| DataTypeActivationRequest request; |
| request.cache_guid = CacheGuidForSuffix(kLocalSuffix); |
| return request; |
| } |
| |
| DeviceInfoSpecifics CreateSpecifics( |
| int suffix, |
| base::Time last_updated = base::Time::Now()) { |
| DeviceInfoSpecifics specifics; |
| specifics.set_cache_guid(CacheGuidForSuffix(suffix)); |
| specifics.set_client_name(ClientNameForSuffix(suffix)); |
| specifics.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX); |
| specifics.set_sync_user_agent(SyncUserAgentForSuffix(suffix)); |
| specifics.set_chrome_version(ChromeVersionForSuffix(suffix)); |
| specifics.set_signin_scoped_device_id(SigninScopedDeviceIdForSuffix(suffix)); |
| specifics.set_last_updated_timestamp(TimeToProtoTime(last_updated)); |
| specifics.mutable_feature_fields()->set_send_tab_to_self_receiving_enabled( |
| true); |
| return specifics; |
| } |
| |
| ModelTypeState StateWithEncryption(const std::string& encryption_key_name) { |
| ModelTypeState state; |
| state.set_initial_sync_done(true); |
| state.set_cache_guid(CacheGuidForSuffix(kLocalSuffix)); |
| state.set_encryption_key_name(encryption_key_name); |
| return state; |
| } |
| |
| // Creates an EntityData around a copy of the given specifics. |
| std::unique_ptr<EntityData> SpecificsToEntity( |
| const DeviceInfoSpecifics& specifics) { |
| auto data = std::make_unique<EntityData>(); |
| *data->specifics.mutable_device_info() = specifics; |
| return data; |
| } |
| |
| std::string CacheGuidToTag(const std::string& guid) { |
| return "DeviceInfo_" + guid; |
| } |
| |
| // Helper method to reduce duplicated code between tests. Wraps the given |
| // specifics objects in an EntityData and EntityChange of type ACTION_ADD, and |
| // returns an EntityChangeList containing them all. Order is maintained. |
| EntityChangeList EntityAddList( |
| const std::vector<DeviceInfoSpecifics>& specifics_list) { |
| EntityChangeList changes; |
| for (const auto& specifics : specifics_list) { |
| changes.push_back(EntityChange::CreateAdd(specifics.cache_guid(), |
| SpecificsToEntity(specifics))); |
| } |
| return changes; |
| } |
| |
| std::map<std::string, sync_pb::EntitySpecifics> DataBatchToSpecificsMap( |
| std::unique_ptr<DataBatch> batch) { |
| std::map<std::string, sync_pb::EntitySpecifics> storage_key_to_specifics; |
| while (batch && batch->HasNext()) { |
| const syncer::KeyAndData& pair = batch->Next(); |
| storage_key_to_specifics[pair.first] = pair.second->specifics; |
| } |
| return storage_key_to_specifics; |
| } |
| |
| class TestLocalDeviceInfoProvider : public MutableLocalDeviceInfoProvider { |
| public: |
| TestLocalDeviceInfoProvider() = default; |
| ~TestLocalDeviceInfoProvider() override = default; |
| |
| // MutableLocalDeviceInfoProvider implementation. |
| void Initialize(const std::string& cache_guid, |
| const std::string& session_name) override { |
| local_device_info_ = std::make_unique<DeviceInfo>( |
| cache_guid, session_name, ChromeVersionForSuffix(kLocalSuffix), |
| SyncUserAgentForSuffix(kLocalSuffix), |
| sync_pb::SyncEnums_DeviceType_TYPE_LINUX, |
| SigninScopedDeviceIdForSuffix(kLocalSuffix), base::Time(), true); |
| } |
| |
| void Clear() override { local_device_info_.reset(); } |
| |
| version_info::Channel GetChannel() const override { |
| return version_info::Channel::UNKNOWN; |
| } |
| |
| const DeviceInfo* GetLocalDeviceInfo() const override { |
| return local_device_info_.get(); |
| } |
| |
| std::unique_ptr<Subscription> RegisterOnInitializedCallback( |
| const base::RepeatingClosure& callback) override { |
| NOTIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| private: |
| std::unique_ptr<DeviceInfo> local_device_info_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestLocalDeviceInfoProvider); |
| }; |
| |
| class DeviceInfoSyncBridgeTest : public testing::Test, |
| public DeviceInfoTracker::Observer { |
| protected: |
| DeviceInfoSyncBridgeTest() |
| : store_(ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()) { |
| ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true)); |
| } |
| |
| ~DeviceInfoSyncBridgeTest() override { |
| // Some tests may never initialize the bridge. |
| if (bridge_) |
| bridge_->RemoveObserver(this); |
| |
| // Force all remaining (store) tasks to execute so we don't leak memory. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void OnDeviceInfoChange() override { change_count_++; } |
| |
| // Initialized the bridge based on the current local device and store. |
| void InitializeBridge() { |
| bridge_ = std::make_unique<DeviceInfoSyncBridge>( |
| std::make_unique<TestLocalDeviceInfoProvider>(), |
| ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()), |
| mock_processor_.CreateForwardingProcessor()); |
| bridge_->AddObserver(this); |
| } |
| |
| // Creates the bridge and runs any outstanding tasks. This will typically |
| // cause all initialization callbacks between the sevice and store to fire. |
| void InitializeAndPump() { |
| InitializeBridge(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Creates the bridge with no prior data on the store, and mimics sync being |
| // enabled by the user with no remote data. |
| void InitializeAndMergeInitialData() { |
| InitializeAndPump(); |
| bridge()->OnSyncStarting(TestDataTypeActivationRequest()); |
| |
| std::unique_ptr<MetadataChangeList> metadata_change_list = |
| bridge()->CreateMetadataChangeList(); |
| |
| metadata_change_list->UpdateModelTypeState(StateWithEncryption("")); |
| bridge()->MergeSyncData(std::move(metadata_change_list), |
| EntityChangeList()); |
| } |
| |
| // Allows access to the store before that will ultimately be used to |
| // initialize the bridge. |
| ModelTypeStore* store() { |
| EXPECT_TRUE(store_); |
| return store_.get(); |
| } |
| |
| // Get the number of times the bridge notifies observers of changes. |
| int change_count() { return change_count_; } |
| |
| LocalDeviceInfoProvider* local_device() { |
| return bridge_->GetLocalDeviceInfoProvider(); |
| } |
| |
| // Allows access to the bridge after InitializeBridge() is called. |
| DeviceInfoSyncBridge* bridge() { |
| EXPECT_TRUE(bridge_); |
| return bridge_.get(); |
| } |
| |
| MockModelTypeChangeProcessor* processor() { return &mock_processor_; } |
| // Should only be called after the bridge has been initialized. Will first |
| // recover the bridge's store, so another can be initialized later, and then |
| // deletes the bridge. |
| void PumpAndShutdown() { |
| ASSERT_TRUE(bridge_); |
| base::RunLoop().RunUntilIdle(); |
| bridge_->RemoveObserver(this); |
| } |
| |
| void RestartBridge() { |
| PumpAndShutdown(); |
| InitializeAndPump(); |
| } |
| |
| void ForcePulse() { bridge()->ForcePulseForTest(); } |
| |
| void CommitToStoreAndWait(std::unique_ptr<WriteBatch> batch) { |
| base::RunLoop loop; |
| store()->CommitWriteBatch( |
| std::move(batch), |
| base::BindOnce( |
| [](base::RunLoop* loop, const base::Optional<ModelError>& result) { |
| EXPECT_FALSE(result.has_value()) << result->ToString(); |
| loop->Quit(); |
| }, |
| &loop)); |
| loop.Run(); |
| } |
| |
| void WriteDataToStore( |
| const std::vector<DeviceInfoSpecifics>& specifics_list) { |
| std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| for (auto& specifics : specifics_list) { |
| batch->WriteData(specifics.cache_guid(), specifics.SerializeAsString()); |
| } |
| CommitToStoreAndWait(std::move(batch)); |
| } |
| |
| void WriteToStoreWithMetadata( |
| const std::vector<DeviceInfoSpecifics>& specifics_list, |
| ModelTypeState state) { |
| std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch(); |
| for (auto& specifics : specifics_list) { |
| batch->WriteData(specifics.cache_guid(), specifics.SerializeAsString()); |
| } |
| batch->GetMetadataChangeList()->UpdateModelTypeState(state); |
| CommitToStoreAndWait(std::move(batch)); |
| } |
| |
| std::map<std::string, DeviceInfoSpecifics> ReadAllFromStore() { |
| std::unique_ptr<ModelTypeStore::RecordList> records; |
| base::RunLoop loop; |
| store()->ReadAllData(base::BindOnce( |
| [](std::unique_ptr<ModelTypeStore::RecordList>* output_records, |
| base::RunLoop* loop, const base::Optional<syncer::ModelError>& error, |
| std::unique_ptr<ModelTypeStore::RecordList> input_records) { |
| EXPECT_FALSE(error) << error->ToString(); |
| EXPECT_THAT(input_records, NotNull()); |
| *output_records = std::move(input_records); |
| loop->Quit(); |
| }, |
| &records, &loop)); |
| loop.Run(); |
| std::map<std::string, DeviceInfoSpecifics> result; |
| if (records) { |
| for (const ModelTypeStore::Record& record : *records) { |
| DeviceInfoSpecifics specifics; |
| EXPECT_TRUE(specifics.ParseFromString(record.value)); |
| result.emplace(record.id, specifics); |
| } |
| } |
| return result; |
| } |
| |
| std::map<std::string, sync_pb::EntitySpecifics> GetAllData() { |
| base::RunLoop loop; |
| std::unique_ptr<DataBatch> batch; |
| bridge_->GetAllDataForDebugging(base::BindOnce( |
| [](base::RunLoop* loop, std::unique_ptr<DataBatch>* out_batch, |
| std::unique_ptr<DataBatch> batch) { |
| *out_batch = std::move(batch); |
| loop->Quit(); |
| }, |
| &loop, &batch)); |
| loop.Run(); |
| EXPECT_NE(nullptr, batch); |
| return DataBatchToSpecificsMap(std::move(batch)); |
| } |
| |
| std::map<std::string, sync_pb::EntitySpecifics> GetData( |
| const std::vector<std::string>& storage_keys) { |
| base::RunLoop loop; |
| std::unique_ptr<DataBatch> batch; |
| bridge_->GetData(storage_keys, base::BindOnce( |
| [](base::RunLoop* loop, |
| std::unique_ptr<DataBatch>* out_batch, |
| std::unique_ptr<DataBatch> batch) { |
| *out_batch = std::move(batch); |
| loop->Quit(); |
| }, |
| &loop, &batch)); |
| loop.Run(); |
| EXPECT_NE(nullptr, batch); |
| return DataBatchToSpecificsMap(std::move(batch)); |
| } |
| |
| private: |
| int change_count_ = 0; |
| |
| // In memory model type store needs to be able to post tasks. |
| base::test::ScopedTaskEnvironment task_environment_; |
| |
| testing::NiceMock<MockModelTypeChangeProcessor> mock_processor_; |
| |
| // Holds the store. |
| const std::unique_ptr<ModelTypeStore> store_; |
| |
| // Not initialized immediately (upon test's constructor). This allows each |
| // test case to modify the dependencies the bridge will be constructed with. |
| std::unique_ptr<DeviceInfoSyncBridge> bridge_; |
| }; |
| |
| TEST_F(DeviceInfoSyncBridgeTest, BeforeSyncEnabled) { |
| InitializeBridge(); |
| EXPECT_THAT(bridge()->GetAllDeviceInfo(), IsEmpty()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_THAT(bridge()->GetAllDeviceInfo(), IsEmpty()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, GetClientTagNormal) { |
| InitializeBridge(); |
| 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(CacheGuidToTag(guid), bridge()->GetClientTag(entity_data)); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, GetClientTagEmpty) { |
| InitializeBridge(); |
| EntitySpecifics entity_specifics; |
| entity_specifics.mutable_device_info(); |
| EntityData entity_data; |
| entity_data.specifics = entity_specifics; |
| EXPECT_EQ(CacheGuidToTag(""), bridge()->GetClientTag(entity_data)); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalDataWithoutMetadata) { |
| const DeviceInfoSpecifics specifics = CreateSpecifics(1); |
| WriteDataToStore({specifics}); |
| InitializeAndPump(); |
| |
| // Local data without sync metadata should be thrown away. |
| EXPECT_TRUE(ReadAllFromStore().empty()); |
| EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalMetadata) { |
| WriteToStoreWithMetadata(std::vector<DeviceInfoSpecifics>(), |
| StateWithEncryption("ekn")); |
| InitializeAndPump(); |
| |
| // Local metadata without data about the local device is corrupt. |
| EXPECT_TRUE(ReadAllFromStore().empty()); |
| EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalDataAndMetadata) { |
| const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix); |
| ModelTypeState state = StateWithEncryption("ekn"); |
| WriteToStoreWithMetadata({local_specifics}, state); |
| |
| EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains( |
| HasEncryptionKeyName("ekn"), |
| /*entities=*/_))); |
| InitializeAndPump(); |
| |
| EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_THAT(*bridge()->GetDeviceInfo(local_specifics.cache_guid()), |
| ModelEqualsSpecifics(local_specifics)); |
| EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, TestWithMultipleLocalDataAndMetadata) { |
| const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix); |
| const DeviceInfoSpecifics remote_specifics = CreateSpecifics(1); |
| ModelTypeState state = StateWithEncryption("ekn"); |
| WriteToStoreWithMetadata({local_specifics, remote_specifics}, state); |
| |
| EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains( |
| HasEncryptionKeyName("ekn"), |
| /*entities=*/_))); |
| InitializeAndPump(); |
| |
| EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_THAT(*bridge()->GetDeviceInfo(local_specifics.cache_guid()), |
| ModelEqualsSpecifics(local_specifics)); |
| EXPECT_THAT(*bridge()->GetDeviceInfo(remote_specifics.cache_guid()), |
| ModelEqualsSpecifics(remote_specifics)); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, GetData) { |
| const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix); |
| const DeviceInfoSpecifics specifics1 = CreateSpecifics(1); |
| const DeviceInfoSpecifics specifics2 = CreateSpecifics(2); |
| const DeviceInfoSpecifics specifics3 = CreateSpecifics(3); |
| WriteToStoreWithMetadata( |
| {local_specifics, specifics1, specifics2, specifics3}, |
| StateWithEncryption("ekn")); |
| InitializeAndPump(); |
| |
| EXPECT_THAT(GetData({specifics1.cache_guid()}), |
| UnorderedElementsAre( |
| Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)))); |
| |
| EXPECT_THAT(GetData({specifics1.cache_guid(), specifics3.cache_guid()}), |
| UnorderedElementsAre( |
| Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)), |
| Pair(specifics3.cache_guid(), HasDeviceInfo(specifics3)))); |
| |
| EXPECT_THAT(GetData({specifics1.cache_guid(), specifics2.cache_guid(), |
| specifics3.cache_guid()}), |
| UnorderedElementsAre( |
| Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)), |
| Pair(specifics2.cache_guid(), HasDeviceInfo(specifics2)), |
| Pair(specifics3.cache_guid(), HasDeviceInfo(specifics3)))); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, GetDataMissing) { |
| InitializeAndPump(); |
| EXPECT_THAT(GetData({"does_not_exist"}), IsEmpty()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, GetAllData) { |
| const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix); |
| const DeviceInfoSpecifics specifics1 = CreateSpecifics(1); |
| const DeviceInfoSpecifics specifics2 = CreateSpecifics(2); |
| WriteToStoreWithMetadata({local_specifics, specifics1, specifics2}, |
| StateWithEncryption("ekn")); |
| InitializeAndPump(); |
| |
| EXPECT_THAT(GetAllData(), |
| UnorderedElementsAre( |
| Pair(local_device()->GetLocalDeviceInfo()->guid(), _), |
| Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)), |
| Pair(specifics2.cache_guid(), HasDeviceInfo(specifics2)))); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesEmpty) { |
| InitializeAndMergeInitialData(); |
| ASSERT_EQ(1, change_count()); |
| |
| auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityChangeList()); |
| EXPECT_FALSE(error); |
| EXPECT_EQ(1, change_count()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesInMemory) { |
| InitializeAndMergeInitialData(); |
| ASSERT_EQ(1, change_count()); |
| |
| const DeviceInfoSpecifics specifics = CreateSpecifics(1); |
| auto error_on_add = bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), EntityAddList({specifics})); |
| |
| EXPECT_FALSE(error_on_add); |
| std::unique_ptr<DeviceInfo> info = |
| bridge()->GetDeviceInfo(specifics.cache_guid()); |
| ASSERT_TRUE(info); |
| EXPECT_THAT(*info, ModelEqualsSpecifics(specifics)); |
| EXPECT_EQ(2, change_count()); |
| |
| syncer::EntityChangeList entity_change_list; |
| entity_change_list.push_back( |
| EntityChange::CreateDelete(specifics.cache_guid())); |
| auto error_on_delete = bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), std::move(entity_change_list)); |
| |
| EXPECT_FALSE(error_on_delete); |
| EXPECT_FALSE(bridge()->GetDeviceInfo(specifics.cache_guid())); |
| EXPECT_EQ(3, change_count()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesStore) { |
| InitializeAndMergeInitialData(); |
| ASSERT_EQ(1, change_count()); |
| |
| const DeviceInfoSpecifics specifics = CreateSpecifics(1); |
| ModelTypeState state = StateWithEncryption("ekn"); |
| std::unique_ptr<MetadataChangeList> metadata_changes = |
| bridge()->CreateMetadataChangeList(); |
| metadata_changes->UpdateModelTypeState(state); |
| |
| auto error = bridge()->ApplySyncChanges(std::move(metadata_changes), |
| EntityAddList({specifics})); |
| EXPECT_FALSE(error); |
| EXPECT_EQ(2, change_count()); |
| |
| EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains( |
| HasEncryptionKeyName("ekn"), |
| /*entities=*/IsEmpty()))); |
| RestartBridge(); |
| |
| std::unique_ptr<DeviceInfo> info = |
| bridge()->GetDeviceInfo(specifics.cache_guid()); |
| ASSERT_TRUE(info); |
| EXPECT_THAT(*info, ModelEqualsSpecifics(specifics)); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesWithLocalGuid) { |
| InitializeAndMergeInitialData(); |
| ASSERT_EQ(1, change_count()); |
| |
| ASSERT_TRUE( |
| bridge()->GetDeviceInfo(local_device()->GetLocalDeviceInfo()->guid())); |
| ASSERT_EQ(1, change_count()); |
| |
| // The bridge should ignore these changes using this specifics because its |
| // guid will match the local device. |
| EXPECT_CALL(*processor(), Put(_, _, _)).Times(0); |
| |
| const DeviceInfoSpecifics specifics = CreateSpecifics(kLocalSuffix); |
| auto error_on_add = bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), EntityAddList({specifics})); |
| EXPECT_FALSE(error_on_add); |
| EXPECT_EQ(1, change_count()); |
| |
| syncer::EntityChangeList entity_change_list; |
| entity_change_list.push_back( |
| EntityChange::CreateDelete(specifics.cache_guid())); |
| auto error_on_delete = bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), std::move(entity_change_list)); |
| EXPECT_FALSE(error_on_delete); |
| EXPECT_EQ(1, change_count()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, ApplyDeleteNonexistent) { |
| InitializeAndMergeInitialData(); |
| ASSERT_EQ(1, change_count()); |
| |
| syncer::EntityChangeList entity_change_list; |
| entity_change_list.push_back(EntityChange::CreateDelete("guid")); |
| EXPECT_CALL(*processor(), Delete(_, _)).Times(0); |
| auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| std::move(entity_change_list)); |
| EXPECT_FALSE(error); |
| EXPECT_EQ(1, change_count()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, MergeEmpty) { |
| const std::string kLocalGuid = CacheGuidForSuffix(kLocalSuffix); |
| |
| InitializeAndPump(); |
| |
| ASSERT_FALSE(local_device()->GetLocalDeviceInfo()); |
| ASSERT_FALSE(bridge()->IsPulseTimerRunningForTest()); |
| |
| EXPECT_CALL(*processor(), Put(kLocalGuid, _, _)); |
| EXPECT_CALL(*processor(), Delete(_, _)).Times(0); |
| |
| bridge()->OnSyncStarting(TestDataTypeActivationRequest()); |
| auto error = bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(), |
| EntityChangeList()); |
| EXPECT_FALSE(error); |
| EXPECT_EQ(1, change_count()); |
| |
| ASSERT_TRUE(local_device()->GetLocalDeviceInfo()); |
| EXPECT_EQ(kLocalGuid, local_device()->GetLocalDeviceInfo()->guid()); |
| EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, MergeLocalGuid) { |
| const std::string kLocalGuid = CacheGuidForSuffix(kLocalSuffix); |
| |
| InitializeAndPump(); |
| |
| ASSERT_FALSE(local_device()->GetLocalDeviceInfo()); |
| |
| EXPECT_CALL(*processor(), Put(kLocalGuid, _, _)); |
| EXPECT_CALL(*processor(), Delete(_, _)).Times(0); |
| |
| bridge()->OnSyncStarting(TestDataTypeActivationRequest()); |
| auto error = |
| bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(), |
| EntityAddList({CreateSpecifics(kLocalSuffix)})); |
| |
| EXPECT_FALSE(error); |
| EXPECT_EQ(1, change_count()); |
| |
| ASSERT_TRUE(local_device()->GetLocalDeviceInfo()); |
| EXPECT_EQ(kLocalGuid, local_device()->GetLocalDeviceInfo()->guid()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevices) { |
| InitializeAndMergeInitialData(); |
| // Local device. |
| EXPECT_EQ(1, bridge()->CountActiveDevices()); |
| |
| ON_CALL(*processor(), GetEntityCreationTime(_)) |
| .WillByDefault(Return(base::Time::Now())); |
| ON_CALL(*processor(), GetEntityModificationTime(_)) |
| .WillByDefault(Return(base::Time::Now())); |
| |
| // Regardless of the time, these following two ApplySyncChanges(...) calls |
| // have the same guid as the local device. |
| bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({CreateSpecifics(kLocalSuffix)})); |
| EXPECT_EQ(1, bridge()->CountActiveDevices()); |
| |
| bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({CreateSpecifics(kLocalSuffix)})); |
| EXPECT_EQ(1, bridge()->CountActiveDevices()); |
| |
| // A different guid will actually contribute to the count. |
| bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({CreateSpecifics(1)})); |
| EXPECT_EQ(2, bridge()->CountActiveDevices()); |
| |
| // Now set time to long ago in the past, it should not be active anymore. |
| bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), |
| EntityAddList({CreateSpecifics( |
| 1, base::Time::Now() - base::TimeDelta::FromDays(365))})); |
| EXPECT_EQ(1, bridge()->CountActiveDevices()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithOverlappingTime) { |
| InitializeAndMergeInitialData(); |
| // Local device. |
| ASSERT_EQ(1, bridge()->CountActiveDevices()); |
| |
| const DeviceInfoSpecifics specifics1 = CreateSpecifics(1); |
| const DeviceInfoSpecifics specifics2 = CreateSpecifics(2); |
| const DeviceInfoSpecifics specifics3 = CreateSpecifics(3); |
| |
| // Time ranges are overlapping. |
| ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3))); |
| ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4))); |
| ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(5))); |
| |
| // With two devices, the local device gets ignored because it doesn't overlap. |
| bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({specifics1, specifics2})); |
| |
| ASSERT_EQ(3u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_EQ(2, bridge()->CountActiveDevices()); |
| |
| // The third device is also overlapping with the first two (and the local one |
| // is still excluded). |
| bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({specifics3})); |
| |
| ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_EQ(3, bridge()->CountActiveDevices()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithNonOverlappingTime) { |
| InitializeAndMergeInitialData(); |
| // Local device. |
| ASSERT_EQ(1, bridge()->CountActiveDevices()); |
| |
| const DeviceInfoSpecifics specifics1 = CreateSpecifics(1); |
| const DeviceInfoSpecifics specifics2 = CreateSpecifics(2); |
| const DeviceInfoSpecifics specifics3 = CreateSpecifics(3); |
| |
| // Time ranges are non-overlapping. |
| ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2))); |
| ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4))); |
| ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(5))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(6))); |
| |
| bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), |
| EntityAddList({specifics1, specifics2, specifics3})); |
| |
| ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_EQ(1, bridge()->CountActiveDevices()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, |
| CountActiveDevicesWithNonOverlappingTimeAndDistictType) { |
| InitializeAndMergeInitialData(); |
| // Local device. |
| ASSERT_EQ(1, bridge()->CountActiveDevices()); |
| |
| DeviceInfoSpecifics specifics1 = CreateSpecifics(1); |
| DeviceInfoSpecifics specifics2 = CreateSpecifics(2); |
| DeviceInfoSpecifics specifics3 = CreateSpecifics(3); |
| // We avoid TYPE_LINUX below to prevent collisions with the local device, |
| // exposed as Linux by LocalDeviceInfoProviderMock. |
| specifics1.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_PHONE); |
| specifics2.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_CROS); |
| specifics3.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN); |
| |
| // Time ranges are non-overlapping. |
| ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2))); |
| ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4))); |
| ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(5))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(6))); |
| |
| bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), |
| EntityAddList({specifics1, specifics2, specifics3})); |
| |
| ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_EQ(4, bridge()->CountActiveDevices()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithMalformedTimestamps) { |
| InitializeAndMergeInitialData(); |
| // Local device. |
| ASSERT_EQ(1, bridge()->CountActiveDevices()); |
| |
| const DeviceInfoSpecifics specifics1 = CreateSpecifics(1); |
| const DeviceInfoSpecifics specifics2 = CreateSpecifics(2); |
| |
| // Time ranges are overlapping. |
| ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4))); |
| ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3))); |
| ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid())) |
| .WillByDefault( |
| Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2))); |
| |
| // With two devices, the local device gets ignored because it doesn't overlap. |
| bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({specifics1, specifics2})); |
| |
| ASSERT_EQ(3u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_EQ(1, bridge()->CountActiveDevices()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, SendLocalData) { |
| // Ensure |last_updated| is about now, plus or minus a little bit. |
| EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _)); |
| InitializeAndMergeInitialData(); |
| EXPECT_EQ(1, change_count()); |
| testing::Mock::VerifyAndClearExpectations(processor()); |
| |
| // Ensure |last_updated| is about now, plus or minus a little bit. |
| EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _)); |
| ForcePulse(); |
| EXPECT_EQ(2, change_count()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, ApplyStopSyncChangesWithClearData) { |
| InitializeAndMergeInitialData(); |
| ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size()); |
| ASSERT_EQ(1, change_count()); |
| ASSERT_FALSE(ReadAllFromStore().empty()); |
| ASSERT_TRUE(bridge()->IsPulseTimerRunningForTest()); |
| |
| const DeviceInfoSpecifics specifics = CreateSpecifics(1); |
| auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({specifics})); |
| |
| ASSERT_FALSE(error); |
| ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size()); |
| ASSERT_EQ(2, change_count()); |
| |
| // Should clear out all local data and notify observers. |
| bridge()->ApplyStopSyncChanges(bridge()->CreateMetadataChangeList()); |
| EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_EQ(3, change_count()); |
| EXPECT_TRUE(ReadAllFromStore().empty()); |
| EXPECT_FALSE(bridge()->IsPulseTimerRunningForTest()); |
| |
| // Reloading from storage shouldn't contain remote data. |
| RestartBridge(); |
| EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size()); |
| |
| // If sync is re-enabled and the remote data is now empty, we shouldn't |
| // contain remote data. |
| bridge()->OnSyncStarting(TestDataTypeActivationRequest()); |
| bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(), |
| EntityChangeList()); |
| // Local device. |
| EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest()); |
| } |
| |
| TEST_F(DeviceInfoSyncBridgeTest, ApplyStopSyncChangesWithKeepData) { |
| InitializeAndMergeInitialData(); |
| ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size()); |
| ASSERT_EQ(1, change_count()); |
| ASSERT_FALSE(ReadAllFromStore().empty()); |
| ASSERT_TRUE(bridge()->IsPulseTimerRunningForTest()); |
| |
| const DeviceInfoSpecifics specifics = CreateSpecifics(1); |
| auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(), |
| EntityAddList({specifics})); |
| |
| ASSERT_FALSE(error); |
| ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size()); |
| ASSERT_EQ(2, change_count()); |
| |
| // Should clear out all local data and notify observers. |
| bridge()->ApplyStopSyncChanges(/*delete_metadata_change_list=*/nullptr); |
| EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_EQ(2, change_count()); |
| EXPECT_FALSE(ReadAllFromStore().empty()); |
| EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest()); |
| |
| // Reloading from storage should still contain remote data. |
| RestartBridge(); |
| EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size()); |
| EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest()); |
| } |
| |
| } // namespace |
| |
| } // namespace syncer |