chromium/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc

// 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 "chrome/browser/policy/messaging_layer/upload/record_handler_impl.h"

#include <cstddef>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>

#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/expected.h"
#include "base/uuid.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/policy/messaging_layer/public/report_client.h"
#include "chrome/browser/policy/messaging_layer/public/report_client_test_util.h"
#include "chrome/browser/policy/messaging_layer/upload/file_upload_job.h"
#include "chrome/browser/policy/messaging_layer/upload/file_upload_job_test_util.h"
#include "chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h"
#include "chrome/browser/policy/messaging_layer/upload/server_uploader.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 "components/policy/core/common/management/scoped_management_service_override_for_testing.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/resources/resource_manager.h"
#include "components/reporting/util/status.h"
#include "components/reporting/util/statusor.h"
#include "components/reporting/util/test_support_callbacks.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

_;
AllOf;
ContainerEq;
Eq;
Gt;
IsEmpty;
Not;
NotNull;
Property;
SizeIs;
StrEq;

namespace reporting {
namespace {

static constexpr size_t kNumTestRecords =;
static constexpr int64_t kGenerationId =;

MATCHER_P(ResponseEquals,
          expected,
          "Compares StatusOr<response> to expected response") {}

class MockFileUploadDelegate : public FileUploadJob::Delegate {};

// Tests for generic events handling.
class RecordHandlerImplTest : public ::testing::TestWithParam<
                                  ::testing::tuple</*need_encryption_key*/ bool,
                                                   /*force_confirm*/ bool>> {};

std::pair<ScopedReservation, std::vector<EncryptedRecord>>
BuildTestRecordsVector(size_t number_of_test_records,
                       int64_t generation_id,
                       std::string generation_guid,
                       scoped_refptr<ResourceManager> memory_resource) {}

std::list<int64_t> GetExpectedCachedSeqIds(
    const std::vector<EncryptedRecord>& records) {}

TEST_P(RecordHandlerImplTest, UploadRecords) {}

TEST_P(RecordHandlerImplTest, MissingPriorityField) {}

TEST_P(RecordHandlerImplTest, InvalidPriorityField) {}

TEST_P(RecordHandlerImplTest, ContainsGenerationGuid) {}

TEST_P(RecordHandlerImplTest, ValidGenerationGuid) {}

#if BUILDFLAG(IS_CHROMEOS)
TEST_P(RecordHandlerImplTest, InvalidGenerationGuid) {
  auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId,
                                             kGenerationGuid, memory_resource_);
  const auto force_confirm_by_server = force_confirm();
  const auto expected_cached_seq_ids =
      GetExpectedCachedSeqIds(test_records.second);

  test::TestEvent<StatusOr<std::list<int64_t>>> enqueued_event;
  test::TestEvent<SignedEncryptionInfo> encryption_key_attached_event;
  test::TestEvent<ConfigFile> config_file_attached_event;
  test::TestEvent<CompletionResponse> responder_event;

  handler_->HandleRecords(need_encryption_key(), /*config_file_version=*/-1,
                          std::move(test_records.second),
                          std::move(test_records.first), enqueued_event.cb(),
                          responder_event.cb(),
                          encryption_key_attached_event.repeating_cb(),
                          config_file_attached_event.repeating_cb());
  const auto& enqueued_result = enqueued_event.result();
  ASSERT_OK(enqueued_result) << enqueued_result.error();
  EXPECT_THAT(enqueued_result.value(), ContainerEq(expected_cached_seq_ids));

  task_environment_.RunUntilIdle();

  ASSERT_THAT(*test_env_->url_loader_factory()->pending_requests(), SizeIs(1u));
  auto request_body = test_env_->request_body(0);
  EXPECT_THAT(request_body, IsDataUploadRequestValid());
  auto response = ResponseBuilder(std::move(request_body))
                      .SetForceConfirm(force_confirm_by_server)
                      .Build();
  ASSERT_TRUE(response.has_value());

  // Generation guids must be parsable into `base::Uuid`.
  response->SetByDottedPath("lastSucceedUploadedRecord.generationGuid",
                            "invalid-generation-guid");

  test_env_->SimulateCustomResponseForRequest(0, std::move(response.value()));

  const auto result = responder_event.result();
  EXPECT_THAT(result.error(),
              Property(&Status::error_code, Eq(error::INTERNAL)));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_P(RecordHandlerImplTest, MissingGenerationGuidFromManagedDeviceIsOk) {}

#if BUILDFLAG(IS_CHROMEOS)
TEST_P(RecordHandlerImplTest,
       MissingGenerationGuidFromUnmanagedDeviceReturnError) {
  // Set device as unmanaged
  policy::ScopedManagementServiceOverrideForTesting scoped_management_service_ =
      policy::ScopedManagementServiceOverrideForTesting(
          policy::ManagementServiceFactory::GetForPlatform(),
          policy::EnterpriseManagementAuthority::NONE);

  auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId,
                                             kGenerationGuid, memory_resource_);
  const auto force_confirm_by_server = force_confirm();
  const auto expected_cached_seq_ids =
      GetExpectedCachedSeqIds(test_records.second);

  test::TestEvent<StatusOr<std::list<int64_t>>> enqueued_event;
  test::TestEvent<SignedEncryptionInfo> encryption_key_attached_event;
  test::TestEvent<ConfigFile> config_file_attached_event;
  test::TestEvent<CompletionResponse> responder_event;

  handler_->HandleRecords(need_encryption_key(), /*config_file_version=*/-1,
                          std::move(test_records.second),
                          std::move(test_records.first), enqueued_event.cb(),
                          responder_event.cb(),
                          encryption_key_attached_event.repeating_cb(),
                          config_file_attached_event.repeating_cb());
  const auto& enqueued_result = enqueued_event.result();
  ASSERT_OK(enqueued_result) << enqueued_result.error();
  EXPECT_THAT(enqueued_result.value(), ContainerEq(expected_cached_seq_ids));

  task_environment_.RunUntilIdle();

  ASSERT_THAT(*test_env_->url_loader_factory()->pending_requests(), SizeIs(1u));
  auto request_body = test_env_->request_body(0);
  EXPECT_THAT(request_body, IsDataUploadRequestValid());
  auto response = ResponseBuilder(std::move(request_body))
                      .SetForceConfirm(force_confirm_by_server)
                      .Build();
  ASSERT_TRUE(response.has_value());

  // Remove the generation guid. This should result in an error since we set
  // the device to an unmanaged state at the beginning of the test.
  response->RemoveByDottedPath("lastSucceedUploadedRecord.generationGuid");

  test_env_->SimulateCustomResponseForRequest(0, std::move(response.value()));

  const auto result = responder_event.result();
  EXPECT_FALSE(result.has_value());
  EXPECT_THAT(result.error(),
              Property(&Status::error_code, Eq(error::INTERNAL)));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_P(RecordHandlerImplTest, MissingSequenceInformation) {}

TEST_P(RecordHandlerImplTest, ReportsUploadFailure) {}

// TODO(b/289534046): Uploading gap records immediately on response will be
// throttled by the uploading client (`CloudPolicyClient` or
// `EncryptedReportingClient`). We need to resolve the issue, update the test
// accordingly then re-enable it.
TEST_P(RecordHandlerImplTest, DISABLED_UploadsGapRecordOnServerFailure) {}

// There may be cases where the server and the client do not align in the
// expected response, clients shouldn't crash in these instances, but simply
// report an internal error.
TEST_P(RecordHandlerImplTest, HandleUnknownResponseFromServer) {}

TEST_P(RecordHandlerImplTest, AssignsRequestIdForRecordUploads) {}

#if BUILDFLAG(IS_CHROMEOS)
TEST_P(RecordHandlerImplTest,
       ContainsConfigFileInResponseWithExperimentEnabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(kShouldRequestConfigurationFile);

  auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId,
                                             kGenerationGuid, memory_resource_);
  const auto force_confirm_by_server = force_confirm();
  const auto expected_cached_seq_ids =
      GetExpectedCachedSeqIds(test_records.second);

  SuccessfulUploadResponse expected_response{
      .sequence_information = test_records.second.back().sequence_information(),
      .force_confirm = force_confirm()};

  test::TestEvent<StatusOr<std::list<int64_t>>> enqueued_event;
  test::TestEvent<SignedEncryptionInfo> encryption_key_attached_event;
  test::TestEvent<ConfigFile> config_file_attached_event;
  test::TestEvent<CompletionResponse> responder_event;

  handler_->HandleRecords(need_encryption_key(), /*config_file_version=*/1,
                          std::move(test_records.second),
                          std::move(test_records.first), enqueued_event.cb(),
                          responder_event.cb(),
                          encryption_key_attached_event.repeating_cb(),
                          config_file_attached_event.repeating_cb());
  const auto& enqueued_result = enqueued_event.result();
  ASSERT_OK(enqueued_result) << enqueued_result.error();
  EXPECT_THAT(enqueued_result.value(), ContainerEq(expected_cached_seq_ids));

  task_environment_.RunUntilIdle();

  ASSERT_THAT(*test_env_->url_loader_factory()->pending_requests(), SizeIs(1));
  auto request_body = test_env_->request_body(0);
  EXPECT_THAT(request_body, IsDataUploadRequestValid());
  auto response = ResponseBuilder(std::move(request_body))
                      .SetForceConfirm(force_confirm_by_server)
                      .Build();
  ASSERT_TRUE(response.has_value());
  test_env_->SimulateCustomResponseForRequest(0, std::move(response.value()));

  if (need_encryption_key()) {
    EXPECT_THAT(
        encryption_key_attached_event.result(),
        AllOf(Property(&SignedEncryptionInfo::public_asymmetric_key,
                       Not(IsEmpty())),
              Property(&SignedEncryptionInfo::public_key_id, Gt(0)),
              Property(&SignedEncryptionInfo::signature, Not(IsEmpty()))));
  }

  EXPECT_THAT(
      config_file_attached_event.result(),
      AllOf(Property(&ConfigFile::config_file_signature, Not(IsEmpty())),
            Property(&ConfigFile::version, Gt(0)),
            Property(&ConfigFile::blocked_event_configs, Not(IsEmpty()))));
  const auto result = responder_event.result();
  EXPECT_THAT(result, ResponseEquals(expected_response));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

INSTANTIATE_TEST_SUITE_P();
}  // namespace
}  // namespace reporting