| // Copyright 2020 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/sharing_message/sharing_message_bridge_impl.h" |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/task_environment.h" |
| #include "components/sharing_message/features.h" |
| #include "components/sync/model/data_batch.h" |
| #include "components/sync/model/data_type_sync_bridge.h" |
| #include "components/sync/model/metadata_batch.h" |
| #include "components/sync/model/metadata_change_list.h" |
| #include "components/sync/test/mock_data_type_local_change_processor.h" |
| #include "net/base/network_change_notifier.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| using sync_pb::SharingMessageCommitError; |
| using sync_pb::SharingMessageSpecifics; |
| using syncer::DataBatch; |
| using syncer::SyncCommitError; |
| using testing::_; |
| using testing::InvokeWithoutArgs; |
| using testing::NotNull; |
| using testing::Pair; |
| using testing::Return; |
| using testing::SaveArg; |
| using testing::UnorderedElementsAre; |
| |
| // Action SaveArgPointeeMove<k>(pointer) saves the value pointed to by the k-th |
| // (0-based) argument of the mock function by moving it to *pointer. |
| ACTION_TEMPLATE(SaveArgPointeeMove, |
| HAS_1_TEMPLATE_PARAMS(int, k), |
| AND_1_VALUE_PARAMS(pointer)) { |
| *pointer = std::move(*testing::get<k>(args)); |
| } |
| |
| MATCHER_P(HasErrorCode, expected_error_code, "") { |
| return arg.error_code() == expected_error_code; |
| } |
| |
| // Fake NetworkChangeNotifier to simulate being offline. NetworkChangeNotifier |
| // is a singleton, so making this instance will apply globally. |
| class OfflineNetworkChangeNotifier : public net::NetworkChangeNotifier { |
| public: |
| // net::NetworkChangeNotifier: |
| ConnectionType GetCurrentConnectionType() const override { |
| return NetworkChangeNotifier::CONNECTION_NONE; |
| } |
| }; |
| |
| std::unordered_map<std::string, std::string> ExtractStorageKeyAndPayloads( |
| std::unique_ptr<DataBatch> batch) { |
| std::unordered_map<std::string, std::string> storage_key_to_payload; |
| DCHECK(batch); |
| while (batch->HasNext()) { |
| const syncer::KeyAndData& pair = batch->Next(); |
| const SharingMessageSpecifics& specifics = |
| pair.second->specifics.sharing_message(); |
| const std::string& storage_key = pair.first; |
| storage_key_to_payload.emplace(storage_key, specifics.payload()); |
| } |
| return storage_key_to_payload; |
| } |
| |
| class SharingMessageBridgeTest : public testing::Test { |
| protected: |
| SharingMessageBridgeTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { |
| EXPECT_CALL(*processor(), ModelReadyToSync(NotNull())); |
| bridge_ = std::make_unique<SharingMessageBridgeImpl>( |
| mock_processor_.CreateForwardingProcessor()); |
| ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true)); |
| } |
| |
| SharingMessageBridgeImpl* bridge() { return bridge_.get(); } |
| syncer::MockDataTypeLocalChangeProcessor* processor() { |
| return &mock_processor_; |
| } |
| |
| std::unique_ptr<SharingMessageSpecifics> CreateSpecifics( |
| const std::string& payload) const { |
| auto specifics = std::make_unique<SharingMessageSpecifics>(); |
| specifics->set_payload(payload); |
| return specifics; |
| } |
| |
| void FastForwardThroughTimeout() { |
| task_environment_.FastForwardBy(SharingMessageBridgeImpl::kCommitTimeout); |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| testing::NiceMock<syncer::MockDataTypeLocalChangeProcessor> mock_processor_; |
| std::unique_ptr<SharingMessageBridgeImpl> bridge_; |
| }; |
| |
| class SharingMessageBridgeErrorsTest |
| : public SharingMessageBridgeTest, |
| public testing::WithParamInterface< |
| testing::tuple<SyncCommitError, |
| SharingMessageCommitError::ErrorCode>> {}; |
| |
| TEST_F(SharingMessageBridgeTest, ShouldWriteMessagesToProcessor) { |
| syncer::EntityData entity_data; |
| EXPECT_CALL(*processor(), Put(_, _, _)) |
| .WillRepeatedly(SaveArgPointeeMove<1>(&entity_data)); |
| bridge()->SendSharingMessage(CreateSpecifics("test_payload"), |
| base::DoNothing()); |
| |
| EXPECT_TRUE(entity_data.specifics.has_sharing_message()); |
| EXPECT_EQ(entity_data.specifics.sharing_message().payload(), "test_payload"); |
| |
| entity_data.specifics.Clear(); |
| bridge()->SendSharingMessage(CreateSpecifics("another_payload"), |
| base::DoNothing()); |
| |
| EXPECT_TRUE(entity_data.specifics.has_sharing_message()); |
| EXPECT_EQ(entity_data.specifics.sharing_message().payload(), |
| "another_payload"); |
| EXPECT_FALSE(bridge()->GetStorageKey(entity_data).empty()); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldGenerateUniqueStorageKey) { |
| std::string first_storage_key; |
| std::string second_storage_key; |
| EXPECT_CALL(*processor(), Put(_, _, _)) |
| .WillOnce(SaveArg<0>(&first_storage_key)) |
| .WillOnce(SaveArg<0>(&second_storage_key)); |
| bridge()->SendSharingMessage(CreateSpecifics("payload"), base::DoNothing()); |
| bridge()->SendSharingMessage(CreateSpecifics("another_payload"), |
| base::DoNothing()); |
| |
| EXPECT_FALSE(first_storage_key.empty()); |
| EXPECT_FALSE(second_storage_key.empty()); |
| EXPECT_NE(first_storage_key, second_storage_key); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldInvokeCallbackOnSuccess) { |
| base::HistogramTester histogram_tester; |
| std::string storage_key; |
| EXPECT_CALL(*processor(), Put(_, _, _)).WillOnce(SaveArg<0>(&storage_key)); |
| |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| bridge()->SendSharingMessage(CreateSpecifics("payload"), callback.Get()); |
| EXPECT_CALL(callback, Run(HasErrorCode(SharingMessageCommitError::NONE))); |
| |
| // Mark data as committed. |
| syncer::EntityChangeList change_list; |
| change_list.push_back( |
| syncer::EntityChange::CreateDelete(storage_key, syncer::EntityData())); |
| bridge()->ApplyIncrementalSyncChanges(nullptr, std::move(change_list)); |
| |
| EXPECT_EQ(bridge()->GetCallbacksCountForTesting(), 0u); |
| histogram_tester.ExpectUniqueSample("Sync.SharingMessage.CommitResult", |
| SharingMessageCommitError::NONE, 1); |
| |
| // Check that GetDataForCommit() doesn't return anything after successful |
| // commit. |
| base::MockCallback<syncer::DataTypeSyncBridge::DataCallback> data_callback; |
| std::unique_ptr<DataBatch> data_batch = |
| bridge()->GetDataForCommit({storage_key}); |
| ASSERT_THAT(data_batch, NotNull()); |
| EXPECT_FALSE(data_batch->HasNext()); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldInvokeCallbackOnFailure) { |
| base::HistogramTester histogram_tester; |
| syncer::EntityData entity_data; |
| EXPECT_CALL(*processor(), Put(_, _, _)) |
| .WillRepeatedly(SaveArgPointeeMove<1>(&entity_data)); |
| |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| SharingMessageCommitError commit_error; |
| EXPECT_CALL(callback, Run).WillOnce(SaveArg<0>(&commit_error)); |
| |
| bridge()->SendSharingMessage(CreateSpecifics("payload"), callback.Get()); |
| |
| EXPECT_FALSE(entity_data.client_tag_hash.value().empty()); |
| |
| // The callback should be called only after committing data. |
| EXPECT_FALSE(commit_error.has_error_code()); |
| |
| syncer::FailedCommitResponseDataList response_list; |
| { |
| syncer::FailedCommitResponseData response; |
| response.client_tag_hash = entity_data.client_tag_hash; |
| response.datatype_specific_error.mutable_sharing_message_error() |
| ->set_error_code(SharingMessageCommitError::PERMISSION_DENIED); |
| response_list.push_back(std::move(response)); |
| } |
| EXPECT_CALL(*processor(), |
| UntrackEntityForClientTagHash(entity_data.client_tag_hash)); |
| |
| bridge()->OnCommitAttemptErrors(response_list); |
| |
| EXPECT_TRUE(commit_error.has_error_code()); |
| EXPECT_EQ(commit_error.error_code(), |
| SharingMessageCommitError::PERMISSION_DENIED); |
| EXPECT_EQ(bridge()->GetCallbacksCountForTesting(), 0u); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.SharingMessage.CommitResult", |
| SharingMessageCommitError::PERMISSION_DENIED, 1); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldInvokeCallbackIfSyncIsDisabled) { |
| base::HistogramTester histogram_tester; |
| ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(false)); |
| EXPECT_CALL(*processor(), Put).Times(0); |
| |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| EXPECT_CALL(callback, |
| Run(HasErrorCode(SharingMessageCommitError::SYNC_TURNED_OFF))); |
| |
| bridge()->SendSharingMessage(CreateSpecifics("test_payload"), callback.Get()); |
| |
| EXPECT_EQ(bridge()->GetCallbacksCountForTesting(), 0u); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.SharingMessage.CommitResult", |
| SharingMessageCommitError::SYNC_TURNED_OFF, 1); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldInvokeCallbackWithErrorOffline) { |
| OfflineNetworkChangeNotifier network_change_notifier; |
| base::HistogramTester histogram_tester; |
| EXPECT_CALL(*processor(), Put).Times(0); |
| |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| EXPECT_CALL(callback, |
| Run(HasErrorCode( |
| sync_pb::SharingMessageCommitError::SYNC_NETWORK_ERROR))); |
| |
| bridge()->SendSharingMessage(CreateSpecifics("test_payload"), callback.Get()); |
| |
| EXPECT_EQ(bridge()->GetCallbacksCountForTesting(), 0u); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.SharingMessage.CommitResult", |
| sync_pb::SharingMessageCommitError::SYNC_NETWORK_ERROR, 1); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldInvokeCallbackOnSyncStoppedEvent) { |
| base::HistogramTester histogram_tester; |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| bridge()->SendSharingMessage(CreateSpecifics("test_payload"), callback.Get()); |
| ASSERT_EQ(bridge()->GetCallbacksCountForTesting(), 1u); |
| |
| EXPECT_CALL(callback, |
| Run(HasErrorCode(SharingMessageCommitError::SYNC_TURNED_OFF))); |
| bridge()->ApplyDisableSyncChanges(nullptr); |
| |
| EXPECT_EQ(bridge()->GetCallbacksCountForTesting(), 0u); |
| histogram_tester.ExpectUniqueSample( |
| "Sync.SharingMessage.CommitResult", |
| SharingMessageCommitError::SYNC_TURNED_OFF, 1); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldInvokeCallbackOnTimeout) { |
| base::HistogramTester histogram_tester; |
| syncer::EntityData entity_data; |
| EXPECT_CALL(*processor(), Put(_, _, _)) |
| .WillRepeatedly(SaveArgPointeeMove<1>(&entity_data)); |
| |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| |
| bridge()->SendSharingMessage(CreateSpecifics("test_payload"), callback.Get()); |
| ASSERT_EQ(bridge()->GetCallbacksCountForTesting(), 1u); |
| |
| EXPECT_CALL(callback, |
| Run(HasErrorCode(SharingMessageCommitError::SYNC_TIMEOUT))); |
| EXPECT_CALL(*processor(), |
| UntrackEntityForClientTagHash(entity_data.client_tag_hash)); |
| |
| FastForwardThroughTimeout(); |
| |
| EXPECT_EQ(bridge()->GetCallbacksCountForTesting(), 0u); |
| histogram_tester.ExpectUniqueSample("Sync.SharingMessage.CommitResult", |
| SharingMessageCommitError::SYNC_TIMEOUT, |
| 1); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldIgnoreSyncAuthError) { |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| EXPECT_CALL(callback, Run(_)).Times(0); |
| bridge()->SendSharingMessage(CreateSpecifics("test_payload"), callback.Get()); |
| bridge()->OnCommitAttemptFailed(syncer::SyncCommitError::kAuthError); |
| |
| EXPECT_EQ(1u, bridge()->GetCallbacksCountForTesting()); |
| } |
| |
| TEST_F(SharingMessageBridgeTest, ShouldReturnUnsyncedData) { |
| const std::string payload1 = "payload_1"; |
| const std::string payload2 = "payload_2"; |
| |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| EXPECT_CALL(callback, Run(_)).Times(0); |
| bridge()->SendSharingMessage(CreateSpecifics(payload1), callback.Get()); |
| bridge()->SendSharingMessage(CreateSpecifics(payload2), callback.Get()); |
| |
| base::MockCallback<syncer::DataTypeSyncBridge::DataCallback> data_callback; |
| std::unique_ptr<DataBatch> data_batch = bridge()->GetAllDataForDebugging(); |
| ASSERT_THAT(data_batch, NotNull()); |
| std::unordered_map<std::string, std::string> storage_key_to_payload = |
| ExtractStorageKeyAndPayloads(std::move(data_batch)); |
| EXPECT_THAT(storage_key_to_payload, |
| UnorderedElementsAre(Pair(_, payload1), Pair(_, payload2))); |
| |
| syncer::DataTypeSyncBridge::StorageKeyList storage_key_list; |
| for (const auto& sk_to_payload : storage_key_to_payload) { |
| storage_key_list.push_back(sk_to_payload.first); |
| } |
| |
| // Add another one invalid storage key. |
| storage_key_list.push_back("invalid_storage_key"); |
| data_batch = bridge()->GetDataForCommit(std::move(storage_key_list)); |
| ASSERT_THAT(data_batch, NotNull()); |
| storage_key_to_payload = ExtractStorageKeyAndPayloads(std::move(data_batch)); |
| EXPECT_THAT(storage_key_to_payload, |
| UnorderedElementsAre(Pair(_, payload1), Pair(_, payload2))); |
| } |
| |
| TEST_P(SharingMessageBridgeErrorsTest, |
| ShouldInvokeCallbackOnSyncCommitFailure) { |
| base::HistogramTester histogram_tester; |
| base::MockCallback<SharingMessageBridge::CommitFinishedCallback> callback; |
| bridge()->SendSharingMessage(CreateSpecifics("test_payload"), callback.Get()); |
| ASSERT_EQ(bridge()->GetCallbacksCountForTesting(), 1u); |
| |
| EXPECT_CALL(callback, Run(HasErrorCode(testing::get<1>(GetParam())))); |
| bridge()->OnCommitAttemptFailed(testing::get<0>(GetParam())); |
| |
| EXPECT_EQ(bridge()->GetCallbacksCountForTesting(), 0u); |
| histogram_tester.ExpectUniqueSample("Sync.SharingMessage.CommitResult", |
| testing::get<1>(GetParam()), 1); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SharingMessageBridgeErrorsTest, |
| testing::Values( |
| testing::make_tuple(SyncCommitError::kNetworkError, |
| SharingMessageCommitError::SYNC_NETWORK_ERROR), |
| testing::make_tuple(SyncCommitError::kServerError, |
| SharingMessageCommitError::SYNC_SERVER_ERROR), |
| testing::make_tuple(SyncCommitError::kBadServerResponse, |
| SharingMessageCommitError::SYNC_SERVER_ERROR))); |
| |
| } // namespace |