chromium/chrome/browser/ui/webui/ash/settings/services/metrics/settings_user_action_tracker_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/ui/webui/ash/settings/services/metrics/settings_user_action_tracker.h"

#include "ash/webui/settings/public/constants/setting.mojom.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/values.h"
#include "chrome/browser/metrics/chrome_metrics_service_client.h"
#include "chrome/browser/ui/webui/ash/settings/search/mojom/user_action_recorder.mojom.h"
#include "chrome/browser/ui/webui/ash/settings/services/metrics/per_session_settings_user_action_tracker.h"
#include "chrome/browser/ui/webui/ash/settings/test_support/fake_hierarchy.h"
#include "chrome/browser/ui/webui/ash/settings/test_support/fake_os_settings_section.h"
#include "chrome/browser/ui/webui/ash/settings/test_support/fake_os_settings_sections.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/test/test_enabled_state_provider.h"
#include "components/metrics/test/test_metrics_service_client.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
class TestUserMetricsServiceClient
    : public ::metrics::TestMetricsServiceClient {
 public:
  std::optional<bool> GetCurrentUserMetricsConsent() const override {
    return current_user_metrics_consent_;
  }

  void UpdateCurrentUserMetricsConsent(bool metrics_consent) override {
    current_user_metrics_consent_ = metrics_consent;
  }

 private:
  bool current_user_metrics_consent_ = true;
};
}  // namespace

namespace ash::settings {

constexpr char kProfileName[] = "[email protected]";

namespace mojom {
using ::chromeos::settings::mojom::Section;
using ::chromeos::settings::mojom::Setting;
}  // namespace mojom

class SettingsUserActionTrackerTest : public testing::Test {
 protected:
  SettingsUserActionTrackerTest() = default;
  ~SettingsUserActionTrackerTest() override = default;

  void SetUpTestingProfile() {
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(profile_manager_->SetUp());
    testing_profile_ = profile_manager_->CreateTestingProfile(kProfileName);
  }

  // metrics setup
  void SetupTestMetricsClient() {
    local_state_ = std::make_unique<TestingPrefServiceSimple>();
    metrics::MetricsService::RegisterPrefs(local_state_->registry());

    test_enabled_state_provider_ =
        std::make_unique<metrics::TestEnabledStateProvider>(true, true);
    test_metrics_state_manager_ = metrics::MetricsStateManager::Create(
        local_state_.get(), test_enabled_state_provider_.get(), std::wstring(),
        base::FilePath());
    test_metrics_service_client_ =
        std::make_unique<TestUserMetricsServiceClient>();
    test_metrics_service_ = std::make_unique<metrics::MetricsService>(
        test_metrics_state_manager_.get(), test_metrics_service_client_.get(),
        local_state_.get());
    TestingBrowserProcess::GetGlobal()->SetMetricsService(
        test_metrics_service_.get());

    // Needs to be set for metrics service.
    base::SetRecordActionTaskRunner(
        task_environment_.GetMainThreadTaskRunner());
  }

  // testing::Test:
  void SetUp() override {
    fake_sections_ = std::make_unique<FakeOsSettingsSections>();
    fake_hierarchy_ = std::make_unique<FakeHierarchy>(fake_sections_.get());
    fake_hierarchy_->AddSettingMetadata(mojom::Section::kBluetooth,
                                        mojom::Setting::kBluetoothOnOff);
    fake_hierarchy_->AddSettingMetadata(mojom::Section::kDevice,
                                        mojom::Setting::kKeyboardFunctionKeys);
    fake_hierarchy_->AddSettingMetadata(mojom::Section::kDevice,
                                        mojom::Setting::kTouchpadSpeed);
    fake_hierarchy_->AddSettingMetadata(mojom::Section::kPeople,
                                        mojom::Setting::kAddAccount);
    fake_hierarchy_->AddSettingMetadata(mojom::Section::kPersonalization,
                                        mojom::Setting::kOpenWallpaper);
    fake_hierarchy_->AddSettingMetadata(mojom::Section::kNetwork,
                                        mojom::Setting::kWifiAddNetwork);

    SetUpTestingProfile();
    test_pref_service_ = testing_profile_->GetPrefs();
    SetupTestMetricsClient();

    tracker_ = std::make_unique<SettingsUserActionTracker>(
        fake_hierarchy_.get(), fake_sections_.get(), test_pref_service_);
    // Initialize per_session_tracker_ manually since BindInterface is never
    // called on tracker_.
    tracker_->per_session_tracker_ =
        std::make_unique<PerSessionSettingsUserActionTracker>(
            testing_profile_->GetPrefs());
  }

  void TearDown() override {
    TestingBrowserProcess::GetGlobal()->SetMetricsService(nullptr);
  }

  // TestingProfile is bound to the IO thread:
  // CurrentlyOn(content::BrowserThread::UI).
  content::BrowserTaskEnvironment task_environment_;
  base::HistogramTester histogram_tester_;
  std::unique_ptr<FakeOsSettingsSections> fake_sections_;
  std::unique_ptr<FakeHierarchy> fake_hierarchy_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
  raw_ptr<TestingProfile> testing_profile_;
  raw_ptr<PrefService> test_pref_service_;
  std::unique_ptr<SettingsUserActionTracker> tracker_;

  // MetricsService.
  std::unique_ptr<TestingPrefServiceSimple> local_state_;
  std::unique_ptr<metrics::TestEnabledStateProvider>
      test_enabled_state_provider_;
  std::unique_ptr<metrics::MetricsStateManager> test_metrics_state_manager_;
  std::unique_ptr<TestUserMetricsServiceClient> test_metrics_service_client_;
  std::unique_ptr<metrics::MetricsService> test_metrics_service_;
};

TEST_F(SettingsUserActionTrackerTest, TestRecordSettingChangedBool) {
  // Record that the bluetooth enabled setting was toggled off.
  tracker_->RecordSettingChangeWithDetails(
      mojom::Setting::kBluetoothOnOff,
      mojom::SettingChangeValue::NewBoolValue(false));

  // The umbrella metric for which setting was changed should be updated. Note
  // that kBluetoothOnOff has enum value of 100.
  histogram_tester_.ExpectTotalCount("ChromeOS.Settings.SettingChanged",
                                     /*count=*/1);
  histogram_tester_.ExpectBucketCount("ChromeOS.Settings.SettingChanged",
                                      /*sample=*/100,
                                      /*count=*/1);

  // The LogMetric fn in the Blutooth section should have been called.
  const FakeOsSettingsSection* bluetooth_section =
      static_cast<const FakeOsSettingsSection*>(
          fake_sections_->GetSection(mojom::Section::kBluetooth));
  EXPECT_TRUE(bluetooth_section->logged_metrics().back() ==
              mojom::Setting::kBluetoothOnOff);
}

TEST_F(SettingsUserActionTrackerTest, TestRecordSettingChangedString) {
  // Record that the user tried to add a 3rd account.
  tracker_->RecordSettingChangeWithDetails(
      mojom::Setting::kWifiAddNetwork,
      mojom::SettingChangeValue::NewStringValue("guid"));

  // The umbrella metric for which setting was changed should be updated. Note
  // that kWifiAddNetwork has enum value of 8.
  histogram_tester_.ExpectTotalCount("ChromeOS.Settings.SettingChanged",
                                     /*count=*/1);
  histogram_tester_.ExpectBucketCount("ChromeOS.Settings.SettingChanged",
                                      /*sample=*/8,
                                      /*count=*/1);

  // The LogMetric fn in the People section should have been called.
  const FakeOsSettingsSection* network_section =
      static_cast<const FakeOsSettingsSection*>(
          fake_sections_->GetSection(mojom::Section::kNetwork));
  EXPECT_TRUE(network_section->logged_metrics().back() ==
              mojom::Setting::kWifiAddNetwork);
}

TEST_F(SettingsUserActionTrackerTest, TestRecordSettingChangedInt) {
  // Record that the user tried to add a 3rd account.
  tracker_->RecordSettingChangeWithDetails(
      mojom::Setting::kAddAccount, mojom::SettingChangeValue::NewIntValue(3));

  // The umbrella metric for which setting was changed should be updated. Note
  // that kAddAccount has enum value of 300.
  histogram_tester_.ExpectTotalCount("ChromeOS.Settings.SettingChanged",
                                     /*count=*/1);
  histogram_tester_.ExpectBucketCount("ChromeOS.Settings.SettingChanged",
                                      /*sample=*/300,
                                      /*count=*/1);

  // The LogMetric fn in the People section should have been called.
  const FakeOsSettingsSection* people_section =
      static_cast<const FakeOsSettingsSection*>(
          fake_sections_->GetSection(mojom::Section::kPeople));
  EXPECT_TRUE(people_section->logged_metrics().back() ==
              mojom::Setting::kAddAccount);
}

TEST_F(SettingsUserActionTrackerTest, TestRecordSettingChangedBoolPref) {
  // Record that the keyboard function keys setting was toggled off. This
  // setting is controlled by a pref and uses the pref-to-setting-metric
  // converter flow.
  tracker_->RecordSettingChangeWithDetails(
      mojom::Setting::kKeyboardFunctionKeys,
      mojom::SettingChangeValue::NewBoolValue(false));

  // The umbrella metric for which setting was changed should be updated. Note
  // that kKeyboardFunctionKeys has enum value of 411.
  histogram_tester_.ExpectTotalCount("ChromeOS.Settings.SettingChanged",
                                     /*count=*/1);
  histogram_tester_.ExpectBucketCount("ChromeOS.Settings.SettingChanged",
                                      /*sample=*/411,
                                      /*count=*/1);

  // The LogMetric fn in the Device section should have been called.
  const FakeOsSettingsSection* device_section =
      static_cast<const FakeOsSettingsSection*>(
          fake_sections_->GetSection(mojom::Section::kDevice));
  EXPECT_TRUE(device_section->logged_metrics().back() ==
              mojom::Setting::kKeyboardFunctionKeys);
}

TEST_F(SettingsUserActionTrackerTest, TestRecordSettingChangedIntPref) {
  // Record that the touchpad speed slider was changed by the user. This
  // setting is controlled by a pref and uses the pref-to-setting-metric
  // converter flow.
  tracker_->RecordSettingChangeWithDetails(
      mojom::Setting::kTouchpadSpeed,
      mojom::SettingChangeValue::NewIntValue(4));

  // The umbrella metric for which setting was changed should be updated. Note
  // that kTouchpadSpeed has enum value of 405.
  histogram_tester_.ExpectTotalCount("ChromeOS.Settings.SettingChanged",
                                     /*count=*/1);
  histogram_tester_.ExpectBucketCount("ChromeOS.Settings.SettingChanged",
                                      /*sample=*/405,
                                      /*count=*/1);

  // The LogMetric fn in the Device section should have been called.
  const FakeOsSettingsSection* device_section =
      static_cast<const FakeOsSettingsSection*>(
          fake_sections_->GetSection(mojom::Section::kDevice));
  EXPECT_TRUE(device_section->logged_metrics().back() ==
              mojom::Setting::kTouchpadSpeed);
}

TEST_F(SettingsUserActionTrackerTest, TestRecordSettingChangedNullValue) {
  // Record that the Wallpaper app is opened.
  tracker_->RecordSettingChangeWithDetails(mojom::Setting::kOpenWallpaper,
                                           nullptr);

  // The umbrella metric for which setting was changed should be updated. Note
  // that kOpenWallpaper has enum value of 500.
  histogram_tester_.ExpectTotalCount("ChromeOS.Settings.SettingChanged",
                                     /*count=*/1);
  histogram_tester_.ExpectBucketCount("ChromeOS.Settings.SettingChanged",
                                      /*sample=*/500,
                                      /*count=*/1);

  // The LogMetric fn in the Personalization section should have been called.
  const FakeOsSettingsSection* personalization_section =
      static_cast<const FakeOsSettingsSection*>(
          fake_sections_->GetSection(mojom::Section::kPersonalization));
  EXPECT_TRUE(personalization_section->logged_metrics().back() ==
              mojom::Setting::kOpenWallpaper);
}

}  // namespace ash::settings