| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "missive/missive/missive_impl.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include <base/functional/bind.h> |
| #include <base/functional/callback_helpers.h> |
| #include <base/memory/scoped_refptr.h> |
| #include <base/test/task_environment.h> |
| #include <brillo/dbus/mock_dbus_method_response.h> |
| #include <dbus/bus.h> |
| #include <dbus/mock_bus.h> |
| #include <featured/fake_platform_features.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "missive/analytics/metrics_test_util.h" |
| #include "missive/analytics/resource_collector_cpu.h" |
| #include "missive/analytics/resource_collector_memory.h" |
| #include "missive/analytics/resource_collector_storage.h" |
| #include "missive/compression/test_compression_module.h" |
| #include "missive/dbus/dbus_test_environment.h" |
| #include "missive/dbus/mock_upload_client.h" |
| #include "missive/encryption/test_encryption_module.h" |
| #include "missive/health/health_module.h" |
| #include "missive/health/health_module_delegate_mock.h" |
| #include "missive/missive/migration.h" |
| #include "missive/storage/storage_configuration.h" |
| #include "missive/storage/storage_module.h" |
| #include "missive/util/server_configuration_controller.h" |
| #include "missive/util/status.h" |
| #include "missive/util/status_macros.h" |
| #include "missive/util/test_support_callbacks.h" |
| #include "missive/util/test_util.h" |
| |
| using ::testing::_; |
| using ::testing::Eq; |
| using ::testing::Invoke; |
| using ::testing::NiceMock; |
| using ::testing::Property; |
| using ::testing::Return; |
| using ::testing::StrEq; |
| using ::testing::WithArg; |
| |
| namespace reporting { |
| |
| class MockStorageModule : public StorageModule { |
| public: |
| // As opposed to the production |StorageModule|, test module does not need to |
| // call factory method - it is created directly by constructor. The |
| MockStorageModule() |
| : StorageModule({.options = StorageOptions(), |
| .queues_container = QueuesContainer::Create( |
| /*storage_degradation_enabled=*/false), |
| .async_start_upload_cb = |
| /*async_start_upload_cb=*/base::DoNothing()}) {} |
| |
| MOCK_METHOD(void, |
| AddRecord, |
| (Priority priority, Record record, EnqueueCallback callback), |
| (override)); |
| |
| MOCK_METHOD(void, |
| Flush, |
| (Priority priority, FlushCallback callback), |
| (override)); |
| |
| MOCK_METHOD(void, |
| ReportSuccess, |
| (SequenceInformation sequence_information, |
| bool force, |
| base::OnceCallback<void(Status)> done_cb), |
| (override)); |
| |
| MOCK_METHOD(void, |
| UpdateEncryptionKey, |
| (SignedEncryptionInfo signed_encryption_key), |
| (override)); |
| }; |
| |
| class MissiveImplTest : public ::testing::Test { |
| public: |
| MissiveImplTest() = default; |
| |
| protected: |
| void SetUp() override { |
| // Migration is expected to fail in the test. |
| ON_CALL(analytics::Metrics::TestEnvironment::GetMockMetricsLibrary(), |
| SendEnumToUMA(kMigrationStatusUmaName, _, |
| static_cast<int>(MigrationStatusForUma::kMaxValue))) |
| .WillByDefault(Return(true)); |
| |
| // Ignore collector UMA |
| ON_CALL(analytics::Metrics::TestEnvironment::GetMockMetricsLibrary(), |
| SendToUMA( |
| /*name=*/analytics::ResourceCollectorStorage::kUmaName, |
| /*sample=*/_, |
| /*min=*/analytics::ResourceCollectorStorage::kMin, |
| /*max=*/analytics::ResourceCollectorStorage::kMax, |
| /*nbuckets=*/ |
| analytics::ResourceCollectorStorage::kUmaNumberOfBuckets)) |
| .WillByDefault(Return(true)); |
| ON_CALL(analytics::Metrics::TestEnvironment::GetMockMetricsLibrary(), |
| SendPercentageToUMA( |
| /*name=*/analytics::ResourceCollectorCpu::kUmaName, |
| /*sample=*/_)) |
| .WillByDefault(Return(true)); |
| ON_CALL(analytics::Metrics::TestEnvironment::GetMockMetricsLibrary(), |
| SendLinearToUMA( |
| /*name=*/analytics::ResourceCollectorMemory::kUmaName, |
| /*sample=*/_, |
| /*max=*/analytics::ResourceCollectorMemory::kUmaMax)) |
| .WillByDefault(Return(true)); |
| |
| missive_ = std::make_unique<MissiveImpl>(); |
| missive_ |
| ->SetUploadClientFactory(base::BindOnce( |
| [](MissiveImplTest* self, scoped_refptr<dbus::Bus> bus, |
| base::OnceCallback<void(StatusOr<scoped_refptr<UploadClient>>)> |
| callback) { |
| self->upload_client_ = |
| base::MakeRefCounted<test::MockUploadClient>(); |
| std::move(callback).Run(self->upload_client_); |
| }, |
| base::Unretained(this))) |
| .SetCompressionModuleFactory(base::BindOnce( |
| [](MissiveImplTest* self, |
| const MissiveArgs::StorageParameters& parameters) { |
| self->compression_module_ = |
| base::MakeRefCounted<test::TestCompressionModule>(); |
| return self->compression_module_; |
| }, |
| base::Unretained(this))) |
| .SetEncryptionModuleFactory(base::BindOnce( |
| [](MissiveImplTest* self, |
| const MissiveArgs::StorageParameters& parameters) { |
| self->encryption_module_ = |
| base::MakeRefCounted<test::TestEncryptionModule>( |
| parameters.encryption_enabled); |
| return self->encryption_module_; |
| }, |
| base::Unretained(this))) |
| .SetServerConfigurationControllerFactory(base::BindOnce( |
| [](MissiveImplTest* self, |
| const MissiveArgs::ConfigFileParameters& parameters) { |
| self->server_configuration_controller_ = |
| ServerConfigurationController::Create( |
| parameters.blocking_destinations_enabled); |
| return self->server_configuration_controller_; |
| }, |
| base::Unretained(this))) |
| .SetHealthModuleFactory(base::BindOnce( |
| [](MissiveImplTest* self, const base::FilePath&) { |
| auto delegate = std::make_unique<HealthModuleDelegateMock>(); |
| self->health_module_delegate_ = delegate.get(); |
| return HealthModule::Create(std::move(delegate)); |
| }, |
| base::Unretained(this))) |
| .SetStorageModuleFactory(base::BindOnce( |
| [](MissiveImplTest* self, MissiveImpl* missive, |
| StorageOptions storage_options, |
| MissiveArgs::StorageParameters parameters, |
| base::OnceCallback<void(StatusOr<scoped_refptr<StorageModule>>)> |
| callback) { |
| self->storage_module_ = base::MakeRefCounted<MockStorageModule>(); |
| std::move(callback).Run(self->storage_module_); |
| }, |
| base::Unretained(this))); |
| |
| fake_platform_features_ = std::make_unique<feature::FakePlatformFeatures>( |
| dbus_test_environment_.mock_bus().get()); |
| fake_platform_features_->SetEnabled(MissiveArgs::kCollectorFeature.name, |
| false); |
| fake_platform_features_->SetEnabled(MissiveArgs::kStorageFeature.name, |
| true); |
| fake_platform_features_ptr_ = fake_platform_features_.get(); |
| |
| test::TestEvent<Status> started; |
| missive_->StartUp(dbus_test_environment_.mock_bus(), |
| fake_platform_features_ptr_, started.cb()); |
| ASSERT_OK(started.result()); |
| EXPECT_TRUE(compression_module_->is_enabled()); |
| EXPECT_TRUE(encryption_module_->is_enabled()); |
| } |
| |
| void TearDown() override { |
| if (missive_) { |
| ASSERT_OK(missive_->ShutDown()); |
| health_module_delegate_ = nullptr; // No longer available. |
| } |
| // Let everything ongoing to finish. |
| task_environment_.RunUntilIdle(); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| test::DBusTestEnvironment dbus_test_environment_; |
| std::unique_ptr<feature::FakePlatformFeatures> fake_platform_features_; |
| feature::FakePlatformFeatures* fake_platform_features_ptr_; |
| |
| // Use the metrics test environment to prevent the real metrics from |
| // initializing. |
| analytics::Metrics::TestEnvironment metrics_test_environment_; |
| HealthModuleDelegateMock* health_module_delegate_ = nullptr; |
| scoped_refptr<UploadClient> upload_client_; |
| scoped_refptr<CompressionModule> compression_module_; |
| scoped_refptr<EncryptionModuleInterface> encryption_module_; |
| scoped_refptr<MockStorageModule> storage_module_; |
| scoped_refptr<ServerConfigurationController> server_configuration_controller_; |
| std::unique_ptr<MissiveImpl> missive_; |
| }; |
| |
| TEST_F(MissiveImplTest, AsyncStartUploadTest) { |
| test::TestEvent<StatusOr<std::unique_ptr<UploaderInterface>>> uploader_event; |
| MissiveImpl::AsyncStartUpload( |
| missive_->GetWeakPtr(), UploaderInterface::UploadReason::IMMEDIATE_FLUSH, |
| base::DoNothing(), uploader_event.cb()); |
| auto response_result = uploader_event.result(); |
| ASSERT_OK(response_result) << response_result.error(); |
| response_result.value()->Completed( |
| Status(error::INTERNAL, "Failing for tests")); |
| } |
| |
| TEST_F(MissiveImplTest, AsyncNoStartUploadTest) { |
| test::TestEvent<StatusOr<std::unique_ptr<UploaderInterface>>> uploader_event; |
| auto weak_ptr = missive_->GetWeakPtr(); |
| missive_.reset(); |
| MissiveImpl::AsyncStartUpload( |
| weak_ptr, UploaderInterface::UploadReason::IMMEDIATE_FLUSH, |
| base::DoNothing(), uploader_event.cb()); |
| auto response_result = uploader_event.result(); |
| EXPECT_THAT(response_result, |
| Property(&StatusOr<std::unique_ptr<UploaderInterface>>::error, |
| Property(&Status::code, Eq(error::UNAVAILABLE)))) |
| << response_result.error(); |
| } |
| |
| TEST_F(MissiveImplTest, EnqueueRecordTest) { |
| EnqueueRecordRequest request; |
| request.mutable_record()->set_data("DATA"); |
| request.mutable_record()->set_destination(HEARTBEAT_EVENTS); |
| request.set_priority(FAST_BATCH); |
| |
| EXPECT_CALL(*storage_module_, AddRecord(Eq(request.priority()), |
| EqualsProto(request.record()), _)) |
| .WillOnce( |
| WithArg<2>([](StorageModuleInterface::EnqueueCallback callback) { |
| std::move(callback).Run(Status::StatusOK()); |
| })); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<EnqueueRecordResponse>>(); |
| test::TestEvent<const EnqueueRecordResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->EnqueueRecord(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&EnqueueRecordResponse::status, |
| Property(&StatusProto::code, Eq(error::OK)))); |
| } |
| |
| TEST_F(MissiveImplTest, FlushPriorityTest) { |
| FlushPriorityRequest request; |
| request.set_priority(MANUAL_BATCH); |
| |
| EXPECT_CALL(*storage_module_, Flush(Eq(request.priority()), _)) |
| .WillOnce(WithArg<1>([](StorageModuleInterface::FlushCallback callback) { |
| std::move(callback).Run(Status::StatusOK()); |
| })); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<FlushPriorityResponse>>(); |
| test::TestEvent<const FlushPriorityResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->FlushPriority(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&FlushPriorityResponse::status, |
| Property(&StatusProto::code, Eq(error::OK)))); |
| } |
| |
| TEST_F(MissiveImplTest, ConfirmRecordUploadTest) { |
| ConfirmRecordUploadRequest request; |
| request.mutable_sequence_information()->set_sequencing_id(1234L); |
| request.mutable_sequence_information()->set_generation_id(9876L); |
| request.mutable_sequence_information()->set_priority(IMMEDIATE); |
| request.set_force_confirm(true); |
| |
| EXPECT_CALL(*storage_module_, |
| ReportSuccess(EqualsProto(request.sequence_information()), |
| Eq(request.force_confirm()), _)) |
| .WillOnce(WithArg<2>(Invoke([](base::OnceCallback<void(Status)> done_cb) { |
| std::move(done_cb).Run(Status::StatusOK()); |
| }))); |
| |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| ConfirmRecordUploadResponse>>(); |
| test::TestEvent<const ConfirmRecordUploadResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->ConfirmRecordUpload(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&ConfirmRecordUploadResponse::status, |
| Property(&StatusProto::code, Eq(error::OK)))); |
| } |
| |
| TEST_F(MissiveImplTest, UpdateEncryptionKeyTest) { |
| UpdateEncryptionKeyRequest request; |
| request.mutable_signed_encryption_info()->set_public_asymmetric_key( |
| "PUBLIC_KEY"); |
| request.mutable_signed_encryption_info()->set_public_key_id(555666); |
| request.mutable_signed_encryption_info()->set_signature("SIGNATURE"); |
| |
| EXPECT_CALL( |
| *storage_module_, |
| UpdateEncryptionKey(EqualsProto(request.signed_encryption_info()))) |
| .Times(1); |
| |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| UpdateEncryptionKeyResponse>>(); |
| test::TestEvent<const UpdateEncryptionKeyResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->UpdateEncryptionKey(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&UpdateEncryptionKeyResponse::status, |
| Property(&StatusProto::code, Eq(error::OK)))); |
| } |
| |
| TEST_F(MissiveImplTest, ResponseWithErrorTest) { |
| const Status error{error::INTERNAL, "Test generated error"}; |
| |
| FlushPriorityRequest request; |
| request.set_priority(SLOW_BATCH); |
| |
| EXPECT_CALL(*storage_module_, Flush(Eq(request.priority()), _)) |
| .WillOnce( |
| WithArg<1>([&error](StorageModuleInterface::FlushCallback callback) { |
| std::move(callback).Run(error); |
| })); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<FlushPriorityResponse>>(); |
| test::TestEvent<const FlushPriorityResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->FlushPriority(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT( |
| response_result, |
| Property(&FlushPriorityResponse::status, |
| AllOf(Property(&StatusProto::code, Eq(error.error_code())), |
| Property(&StatusProto::error_message, |
| StrEq(error.error_message()))))); |
| } |
| |
| TEST_F(MissiveImplTest, DisabledReportingTest) { |
| // Simulate upload with FAILURE_PRECONDITION response to disable reporting. |
| missive_->SetEnabled(/*is_enabled=*/false); |
| |
| { |
| EnqueueRecordRequest request; |
| request.mutable_record()->set_data("DATA"); |
| request.mutable_record()->set_destination(HEARTBEAT_EVENTS); |
| request.set_priority(FAST_BATCH); |
| |
| EXPECT_CALL(*storage_module_, AddRecord).Times(0); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<EnqueueRecordResponse>>(); |
| test::TestEvent<const EnqueueRecordResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->EnqueueRecord(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&EnqueueRecordResponse::status, |
| AllOf(Property(&StatusProto::code, |
| Eq(error::FAILED_PRECONDITION)), |
| Property(&StatusProto::error_message, |
| StrEq("Reporting is disabled"))))); |
| } |
| |
| { |
| FlushPriorityRequest request; |
| request.set_priority(MANUAL_BATCH); |
| |
| EXPECT_CALL(*storage_module_, Flush).Times(0); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<FlushPriorityResponse>>(); |
| test::TestEvent<const FlushPriorityResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->FlushPriority(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&FlushPriorityResponse::status, |
| AllOf(Property(&StatusProto::code, |
| Eq(error::FAILED_PRECONDITION)), |
| Property(&StatusProto::error_message, |
| StrEq("Reporting is disabled"))))); |
| } |
| |
| { |
| ConfirmRecordUploadRequest request; |
| request.mutable_sequence_information()->set_sequencing_id(1234L); |
| request.mutable_sequence_information()->set_generation_id(9876L); |
| request.mutable_sequence_information()->set_priority(IMMEDIATE); |
| request.set_force_confirm(true); |
| |
| EXPECT_CALL(*storage_module_, ReportSuccess).Times(0); |
| |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| ConfirmRecordUploadResponse>>(); |
| test::TestEvent<const ConfirmRecordUploadResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->ConfirmRecordUpload(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&ConfirmRecordUploadResponse::status, |
| AllOf(Property(&StatusProto::code, |
| Eq(error::FAILED_PRECONDITION)), |
| Property(&StatusProto::error_message, |
| StrEq("Reporting is disabled"))))); |
| } |
| |
| { |
| UpdateEncryptionKeyRequest request; |
| request.mutable_signed_encryption_info()->set_public_asymmetric_key( |
| "PUBLIC_KEY"); |
| request.mutable_signed_encryption_info()->set_public_key_id(555666); |
| request.mutable_signed_encryption_info()->set_signature("SIGNATURE"); |
| |
| EXPECT_CALL(*storage_module_, UpdateEncryptionKey).Times(0); |
| |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| UpdateEncryptionKeyResponse>>(); |
| test::TestEvent<const UpdateEncryptionKeyResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->UpdateEncryptionKey(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&UpdateEncryptionKeyResponse::status, |
| AllOf(Property(&StatusProto::code, |
| Eq(error::FAILED_PRECONDITION)), |
| Property(&StatusProto::error_message, |
| StrEq("Reporting is disabled"))))); |
| } |
| |
| { |
| test::TestEvent<StatusOr<std::unique_ptr<UploaderInterface>>> |
| uploader_event; |
| MissiveImpl::AsyncStartUpload( |
| missive_->GetWeakPtr(), |
| UploaderInterface::UploadReason::IMMEDIATE_FLUSH, base::DoNothing(), |
| uploader_event.cb()); |
| const auto& response_result = uploader_event.result(); |
| EXPECT_THAT(response_result, |
| Property(&StatusOr<std::unique_ptr<UploaderInterface>>::error, |
| AllOf(Property(&Status::error_code, |
| Eq(error::FAILED_PRECONDITION)), |
| Property(&Status::error_message, |
| StrEq("Reporting is disabled"))))); |
| } |
| |
| { |
| UpdateConfigInMissiveRequest request; |
| request.mutable_list_of_blocked_destinations()->add_destinations( |
| Destination::CRD_EVENTS); |
| request.mutable_list_of_blocked_destinations()->add_destinations( |
| Destination::PERIPHERAL_EVENTS); |
| |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| UpdateConfigInMissiveResponse>>(); |
| test::TestEvent<const UpdateConfigInMissiveResponse&> response_event; |
| response->set_return_callback(response_event.cb()); |
| missive_->UpdateConfigInMissive(request, std::move(response)); |
| const auto& response_result = response_event.ref_result(); |
| EXPECT_THAT(response_result, |
| Property(&UpdateConfigInMissiveResponse::status, |
| AllOf(Property(&StatusProto::code, |
| Eq(error::FAILED_PRECONDITION)), |
| Property(&StatusProto::error_message, |
| StrEq("Reporting is disabled"))))); |
| } |
| } |
| |
| TEST_F(MissiveImplTest, StorageDynamicParametersUpdateTest) { |
| // Change parameters and refresh. |
| fake_platform_features_ptr_->SetParam( |
| MissiveArgs::kStorageFeature.name, |
| MissiveArgs::kCompressionEnabledParameter, "False"); |
| fake_platform_features_ptr_->SetParam( |
| MissiveArgs::kStorageFeature.name, |
| MissiveArgs::kEncryptionEnabledParameter, "False"); |
| fake_platform_features_ptr_->TriggerRefetchSignal(); |
| |
| // Let asynchronous update finish. |
| task_environment_.RunUntilIdle(); |
| |
| EXPECT_FALSE(compression_module_->is_enabled()); |
| EXPECT_FALSE(encryption_module_->is_enabled()); |
| } |
| |
| TEST_F(MissiveImplTest, ConfigFileDynamicParametersUpdateTest) { |
| // Change parameters and refresh. |
| fake_platform_features_ptr_->SetEnabled(MissiveArgs::kConfigFileFeature.name, |
| true); |
| fake_platform_features_ptr_->SetParam( |
| MissiveArgs::kConfigFileFeature.name, |
| MissiveArgs::kBlockingDestinationsEnabledParameter, "True"); |
| fake_platform_features_ptr_->TriggerRefetchSignal(); |
| |
| // Let asynchronous update finish. |
| task_environment_.RunUntilIdle(); |
| |
| EXPECT_TRUE(server_configuration_controller_->is_enabled()); |
| } |
| } // namespace reporting |