chromium/chrome/services/system_signals/win/win_system_signals_service_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/services/system_signals/win/win_system_signals_service.h"

#include <array>
#include <memory>
#include <optional>
#include <utility>

#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_os_info_override_win.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/device_signals/core/common/common_types.h"
#include "components/device_signals/core/common/win/win_types.h"
#include "components/device_signals/core/system_signals/file_system_service.h"
#include "components/device_signals/core/system_signals/mock_file_system_service.h"
#include "components/device_signals/core/system_signals/win/mock_wmi_client.h"
#include "components/device_signals/core/system_signals/win/mock_wsc_client.h"
#include "components/device_signals/core/system_signals/win/wmi_client.h"
#include "components/device_signals/core/system_signals/win/wsc_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using device_signals::MockFileSystemService;
using device_signals::MockWmiClient;
using device_signals::MockWscClient;
using testing::Return;

namespace system_signals {

class WinSystemSignalsServiceTest : public testing::Test {
 protected:
  WinSystemSignalsServiceTest() {
    auto file_system_service =
        std::make_unique<testing::StrictMock<MockFileSystemService>>();
    file_system_service_ = file_system_service.get();

    auto wmi_client = std::make_unique<testing::StrictMock<MockWmiClient>>();
    wmi_client_ = wmi_client.get();

    auto wsc_client = std::make_unique<testing::StrictMock<MockWscClient>>();
    wsc_client_ = wsc_client.get();

    mojo::PendingReceiver<device_signals::mojom::SystemSignalsService>
        fake_receiver;

    // Have to use "new" since make_unique doesn't have access to friend private
    // constructor.
    win_system_signals_service_ =
        std::unique_ptr<WinSystemSignalsService>(new WinSystemSignalsService(
            std::move(fake_receiver), std::move(file_system_service),
            std::move(wmi_client), std::move(wsc_client)));
  }

  base::test::TaskEnvironment task_environment_;
  base::HistogramTester histogram_tester_;
  std::optional<base::test::ScopedOSInfoOverride> os_info_override_;

  std::unique_ptr<WinSystemSignalsService> win_system_signals_service_;

  // Owned by win_system_signals_service_.
  raw_ptr<MockFileSystemService> file_system_service_;
  raw_ptr<MockWmiClient> wmi_client_;
  raw_ptr<MockWscClient> wsc_client_;
};

// Tests that GetFileSystemSignals forwards the signal collection to
// FileSystemService.
TEST_F(WinSystemSignalsServiceTest, GetFileSystemSignals) {
  device_signals::GetFileSystemInfoOptions options;
  options.file_path = base::FilePath::FromUTF8Unsafe("/some/file/path");

  std::vector<device_signals::GetFileSystemInfoOptions> requests;
  requests.push_back(std::move(options));

  device_signals::FileSystemItem returned_item;
  returned_item.file_path =
      base::FilePath::FromUTF8Unsafe("/some/other/file/path");
  returned_item.presence = device_signals::PresenceValue::kFound;

  std::vector<device_signals::FileSystemItem> response;
  response.push_back(std::move(returned_item));

  EXPECT_CALL(*file_system_service_, GetSignals(requests))
      .WillOnce(Return(response));

  base::test::TestFuture<const std::vector<device_signals::FileSystemItem>&>
      future;
  win_system_signals_service_->GetFileSystemSignals(requests,
                                                    future.GetCallback());

  auto results = future.Get();
  EXPECT_EQ(results.size(), response.size());
  EXPECT_EQ(results[0], response[0]);
}

// Tests that AV products cannot be retrieved on Win Server environments.
TEST_F(WinSystemSignalsServiceTest, GetAntiVirusSignals_Server) {
  std::array<base::test::ScopedOSInfoOverride::Type, 2> server_versions = {
      base::test::ScopedOSInfoOverride::Type::kWinServer2016,
      base::test::ScopedOSInfoOverride::Type::kWinServer2022,
  };

  for (const auto server_version : server_versions) {
    os_info_override_.emplace(server_version);

    base::test::TestFuture<const std::vector<device_signals::AvProduct>&>
        future;
    win_system_signals_service_->GetAntiVirusSignals(future.GetCallback());

    EXPECT_EQ(future.Get().size(), 0U);
  }
}

// Tests that AV products are retrieved through WSC on Win10 and above.
TEST_F(WinSystemSignalsServiceTest, GetAntiVirusSignals_Wsc_Success) {
  std::array<base::test::ScopedOSInfoOverride::Type, 4> win_versions = {
      base::test::ScopedOSInfoOverride::Type::kWin10Pro,
      base::test::ScopedOSInfoOverride::Type::kWin10Pro21H1,
      base::test::ScopedOSInfoOverride::Type::kWin11Home,
      base::test::ScopedOSInfoOverride::Type::kWin11Pro,
  };

  int counter = 0;
  for (const auto win_version : win_versions) {
    os_info_override_.emplace(win_version);

    device_signals::AvProduct fake_av_product;
    fake_av_product.display_name = "some display name";
    fake_av_product.product_id = "some product id";
    fake_av_product.state = device_signals::AvProductState::kOn;

    device_signals::WscAvProductsResponse fake_response;
    fake_response.av_products.push_back(fake_av_product);

    EXPECT_CALL(*wsc_client_, GetAntiVirusProducts())
        .WillOnce(Return(fake_response));

    base::test::TestFuture<const std::vector<device_signals::AvProduct>&>
        future;
    win_system_signals_service_->GetAntiVirusSignals(future.GetCallback());

    const auto& av_products = future.Get();
    EXPECT_EQ(av_products.size(), fake_response.av_products.size());
    EXPECT_EQ(av_products[0].product_id,
              fake_response.av_products[0].product_id);

    histogram_tester_.ExpectUniqueSample(
        "Enterprise.SystemSignals.Collection.WSC.AntiVirus.ParsingError.Rate",
        /*error_rate=*/0, ++counter);
  }
}

// Tests the behavior when WSC returns no items nor error.
TEST_F(WinSystemSignalsServiceTest, GetAntiVirusSignals_Wsc_Empty) {
  os_info_override_.emplace(base::test::ScopedOSInfoOverride::Type::kWin10Pro);

  device_signals::WscAvProductsResponse fake_response;

  EXPECT_CALL(*wsc_client_, GetAntiVirusProducts())
      .WillOnce(Return(fake_response));

  base::test::TestFuture<const std::vector<device_signals::AvProduct>&> future;
  win_system_signals_service_->GetAntiVirusSignals(future.GetCallback());

  const auto& av_products = future.Get();
  EXPECT_TRUE(av_products.empty());

  histogram_tester_.ExpectUniqueSample(
      "Enterprise.SystemSignals.Collection.WSC.AntiVirus.ParsingError.Rate",
      /*error_rate=*/0, 1);
}

// Tests the behavior when a Query error is returned from querying WSC.
TEST_F(WinSystemSignalsServiceTest, GetAntiVirusSignals_Wsc_QueryError) {
  os_info_override_.emplace(base::test::ScopedOSInfoOverride::Type::kWin10Pro);

  device_signals::WscAvProductsResponse fake_response;
  fake_response.query_error =
      device_signals::WscQueryError::kFailedToCreateInstance;

  EXPECT_CALL(*wsc_client_, GetAntiVirusProducts())
      .WillOnce(Return(fake_response));

  base::test::TestFuture<const std::vector<device_signals::AvProduct>&> future;
  win_system_signals_service_->GetAntiVirusSignals(future.GetCallback());

  const auto& av_products = future.Get();
  EXPECT_TRUE(av_products.empty());

  histogram_tester_.ExpectUniqueSample(
      "Enterprise.SystemSignals.Collection.WSC.AntiVirus.QueryError",
      fake_response.query_error.value(), 1);
}

// Tests the behavior when parsing errors are returned from querying WSC.
TEST_F(WinSystemSignalsServiceTest, GetAntiVirusSignals_Wsc_MixedParsingError) {
  os_info_override_.emplace(base::test::ScopedOSInfoOverride::Type::kWin10Pro);
  device_signals::AvProduct fake_av_product;
  fake_av_product.display_name = "some display name";
  fake_av_product.product_id = "some product id";
  fake_av_product.state = device_signals::AvProductState::kOn;

  // Adding 2 success and 2 failures, so the error rate should be 50%.
  device_signals::WscAvProductsResponse fake_response;
  fake_response.av_products.push_back(fake_av_product);
  fake_response.av_products.push_back(fake_av_product);
  fake_response.parsing_errors.push_back(
      device_signals::WscParsingError::kFailedToGetState);
  fake_response.parsing_errors.push_back(
      device_signals::WscParsingError::kStateInvalid);

  EXPECT_CALL(*wsc_client_, GetAntiVirusProducts())
      .WillOnce(Return(fake_response));

  base::test::TestFuture<const std::vector<device_signals::AvProduct>&> future;
  win_system_signals_service_->GetAntiVirusSignals(future.GetCallback());

  const auto& av_products = future.Get();
  EXPECT_EQ(av_products.size(), fake_response.av_products.size());

  histogram_tester_.ExpectBucketCount(
      "Enterprise.SystemSignals.Collection.WSC.AntiVirus.ParsingError",
      device_signals::WscParsingError::kFailedToGetState, 1);
  histogram_tester_.ExpectBucketCount(
      "Enterprise.SystemSignals.Collection.WSC.AntiVirus.ParsingError",
      device_signals::WscParsingError::kStateInvalid, 1);
  histogram_tester_.ExpectBucketCount(
      "Enterprise.SystemSignals.Collection.WSC.AntiVirus.ParsingError.Rate",
      /*error_rate=*/50, 1);
}

// Tests that Hotfix information is retrieved via WMI.
TEST_F(WinSystemSignalsServiceTest, GetHotfixSignals_Empty) {
  device_signals::InstalledHotfix fake_hotfix{"some hotfix id"};
  device_signals::WmiHotfixesResponse fake_response;

  EXPECT_CALL(*wmi_client_, GetInstalledHotfixes())
      .WillOnce(Return(fake_response));

  base::test::TestFuture<const std::vector<device_signals::InstalledHotfix>&>
      future;
  win_system_signals_service_->GetHotfixSignals(future.GetCallback());

  const auto& hotfixes_response = future.Get();
  EXPECT_TRUE(hotfixes_response.empty());

  histogram_tester_.ExpectUniqueSample(
      "Enterprise.SystemSignals.Collection.WMI.Hotfixes.ParsingError.Rate",
      /*error_rate=*/0, 1);
}

// Tests that Hotfix information is retrieved via WMI.
TEST_F(WinSystemSignalsServiceTest, GetHotfixSignals_Success) {
  device_signals::InstalledHotfix fake_hotfix{"some hotfix id"};
  device_signals::WmiHotfixesResponse fake_response;
  fake_response.hotfixes.push_back(fake_hotfix);

  EXPECT_CALL(*wmi_client_, GetInstalledHotfixes())
      .WillOnce(Return(fake_response));

  base::test::TestFuture<const std::vector<device_signals::InstalledHotfix>&>
      future;
  win_system_signals_service_->GetHotfixSignals(future.GetCallback());

  const auto& hotfixes_response = future.Get();
  EXPECT_EQ(hotfixes_response.size(), fake_response.hotfixes.size());
  EXPECT_EQ(hotfixes_response[0].hotfix_id,
            fake_response.hotfixes[0].hotfix_id);

  histogram_tester_.ExpectUniqueSample(
      "Enterprise.SystemSignals.Collection.WMI.Hotfixes.ParsingError.Rate",
      /*error_rate=*/0, 1);
}

// Tests that a query error is returned when querying Hotfixes via WMI.
TEST_F(WinSystemSignalsServiceTest, GetHotfixSignals_QueryError) {
  device_signals::WmiHotfixesResponse fake_response;
  fake_response.query_error = base::win::WmiError::kFailedToCreateInstance;

  EXPECT_CALL(*wmi_client_, GetInstalledHotfixes())
      .WillOnce(Return(fake_response));

  base::test::TestFuture<const std::vector<device_signals::InstalledHotfix>&>
      future;
  win_system_signals_service_->GetHotfixSignals(future.GetCallback());

  const auto& hotfixes_response = future.Get();
  EXPECT_TRUE(hotfixes_response.empty());

  histogram_tester_.ExpectUniqueSample(
      "Enterprise.SystemSignals.Collection.WMI.Hotfixes.QueryError",
      fake_response.query_error.value(), 1);
}

// Tests that items and parsing errors are returned when querying Hotfixes via
// WMI.
TEST_F(WinSystemSignalsServiceTest, GetHotfixSignals_MixedParsingErrors) {
  device_signals::InstalledHotfix fake_hotfix{"some hotfix id"};

  // Adding 2 success and 2 failures, so the error rate should be 50%.
  device_signals::WmiHotfixesResponse fake_response;
  fake_response.hotfixes.push_back(fake_hotfix);
  fake_response.hotfixes.push_back(fake_hotfix);
  fake_response.parsing_errors.push_back(
      device_signals::WmiParsingError::kFailedToGetState);
  fake_response.parsing_errors.push_back(
      device_signals::WmiParsingError::kFailedToGetId);

  EXPECT_CALL(*wmi_client_, GetInstalledHotfixes())
      .WillOnce(Return(fake_response));

  base::test::TestFuture<const std::vector<device_signals::InstalledHotfix>&>
      future;
  win_system_signals_service_->GetHotfixSignals(future.GetCallback());

  const auto& hotfixes_response = future.Get();
  EXPECT_EQ(hotfixes_response.size(), fake_response.hotfixes.size());

  histogram_tester_.ExpectBucketCount(
      "Enterprise.SystemSignals.Collection.WMI.Hotfixes.ParsingError",
      device_signals::WmiParsingError::kFailedToGetState, 1);
  histogram_tester_.ExpectBucketCount(
      "Enterprise.SystemSignals.Collection.WMI.Hotfixes.ParsingError",
      device_signals::WmiParsingError::kFailedToGetId, 1);
  histogram_tester_.ExpectBucketCount(
      "Enterprise.SystemSignals.Collection.WMI.Hotfixes.ParsingError.Rate",
      /*error_rate=*/50, 1);
}

}  // namespace system_signals