| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/sync/model/syncable_service_based_bridge.h" |
| |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/task_environment.h" |
| #include "components/sync/base/client_tag_hash.h" |
| #include "components/sync/engine/data_type_activation_response.h" |
| #include "components/sync/model/client_tag_based_data_type_processor.h" |
| #include "components/sync/model/conflict_resolution.h" |
| #include "components/sync/model/model_error.h" |
| #include "components/sync/model/sync_change.h" |
| #include "components/sync/model/syncable_service.h" |
| #include "components/sync/protocol/persisted_entity_data.pb.h" |
| #include "components/sync/test/data_type_store_test_util.h" |
| #include "components/sync/test/mock_data_type_local_change_processor.h" |
| #include "components/sync/test/mock_data_type_worker.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace syncer { |
| namespace { |
| |
| using testing::_; |
| using testing::ElementsAre; |
| using testing::Eq; |
| using testing::IsEmpty; |
| using testing::NotNull; |
| using testing::Pair; |
| using testing::Return; |
| |
| const DataType kDataType = PREFERENCES; |
| const std::string_view kSyncableServiceStartTimeHistogramName = |
| "Sync.SyncableServiceStartTime.PREFERENCE"; |
| const std::string_view kMaybeClearDataHistogramName = |
| "Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.PREFERENCE"; |
| |
| sync_pb::EntitySpecifics GetTestSpecifics(const std::string& name = "name") { |
| sync_pb::EntitySpecifics specifics; |
| // Make specifics non empty, to avoid it being interpreted as a tombstone. |
| specifics.mutable_preference()->set_name(name); |
| return specifics; |
| } |
| |
| MATCHER_P(SyncDataMatches, name, "") { |
| return arg.IsValid() && arg.GetDataType() == kDataType && |
| arg.GetSpecifics().preference().name() == name; |
| } |
| |
| MATCHER_P2(SyncChangeMatches, change_type, name, "") { |
| return arg.change_type() == change_type && |
| arg.sync_data().GetDataType() == kDataType && |
| arg.sync_data().GetSpecifics().preference().name() == name; |
| } |
| |
| MATCHER_P(HasName, name, "") { |
| return arg && arg->specifics.preference().name() == name; |
| } |
| |
| class MockSyncableService : public SyncableService { |
| public: |
| MOCK_METHOD(void, WaitUntilReadyToSync, (base::OnceClosure done), (override)); |
| MOCK_METHOD(void, WillStartInitialSync, (), (override)); |
| MOCK_METHOD(std::optional<syncer::ModelError>, |
| MergeDataAndStartSyncing, |
| (DataType type, |
| const SyncDataList& initial_sync_data, |
| std::unique_ptr<SyncChangeProcessor> sync_processor), |
| (override)); |
| MOCK_METHOD(void, StopSyncing, (DataType type), (override)); |
| MOCK_METHOD(void, StayStoppedAndMaybeClearData, (DataType type), (override)); |
| MOCK_METHOD(std::optional<ModelError>, |
| ProcessSyncChanges, |
| (const base::Location& from_here, |
| const SyncChangeList& change_list), |
| (override)); |
| MOCK_METHOD(SyncDataList, GetAllSyncData, (DataType type), (const override)); |
| |
| base::WeakPtr<SyncableService> AsWeakPtr() override { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| MOCK_METHOD(std::string, |
| GetClientTag, |
| (const EntityData& entity_data), |
| (const override)); |
| MOCK_METHOD(bool, SupportsGetClientTag, (), (const override)); |
| |
| private: |
| base::WeakPtrFactory<MockSyncableService> weak_ptr_factory_{this}; |
| }; |
| |
| class SyncableServiceBasedBridgeTest : public ::testing::Test { |
| public: |
| SyncableServiceBasedBridgeTest(const SyncableServiceBasedBridgeTest&) = |
| delete; |
| SyncableServiceBasedBridgeTest& operator=( |
| const SyncableServiceBasedBridgeTest&) = delete; |
| |
| protected: |
| SyncableServiceBasedBridgeTest() |
| : store_(DataTypeStoreTestUtil::CreateInMemoryStoreForTest()) { |
| ON_CALL(syncable_service_, WaitUntilReadyToSync) |
| .WillByDefault([](base::OnceClosure done) { std::move(done).Run(); }); |
| ON_CALL(syncable_service_, MergeDataAndStartSyncing) |
| .WillByDefault( |
| [&](DataType type, const SyncDataList& initial_sync_data, |
| std::unique_ptr<SyncChangeProcessor> sync_processor) { |
| start_syncing_sync_processor_ = std::move(sync_processor); |
| return std::nullopt; |
| }); |
| } |
| |
| ~SyncableServiceBasedBridgeTest() override = default; |
| |
| void InitializeBridge(DataType data_type = kDataType) { |
| real_processor_ = std::make_unique<syncer::ClientTagBasedDataTypeProcessor>( |
| data_type, /*dump_stack=*/base::DoNothing()); |
| mock_processor_.DelegateCallsByDefaultTo(real_processor_.get()); |
| bridge_ = std::make_unique<SyncableServiceBasedBridge>( |
| data_type, |
| DataTypeStoreTestUtil::FactoryForForwardingStore(store_.get()), |
| mock_processor_.CreateForwardingProcessor(), &syncable_service_); |
| } |
| |
| void ShutdownBridge() { |
| // `bridge_` must outlive `start_syncing_sync_processor_`, so reset it |
| // first. |
| start_syncing_sync_processor_.reset(); |
| bridge_.reset(); |
| // The mock is still delegating to `real_processor_`, so we reset it too. |
| ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_processor_)); |
| real_processor_.reset(); |
| } |
| |
| syncer::DataTypeActivationRequest GetTestActivationRequest() { |
| syncer::DataTypeActivationRequest request; |
| request.error_handler = mock_error_handler_.Get(); |
| request.cache_guid = "TestCacheGuid"; |
| request.authenticated_gaia_id = GaiaId("SomeGaiaId"); |
| return request; |
| } |
| |
| void StartSyncing() { |
| base::RunLoop loop; |
| real_processor_->OnSyncStarting( |
| GetTestActivationRequest(), |
| base::BindLambdaForTesting( |
| [&](std::unique_ptr<syncer::DataTypeActivationResponse> response) { |
| worker_ = MockDataTypeWorker::CreateWorkerAndConnectSync( |
| std::move(response)); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| |
| ASSERT_NE(nullptr, worker_); |
| } |
| |
| std::map<std::string, std::unique_ptr<EntityData>> GetAllData() { |
| std::unique_ptr<DataBatch> batch = bridge_->GetAllDataForDebugging(); |
| EXPECT_NE(nullptr, batch); |
| |
| std::map<std::string, std::unique_ptr<EntityData>> storage_key_to_data; |
| while (batch && batch->HasNext()) { |
| storage_key_to_data.insert(batch->Next()); |
| } |
| return storage_key_to_data; |
| } |
| |
| const std::string kClientTag = "clienttag"; |
| const ClientTagHash kClientTagHash = |
| ClientTagHash::FromUnhashed(kDataType, kClientTag); |
| |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| testing::NiceMock<MockSyncableService> syncable_service_; |
| testing::NiceMock<MockDataTypeLocalChangeProcessor> mock_processor_; |
| base::MockCallback<ModelErrorHandler> mock_error_handler_; |
| const std::unique_ptr<DataTypeStore> store_; |
| std::unique_ptr<syncer::ClientTagBasedDataTypeProcessor> real_processor_; |
| std::unique_ptr<SyncableServiceBasedBridge> bridge_; |
| std::unique_ptr<MockDataTypeWorker> worker_; |
| // SyncChangeProcessor received via MergeDataAndStartSyncing(), or null if it |
| // hasn't been called. |
| std::unique_ptr<SyncChangeProcessor> start_syncing_sync_processor_; |
| }; |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldStartSyncingWithEmptyInitialRemoteData) { |
| // Bridge initialization alone, without sync itself starting, should not |
| // issue calls to the syncable service. |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0); |
| InitializeBridge(); |
| |
| // Starting sync itself is also not sufficient, until initial remote data is |
| // received. |
| StartSyncing(); |
| |
| // Once the initial data is fetched from the server, |
| // MergeDataAndStartSyncing() should be exercised. |
| EXPECT_CALL(syncable_service_, |
| MergeDataAndStartSyncing(kDataType, IsEmpty(), NotNull())); |
| worker_->UpdateFromServer(); |
| EXPECT_THAT(GetAllData(), IsEmpty()); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldStartSyncingWithNonEmptyInitialRemoteData) { |
| InitializeBridge(); |
| StartSyncing(); |
| |
| // Once the initial data is fetched from the server, |
| // MergeDataAndStartSyncing() should be exercised. |
| EXPECT_CALL(syncable_service_, |
| MergeDataAndStartSyncing( |
| kDataType, ElementsAre(SyncDataMatches("name1")), NotNull())); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _))); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldWaitUntilModelReadyToSync) { |
| base::OnceClosure syncable_service_ready_cb; |
| ON_CALL(syncable_service_, WaitUntilReadyToSync) |
| .WillByDefault([&](base::OnceClosure done) { |
| syncable_service_ready_cb = std::move(done); |
| }); |
| |
| EXPECT_CALL(mock_processor_, ModelReadyToSync).Times(0); |
| EXPECT_CALL(syncable_service_, WaitUntilReadyToSync).Times(0); |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0); |
| |
| // Bridge initialization alone, without sync itself starting, should not |
| // issue calls to the syncable service. |
| InitializeBridge(); |
| |
| EXPECT_CALL(syncable_service_, WaitUntilReadyToSync); |
| // Required to initialize the store. |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(syncable_service_ready_cb); |
| |
| // Sync itself starting should wait until the syncable service becomes ready, |
| // before issuing any other call (e.g. MergeDataAndStartSyncing()). |
| real_processor_->OnSyncStarting(GetTestActivationRequest(), |
| base::DoNothing()); |
| |
| // When the SyncableService gets ready, the bridge should propagate this |
| // information to the processor. |
| EXPECT_CALL(mock_processor_, ModelReadyToSync); |
| std::move(syncable_service_ready_cb).Run(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldStopSyncableServiceIfPreviouslyStarted) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(); |
| |
| EXPECT_CALL(syncable_service_, StopSyncing(kDataType)); |
| real_processor_->OnSyncStopping(CLEAR_METADATA); |
| |
| EXPECT_CALL(syncable_service_, StopSyncing).Times(0); |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldStopSyncableServiceDuringShutdownIfPreviouslyStarted) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(); |
| |
| EXPECT_CALL(syncable_service_, StopSyncing(kDataType)); |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldNotStopSyncableServiceIfNotPreviouslyStarted) { |
| EXPECT_CALL(syncable_service_, StopSyncing).Times(0); |
| InitializeBridge(); |
| StartSyncing(); |
| real_processor_->OnSyncStopping(KEEP_METADATA); |
| } |
| |
| // Regression test for crbug.com/401453180. |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldMaybeClearDataIfPendingClearMetadataOnStart) { |
| // Simulate prior initial sync done. |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| ShutdownBridge(); |
| |
| EXPECT_CALL(syncable_service_, StopSyncing).Times(0); |
| EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData).Times(0); |
| InitializeBridge(); |
| |
| base::RunLoop loop; |
| ON_CALL(syncable_service_, WaitUntilReadyToSync) |
| .WillByDefault([&](base::OnceClosure done) { |
| // This should mark sync metadata as pending clear on start. |
| real_processor_->ClearMetadataIfStopped(); |
| // Metadata has not been cleared yet. |
| ASSERT_THAT(GetAllData(), Not(IsEmpty())); |
| std::move(done).Run(); |
| loop.Quit(); |
| }); |
| |
| base::HistogramTester histogram_tester; |
| EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType)); |
| // The processor should clear the pre-existing metadata and lead to |
| // StayStoppedAndMaybeClearData() being called. |
| loop.Run(); |
| histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName, |
| /*sample=*/true, |
| /*expected_bucket_count=*/1); |
| ASSERT_THAT(GetAllData(), IsEmpty()); |
| |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldMaybeClearDataIfMetadataEmpty) { |
| base::RunLoop loop; |
| EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType)) |
| .WillOnce([&]() { loop.Quit(); }); |
| base::HistogramTester histogram_tester; |
| // No metadata exists, thus any account data in the syncable service should be |
| // cleared. |
| InitializeBridge(); |
| loop.Run(); |
| histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName, |
| /*sample=*/true, |
| /*expected_bucket_count=*/1); |
| |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldMaybeClearDataIfMetadataInconsistentOnNextStartup) { |
| // Simulate pre-existing metadata in the store without initial sync done. |
| { |
| std::unique_ptr<DataTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| batch->WriteData(kClientTagHash.value(), |
| GetTestSpecifics().SerializeAsString()); |
| base::RunLoop loop; |
| store_->CommitWriteBatch( |
| std::move(batch), |
| base::BindLambdaForTesting( |
| [&](const std::optional<ModelError>& error) { loop.Quit(); })); |
| loop.Run(); |
| } |
| |
| // Simulate a successful initial sync. |
| InitializeBridge(); |
| StartSyncing(); |
| ASSERT_THAT(GetAllData(), IsEmpty()); |
| ShutdownBridge(); |
| |
| base::RunLoop loop; |
| // On the next startup, the metadata is currently empty, which should trigger |
| // StayStoppedAndMaybeClearData(). |
| EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType)) |
| .WillOnce([&]() { loop.Quit(); }); |
| base::HistogramTester histogram_tester; |
| // Metadata exists but initial sync is not done, thus leading to an |
| // inconsistent state, which should trigger StayStoppedAndMaybeClearData(). |
| InitializeBridge(); |
| loop.Run(); |
| histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName, |
| /*sample=*/true, |
| /*expected_bucket_count=*/1); |
| |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldNotMaybeClearDataIfMetadataNotEmpty) { |
| // Simulate prior initial sync done. |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| ShutdownBridge(); |
| |
| base::RunLoop loop; |
| ON_CALL(syncable_service_, WaitUntilReadyToSync) |
| .WillByDefault([&](base::OnceClosure done) { |
| std::move(done).Run(); |
| loop.Quit(); |
| }); |
| EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType)) |
| .Times(0); |
| base::HistogramTester histogram_tester; |
| InitializeBridge(); |
| // Metadata exists and initial sync is done, thus leading to no need to clear |
| // data. |
| loop.Run(); |
| histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName, |
| /*sample=*/false, |
| /*expected_bucket_count=*/1); |
| |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldNotStopSyncableServiceDuringShutdownIfNotPreviouslyStarted) { |
| EXPECT_CALL(syncable_service_, StopSyncing).Times(0); |
| InitializeBridge(); |
| StartSyncing(); |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateErrorDuringStart) { |
| // Instrument MergeDataAndStartSyncing() to return an error. |
| ON_CALL(syncable_service_, MergeDataAndStartSyncing) |
| .WillByDefault(Return( |
| ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError))); |
| |
| EXPECT_CALL(mock_error_handler_, Run); |
| |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(); |
| |
| // Since the syncable service failed to start, it shouldn't be stopped. |
| EXPECT_CALL(syncable_service_, StopSyncing).Times(0); |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldKeepSyncingWhenSyncStoppedTemporarily) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| |
| // Stopping Sync temporarily (KEEP_METADATA) should *not* result in the |
| // SyncableService being stopped. |
| EXPECT_CALL(syncable_service_, StopSyncing).Times(0); |
| real_processor_->OnSyncStopping(KEEP_METADATA); |
| EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _))); |
| |
| // Since the SyncableService wasn't stopped, it shouldn't get restarted either |
| // when Sync starts up again. |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0); |
| StartSyncing(); |
| |
| // Finally, shutting down the bridge (during browser shutdown) should also |
| // stop the SyncableService. |
| EXPECT_CALL(syncable_service_, StopSyncing(kDataType)); |
| ShutdownBridge(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldStartSyncingWithPreviousDirectoryDataAfterRestart) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| |
| // Mimic restart, which shouldn't start syncing until OnSyncStarting() is |
| // received (exercised in StartSyncing()). |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0); |
| ShutdownBridge(); |
| InitializeBridge(); |
| |
| EXPECT_CALL(syncable_service_, |
| MergeDataAndStartSyncing( |
| kDataType, ElementsAre(SyncDataMatches("name1")), NotNull())); |
| StartSyncing(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldSupportDisableReenableSequence) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics()); |
| real_processor_->OnSyncStopping(CLEAR_METADATA); |
| EXPECT_THAT(GetAllData(), IsEmpty()); |
| |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0); |
| StartSyncing(); |
| EXPECT_CALL(syncable_service_, |
| MergeDataAndStartSyncing(kDataType, IsEmpty(), NotNull())); |
| worker_->UpdateFromServer(); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldPropagateLocalEntitiesDuringMerge) { |
| ON_CALL(syncable_service_, MergeDataAndStartSyncing) |
| .WillByDefault([&](DataType type, const SyncDataList& initial_sync_data, |
| std::unique_ptr<SyncChangeProcessor> sync_processor) { |
| SyncChangeList change_list; |
| change_list.emplace_back( |
| FROM_HERE, SyncChange::ACTION_ADD, |
| SyncData::CreateLocalData(kClientTag, "title", GetTestSpecifics())); |
| const std::optional<ModelError> error = |
| sync_processor->ProcessSyncChanges(FROM_HERE, change_list); |
| EXPECT_FALSE(error.has_value()); |
| return std::nullopt; |
| }); |
| |
| InitializeBridge(); |
| StartSyncing(); |
| |
| EXPECT_CALL(mock_processor_, |
| Put(kClientTagHash.value(), NotNull(), NotNull())); |
| worker_->UpdateFromServer(); |
| EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _))); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateLocalCreation) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(); |
| ASSERT_THAT(start_syncing_sync_processor_, NotNull()); |
| ASSERT_THAT(GetAllData(), IsEmpty()); |
| |
| EXPECT_CALL(mock_processor_, |
| Put(kClientTagHash.value(), NotNull(), NotNull())); |
| |
| SyncChangeList change_list; |
| change_list.emplace_back( |
| FROM_HERE, SyncChange::ACTION_ADD, |
| SyncData::CreateLocalData(kClientTag, "title", GetTestSpecifics())); |
| const std::optional<ModelError> error = |
| start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list); |
| EXPECT_FALSE(error.has_value()); |
| EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _))); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateLocalUpdate) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| ASSERT_THAT(start_syncing_sync_processor_, NotNull()); |
| ASSERT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name1")))); |
| |
| EXPECT_CALL(mock_processor_, |
| Put(kClientTagHash.value(), NotNull(), NotNull())); |
| |
| SyncChangeList change_list; |
| change_list.emplace_back(FROM_HERE, SyncChange::ACTION_UPDATE, |
| SyncData::CreateLocalData( |
| kClientTag, "title", GetTestSpecifics("name2"))); |
| const std::optional<ModelError> error = |
| start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list); |
| EXPECT_FALSE(error.has_value()); |
| EXPECT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name2")))); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateLocalDeletion) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| ASSERT_THAT(start_syncing_sync_processor_, NotNull()); |
| ASSERT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name1")))); |
| |
| EXPECT_CALL(mock_processor_, Delete(kClientTagHash.value(), _, NotNull())); |
| |
| SyncChangeList change_list; |
| change_list.emplace_back(FROM_HERE, SyncChange::ACTION_DELETE, |
| SyncData::CreateLocalDelete(kClientTag, kDataType)); |
| |
| const std::optional<ModelError> error = |
| start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list); |
| EXPECT_FALSE(error.has_value()); |
| EXPECT_THAT(GetAllData(), IsEmpty()); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldIgnoreLocalCreationIfPreviousError) { |
| EXPECT_CALL(mock_processor_, Put).Times(0); |
| |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(); |
| ASSERT_THAT(start_syncing_sync_processor_, NotNull()); |
| ASSERT_THAT(GetAllData(), IsEmpty()); |
| |
| // We fake an error, reported by the bridge. |
| EXPECT_CALL(mock_error_handler_, Run); |
| real_processor_->ReportError( |
| ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError)); |
| ASSERT_TRUE(real_processor_->GetError()); |
| |
| // Further local changes should be ignored. |
| SyncChangeList change_list; |
| change_list.emplace_back( |
| FROM_HERE, SyncChange::ACTION_ADD, |
| SyncData::CreateLocalData(kClientTag, "title", GetTestSpecifics())); |
| const std::optional<ModelError> error = |
| start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list); |
| EXPECT_TRUE(error.has_value()); |
| EXPECT_THAT(GetAllData(), IsEmpty()); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteCreation) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(); |
| ASSERT_THAT(start_syncing_sync_processor_, NotNull()); |
| ASSERT_THAT(GetAllData(), IsEmpty()); |
| |
| EXPECT_CALL(syncable_service_, |
| ProcessSyncChanges(_, ElementsAre(SyncChangeMatches( |
| SyncChange::ACTION_ADD, "name1")))); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| EXPECT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name1")))); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteUpdates) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| ASSERT_THAT(start_syncing_sync_processor_, NotNull()); |
| ASSERT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name1")))); |
| |
| EXPECT_CALL(syncable_service_, |
| ProcessSyncChanges(_, ElementsAre(SyncChangeMatches( |
| SyncChange::ACTION_UPDATE, "name2")))); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name2")); |
| EXPECT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name2")))); |
| |
| // A second update for the same entity. |
| EXPECT_CALL(syncable_service_, |
| ProcessSyncChanges(_, ElementsAre(SyncChangeMatches( |
| SyncChange::ACTION_UPDATE, "name3")))); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name3")); |
| EXPECT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name3")))); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteDeletion) { |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| ASSERT_THAT(start_syncing_sync_processor_, NotNull()); |
| ASSERT_THAT(GetAllData(), |
| ElementsAre(Pair(kClientTagHash.value(), HasName("name1")))); |
| |
| EXPECT_CALL(syncable_service_, |
| ProcessSyncChanges(_, ElementsAre(SyncChangeMatches( |
| SyncChange::ACTION_DELETE, "name1")))); |
| worker_->TombstoneFromServer(kClientTagHash); |
| EXPECT_THAT(GetAllData(), IsEmpty()); |
| } |
| |
| TEST(SyncableServiceBasedBridgeLocalChangeProcessorTest, |
| ShouldDropIfCommitted) { |
| const std::string kClientTagHash = "clienttaghash1"; |
| |
| base::test::SingleThreadTaskEnvironment task_environment; |
| std::unique_ptr<DataTypeStore> store = |
| DataTypeStoreTestUtil::CreateInMemoryStoreForTest(); |
| SyncableServiceBasedBridge::InMemoryStore in_memory_store; |
| testing::NiceMock<MockDataTypeLocalChangeProcessor> mock_processor; |
| |
| in_memory_store[kClientTagHash] = sync_pb::PersistedEntityData(); |
| |
| std::unique_ptr<SyncChangeProcessor> sync_change_processor = |
| SyncableServiceBasedBridge::CreateLocalChangeProcessorForTesting( |
| HISTORY_DELETE_DIRECTIVES, store.get(), &in_memory_store, |
| &mock_processor); |
| |
| EXPECT_CALL(mock_processor, IsEntityUnsynced(kClientTagHash)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(mock_processor, UntrackEntityForStorageKey(kClientTagHash)); |
| |
| sync_pb::EntitySpecifics specifics; |
| specifics.mutable_history_delete_directive(); |
| |
| SyncChangeList change_list; |
| change_list.push_back( |
| SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, |
| SyncData::CreateRemoteData( |
| specifics, ClientTagHash::FromHashed(kClientTagHash)))); |
| |
| sync_change_processor->ProcessSyncChanges(FROM_HERE, change_list); |
| |
| EXPECT_EQ(0U, in_memory_store.count(kClientTagHash)); |
| } |
| |
| TEST(SyncableServiceBasedBridgeLocalChangeProcessorTest, |
| ShouldNotDropIfUnsynced) { |
| const std::string kClientTagHash = "clienttaghash1"; |
| |
| base::test::SingleThreadTaskEnvironment task_environment; |
| std::unique_ptr<DataTypeStore> store = |
| DataTypeStoreTestUtil::CreateInMemoryStoreForTest(); |
| SyncableServiceBasedBridge::InMemoryStore in_memory_store; |
| testing::NiceMock<MockDataTypeLocalChangeProcessor> mock_processor; |
| |
| in_memory_store[kClientTagHash] = sync_pb::PersistedEntityData(); |
| |
| std::unique_ptr<SyncChangeProcessor> sync_change_processor = |
| SyncableServiceBasedBridge::CreateLocalChangeProcessorForTesting( |
| HISTORY_DELETE_DIRECTIVES, store.get(), &in_memory_store, |
| &mock_processor); |
| |
| EXPECT_CALL(mock_processor, IsEntityUnsynced(kClientTagHash)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(mock_processor, UntrackEntityForStorageKey).Times(0); |
| |
| sync_pb::EntitySpecifics specifics; |
| specifics.mutable_history_delete_directive(); |
| |
| SyncChangeList change_list; |
| change_list.push_back( |
| SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, |
| SyncData::CreateRemoteData( |
| specifics, ClientTagHash::FromHashed(kClientTagHash)))); |
| |
| sync_change_processor->ProcessSyncChanges(FROM_HERE, change_list); |
| |
| EXPECT_EQ(1U, in_memory_store.count(kClientTagHash)); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ConflictShouldUseRemote) { |
| InitializeBridge(); |
| |
| EntityData remote_data; |
| remote_data.client_tag_hash = kClientTagHash; |
| remote_data.specifics = GetTestSpecifics(); |
| ASSERT_FALSE(remote_data.is_deleted()); |
| |
| EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data), |
| Eq(ConflictResolution::kUseRemote)); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ConflictWithRemoteDeletionShouldUseLocal) { |
| InitializeBridge(); |
| |
| EntityData remote_data; |
| remote_data.client_tag_hash = kClientTagHash; |
| ASSERT_TRUE(remote_data.is_deleted()); |
| |
| EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data), |
| Eq(ConflictResolution::kUseLocal)); |
| } |
| |
| // This ensures that for extensions, the conflict is resolved in favor of the |
| // server, to prevent extensions from being reinstalled after uninstall. |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ConflictWithRemoteExtensionUninstallShouldUseRemote) { |
| InitializeBridge(EXTENSIONS); |
| |
| EntityData remote_data; |
| remote_data.client_tag_hash = kClientTagHash; |
| ASSERT_TRUE(remote_data.is_deleted()); |
| |
| EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data), |
| Eq(ConflictResolution::kUseRemote)); |
| } |
| |
| // Same as above but for APPS. |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ConflictWithRemoteAppUninstallShouldUseRemote) { |
| InitializeBridge(APPS); |
| |
| EntityData remote_data; |
| remote_data.client_tag_hash = kClientTagHash; |
| ASSERT_TRUE(remote_data.is_deleted()); |
| |
| EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data), |
| Eq(ConflictResolution::kUseRemote)); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldMeasureSyncableServiceStartTime) { |
| // The following writes data into store for the next run. |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| // Mimic restart. |
| ShutdownBridge(); |
| |
| base::RunLoop loop; |
| ON_CALL(syncable_service_, WaitUntilReadyToSync) |
| .WillByDefault([&](base::OnceClosure done) { |
| std::move(done).Run(); |
| loop.Quit(); |
| }); |
| |
| base::HistogramTester histogram_tester; |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing); |
| // Initial data is loaded from the store. |
| InitializeBridge(); |
| loop.Run(); |
| histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 1); |
| } |
| |
| // This also covers the case where the user opts in for sync later. |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldNotMeasureSyncableServiceStartTimeIfNoInitialData) { |
| base::HistogramTester histogram_tester; |
| // No initial data. |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0); |
| InitializeBridge(); |
| StartSyncing(); |
| histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0); |
| |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing); |
| // Initial merge happens on response from server. |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldNotMeasureSyncableServiceStartTimeOnSyncRestart) { |
| // The following writes data into store for the next run. |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| // Mimic restart, which shouldn't start syncing until OnSyncStarting() is |
| // received (exercised in StartSyncing()). |
| ShutdownBridge(); |
| |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing); |
| // Initial data is loaded from the store. |
| InitializeBridge(); |
| StartSyncing(); |
| |
| base::HistogramTester histogram_tester; |
| // Mimic sync restart. |
| real_processor_->OnSyncStopping(CLEAR_METADATA); |
| StartSyncing(); |
| |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| // This case shouldn't be logged into the metric. |
| histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldNotMeasureSyncableServiceStartTimeOnError) { |
| base::HistogramTester histogram_tester; |
| InitializeBridge(); |
| StartSyncing(); |
| histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0); |
| |
| // Instrument MergeDataAndStartSyncing() to return an error. |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing) |
| .WillOnce(Return( |
| ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError))); |
| EXPECT_CALL(mock_error_handler_, Run); |
| |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, ShouldCallWillStartInitialSync) { |
| ::testing::InSequence in_sequence; |
| EXPECT_CALL(syncable_service_, WillStartInitialSync); |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing); |
| |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| } |
| |
| TEST_F(SyncableServiceBasedBridgeTest, |
| ShouldNotCallWillStartInitialSyncUponBrowserRestart) { |
| // The following writes data into store for the next run. |
| InitializeBridge(); |
| StartSyncing(); |
| worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1")); |
| |
| // Mimic restart. |
| ShutdownBridge(); |
| |
| EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing); |
| EXPECT_CALL(syncable_service_, WillStartInitialSync).Times(0); |
| // Initial data is loaded from the store. |
| InitializeBridge(); |
| StartSyncing(); |
| } |
| |
| } // namespace |
| } // namespace syncer |