// Copyright 2022 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/enterprise/browser/reporting/chrome_profile_request_generator.h"

#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/certificate_matching/certificate_principal_pattern.h"
#include "components/device_signals/core/browser/mock_signals_aggregator.h"
#include "components/device_signals/core/common/signals_features.h"
#include "components/enterprise/browser/reporting/fake_browser_report_generator_delegate.h"
#include "components/enterprise/browser/reporting/report_generation_config.h"
#include "components/enterprise/browser/reporting/report_util.h"
#include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
#include "components/enterprise/connectors/core/reporting_constants.h"
#include "components/enterprise/device_attestation/scoped_device_attestation_service_factory.h"
#include "components/policy/core/common/cloud/cloud_policy_util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/version_info/channel.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::StrictMock;

namespace enterprise_reporting {

namespace em = enterprise_management;

const char kFakeSignalOsName[] = "os_name_from_signals";
const char kFakeSignalOsVersion[] = "100.0.from_signals";
const char kFakeSignalDisplayName[] = "user_from_signals";
const char kFakeSignalHostname[] = "host_name_from_signals";
const char kFakeProfileId[] = "profile_id_from_signals";
const char kFakeDistoVersion[] = "1.2.3";

const char kFakeSignalMacAddr1[] = "00-11-22-33-44-55-66";
const char kFakeSignalMacAddr2[] = "AA-BB-CC-DD-EE-FF";
const char kFakeSignalMacAddr3[] = "A0-B1-C2-D3-E4-F5";

#if BUILDFLAG(IS_WIN)
const char kFakeSignalAvName[] = "AV_name_from_signals";

const char kFakeFirstHotfix[] = "hotfix_1";
const char kFakeSecondHotfix[] = "hotfix_2";
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_ANDROID)
const bool kFakeHasHarmfulApps = false;
const bool kFakeVerifiedAppsEnabled = true;

const int64_t kFakeSecurityPatchLevel = 1735689600000;
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_IOS)
constexpr char kFakeVendorId[] = "fake-vendor-id";
#endif  // BUILDFLAG(IS_IOS)

constexpr char kFakeCertData[] = "fake_cert_data";
constexpr char kFakeSignature[] = "fake_signature";

namespace {

const base::FilePath::CharType kProfilePath[] =
    FILE_PATH_LITERAL("profile-path");
constexpr char kBrowserExePath[] = "browser-path";

device_signals::SignalsAggregationResponse CreateFilledResponse(
    bool nullify_profile_id = false,
    bool include_cert_signals = false) {
  device_signals::SignalsAggregationResponse response;

  device_signals::OsSignalsResponse os_signals;
  os_signals.operating_system = kFakeSignalOsName;
  os_signals.os_version = kFakeSignalOsVersion;
  os_signals.display_name = kFakeSignalDisplayName;
  os_signals.hostname = kFakeSignalHostname;
  os_signals.screen_lock_secured = device_signals::SettingValue::ENABLED;
  os_signals.distribution_version = kFakeDistoVersion;
  // Test vector field.
  os_signals.mac_addresses = {kFakeSignalMacAddr1, kFakeSignalMacAddr2,
                              kFakeSignalMacAddr3};

#if BUILDFLAG(IS_ANDROID)
  os_signals.has_potentially_harmful_apps = kFakeHasHarmfulApps;
  os_signals.verified_apps_enabled = kFakeVerifiedAppsEnabled;
  os_signals.security_patch_ms = kFakeSecurityPatchLevel;
#endif

#if BUILDFLAG(IS_IOS)
  os_signals.vendor_id = kFakeVendorId;
#endif  // BUILDFLAG(IS_IOS)

  response.os_signals_response = os_signals;

  device_signals::AgentSignalsResponse agent_signals;
  agent_signals.detected_agents = {device_signals::Agents::kCrowdStrikeFalcon};
  response.agent_signals_response = agent_signals;

#if BUILDFLAG(IS_WIN)
  device_signals::AvProduct av_product;
  av_product.display_name = kFakeSignalAvName;
  av_product.state = device_signals::AvProductState::kExpired;
  device_signals::AntiVirusSignalResponse av_response;
  av_response.av_products.push_back(av_product);
  response.av_signal_response = av_response;

  device_signals::HotfixSignalResponse hotfix_response;
  hotfix_response.hotfixes.push_back({kFakeFirstHotfix});
  hotfix_response.hotfixes.push_back({kFakeSecondHotfix});
  response.hotfix_signal_response = hotfix_response;
#endif  // BUILDFLAG(IS_WIN)

  if (include_cert_signals) {
    device_signals::CertificateSignalsResponse cert_response;
    em::SignedCertificateDetails fake_cert;
    fake_cert.set_data(kFakeCertData);
    fake_cert.set_signature(kFakeSignature);
    cert_response.serialized_caa_responses.push_back(
        fake_cert.SerializeAsString());
    cert_response.truncated_certificates = false;
    response.certificate_signals_response = cert_response;
  }

  device_signals::ProfileSignalsResponse profile_signals;
  profile_signals.built_in_dns_client_enabled = true;
  profile_signals.chrome_remote_desktop_app_blocked = false;
  profile_signals.password_protection_warning_trigger =
      safe_browsing::PasswordProtectionTrigger::PHISHING_REUSE;
  // Test that there is no issue if domain can't be collected.
  profile_signals.profile_enrollment_domain = std::nullopt;
  profile_signals.safe_browsing_protection_level =
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION;
  profile_signals.site_isolation_enabled = true;

  profile_signals.profile_id = kFakeProfileId;
  if (nullify_profile_id) {
    profile_signals.profile_id = std::nullopt;
  }

  profile_signals.realtime_url_check_mode = enterprise_connectors::
      EnterpriseRealTimeUrlCheckMode::REAL_TIME_CHECK_FOR_MAINFRAME_ENABLED;

  response.profile_signals_response = profile_signals;

  return response;
}

}  // namespace

class ChromeProfileRequestGeneratorTest
    : public testing::Test,
      public testing::WithParamInterface<bool> {
 protected:
  ChromeProfileRequestGeneratorTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
        generator_(base::FilePath(kProfilePath),
                   &delegate_factory_,
                   &mock_aggregator_) {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;

    if (is_agent_collection_enabled()) {
      enabled_features.push_back(
          enterprise_signals::features::kDetectedAgentSignalCollectionEnabled);
    } else {
      disabled_features.push_back(
          enterprise_signals::features::kDetectedAgentSignalCollectionEnabled);
    }

    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

  bool is_agent_collection_enabled() const { return GetParam(); }

  device_signals::SignalsAggregationRequest CreateExpectedRequest(
      const std::optional<std::string>& challenge = std::nullopt,
      const base::ListValue& cert_selectors = {}) const {
    device_signals::SignalsAggregationRequest request;
    request.signal_names.emplace(device_signals::SignalName::kOsSignals);
    request.signal_names.emplace(
        device_signals::SignalName::kBrowserContextSignals);

    if (is_agent_collection_enabled()) {
      request.signal_names.emplace(device_signals::SignalName::kAgent);
      request.agent_signal_parameters.emplace(
          device_signals::AgentSignalCollectionType::kDetectedAgents);
    }

#if BUILDFLAG(IS_WIN)
    request.signal_names.emplace(device_signals::SignalName::kAntiVirus);
    request.signal_names.emplace(device_signals::SignalName::kHotfixes);
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
    if (enterprise_signals::features::IsCertificateCollectionEnabled() &&
        challenge.has_value() && !challenge.value().empty()) {
      request.signal_names.emplace(device_signals::SignalName::kCertificates);
      if (cert_selectors.empty()) {
        device_signals::GetCertificateOptions cert_option;
        cert_option.challenge = challenge.value();
        request.certificate_signal_parameters.push_back(std::move(cert_option));
      } else {
        for (const auto& entry : cert_selectors) {
          const base::DictValue* selector_dict = entry.GetIfDict();
          if (!selector_dict) {
            continue;
          }
          device_signals::GetCertificateOptions cert_option;
          cert_option.challenge = challenge.value();
          cert_option.issuer_pattern = certificate_matching::
              CertificatePrincipalPattern::ParseFromOptionalDict(
                  selector_dict->FindDict("ISSUER"), "CN", "L", "O", "OU");
          cert_option.subject_pattern = certificate_matching::
              CertificatePrincipalPattern::ParseFromOptionalDict(
                  selector_dict->FindDict("SUBJECT"), "CN", "L", "O", "OU");
          request.certificate_signal_parameters.push_back(
              std::move(cert_option));
        }
      }
    }
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

    request.trigger = device_signals::Trigger::kSignalsReport;
    return request;
  }

  void VerifyReportContent(
      const ReportRequestQueue& requests,
      em::ChromeProfileReportRequest::ReportType expected_report_type,
      bool is_profile_id_null = false,
      bool agent_collection_enabled = false,
      bool expect_cert_signals = false) {
    // True if a status report-exclusive field is expected to be filled
    // correctly, status reports with signals also count.
    bool expect_status_report_only_value =
        expected_report_type !=
        em::ChromeProfileReportRequest::PROFILE_SECURITY_SIGNALS;
    bool expect_signals_override_value =
        expected_report_type != em::ChromeProfileReportRequest::PROFILE_REPORT;

    ASSERT_EQ(1u, requests.size());
    ReportRequest* request = requests.front().get();
    ASSERT_TRUE(request);
    EXPECT_EQ(request->GetChromeProfileReportRequest().report_type(),
              expected_report_type);

    EXPECT_EQ(request->GetChromeProfileReportRequest()
                  .has_browser_device_identifier(),
              expect_signals_override_value);

    if (expect_signals_override_value) {
      auto browser_device_identifier =
          request->GetChromeProfileReportRequest().browser_device_identifier();
      EXPECT_EQ(browser_device_identifier.computer_name(),
                kFakeSignalDisplayName);
      // Test the case when a field value cannot be collected
      EXPECT_EQ(browser_device_identifier.serial_number(), std::string());
      EXPECT_EQ(browser_device_identifier.host_name(), kFakeSignalHostname);
    }

    ASSERT_TRUE(request->GetChromeProfileReportRequest().has_os_report());
    auto os_report = request->GetChromeProfileReportRequest().os_report();
    if (expect_signals_override_value) {
      EXPECT_EQ(os_report.name(), kFakeSignalOsName);
      // If a field does not get overwritten by value collected from device
      // signals, it should use the default OS report provided value.
      EXPECT_EQ(os_report.arch(), policy::GetOSArchitecture());
      EXPECT_EQ(os_report.version(), kFakeSignalOsVersion);
      EXPECT_EQ(os_report.screen_lock_secured(), em::SettingValue::ENABLED);
      EXPECT_EQ(os_report.distribution_version(), kFakeDistoVersion);

      EXPECT_EQ(3, os_report.mac_addresses_size());
      EXPECT_EQ(os_report.mac_addresses(0), kFakeSignalMacAddr1);
      EXPECT_EQ(os_report.mac_addresses(1), kFakeSignalMacAddr2);
      EXPECT_EQ(os_report.mac_addresses(2), kFakeSignalMacAddr3);

#if BUILDFLAG(IS_ANDROID)
      EXPECT_EQ(os_report.has_potentially_harmful_apps(), kFakeHasHarmfulApps);
      EXPECT_EQ(os_report.verified_apps_enabled(), kFakeVerifiedAppsEnabled);
      EXPECT_EQ(os_report.security_patch_ms(), kFakeSecurityPatchLevel);
#endif

#if BUILDFLAG(IS_IOS)
      ASSERT_TRUE(os_report.has_ios_specific_attributes());
      EXPECT_EQ(os_report.ios_specific_attributes().vendor_id(),
                kFakeVendorId);
#endif  // BUILDFLAG(IS_IOS)

      if (agent_collection_enabled) {
        EXPECT_EQ(os_report.detected_agents(0), em::Agent::CROWDSTRIKE_FALCON);
      }

#if BUILDFLAG(IS_WIN)
      auto av_info = os_report.antivirus_info(0);
      EXPECT_EQ(av_info.display_name(), kFakeSignalAvName);
      EXPECT_EQ(av_info.state(), em::AntiVirusProduct::EXPIRED);

      auto first_hotfix = os_report.hotfixes(0);
      EXPECT_EQ(first_hotfix, kFakeFirstHotfix);
      auto second_hotfix = os_report.hotfixes(1);
      EXPECT_EQ(second_hotfix, kFakeSecondHotfix);
#endif  // BUILDFLAG(IS_WIN)
    } else {
      EXPECT_EQ(os_report.name(), policy::GetOSPlatform());
      EXPECT_EQ(os_report.arch(), policy::GetOSArchitecture());
      EXPECT_EQ(os_report.version(), policy::GetOSVersion());

      // Signals report only fields should not be written
      ASSERT_FALSE(os_report.has_device_enrollment_domain());
      ASSERT_FALSE(os_report.has_screen_lock_secured());

      EXPECT_EQ(0, os_report.mac_addresses_size());
#if BUILDFLAG(IS_WIN)
      EXPECT_EQ(0, os_report.antivirus_info_size());
      EXPECT_EQ(0, os_report.hotfixes_size());
#endif  // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_IOS)
      EXPECT_FALSE(os_report.has_ios_specific_attributes());
#endif  // BUILDFLAG(IS_IOS)
    }

    EXPECT_EQ(
        request->GetChromeProfileReportRequest().has_attestation_payload(),
        expect_signals_override_value);

    if (expect_signals_override_value) {
      auto attestation_payload =
          request->GetChromeProfileReportRequest().attestation_payload();
      EXPECT_FALSE(attestation_payload.timestamp().empty());
      EXPECT_FALSE(attestation_payload.nonce().empty());
      EXPECT_EQ(attestation_payload.attestation_blob(),
                scoped_service_factory_.GetExpectedAttestationBlob());
      EXPECT_TRUE(attestation_payload.has_content_binding_version());
      EXPECT_EQ(attestation_payload.content_binding_version(),
                scoped_service_factory_.GetExpectedContentBindingVersion());
    }

    ASSERT_TRUE(request->GetChromeProfileReportRequest().has_browser_report());
    auto browser_report =
        request->GetChromeProfileReportRequest().browser_report();

    // These fields are only filled if status report is enabled.
    EXPECT_EQ(
        ObfuscateFilePath(kBrowserExePath) == browser_report.executable_path(),
        expect_status_report_only_value);
    EXPECT_EQ(browser_report.is_extended_stable_channel(),
              expect_status_report_only_value);

    EXPECT_EQ(1, browser_report.chrome_user_profile_infos_size());
    auto chrome_user_profile_info = browser_report.chrome_user_profile_infos(0);

    // These fields are now always filled for profile reports.
    EXPECT_EQ(base::FilePath(kProfilePath).AsUTF8Unsafe(),
              chrome_user_profile_info.id());
    EXPECT_TRUE(chrome_user_profile_info.is_detail_available());

    // Affiliation info is now always included.
    EXPECT_TRUE(chrome_user_profile_info.has_affiliation());

    // `profile_signals_report` is a signals report only sub-proto.
    EXPECT_EQ(chrome_user_profile_info.has_profile_signals_report(),
              expect_signals_override_value);

    if (expect_signals_override_value) {
      auto profile_signals_report =
          chrome_user_profile_info.profile_signals_report();

      EXPECT_EQ(profile_signals_report.built_in_dns_client_enabled(), true);
      EXPECT_EQ(profile_signals_report.chrome_remote_desktop_app_blocked(),
                false);
      EXPECT_EQ(profile_signals_report.password_protection_warning_trigger(),
                em::ProfileSignalsReport::PHISHING_REUSE);
      // If a value cannot be collected, this report field will be empty.
      EXPECT_EQ(profile_signals_report.profile_enrollment_domain(),
                std::string());
      EXPECT_EQ(profile_signals_report.safe_browsing_protection_level(),
                em::ProfileSignalsReport::STANDARD_PROTECTION);
      EXPECT_EQ(profile_signals_report.site_isolation_enabled(), true);
      EXPECT_EQ(chrome_user_profile_info.profile_id(),
                is_profile_id_null ? std::string() : kFakeProfileId);
      EXPECT_EQ(profile_signals_report.realtime_url_check_mode(),
                em::ProfileSignalsReport::ENABLED_MAIN_FRAME);

      if (expect_cert_signals) {
        EXPECT_EQ(1, chrome_user_profile_info.certificates_size());
        EXPECT_FALSE(chrome_user_profile_info.certificates_were_truncated());
        const auto& cert = chrome_user_profile_info.certificates(0);
        EXPECT_EQ(cert.data(), kFakeCertData);
        EXPECT_EQ(cert.signature(), kFakeSignature);
      } else {
        EXPECT_EQ(0, chrome_user_profile_info.certificates_size());
        EXPECT_FALSE(chrome_user_profile_info.certificates_were_truncated());
      }
    }
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  base::test::TaskEnvironment task_environment_;
  test::FakeReportingDelegateFactory delegate_factory_{kBrowserExePath};
  StrictMock<device_signals::MockSignalsAggregator> mock_aggregator_;
  enterprise::test::ScopedDeviceAttestationServiceFactory
      scoped_service_factory_;
  ChromeProfileRequestGenerator generator_;
};

TEST_P(ChromeProfileRequestGeneratorTest, GenerateFullReportNoSecuritySignals) {
  EXPECT_CALL(mock_aggregator_, GetSignals(_, _)).Times(0);
  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;
  generator_.Generate(ReportGenerationConfig(ReportTrigger::kTriggerTimer,
                                             ReportType::kProfileReport,
                                             SecuritySignalsMode::kNoSignals,
                                             /*use_cookies=*/false),
                      test_future.GetCallback());

  const auto& requests = test_future.Get();
  VerifyReportContent(
      requests.value(), em::ChromeProfileReportRequest::PROFILE_REPORT,
      /*is_profile_id_null=*/false, is_agent_collection_enabled());
}

TEST_P(ChromeProfileRequestGeneratorTest,
       GenerateFullReportWithSecuritySignals) {
  bool agent_collection_enabled = is_agent_collection_enabled();
  EXPECT_CALL(mock_aggregator_, GetSignals(CreateExpectedRequest(), _))
      .WillOnce([](const device_signals::SignalsAggregationRequest& request,
                   base::OnceCallback<void(
                       device_signals::SignalsAggregationResponse)> callback) {
        std::move(callback).Run(CreateFilledResponse());
      });

  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;
  generator_.Generate(
      ReportGenerationConfig(ReportTrigger::kTriggerTimer,
                             ReportType::kProfileReport,
                             SecuritySignalsMode::kSignalsAttached,
                             /*use_cookies=*/false),
      test_future.GetCallback());

  const auto& requests = test_future.Get();
  VerifyReportContent(
      requests.value(),
      em::ChromeProfileReportRequest::PROFILE_REPORT_WITH_SECURITY_SIGNALS,
      /*is_profile_id_null=*/false, agent_collection_enabled);
}

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
TEST_P(ChromeProfileRequestGeneratorTest,
       GenerateFullReportWithCertificateSignals) {
  constexpr char kFakeChallenge[] = "fake_challenge";
  bool agent_collection_enabled = is_agent_collection_enabled();
  EXPECT_CALL(mock_aggregator_,
              GetSignals(CreateExpectedRequest(kFakeChallenge), _))
      .WillOnce([](const device_signals::SignalsAggregationRequest& request,
                   base::OnceCallback<void(
                       device_signals::SignalsAggregationResponse)> callback) {
        std::move(callback).Run(
            CreateFilledResponse(/*nullify_profile_id=*/false,
                                 /*include_cert_signals=*/true));
      });

  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;
  ReportGenerationConfig config(ReportTrigger::kTriggerTimer,
                                ReportType::kProfileReport,
                                SecuritySignalsMode::kSignalsAttached,
                                /*use_cookies=*/false);
  config.challenge = kFakeChallenge;
  generator_.Generate(config, test_future.GetCallback());

  const auto& requests = test_future.Get();
  VerifyReportContent(
      requests.value(),
      em::ChromeProfileReportRequest::PROFILE_REPORT_WITH_SECURITY_SIGNALS,
      /*is_profile_id_null=*/false, agent_collection_enabled,
      /*expect_cert_signals=*/true);
}

TEST_P(ChromeProfileRequestGeneratorTest,
       GenerateFullReportWithCertificateSelectors) {
  constexpr char kFakeChallenge[] = "fake_challenge";
  bool agent_collection_enabled = is_agent_collection_enabled();

  base::ListValue selectors;
  base::DictValue selector;
  base::DictValue issuer;
  issuer.Set("CN", "TestIssuerCN");
  selector.Set("ISSUER", std::move(issuer));
  base::DictValue subject;
  subject.Set("CN", "TestSubjectCN");
  selector.Set("SUBJECT", std::move(subject));
  selectors.Append(std::move(selector));

  EXPECT_CALL(mock_aggregator_,
              GetSignals(CreateExpectedRequest(kFakeChallenge, selectors), _))
      .WillOnce([](const device_signals::SignalsAggregationRequest& request,
                   base::OnceCallback<void(
                       device_signals::SignalsAggregationResponse)> callback) {
        std::move(callback).Run(
            CreateFilledResponse(/*nullify_profile_id=*/false,
                                 /*include_cert_signals=*/true));
      });

  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;
  ReportGenerationConfig config(
      ReportTrigger::kTriggerTimer, ReportType::kProfileReport,
      SecuritySignalsMode::kSignalsAttached,
      /*use_cookies=*/false, kFakeChallenge, selectors.Clone());
  generator_.Generate(config, test_future.GetCallback());

  const auto& requests = test_future.Get();
  VerifyReportContent(
      requests.value(),
      em::ChromeProfileReportRequest::PROFILE_REPORT_WITH_SECURITY_SIGNALS,
      /*is_profile_id_null=*/false, agent_collection_enabled,
      /*expect_cert_signals=*/true);
}
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

TEST_P(ChromeProfileRequestGeneratorTest, GenerateSecuritySignalsOnlyReport) {
  bool agent_collection_enabled = is_agent_collection_enabled();
  EXPECT_CALL(mock_aggregator_, GetSignals(CreateExpectedRequest(), _))
      .WillOnce([](const device_signals::SignalsAggregationRequest& request,
                   base::OnceCallback<void(
                       device_signals::SignalsAggregationResponse)> callback) {
        std::move(callback).Run(CreateFilledResponse());
      });
  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;

  generator_.Generate(ReportGenerationConfig(ReportTrigger::kTriggerNone,
                                             ReportType::kProfileReport,
                                             SecuritySignalsMode::kSignalsOnly,
                                             /*use_cookies=*/false),
                      test_future.GetCallback());

  const auto& requests = test_future.Get();
  VerifyReportContent(requests.value(),
                      em::ChromeProfileReportRequest::PROFILE_SECURITY_SIGNALS,
                      /*is_profile_id_null=*/false, agent_collection_enabled);
}

// Test that no issue is encountered when a nullopt value is collected, on an
// optional field
TEST_P(ChromeProfileRequestGeneratorTest, NoProfileId) {
  bool agent_collection_enabled = is_agent_collection_enabled();
  EXPECT_CALL(mock_aggregator_, GetSignals(CreateExpectedRequest(), _))
      .WillOnce([](const device_signals::SignalsAggregationRequest& request,
                   base::OnceCallback<void(
                       device_signals::SignalsAggregationResponse)> callback) {
        std::move(callback).Run(
            CreateFilledResponse(/*nullify_profile_id=*/true));
      });
  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;

  generator_.Generate(ReportGenerationConfig(ReportTrigger::kTriggerNone,
                                             ReportType::kProfileReport,
                                             SecuritySignalsMode::kSignalsOnly,
                                             /*use_cookies=*/false),
                      test_future.GetCallback());

  const auto& requests = test_future.Get();
  VerifyReportContent(requests.value(),
                      em::ChromeProfileReportRequest::PROFILE_SECURITY_SIGNALS,
                      /*is_profile_id_null=*/true, agent_collection_enabled);
}

TEST_P(ChromeProfileRequestGeneratorTest, IncorrectReportType) {
  EXPECT_CALL(mock_aggregator_, GetSignals(_, _)).Times(0);
  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;
  generator_.Generate(ReportGenerationConfig(), test_future.GetCallback());

  const auto& requests = test_future.Get();
  // When the wrong report type is provided, generator should still return the
  // correct request, but with empty content.
  ASSERT_EQ(1u, requests->size());
  ReportRequest* request = requests->front().get();
  ASSERT_TRUE(request);
  ASSERT_FALSE(request->GetDeviceReportRequest().has_browser_report());
}

// Tests that if the base profile report generation results in empty reports,
// the generator aborts the entire process, skips security signal collection,
// and returns an empty queue.
TEST_P(ChromeProfileRequestGeneratorTest, AbortsWhenProfileReportIsEmpty) {
  // Simulate that generating a report failed.
  delegate_factory_.SetProfileInitResult(false);
  ChromeProfileRequestGenerator generator(
      base::FilePath(kProfilePath), &delegate_factory_, &mock_aggregator_);
  EXPECT_CALL(mock_aggregator_, GetSignals(testing::_, testing::_)).Times(0);
  base::test::TestFuture<
      base::expected<ReportRequestQueue, ReportGenerationError>>
      test_future;

  generator.Generate(
      ReportGenerationConfig(ReportTrigger::kTriggerTimer,
                             ReportType::kProfileReport,
                             SecuritySignalsMode::kSignalsAttached,
                             /*use_cookies=*/false),
      test_future.GetCallback());

  const auto& result = test_future.Get();
  ASSERT_FALSE(result.has_value());
  EXPECT_EQ(result.error(), ReportGenerationError::kProfileEmptyReport);
}

INSTANTIATE_TEST_SUITE_P(, ChromeProfileRequestGeneratorTest, testing::Bool());

}  // namespace enterprise_reporting
