chromium/chrome/browser/ash/dbus/encrypted_reporting_service_provider_unittest.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "chrome/browser/ash/dbus/encrypted_reporting_service_provider.h"

#include <memory>
#include <utility>

#include "base/base64.h"
#include "base/memory/ref_counted.h"
#include "base/task/thread_pool.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/scoped_feature_list.h"
#include "base/uuid.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/policy/messaging_layer/upload/fake_upload_client.h"
#include "chrome/browser/policy/messaging_layer/upload/upload_client.h"
#include "chrome/browser/policy/messaging_layer/util/reporting_server_connector.h"
#include "chrome/browser/policy/messaging_layer/util/reporting_server_connector_test_util.h"
#include "chrome/browser/policy/messaging_layer/util/test_request_payload.h"
#include "chrome/browser/policy/messaging_layer/util/test_response_payload.h"
#include "chrome/browser/policy/messaging_layer/util/upload_declarations.h"
#include "chromeos/ash/components/dbus/services/service_provider_test_helper.h"
#include "chromeos/dbus/missive/missive_client.h"
#include "components/policy/core/common/management/scoped_management_service_override_for_testing.h"
#include "components/reporting/proto/synced/interface.pb.h"
#include "content/public/test/browser_task_environment.h"
#include "dbus/exported_object.h"
#include "dbus/message.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using ReportSuccessfulUploadCallback =
    ::reporting::ReportSuccessfulUploadCallback;
using EncryptionKeyAttachedCallback =
    ::reporting::EncryptionKeyAttachedCallback;
using UpdateConfigInMissiveCallback =
    ::reporting::UpdateConfigInMissiveCallback;

using UploadProvider = ::reporting::EncryptedReportingUploadProvider;

using ::base::test::EqualsProto;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;

namespace ash {
namespace {

// CloudPolicyClient and UploadClient are not usable outside of a managed
// environment, to sidestep this we override the functions that normally build
// and retrieve these clients and provide a MockCloudPolicyClient and a
// FakeUploadClient.
class TestEncryptedReportingServiceProvider
    : public EncryptedReportingServiceProvider {
 public:
  TestEncryptedReportingServiceProvider(
      ReportSuccessfulUploadCallback report_successful_upload_cb,
      EncryptionKeyAttachedCallback encrypted_key_cb,
      UpdateConfigInMissiveCallback update_config_in_missive_cb)
      : EncryptedReportingServiceProvider(std::make_unique<UploadProvider>(
            report_successful_upload_cb,
            encrypted_key_cb,
            update_config_in_missive_cb,
            /*upload_client_builder_cb=*/
            base::BindRepeating(&::reporting::FakeUploadClient::Create))) {}

  TestEncryptedReportingServiceProvider(
      const TestEncryptedReportingServiceProvider& other) = delete;
  TestEncryptedReportingServiceProvider& operator=(
      const TestEncryptedReportingServiceProvider& other) = delete;
};

class EncryptedReportingServiceProviderTest : public ::testing::Test {
 public:
  MOCK_METHOD(void,
              ReportSuccessfulUpload,
              (::reporting::SequenceInformation, bool),
              ());
  MOCK_METHOD(void,
              EncryptionKeyCallback,
              (::reporting::SignedEncryptionInfo),
              ());
  MOCK_METHOD(void,
              ConfigFileCallback,
              (::reporting::ListOfBlockedDestinations),
              ());

 protected:
  void SetUp() override {
    chromeos::MissiveClient::InitializeFake();

    auto successful_upload_cb = base::BindRepeating(
        &EncryptedReportingServiceProviderTest::ReportSuccessfulUpload,
        base::Unretained(this));
    auto encryption_key_cb = base::BindRepeating(
        &EncryptedReportingServiceProviderTest::EncryptionKeyCallback,
        base::Unretained(this));
    auto config_file_cb = base::BindRepeating(
        &EncryptedReportingServiceProviderTest::ConfigFileCallback,
        base::Unretained(this));
    service_provider_ = std::make_unique<TestEncryptedReportingServiceProvider>(
        successful_upload_cb, encryption_key_cb, config_file_cb);

    record_.set_encrypted_wrapped_record("TEST_DATA");

    auto* sequence_information = record_.mutable_sequence_information();
    sequence_information->set_sequencing_id(42);
    sequence_information->set_generation_id(1701);
    sequence_information->set_generation_guid(
        base::Uuid::GenerateRandomV4().AsLowercaseString());
    sequence_information->set_priority(::reporting::Priority::SLOW_BATCH);
  }

  void TearDown() override {
    // Destruct test helper before the client shut down.
    test_helper_.TearDown();
    chromeos::MissiveClient::Shutdown();
  }

  void SetupForRequestUploadEncryptedRecord() {
    test_helper_.SetUp(
        chromeos::kChromeReportingServiceName,
        dbus::ObjectPath(chromeos::kChromeReportingServicePath),
        chromeos::kChromeReportingServiceInterface,
        chromeos::kChromeReportingServiceUploadEncryptedRecordMethod,
        service_provider_.get());
    // There are multiple Tasks that are started by calling the Upload request.
    // We need to wait for them to complete, or we will get race conditions on
    // exit and some test runs will be flakey.
    task_environment_.RunUntilIdle();
  }

  void CallRequestUploadEncryptedRecord(
      const ::reporting::UploadEncryptedRecordRequest& request,
      ::reporting::UploadEncryptedRecordResponse* encrypted_record_response) {
    dbus::MethodCall method_call(
        chromeos::kChromeReportingServiceInterface,
        chromeos::kChromeReportingServiceUploadEncryptedRecordMethod);
    dbus::MessageWriter writer(&method_call);
    writer.AppendProtoAsArrayOfBytes(request);

    std::unique_ptr<dbus::Response> response =
        test_helper_.CallMethod(&method_call);

    ASSERT_TRUE(response);
    dbus::MessageReader reader(response.get());
    ASSERT_TRUE(reader.PopArrayOfBytesAsProto(encrypted_record_response));
  }

  // Must be initialized before any other class member.
  content::BrowserTaskEnvironment task_environment_;

  // Set up device as a managed device by default. To set the device as
  // unmanaged, create a new `policy::ScopedManagementServiceOverrideForTesting`
  // inside the test.
  policy::ScopedManagementServiceOverrideForTesting scoped_management_service_ =
      policy::ScopedManagementServiceOverrideForTesting(
          policy::ManagementServiceFactory::GetForPlatform(),
          policy::EnterpriseManagementAuthority::CLOUD_DOMAIN);

  ::reporting::ReportingServerConnector::TestEnvironment test_env_;
  ::reporting::EncryptedRecord record_;

  base::test::ScopedFeatureList scoped_feature_list_;

  std::unique_ptr<TestEncryptedReportingServiceProvider> service_provider_;
  ServiceProviderTestHelper test_helper_;
};

TEST_F(EncryptedReportingServiceProviderTest, SuccessfullyUploadsRecord) {
  SetupForRequestUploadEncryptedRecord();
  EXPECT_CALL(*this, ReportSuccessfulUpload(
                         EqualsProto(record_.sequence_information()), _))
      .Times(1);

  ::reporting::UploadEncryptedRecordRequest request;
  request.add_encrypted_record()->CheckTypeAndMergeFrom(record_);

  ::reporting::UploadEncryptedRecordResponse response;
  CallRequestUploadEncryptedRecord(request, &response);
  task_environment_.RunUntilIdle();

  ASSERT_THAT(*test_env_.url_loader_factory()->pending_requests(),
              testing::SizeIs(1));
  EXPECT_THAT(test_env_.request_body(0),
              ::reporting::IsDataUploadRequestValid());

  test_env_.SimulateResponseForRequest(0);

  EXPECT_THAT(response.status().code(), Eq(::reporting::error::OK));
  EXPECT_THAT(response.cached_events_seq_ids(),
              ElementsAre(record_.sequence_information().sequencing_id()));
}
}  // namespace
}  // namespace ash