chromium/chrome/browser/chromeos/reporting/metric_reporting_manager_lacros_unittest.cc

// 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 "chrome/browser/chromeos/reporting/metric_reporting_manager_lacros.h"

#include <memory>
#include <optional>
#include <string>

#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/app_service_proxy_lacros.h"
#include "chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h"
#include "chrome/browser/chromeos/reporting/device_reporting_settings_lacros.h"
#include "chrome/browser/chromeos/reporting/metric_default_utils.h"
#include "chrome/browser/chromeos/reporting/metric_reporting_prefs.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/crosapi/mojom/device_settings_service.mojom.h"
#include "components/policy/policy_constants.h"
#include "components/reporting/client/report_queue_configuration.h"
#include "components/reporting/metrics/collector_base.h"
#include "components/reporting/metrics/fakes/fake_metric_event_observer.h"
#include "components/reporting/metrics/fakes/fake_metric_report_queue.h"
#include "components/reporting/metrics/fakes/fake_reporting_settings.h"
#include "components/reporting/metrics/metric_event_observer_manager.h"
#include "components/reporting/metrics/reporting_settings.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/util/rate_limiter_interface.h"
#include "components/reporting/util/rate_limiter_slide_window.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::Address;
using ::testing::AllOf;
using ::testing::ByMove;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Optional;
using ::testing::Property;
using ::testing::Return;

namespace reporting {
namespace {

constexpr char kUserId[] = "123";

// Fake metric event observer manager implementation used by tests to monitor
// event observer manager instantiation.
class FakeMetricEventObserverManager : public MetricEventObserverManager {
 public:
  FakeMetricEventObserverManager(ReportingSettings* reporting_settings,
                                 int* observer_manager_count)
      : MetricEventObserverManager(
            std::make_unique<test::FakeMetricEventObserver>(),
            /*metric_report_queue=*/nullptr,
            reporting_settings,
            /*enable_setting_path=*/"",
            /*setting_enabled_default_value=*/false,
            /*collector_pool=*/nullptr),
        observer_manager_count_(observer_manager_count) {
    ++(*observer_manager_count_);
  }

  FakeMetricEventObserverManager(const FakeMetricEventObserverManager& other) =
      delete;
  FakeMetricEventObserverManager& operator=(
      const FakeMetricEventObserverManager& other) = delete;

  ~FakeMetricEventObserverManager() override { --(*observer_manager_count_); }

 private:
  raw_ptr<int> observer_manager_count_;
};

class FakeCollector : public CollectorBase {
 public:
  explicit FakeCollector(int* collector_count)
      : CollectorBase(nullptr), collector_count_(collector_count) {
    ++(*collector_count_);
  }

  FakeCollector(const FakeCollector& other) = delete;
  FakeCollector& operator=(const FakeCollector& other) = delete;

  ~FakeCollector() override { --(*collector_count_); }

 protected:
  // CollectorBase:
  void OnMetricDataCollected(bool is_event_driven,
                             std::optional<MetricData> metric_data) override {}
  bool CanCollect() const override { return true; }

 private:
  raw_ptr<int> collector_count_;
};

class MockDelegate : public metrics::MetricReportingManagerLacros::Delegate {
 public:
  MockDelegate() = default;
  MockDelegate(const MockDelegate& other) = delete;
  MockDelegate& operator=(const MockDelegate& other) = delete;
  ~MockDelegate() override = default;

  // Wraps around the `CreateMetricReportQueueMock` to simplify mocking
  // `MetricReportQueue` creation for a specific rate limiter.
  std::unique_ptr<MetricReportQueue> CreateMetricReportQueue(
      EventType event_type,
      Destination destination,
      Priority priority,
      std::unique_ptr<RateLimiterInterface> rate_limiter,
      std::optional<SourceInfo> source_info) override {
    return CreateMetricReportQueueMock(event_type, destination, priority,
                                       rate_limiter.get(), source_info);
  }

  MOCK_METHOD(std::unique_ptr<MetricReportQueue>,
              CreateMetricReportQueueMock,
              (EventType event_type,
               Destination destination,
               Priority priority,
               RateLimiterInterface* rate_limiter,
               std::optional<SourceInfo> source_info),
              ());

  MOCK_METHOD(std::unique_ptr<RateLimiterSlideWindow>,
              CreateSlidingWindowRateLimiter,
              (size_t total_size,
               base::TimeDelta time_window,
               size_t bucket_count),
              (override));

  MOCK_METHOD(bool, IsUserAffiliated, (Profile & profile), (const, override));

  MOCK_METHOD(
      void,
      CheckDeviceDeprovisioned,
      (crosapi::mojom::DeviceSettingsService::IsDeviceDeprovisionedCallback
           callback),
      (override));

  MOCK_METHOD(std::unique_ptr<DeviceReportingSettingsLacros>,
              CreateDeviceReportingSettings,
              (),
              (override));

  MOCK_METHOD(void,
              RegisterObserverWithCrosApiClient,
              (metrics::MetricReportingManagerLacros* const),
              (override));

  MOCK_METHOD(std::unique_ptr<CollectorBase>,
              CreatePeriodicCollector,
              (Sampler * sampler,
               MetricReportQueue* metric_report_queue,
               ReportingSettings* reporting_settings,
               const std::string& enable_setting_path,
               bool setting_enabled_default_value,
               const std::string& rate_setting_path,
               base::TimeDelta default_rate,
               int rate_unit_to_ms,
               base::TimeDelta init_delay),
              (override));

  MOCK_METHOD(std::unique_ptr<MetricEventObserverManager>,
              CreateEventObserverManager,
              (std::unique_ptr<MetricEventObserver> event_observer,
               MetricReportQueue* metric_report_queue,
               ReportingSettings* reporting_settings,
               const std::string& enable_setting_path,
               bool setting_enabled_default_value,
               EventDrivenTelemetryCollectorPool* collector_pool,
               base::TimeDelta init_delay),
              (override));

  MOCK_METHOD(std::unique_ptr<MetricReportQueue>,
              CreatePeriodicUploadReportQueue,
              (EventType event_type,
               Destination destination,
               Priority priority,
               ReportingSettings* reporting_settings,
               const std::string& rate_setting_path,
               base::TimeDelta default_rate,
               int rate_unit_to_ms,
               std::optional<SourceInfo> source_info),
              (override));
};

struct MetricReportingSettingData {
  std::string enable_setting_path;
  bool setting_enabled_default_value;
  std::string rate_setting_path;
  int rate_unit_to_ms;
};

const MetricReportingSettingData network_telemetry_settings = {
    ::policy::key::kReportDeviceNetworkStatus,
    metrics::kReportDeviceNetworkStatusDefaultValue,
    ::policy::key::kReportDeviceNetworkTelemetryCollectionRateMs, 1};
const MetricReportingSettingData website_event_settings = {
    kReportWebsiteActivityAllowlist,
    metrics::kReportWebsiteActivityEnabledDefaultValue, "", 1};

struct TestCase {
  std::string test_name;
  // Is the user affiliated.
  bool is_affiliated;
  MetricReportingSettingData setting_data;
  // Count of initialized components.
  int expected_count;
};

class MetricReportingManagerLacrosTest
    : public ::testing::TestWithParam<TestCase> {
 protected:
  MetricReportingManagerLacrosTest()
      : profile_manager_(TestingBrowserProcess::GetGlobal()) {}

  // ::testing::TestWithParam:
  void SetUp() override {
    TestWithParam::SetUp();
    ASSERT_TRUE(profile_manager_.SetUp());
    profile_ = profile_manager_.CreateTestingProfile(kUserId);
    delegate_ = std::make_unique<NiceMock<MockDelegate>>();

    auto telemetry_queue = std::make_unique<test::FakeMetricReportQueue>();
    telemetry_queue_ptr_ = telemetry_queue.get();
    ON_CALL(*delegate_,
            CreatePeriodicUploadReportQueue(
                EventType::kUser, Destination::TELEMETRY_METRIC,
                Priority::MANUAL_BATCH_LACROS, _, _, _, 1,
                Optional(AllOf(
                    Property(&SourceInfo::source, Eq(SourceInfo::LACROS)),
                    Property(&SourceInfo::source_version, Not(IsEmpty()))))))
        .WillByDefault(Return(ByMove(std::move(telemetry_queue))));

    auto rate_limiter_slide_window = std::make_unique<RateLimiterSlideWindow>(
        metrics::kWebsiteEventsTotalSize, metrics::kWebsiteEventsWindow,
        metrics::kWebsiteEventsBucketCount);
    auto* const rate_limiter_slide_window_ptr = rate_limiter_slide_window.get();
    ON_CALL(*delegate_,
            CreateSlidingWindowRateLimiter(metrics::kWebsiteEventsTotalSize,
                                           metrics::kWebsiteEventsWindow,
                                           metrics::kWebsiteEventsBucketCount))
        .WillByDefault(Return(ByMove(std::move(rate_limiter_slide_window))));

    auto website_event_queue = std::make_unique<test::FakeMetricReportQueue>();
    website_event_queue_ptr_ = website_event_queue.get();
    ON_CALL(*delegate_,
            CreateMetricReportQueueMock(
                EventType::kUser, Destination::EVENT_METRIC,
                Priority::SLOW_BATCH, rate_limiter_slide_window_ptr, _))
        .WillByDefault(Return(ByMove(std::move(website_event_queue))));

    ON_CALL(*delegate_, RegisterObserverWithCrosApiClient)
        .WillByDefault([]() { /** Do nothing **/ });

    ON_CALL(*delegate_, CreateDeviceReportingSettings)
        .WillByDefault(Return(
            ByMove(std::unique_ptr<DeviceReportingSettingsLacros>(nullptr))));

    ON_CALL(*delegate_, CheckDeviceDeprovisioned(_))
        .WillByDefault([](crosapi::mojom::DeviceSettingsService::
                              IsDeviceDeprovisionedCallback callback) {
          std::move(callback).Run(false);
        });

    // The app service proxy does not initialize website metrics service for
    // test profiles by default, so we set up website metrics service to
    // simplify testing.
    auto website_metrics_service =
        std::make_unique<::apps::WebsiteMetricsServiceLacros>(profile_);
    ::apps::AppServiceProxyFactory::GetForProfile(profile_)
        ->SetWebsiteMetricsServiceForTesting(
            std::move(website_metrics_service));
  }

  void ResetExpectationReferences() {
    website_event_queue_ptr_ = nullptr;
    telemetry_queue_ptr_ = nullptr;
  }

  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  TestingProfileManager profile_manager_;
  raw_ptr<TestingProfile> profile_;
  std::unique_ptr<MockDelegate> delegate_;

  // These are necessary to set expectations in various test cases. The objects
  // they point to are consumed when the expectation is triggered. Test cases
  // must reset these pointers after setting expectations and before
  // expectations are triggered to avoid dangling pointers.
  raw_ptr<test::FakeMetricReportQueue> telemetry_queue_ptr_;
  raw_ptr<test::FakeMetricReportQueue> website_event_queue_ptr_;
};

TEST_F(MetricReportingManagerLacrosTest, InitiallyDeprovisioned) {
  int periodic_collector_count = 0;
  int observer_manager_count = 0;
  auto fake_reporting_settings =
      std::make_unique<test::FakeReportingSettings>();

  ON_CALL(*delegate_, IsUserAffiliated(Address(profile_)))
      .WillByDefault(Return(true));
  ON_CALL(*delegate_, CheckDeviceDeprovisioned(_))
      .WillByDefault([](crosapi::mojom::DeviceSettingsService::
                            IsDeviceDeprovisionedCallback callback) {
        std::move(callback).Run(true);
      });
  ON_CALL(*delegate_, CreatePeriodicCollector)
      .WillByDefault([&periodic_collector_count]() {
        return std::make_unique<FakeCollector>(&periodic_collector_count);
      });
  ON_CALL(*delegate_, CreateEventObserverManager).WillByDefault([&]() {
    return std::make_unique<FakeMetricEventObserverManager>(
        fake_reporting_settings.get(), &observer_manager_count);
  });
  ResetExpectationReferences();

  auto* const delegate_ptr = delegate_.get();
  metrics::MetricReportingManagerLacros metric_reporting_manager(
      profile_.get(), std::move(delegate_));

  task_environment_.FastForwardBy(delegate_ptr->GetInitDelay());
  task_environment_.RunUntilIdle();
  EXPECT_THAT(periodic_collector_count, Eq(0));
  EXPECT_THAT(observer_manager_count, Eq(0));
}

class MetricReportingManagerLacrosTelemetryTest
    : public MetricReportingManagerLacrosTest {};

TEST_P(MetricReportingManagerLacrosTelemetryTest, Default) {
  const TestCase& test_case = GetParam();
  int periodic_collector_count = 0;
  ON_CALL(*delegate_, CreatePeriodicCollector(
                          _, telemetry_queue_ptr_.get(), _,
                          test_case.setting_data.enable_setting_path,
                          test_case.setting_data.setting_enabled_default_value,
                          test_case.setting_data.rate_setting_path, _,
                          test_case.setting_data.rate_unit_to_ms, _))
      .WillByDefault([&periodic_collector_count]() {
        return std::make_unique<FakeCollector>(&periodic_collector_count);
      });
  ON_CALL(*delegate_, IsUserAffiliated(Address(profile_)))
      .WillByDefault(Return(test_case.is_affiliated));
  ResetExpectationReferences();

  auto* const delegate_ptr = delegate_.get();
  metrics::MetricReportingManagerLacros metric_reporting_manager(
      profile_.get(), std::move(delegate_));

  task_environment_.FastForwardBy(delegate_ptr->GetInitDelay());
  task_environment_.RunUntilIdle();
  ASSERT_THAT(periodic_collector_count, Eq(test_case.expected_count));

  // Simulate device deprovisioning and verify collectors are destroyed.
  EXPECT_CALL(*delegate_ptr, CheckDeviceDeprovisioned(_))
      .WillRepeatedly([](crosapi::mojom::DeviceSettingsService::
                             IsDeviceDeprovisionedCallback callback) {
        std::move(callback).Run(true);
      });
  metric_reporting_manager.OnDeviceSettingsUpdated();
  task_environment_.RunUntilIdle();
  EXPECT_THAT(periodic_collector_count, Eq(0));
}

INSTANTIATE_TEST_SUITE_P(
    MetricReportingManagerLacrosTelemetryTests,
    MetricReportingManagerLacrosTelemetryTest,
    ::testing::ValuesIn<TestCase>({
        {"NetworkTelemetry_Unaffiliated",
         /*is_affiliated=*/false, network_telemetry_settings,
         /*expected_count=*/0},
        {"NetworkTelemetry_Affiliated",
         /*is_affiliated=*/true, network_telemetry_settings,
         /*expected_count=*/1},
    }),
    [](const ::testing::TestParamInfo<
        MetricReportingManagerLacrosTest::ParamType>& info) {
      return info.param.test_name;
    });

class MetricReportingManagerLacrosEventTest
    : public MetricReportingManagerLacrosTest {};

TEST_P(MetricReportingManagerLacrosEventTest, Default) {
  const TestCase& test_case = GetParam();
  auto fake_reporting_settings =
      std::make_unique<test::FakeReportingSettings>();
  int observer_manager_count = 0;
  ON_CALL(*delegate_, IsUserAffiliated(Address(profile_)))
      .WillByDefault(Return(test_case.is_affiliated));
  ON_CALL(*delegate_, CreateEventObserverManager(
                          _, website_event_queue_ptr_.get(), _,
                          test_case.setting_data.enable_setting_path,
                          test_case.setting_data.setting_enabled_default_value,
                          _, base::TimeDelta()))
      .WillByDefault([&]() {
        return std::make_unique<FakeMetricEventObserverManager>(
            fake_reporting_settings.get(), &observer_manager_count);
      });
  ResetExpectationReferences();

  auto* const delegate_ptr = delegate_.get();
  metrics::MetricReportingManagerLacros metric_reporting_manager(
      profile_.get(), std::move(delegate_));
  task_environment_.RunUntilIdle();
  EXPECT_THAT(observer_manager_count, Eq(test_case.expected_count));

  // Simulate device deprovisioning and verify event observer managers are
  // destroyed.
  EXPECT_CALL(*delegate_ptr, CheckDeviceDeprovisioned(_))
      .WillRepeatedly([](crosapi::mojom::DeviceSettingsService::
                             IsDeviceDeprovisionedCallback callback) {
        std::move(callback).Run(true);
      });
  metric_reporting_manager.OnDeviceSettingsUpdated();
  EXPECT_THAT(observer_manager_count, Eq(0));
}

INSTANTIATE_TEST_SUITE_P(
    MetricReportingManagerLacrosEventTests,
    MetricReportingManagerLacrosEventTest,
    ::testing::ValuesIn<TestCase>({
        {"WebsiteEvents_Unaffiliated",
         /*is_affiliated=*/false, website_event_settings,
         /*expected_count=*/0},
        {"WebsiteEvents_Affiliated",
         /*is_affiliated=*/true, website_event_settings,
         /*expected_count=*/1},
    }),
    [](const ::testing::TestParamInfo<
        MetricReportingManagerLacrosTest::ParamType>& info) {
      return info.param.test_name;
    });
}  // namespace
}  // namespace reporting