chromium/chrome/browser/extensions/forced_extensions/force_installed_metrics_unittest.cc

// Copyright 2018 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/extensions/forced_extensions/force_installed_metrics.h"

#include <optional>

#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "base/timer/mock_timer.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/external_provider_impl.h"
#include "chrome/browser/extensions/forced_extensions/force_installed_test_base.h"
#include "chrome/browser/extensions/forced_extensions/force_installed_tracker.h"
#include "chrome/browser/extensions/forced_extensions/install_stage_tracker.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/install/crx_install_error.h"
#include "extensions/browser/install/sandboxed_unpacker_failure_reason.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/updater/safe_manifest_parser.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest.h"
#include "net/base/net_errors.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/components/arc/arc_prefs.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_names.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

namespace {

// Intentionally invalid extension id.
constexpr char kExtensionId3[] =;

const int kFetchTries =;
// HTTP_FORBIDDEN
const int kHttpCodeForbidden =;

constexpr char kLoadTimeStats[] =;
constexpr char kReadyTimeStats[] =;
constexpr char kTimedOutStats[] =;
constexpr char kTimedOutNotInstalledStats[] =;
constexpr char kInstallationFailureCacheStatus[] =;
constexpr char kFailureReasonsCWS[] =;
constexpr char kFailureReasonsSH[] =;
constexpr char kInstallationStages[] =;
constexpr char kInstallCreationStages[] =;
constexpr char kInstallationDownloadingStages[] =;
constexpr char kFailureCrxInstallErrorStats[] =;
constexpr char kTotalCountStats[] =;
constexpr char kNetworkErrorCodeCrxFetchRetryStats[] =;
constexpr char kHttpErrorCodeCrxFetchRetryStatsCWS[] =;
constexpr char kHttpErrorCodeCrxFetchRetryStatsSH[] =;
constexpr char kFetchRetriesCrxFetchRetryStats[] =;
constexpr char kNetworkErrorCodeCrxFetchFailedStats[] =;
constexpr char kHttpErrorCodeCrxFetchFailedStats[] =;
constexpr char kHttpErrorCodeCrxFetchFailedStatsCWS[] =;
constexpr char kHttpErrorCodeCrxFetchFailedStatsSH[] =;
constexpr char kFetchRetriesCrxFetchFailedStats[] =;
constexpr char kNetworkErrorCodeManifestFetchRetryStats[] =;
constexpr char kHttpErrorCodeManifestFetchRetryStatsCWS[] =;
constexpr char kHttpErrorCodeManifestFetchRetryStatsSH[] =;
constexpr char kFetchRetriesManifestFetchRetryStats[] =;
constexpr char kNetworkErrorCodeManifestFetchFailedStats[] =;
constexpr char kHttpErrorCodeManifestFetchFailedStats[] =;
constexpr char kHttpErrorCodeManifestFetchFailedStatsCWS[] =;
constexpr char kHttpErrorCodeManifestFetchFailedStatsSH[] =;
constexpr char kFetchRetriesManifestFetchFailedStats[] =;
constexpr char kSandboxUnpackFailureReason[] =;
#if BUILDFLAG(IS_CHROMEOS_ASH)
constexpr char kFailureSessionStats[] =
    "Extensions.ForceInstalledFailureSessionType";
constexpr char kStuckInCreateStageSessionType[] =
    "Extensions.ForceInstalledFailureSessionType."
    "ExtensionStuckInInitialCreationStage";
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
constexpr char kPossibleNonMisconfigurationFailures[] =;
constexpr char kDisableReason[] =;
constexpr char kBlocklisted[] =;
constexpr char kWebStoreExtensionManifestInvalid[] =;
constexpr char kOffStoreExtensionManifestInvalid[] =;
constexpr char kManifestNoUpdatesInfo[] =;
constexpr char kExtensionManifestInvalidAppStatusError[] =;
constexpr char kManifestDownloadTimeStats[] =;
constexpr char kCRXDownloadTimeStats[] =;
constexpr char kVerificationTimeStats[] =;
constexpr char kCopyingTimeStats[] =;
constexpr char kUnpackingTimeStats[] =;
constexpr char kCheckingExpectationsTimeStats[] =;
constexpr char kFinalizingTimeStats[] =;
constexpr char kCrxHeaderInvalidFailureIsCWS[] =;
constexpr char kCrxHeaderInvalidFailureFromCache[] =;
constexpr char kStuckInCreatedStageAreExtensionsEnabled[] =;

}  // namespace

namespace extensions {

ExtensionStatus;
ExtensionOrigin;
_;
Return;

class ForceInstalledMetricsTest : public ForceInstalledTestBase {};

TEST_F(ForceInstalledMetricsTest, EmptyForcelist) {}

TEST_F(ForceInstalledMetricsTest, ExtensionsInstalled) {}

// Verifies that failure is reported for the extensions which are listed in
// forced list, and their installation mode are overridden by ExtensionSettings
// policy to something else.
TEST_F(ForceInstalledMetricsTest, ExtensionSettingsOverrideForcedList) {}

TEST_F(ForceInstalledMetricsTest, ExtensionsInstallationTimedOut) {}

// Reporting the time for downloading the manifest of an extension and verifying
// that it is correctly recorded in the histogram.
TEST_F(ForceInstalledMetricsTest, ExtensionsManifestDownloadTime) {}

// Reporting the time for downloading the CRX file of an extension and verifying
// that it is correctly recorded in the histogram.
TEST_F(ForceInstalledMetricsTest, ExtensionsCrxDownloadTime) {}

TEST_F(ForceInstalledMetricsTest,
       ExtensionsCrxDownloadTimeWhenFetchedFromCache) {}

// Reporting the times for various stages in the extension installation process
// and verifying that the time consumed at each stage is correctly recorded in
// the histogram.
TEST_F(ForceInstalledMetricsTest, ExtensionsReportInstallationStageTimes) {}

// Reporting disable reason for the force installed extensions which are
// installed but not loaded when extension is disable due to single reason.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsInstalledButNotLoadedUniqueDisableReason) {}

// Reporting disable reasons for the force installed extensions which are
// installed but not loaded when extension is disable due to multiple reasons.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsInstalledButNotLoadedMultipleDisableReason) {}

// Reporting DisableReason::DISABLE_NONE for the force installed extensions
// which are installed but not loaded when extension is enabled.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsInstalledButNotLoadedNoDisableReason) {}

// Verifies if the metrics related to whether the extensions are enabled or not
// are recorded correctly for extensions stuck in
// NOTIFIED_FROM_MANAGEMENT_INITIAL_CREATION_FORCED stage.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsStuckInCreatedStageAreExtensionsEnabled) {}

TEST_F(ForceInstalledMetricsTest, ExtensionForceInstalledAndBlocklisted) {}

TEST_F(ForceInstalledMetricsTest, ExtensionsInstallationCancelled) {}

TEST_F(ForceInstalledMetricsTest, ForcedExtensionsAddedAfterManualExtensions) {}

TEST_F(ForceInstalledMetricsTest,
       ExtensionsInstallationTimedOutDifferentReasons) {}

// Reporting SandboxedUnpackerFailureReason when the force installed extension
// fails to install with error CRX_INSTALL_ERROR_SANDBOXED_UNPACKER_FAILURE.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsCrxInstallErrorSandboxUnpackFailure) {}

// Reporting when the extension is downloaded from cache and it fails to install
// with error CRX_HEADER_INVALID.
TEST_F(ForceInstalledMetricsTest, ExtensionsCrxHeaderInvalidFromCache) {}

// Reporting when the extension is not downloaded from cache and it fails to
// install with error CRX_HEADER_INVALID.
TEST_F(ForceInstalledMetricsTest, ExtensionsCrxHeaderInvalidNotFromCache) {}

// Verifies that offstore extension that is downloaded from the update server
// and fails with CRX_HEADER_INVALID error is considered as a misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsCrxHeaderInvalidIsMisconfiguration) {}

// Verifies that extension failing with REPLACED_BY_SYSTEM_APP error is
// considered as a misconfiguration in ChromeOS.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsFailureReplacedBySystemAppIsMisconfiguration) {}

// Reporting info when the force installed extension fails to install with error
// CRX_FETCH_URL_EMPTY due to no updates from the server.
TEST_F(ForceInstalledMetricsTest, ExtensionsNoUpdatesInfoReporting) {}

// Regression test to check if the metrics are collected properly for the
// extensions which are already installed and loaded and then fail with error
// ALREADY_INSTALLED.
TEST_F(ForceInstalledMetricsTest,
       ExtensionLoadedThenFailedWithAlreadyInstalledError) {}

// Regression test to check if the metrics are collected properly for the
// extensions which are in state |kReady|. Also verifies that the failure
// reported after |kReady| state is not reflected in the statistics.
TEST_F(ForceInstalledMetricsTest, ExtensionsReady) {}

// Regression test to check if no metrics are reported for |kReady| state when
// some extensions are failed.
TEST_F(ForceInstalledMetricsTest, AllExtensionsNotReady) {}

// Verifies that the installation stage is not overwritten by a previous stage.
TEST_F(ForceInstalledMetricsTest,
       ExtensionsPreviousInstallationStageReportedAgain) {}

// Verifies that the installation stage is overwritten if DOWNLOADING stage is
// reported again after INSTALLING stage.
TEST_F(ForceInstalledMetricsTest, ExtensionsDownloadingStageReportedAgain) {}

TEST_F(ForceInstalledMetricsTest, ExtensionsStuck) {}

TEST_F(ForceInstalledMetricsTest, ExtensionStuckInCreatedStage) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(ForceInstalledMetricsTest, ReportManagedGuestSessionOnExtensionFailure) {
  auto* fake_user_manager = new ash::FakeChromeUserManager();
  user_manager::ScopedUserManager scoped_user_manager(
      base::WrapUnique(fake_user_manager));
  const AccountId account_id =
      AccountId::FromUserEmail(profile()->GetProfileUserName());
  user_manager::User* user =
      fake_user_manager->AddPublicAccountUser(account_id);
  fake_user_manager->UserLoggedIn(account_id, user->username_hash(),
                                  false /* browser_restart */,
                                  false /* is_child */);
  SetupForceList(ExtensionOrigin::kWebStore);
  install_stage_tracker()->ReportFailure(
      kExtensionId1, InstallStageTracker::FailureReason::INVALID_ID);
  install_stage_tracker()->ReportCrxInstallError(
      kExtensionId2,
      InstallStageTracker::FailureReason::CRX_INSTALL_ERROR_OTHER,
      CrxInstallErrorDetail::UNEXPECTED_ID);
  // ForceInstalledMetrics shuts down timer because all extension are either
  // loaded or failed.
  EXPECT_FALSE(fake_timer_->IsRunning());
  histogram_tester_.ExpectBucketCount(
      kFailureSessionStats,
      ForceInstalledMetrics::UserType::USER_TYPE_PUBLIC_ACCOUNT, 2);
}

TEST_F(ForceInstalledMetricsTest, ReportGuestSessionOnExtensionFailure) {
  auto* fake_user_manager = new ash::FakeChromeUserManager();
  user_manager::ScopedUserManager scoped_user_manager(
      base::WrapUnique(fake_user_manager));
  user_manager::User* user = fake_user_manager->AddGuestUser();
  fake_user_manager->UserLoggedIn(user->GetAccountId(), user->username_hash(),
                                  false /* browser_restart */,
                                  false /* is_child */);
  SetupForceList(ExtensionOrigin::kWebStore);
  install_stage_tracker()->ReportFailure(
      kExtensionId1, InstallStageTracker::FailureReason::INVALID_ID);
  install_stage_tracker()->ReportCrxInstallError(
      kExtensionId2,
      InstallStageTracker::FailureReason::CRX_INSTALL_ERROR_OTHER,
      CrxInstallErrorDetail::UNEXPECTED_ID);
  // ForceInstalledMetrics shuts down timer because all extension are either
  // loaded or failed.
  EXPECT_FALSE(fake_timer_->IsRunning());
  histogram_tester_.ExpectBucketCount(
      kFailureSessionStats, ForceInstalledMetrics::UserType::USER_TYPE_GUEST,
      2);
}

// Verified that the metrics related to user type are reported correctly for
// extension stuck in NOTIFIED_FROM_MANAGEMENT_INITIAL_CREATION_FORCED stage.
TEST_F(ForceInstalledMetricsTest,
       ReportGuestSessionForExtensionsStuckInCreatedStage) {
  auto* fake_user_manager = new ash::FakeChromeUserManager();
  user_manager::ScopedUserManager scoped_user_manager(
      base::WrapUnique(fake_user_manager));
  user_manager::User* user = fake_user_manager->AddGuestUser();
  fake_user_manager->UserLoggedIn(user->GetAccountId(), user->username_hash(),
                                  false /* browser_restart */,
                                  false /* is_child */);

  SetupForceList(ExtensionOrigin::kWebStore);
  CreateExtensionService(/*extensions_enabled=*/true);

  scoped_refptr<const Extension> ext1 = CreateNewExtension(
      kExtensionName1, kExtensionId1, ExtensionStatus::kLoaded);
  install_stage_tracker()->ReportInstallationStage(
      kExtensionId2, InstallStageTracker::Stage::CREATED);
  install_stage_tracker()->ReportInstallCreationStage(
      kExtensionId2, InstallStageTracker::InstallCreationStage::
                         NOTIFIED_FROM_MANAGEMENT_INITIAL_CREATION_FORCED);

  EXPECT_TRUE(fake_timer_->IsRunning());
  fake_timer_->Fire();
  histogram_tester_.ExpectUniqueSample(
      kFailureReasonsCWS, InstallStageTracker::FailureReason::IN_PROGRESS, 1);
  histogram_tester_.ExpectBucketCount(kInstallationStages,
                                      InstallStageTracker::Stage::CREATED, 1);
  histogram_tester_.ExpectBucketCount(
      kInstallCreationStages,
      InstallStageTracker::InstallCreationStage::
          NOTIFIED_FROM_MANAGEMENT_INITIAL_CREATION_FORCED,
      1);
  histogram_tester_.ExpectUniqueSample(kStuckInCreatedStageAreExtensionsEnabled,
                                       true, 1);
  histogram_tester_.ExpectBucketCount(
      kFailureSessionStats, ForceInstalledMetrics::UserType::USER_TYPE_GUEST,
      1);
  histogram_tester_.ExpectBucketCount(
      kStuckInCreateStageSessionType,
      ForceInstalledMetrics::UserType::USER_TYPE_GUEST, 1);
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

TEST_F(ForceInstalledMetricsTest, ExtensionsAreDownloading) {}

// Error Codes in case of CRX_FETCH_FAILED for CWS extensions.
TEST_F(ForceInstalledMetricsTest, ExtensionCrxFetchFailedCWS) {}

// Error Codes in case of CRX_FETCH_FAILED for self hosted extension.
TEST_F(ForceInstalledMetricsTest, ExtensionCrxFetchFailedSelfHosted) {}

// Error Codes in case of MANIFEST_FETCH_FAILED for CWS extensions.
TEST_F(ForceInstalledMetricsTest, ExtensionManifestFetchFailedCWS) {}

// Error Codes in case of MANIFEST_FETCH_FAILED for self hosted extensions. This
// test verifies that failure MANIFEST_FETCH_FAILED with 403 http error code
// (Forbidden) is considered as misconfiguration.
TEST_F(ForceInstalledMetricsTest, ExtensionManifestFetchFailedSelfHosted) {}

// Error Codes in case of CWS extensions stuck in DOWNLOADING_MANIFEST_RETRY
// stage.
TEST_F(ForceInstalledMetricsTest, ExtensionManifestFetchRetryCWS) {}

// This test verifies that failure MANIFEST_INVALID in case of offstore
// extensions is considered as misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       OffStoreExtensionManifestInvalidIsMisconfiguration) {}

// This test verifies that failure OVERRIDDEN_BY_SETTINGS in case of offstore
// extensions is considered as misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       ExtensionOverridenBySettingsFailureIsMisconfiguration) {}

// Error Codes in case of self hosted extensions stuck in
// DOWNLOADING_MANIFEST_RETRY stage.
TEST_F(ForceInstalledMetricsTest, ExtensionManifestFetchRetrySelfHosted) {}

// Error Codes in case of CWS extensions stuck in DOWNLOADING_CRX_RETRY stage.
TEST_F(ForceInstalledMetricsTest, ExtensionCrxFetchRetryCWS) {}

// Error Codes in case of self hosted extensions stuck in DOWNLOADING_CRX_RETRY
// stage.
TEST_F(ForceInstalledMetricsTest, ExtensionCrxFetchRetrySelfHosted) {}

// Errors occurred because the fetched update manifest for webstore extension
// was invalid.
TEST_F(ForceInstalledMetricsTest, ExtensionManifestInvalidWebStore) {}

// Errors occurred because the fetched update manifest for offstore extension
// was invalid.
TEST_F(ForceInstalledMetricsTest, ExtensionManifestInvalidOffStore) {}

// Errors occurred because the fetched update manifest was invalid because app
// status was not OK. Verifies that this error with app status error as
// "error-unknownApplication" is considered as a misconfiguration.
TEST_F(ForceInstalledMetricsTest, ExtensionManifestInvalidAppStatusError) {}

// Session in which either all the extensions installed successfully, or all
// failures are admin-side misconfigurations. This test verifies that failure
// CRX_INSTALL_ERROR with detailed error KIOSK_MODE_ONLY is considered as
// misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailureNotPresentKioskModeOnlyError) {}

// Session in which either all the extensions installed successfully, or all
// failures are admin-side misconfigurations. This test verifies that failure
// CRX_INSTALL_ERROR with detailed error DISALLOWED_BY_POLICY and when extension
// type which is not allowed to install according to policy
// kExtensionAllowedTypes is considered as misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailureNotPresentDisallowedByPolicyTypeError) {}

// Session in which at least one non misconfiguration failure occurred. One of
// the extension fails to install with DISALLOWED_BY_POLICY error but has
// extension type which is allowed by policy ExtensionAllowedTypes. This is not
// a misconfiguration failure.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailurePresentDisallowedByPolicyError) {}

// Session in which at least one non misconfiguration failure occurred.
// Misconfiguration failure includes error KIOSK_MODE_ONLY, when force installed
// extension fails to install with failure reason CRX_INSTALL_ERROR.
TEST_F(ForceInstalledMetricsTest, NonMisconfigurationFailurePresent) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
// Session in which either all the extensions installed successfully, or all
// failures are admin-side misconfigurations. This test verifies that failure
// REPLACED_BY_ARC_APP is not considered as misconfiguration when ARC++ is
// enabled for the profile.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailureNotPresentReplacedByArcAppErrorArcEnabled) {
  // Enable ARC++ for this profile.
  prefs()->SetManagedPref(arc::prefs::kArcEnabled,
                          std::make_unique<base::Value>(true));
  SetupForceList(ExtensionOrigin::kWebStore);
  scoped_refptr<const Extension> ext1 = CreateNewExtension(
      kExtensionName1, kExtensionId1, ExtensionStatus::kLoaded);
  install_stage_tracker()->ReportFailure(
      kExtensionId2, InstallStageTracker::FailureReason::REPLACED_BY_ARC_APP);
  // ForceInstalledMetrics shuts down timer because all extension are either
  // loaded or failed.
  EXPECT_FALSE(fake_timer_->IsRunning());
  histogram_tester_.ExpectBucketCount(kPossibleNonMisconfigurationFailures, 0,
                                      1);
}

// Session in which at least one non misconfiguration failure occurred. This
// test verifies that failure REPLACED_BY_ARC_APP is not considered as
// misconfiguration when ARC++ is disabled for the profile.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailureNotPresentReplacedByArcAppErrorArcDisabled) {
  // Enable ARC++ for this profile.
  prefs()->SetManagedPref(arc::prefs::kArcEnabled,
                          std::make_unique<base::Value>(false));
  SetupForceList(ExtensionOrigin::kWebStore);
  scoped_refptr<const Extension> ext1 = CreateNewExtension(
      kExtensionName1, kExtensionId1, ExtensionStatus::kLoaded);
  install_stage_tracker()->ReportFailure(
      kExtensionId2, InstallStageTracker::FailureReason::REPLACED_BY_ARC_APP);
  // ForceInstalledMetrics shuts down timer because all extension are either
  // loaded or failed.
  EXPECT_FALSE(fake_timer_->IsRunning());
  histogram_tester_.ExpectBucketCount(kPossibleNonMisconfigurationFailures, 1,
                                      1);
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

// Session in which either all the extensions installed successfully, or all
// failures are admin-side misconfigurations. This test verifies that failure
// NOT_PERFORMING_NEW_INSTALL is considered as misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailureNotPresentNotPerformingNewInstallError) {}

// Session in which either all the extensions installed successfully, or all
// failures are admin-side misconfigurations. This test verifies that failure
// CRX_FETCH_URL_EMPTY with empty info field is considered as misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailureNotPresentCrxFetchUrlEmptyError) {}

// This test verifies that failure CRX_FETCH_URL_EMPTY with non empty info field
// is not considered as a misconfiguration.
TEST_F(ForceInstalledMetricsTest,
       NonMisconfigurationFailurePresentCrxFetchUrlEmptyError) {}

TEST_F(ForceInstalledMetricsTest, NoExtensionsConfigured) {}

TEST_F(ForceInstalledMetricsTest, CachedExtensions) {}

}  // namespace extensions