chromium/components/ukm/ukm_service_unittest.cc

// Copyright 2017 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/ukm/ukm_service.h"

#include <map>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/hash/hash.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/metrics_hashes.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "components/metrics/cloned_install_detector.h"
#include "components/metrics/log_decoder.h"
#include "components/metrics/metrics_features.h"
#include "components/metrics/metrics_log.h"
#include "components/metrics/metrics_log_uploader.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/test/test_metrics_provider.h"
#include "components/metrics/test/test_metrics_service_client.h"
#include "components/metrics/ukm_demographic_metrics_provider.h"
#include "components/metrics/unsent_log_store.h"
#include "components/prefs/testing_pref_service.h"
#include "components/ukm/observers/ukm_consent_state_observer.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/ukm/ukm_entry_filter.h"
#include "components/ukm/ukm_pref_names.h"
#include "components/ukm/ukm_recorder_impl.h"
#include "components/ukm/ukm_recorder_observer.h"
#include "components/ukm/unsent_log_store_metrics_impl.h"
#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_entry_builder.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/metrics/ukm_recorder_factory_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/ukm/report.pb.h"
#include "third_party/metrics_proto/ukm/source.pb.h"
#include "third_party/metrics_proto/ukm/web_features.pb.h"
#include "third_party/metrics_proto/user_demographics.pb.h"

namespace ukm {

// Some arbitrary events used in tests.
TestEvent1;
const char* kTestEvent1Metric1 =;
const char* kTestEvent1Metric2 =;
TestEvent2;
const char* kTestEvent2Metric1 =;
const char* kTestEvent2Metric2 =;
TestEvent3;
TestProviderEvent;
const int32_t kWebDXFeature1 =;
const int32_t kWebDXFeature3 =;
const size_t kWebDXFeatureNumberOfFeaturesForTesting =;

SourceId ConvertSourceIdToAllowlistedType(SourceId id, SourceIdType type) {}

// A small shim exposing UkmRecorder methods to tests.
class TestRecordingHelper {};

class TestMetricsServiceClientWithClonedInstallDetector
    : public metrics::TestMetricsServiceClient {};

namespace {

bool TestIsWebstoreExtension(std::string_view id) {}

int GetPersistedLogCount(TestingPrefServiceSimple& prefs) {}

Report GetPersistedReport(TestingPrefServiceSimple& prefs) {}

metrics::LogMetadata GetPersistedLogMetadata(TestingPrefServiceSimple& prefs) {}

void AddSourceToReport(Report& report,
                       int64_t other_id,
                       SourceIdType id_type,
                       std::string url) {}

bool WebDXFeaturesStrictlyContains(const HighLevelWebFeatures& actual_features,
                                   const std::set<int32_t>& expected_features) {}

class ScopedUkmFeatureParams {};

class MockDemographicMetricsProvider
    : public metrics::UkmDemographicMetricsProvider {};

class MockUkmRecorderObserver : public UkmRecorder::Observer {};

// A simple Provider that emits a 'TestProviderEvent' on session close (i.e. a
// Report being emitted).
class UkmTestMetricsProvider : public metrics::TestMetricsProvider {};

class UkmServiceTest : public testing::Test {};

class UkmReduceAddEntryIpcTest : public testing::Test {};
}  // namespace

TEST_F(UkmServiceTest, ClientIdMigration) {}

TEST_F(UkmServiceTest, ClientIdClonedInstall) {}

TEST_F(UkmServiceTest, EnableDisableSchedule) {}

TEST_F(UkmServiceTest, PersistAndPurge) {}

TEST_F(UkmServiceTest, Purge) {}

TEST_F(UkmServiceTest, PurgeExtensionDataFromUnsentLogStore) {}

TEST_F(UkmServiceTest, PurgeExtensionDataFromUnsentLogStoreWithVersionChange) {}

TEST_F(UkmServiceTest, PurgeAppDataFromUnsentLogStore) {}

TEST_F(UkmServiceTest, PurgeMsbbDataFromUnsentLogStore) {}

TEST_F(UkmServiceTest, PurgeAppDataLogMetadataUpdate) {}

TEST_F(UkmServiceTest, SourceSerialization) {}

TEST_F(UkmServiceTest, SourceSerializationForAllowlistedButNonNavigationType) {}

TEST_F(UkmServiceTest, LogMetadataOnlyAppKMSourceType) {}

TEST_F(UkmServiceTest, LogMetadataOnlyUKMSourceType) {}

TEST_F(UkmServiceTest, LogMetadataBothSourceType) {}

TEST_F(UkmServiceTest, AddEntryWithEmptyMetrics) {}

TEST_F(UkmServiceTest, MetricsProviderTest) {}

// Currently just testing brand is set, would be good to test other core
// system profile fields.
TEST_F(UkmServiceTest, SystemProfileTest) {}

TEST_F(UkmServiceTest, AddUserDemograhicsWhenAvailableAndFeatureEnabled) {}

TEST_F(UkmServiceTest,
       DontAddUserDemograhicsWhenNotAvailableAndFeatureEnabled) {}

TEST_F(UkmServiceTest, DontAddUserDemograhicsWhenFeatureDisabled) {}

TEST_F(UkmServiceTest, LogsRotation) {}

TEST_F(UkmServiceTest, LogsUploadedOnlyWhenHavingData) {}

TEST_F(UkmServiceTest, GetNewSourceID) {}

TEST_F(UkmServiceTest, RecordRedirectedUrl) {}

TEST_F(UkmServiceTest, RecordSessionId) {}

TEST_F(UkmServiceTest, SourceSize) {}

TEST_F(UkmServiceTest, PurgeMidUpload) {}

TEST_F(UkmServiceTest, SourceURLLength) {}

TEST_F(UkmServiceTest, UnreferencedNonAllowlistedSources) {}

TEST_F(UkmServiceTest, NonAllowlistedUrls) {}

TEST_F(UkmServiceTest, AllowlistIdType) {}

TEST_F(UkmServiceTest, SupportedSchemes) {}

TEST_F(UkmServiceTest, SupportedSchemesNoExtensions) {}

TEST_F(UkmServiceTest, SanitizeUrlAuthParams) {}

TEST_F(UkmServiceTest, SanitizeChromeUrlParams) {}

TEST_F(UkmServiceTest, MarkSourceForDeletion) {}

// Verifies that sources of some types are deleted at the end of reporting
// cycle.
TEST_F(UkmServiceTest, PurgeNonCarriedOverSources) {}

TEST_F(UkmServiceTest, IdentifiabilityMetricsDontExplode) {}

TEST_F(UkmServiceTest, FilterCanRemoveMetrics) {}

TEST_F(UkmServiceTest, FilterRejectsEvent) {}

TEST_F(UkmServiceTest, PruneOldSources) {}

TEST_F(UkmServiceTest, UseExternalClientID) {}

// Verifies that when a cloned install is detected, logs are purged.
TEST_F(UkmServiceTest, PurgeLogsOnClonedInstallDetected) {}

TEST_F(UkmServiceTest, WebDXFeatures) {}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(UkmServiceTest, NotifyObserverOnShutdown) {
  MockUkmRecorderObserver observer;
  UkmService service(&prefs_, &client_,
                     std::make_unique<MockDemographicMetricsProvider>());
  ukm::UkmRecorder::Get()->AddObserver(&observer);
  EXPECT_CALL(observer, OnStartingShutdown()).Times(1);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_CHROMEOS_ASH)
namespace {

class UkmServiceTestWithIndependentAppKM
    : public testing::TestWithParam<UkmConsentType> {
 public:
  UkmServiceTestWithIndependentAppKM()
      : task_runner_(new base::TestSimpleTaskRunner),
        task_runner_current_default_handle_(task_runner_) {
    UkmService::RegisterPrefs(prefs_.registry());

    prefs_.ClearPref(prefs::kUkmClientId);
    prefs_.ClearPref(prefs::kUkmSessionId);
    prefs_.ClearPref(prefs::kUkmUnsentLogStore);
  }

  int GetPersistedLogCount() { return ukm::GetPersistedLogCount(prefs_); }

  Report GetPersistedReport() { return ukm::GetPersistedReport(prefs_); }

 protected:
  TestingPrefServiceSimple prefs_;
  metrics::TestMetricsServiceClient client_;
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
  base::SingleThreadTaskRunner::CurrentDefaultHandle
      task_runner_current_default_handle_;
};

}  // namespace

TEST_P(UkmServiceTestWithIndependentAppKM, RejectWhenNotConsented) {
  const GURL kURL("https://google.com/foobar");
  const GURL kAppURL("app://google.com/foobar");

  // Setup test constants from param.
  const auto consent = GetParam();
  const std::vector<int> app_indices = {1, 4};
  const std::vector<int> url_indices = {0, 2, 3};
  const std::vector<int>& expected_source_indices =
      (consent == UkmConsentType::APPS ? app_indices : url_indices);

  const int expected_result = expected_source_indices.size();

  UkmService service(&prefs_, &client_,
                     std::make_unique<MockDemographicMetricsProvider>());
  TestRecordingHelper recorder(&service);
  EXPECT_EQ(0, GetPersistedLogCount());
  service.Initialize();
  task_runner_->RunUntilIdle();
  service.UpdateRecording({consent});
  service.EnableReporting();

  std::vector<SourceId> source_ids;
  for (int i = 0; i < 5; ++i) {
    if (base::Contains(app_indices, i)) {
      source_ids.push_back(UkmServiceTest::GetAppIDSourceId(i));
      recorder.UpdateSourceURL(source_ids.back(), kAppURL);
    } else {
      source_ids.push_back(UkmServiceTest::GetAllowlistedSourceId(i));
      recorder.UpdateSourceURL(source_ids.back(), kURL);
    }

    TestEvent1(source_ids.back()).Record(&service);
  }

  service.Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);
  EXPECT_EQ(1, GetPersistedLogCount());

  // Has the sources and entries associated with AppIDs.
  Report report = GetPersistedReport();
  EXPECT_EQ(expected_result, report.sources_size());
  EXPECT_EQ(expected_result, report.entries_size());

  for (int i = 0; i < expected_result; ++i) {
    EXPECT_EQ(source_ids[expected_source_indices[i]], report.sources(i).id());
    EXPECT_EQ(source_ids[expected_source_indices[i]],
              report.entries(i).source_id());
  }
}

INSTANTIATE_TEST_SUITE_P(
    UkmServiceTestWithIndependentAppKMGroup,
    UkmServiceTestWithIndependentAppKM,
    testing::Values(UkmConsentType::APPS, UkmConsentType::MSBB),
    [](const testing::TestParamInfo<
        UkmServiceTestWithIndependentAppKM::ParamType>& info) {
      if (info.param == UkmConsentType::APPS) {
        return "TestApps";
      } else {
        return "TestMSBB";
      }
    });

namespace {

class UkmServiceTestWithIndependentAppKMFullConsent
    : public testing::TestWithParam<bool> {
 public:
  UkmServiceTestWithIndependentAppKMFullConsent()
      : task_runner_(new base::TestSimpleTaskRunner),
        task_runner_current_default_handle_(task_runner_) {
    UkmService::RegisterPrefs(prefs_.registry());

    prefs_.ClearPref(prefs::kUkmClientId);
    prefs_.ClearPref(prefs::kUkmSessionId);
    prefs_.ClearPref(prefs::kUkmUnsentLogStore);
  }

  int GetPersistedLogCount() { return ukm::GetPersistedLogCount(prefs_); }

  Report GetPersistedReport() { return ukm::GetPersistedReport(prefs_); }

 protected:
  TestingPrefServiceSimple prefs_;
  metrics::TestMetricsServiceClient client_;
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
  base::SingleThreadTaskRunner::CurrentDefaultHandle
      task_runner_current_default_handle_;
};

}  // namespace

TEST_P(UkmServiceTestWithIndependentAppKMFullConsent, VerifyAllAndNoneConsent) {
  const GURL kURL("https://google.com/foobar");
  const GURL kAppURL("app://google.com/foobar");
  const int kExpectedResultWithConsent = 5;

  // Setup test constants from param.
  const auto has_consent = GetParam();
  const auto consent_state =
      (has_consent ? UkmConsentState::All() : UkmConsentState());

  UkmService service(&prefs_, &client_,
                     std::make_unique<MockDemographicMetricsProvider>());
  TestRecordingHelper recorder(&service);
  EXPECT_EQ(0, GetPersistedLogCount());
  service.Initialize();
  task_runner_->RunUntilIdle();
  service.UpdateRecording(consent_state);
  service.EnableReporting();

  const std::vector<int> app_indices = {1, 4};
  const std::vector<int> url_indices = {0, 2, 3};

  std::vector<SourceId> source_ids;
  for (int i = 0; i < 5; ++i) {
    if (base::Contains(app_indices, i)) {
      source_ids.push_back(UkmServiceTest::GetAppIDSourceId(i));
      recorder.UpdateSourceURL(source_ids.back(), kAppURL);
    } else {
      source_ids.push_back(UkmServiceTest::GetAllowlistedSourceId(i));
      recorder.UpdateSourceURL(source_ids.back(), kURL);
    }

    TestEvent1(source_ids.back()).Record(&service);
  }

  service.Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);

  EXPECT_EQ(GetPersistedLogCount(), static_cast<int>(has_consent));

  if (has_consent) {
    const auto report = GetPersistedReport();

    EXPECT_EQ(report.sources_size(), kExpectedResultWithConsent);
    EXPECT_EQ(report.entries_size(), kExpectedResultWithConsent);

    for (int i = 0; i < kExpectedResultWithConsent; ++i) {
      EXPECT_EQ(source_ids[i], report.sources(i).id());
      EXPECT_EQ(source_ids[i], report.entries(i).source_id());
    }
  }
}

INSTANTIATE_TEST_SUITE_P(
    UkmServiceTestWithIndependentAppKMFullConsentGroup,
    UkmServiceTestWithIndependentAppKMFullConsent,
    testing::Values(true, false),
    [](const testing::TestParamInfo<
        UkmServiceTestWithIndependentAppKMFullConsent::ParamType>& info) {
      if (info.param) {
        return "TestAllConsent";
      } else {
        return "TestNoConsent";
      }
    });

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

class MockUkmRecorder : public ukm::UkmRecorder {};

TEST_F(UkmReduceAddEntryIpcTest, RecordingEnabled) {}

TEST_F(UkmReduceAddEntryIpcTest, RecordingDisabled) {}

TEST_F(UkmReduceAddEntryIpcTest, AddRemoveUkmObserver) {}

TEST_F(UkmReduceAddEntryIpcTest, MultipleDelegates) {}
}  // namespace ukm