chromium/chrome/browser/ash/bruschetta/bruschetta_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/browser/ash/bruschetta/bruschetta_service.h"

#include "ash/constants/ash_features.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/bruschetta/bruschetta_pref_names.h"
#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
#include "chrome/browser/ash/guest_os/dbus_test_helper.h"
#include "chrome/browser/ash/guest_os/guest_os_pref_names.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/guest_os/virtual_machines/virtual_machines_util.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
#include "chromeos/ash/components/dbus/dlcservice/fake_dlcservice_client.h"
#include "chromeos/ash/components/dbus/vm_concierge/concierge_service.pb.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace bruschetta {

namespace {
const char kTestVmName[] = "vm_name";
const char kTestVmConfig[] = "vm_config";
}  // namespace

class BruschettaServiceTest : public testing::Test,
                              public guest_os::FakeVmServicesHelper {
 public:
  BruschettaServiceTest() = default;
  BruschettaServiceTest(const BruschettaServiceTest&) = delete;
  BruschettaServiceTest& operator=(const BruschettaServiceTest&) = delete;
  ~BruschettaServiceTest() override = default;

 protected:
  void SetUp() override {
    SetupPrefs();

    service_ = std::make_unique<BruschettaService>(&profile_);
  }

  void TearDown() override {}

  void SetupPrefs() {
    base::Value::Dict pref;
    base::Value::Dict config;
    config.Set(prefs::kPolicyEnabledKey,
               static_cast<int>(prefs::PolicyEnabledState::RUN_ALLOWED));

    base::Value::Dict vtpm;
    vtpm.Set(prefs::kPolicyVTPMEnabledKey, false);
    vtpm.Set(prefs::kPolicyVTPMUpdateActionKey,
             static_cast<int>(
                 prefs::PolicyUpdateAction::FORCE_SHUTDOWN_IF_MORE_RESTRICTED));

    config.Set(prefs::kPolicyVTPMKey, std::move(vtpm));
    config.Set(prefs::kPolicyNameKey, "Display Name");

    pref.Set(kTestVmConfig, std::move(config));
    profile_.GetPrefs()->SetDict(prefs::kBruschettaVMConfiguration,
                                 std::move(pref));
  }

  void EnableByPolicy() {
    auto updater = ScopedDictPrefUpdate(profile_.GetPrefs(),
                                        prefs::kBruschettaVMConfiguration);

    updater.Get()
        .FindDict(kTestVmConfig)
        ->Set(prefs::kPolicyEnabledKey,
              static_cast<int>(prefs::PolicyEnabledState::RUN_ALLOWED));
  }

  void DisableByPolicy() {
    auto updater = ScopedDictPrefUpdate(profile_.GetPrefs(),
                                        prefs::kBruschettaVMConfiguration);

    updater.Get()
        .FindDict(kTestVmConfig)
        ->Set(prefs::kPolicyEnabledKey,
              static_cast<int>(prefs::PolicyEnabledState::BLOCKED));
  }

  void SetVtpmConfig(bool enabled, prefs::PolicyUpdateAction action) {
    auto updater = ScopedDictPrefUpdate(profile_.GetPrefs(),
                                        prefs::kBruschettaVMConfiguration);

    auto* vtpm =
        updater.Get().FindDict(kTestVmConfig)->FindDict(prefs::kPolicyVTPMKey);
    vtpm->Set(prefs::kPolicyVTPMEnabledKey, enabled);
    vtpm->Set(prefs::kPolicyVTPMUpdateActionKey, static_cast<int>(action));
  }

  content::BrowserTaskEnvironment task_environment_;
  TestingProfile profile_;
  std::unique_ptr<BruschettaService> service_;
};

TEST_F(BruschettaServiceTest, GetLauncherPolicyEnabled) {
  EnableByPolicy();
  service_->RegisterInPrefs(MakeBruschettaId(kTestVmName), kTestVmConfig);
  ASSERT_NE(service_->GetLauncher(kTestVmName), nullptr);
}

TEST_F(BruschettaServiceTest, GetLauncherPolicyDisabled) {
  DisableByPolicy();
  service_->RegisterInPrefs(MakeBruschettaId(kTestVmName), kTestVmConfig);
  ASSERT_EQ(service_->GetLauncher(kTestVmName), nullptr);
}

TEST_F(BruschettaServiceTest, GetLauncherPolicyUpdate) {
  EnableByPolicy();
  service_->RegisterInPrefs(MakeBruschettaId(kTestVmName), kTestVmConfig);
  DisableByPolicy();
  ASSERT_EQ(service_->GetLauncher(kTestVmName), nullptr);
  EnableByPolicy();
  ASSERT_NE(service_->GetLauncher(kTestVmName), nullptr);
}

TEST_F(BruschettaServiceTest, DynamicPolicyDisableForcesShutdown) {
  vm_tools::concierge::VmStoppedSignal stop_signal;
  stop_signal.set_name(kTestVmName);

  service_->RegisterInPrefs(MakeBruschettaId(kTestVmName), kTestVmConfig);

  EnableByPolicy();
  service_->RegisterVmLaunch(kTestVmName, {});
  ASSERT_TRUE(service_->GetRunningVmsForTesting().contains(kTestVmName));

  DisableByPolicy();
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 1);
  base::RunLoop().RunUntilIdle();
  ASSERT_FALSE(service_->GetRunningVmsForTesting().contains(kTestVmName));
}

TEST_F(BruschettaServiceTest, DynamicPolicyUpdateVtpmFromEnabled) {
  vm_tools::concierge::VmStoppedSignal stop_signal;
  stop_signal.set_name(kTestVmName);

  service_->RegisterInPrefs(MakeBruschettaId(kTestVmName), kTestVmConfig);

  EnableByPolicy();
  service_->RegisterVmLaunch(kTestVmName, {.vtpm_enabled = true});
  ASSERT_TRUE(service_->GetRunningVmsForTesting().contains(kTestVmName));

  SetVtpmConfig(false, prefs::PolicyUpdateAction::NONE);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 0);
  SetVtpmConfig(false,
                prefs::PolicyUpdateAction::FORCE_SHUTDOWN_IF_MORE_RESTRICTED);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 1);
  SetVtpmConfig(false, prefs::PolicyUpdateAction::FORCE_SHUTDOWN_ALWAYS);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 2);
  SetVtpmConfig(true, prefs::PolicyUpdateAction::NONE);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 2);
  SetVtpmConfig(true,
                prefs::PolicyUpdateAction::FORCE_SHUTDOWN_IF_MORE_RESTRICTED);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 2);
  SetVtpmConfig(true, prefs::PolicyUpdateAction::FORCE_SHUTDOWN_ALWAYS);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 2);
}

TEST_F(BruschettaServiceTest, DynamicPolicyUpdateVtpmFromDisabled) {
  vm_tools::concierge::VmStoppedSignal stop_signal;
  stop_signal.set_name(kTestVmName);

  service_->RegisterInPrefs(MakeBruschettaId(kTestVmName), kTestVmConfig);

  EnableByPolicy();
  service_->RegisterVmLaunch(kTestVmName, {.vtpm_enabled = false});
  ASSERT_TRUE(service_->GetRunningVmsForTesting().contains(kTestVmName));

  SetVtpmConfig(false, prefs::PolicyUpdateAction::NONE);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 0);
  SetVtpmConfig(false,
                prefs::PolicyUpdateAction::FORCE_SHUTDOWN_IF_MORE_RESTRICTED);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 0);
  SetVtpmConfig(false, prefs::PolicyUpdateAction::FORCE_SHUTDOWN_ALWAYS);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 0);
  SetVtpmConfig(true, prefs::PolicyUpdateAction::NONE);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 0);
  SetVtpmConfig(true,
                prefs::PolicyUpdateAction::FORCE_SHUTDOWN_IF_MORE_RESTRICTED);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 0);
  SetVtpmConfig(true, prefs::PolicyUpdateAction::FORCE_SHUTDOWN_ALWAYS);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 1);
}

TEST_F(BruschettaServiceTest, VirtualMachinesAllowed) {
  vm_tools::concierge::VmStoppedSignal stop_signal;
  stop_signal.set_name(kTestVmName);

  service_->RegisterInPrefs(MakeBruschettaId(kTestVmName), kTestVmConfig);

  EnableByPolicy();
  service_->RegisterVmLaunch(kTestVmName, {});
  ASSERT_TRUE(service_->GetRunningVmsForTesting().contains(kTestVmName));

  profile_.ScopedCrosSettingsTestHelper()
      ->ReplaceDeviceSettingsProviderWithStub();
  profile_.ScopedCrosSettingsTestHelper()->GetStubbedProvider()->SetBoolean(
      ash::kVirtualMachinesAllowed, false);

  ASSERT_FALSE(virtual_machines::AreVirtualMachinesAllowedByPolicy());
  ASSERT_EQ(service_->GetLauncher(kTestVmName), nullptr);
  ASSERT_EQ(FakeConciergeClient()->stop_vm_call_count(), 1);
  base::RunLoop().RunUntilIdle();
  ASSERT_FALSE(service_->GetRunningVmsForTesting().contains(kTestVmName));
}

TEST_F(BruschettaServiceTest, RemoveVmSuccess) {
  base::RunLoop run_loop;
  bool result = false;
  service_->RemoveVm(MakeBruschettaId(kTestVmName),
                     base::BindLambdaForTesting([&](bool success) {
                       result = success;
                       run_loop.Quit();
                     }));
  run_loop.Run();
  ASSERT_TRUE(result);
}

TEST_F(BruschettaServiceTest, RemoveVmFailedToRemoveDisk) {
  vm_tools::concierge::DestroyDiskImageResponse response;
  response.set_status(
      ::vm_tools::concierge::DiskImageStatus::DISK_STATUS_FAILED);
  FakeConciergeClient()->set_destroy_disk_image_response(response);
  base::RunLoop run_loop;
  bool result = false;
  service_->RemoveVm(MakeBruschettaId(kTestVmName),
                     base::BindLambdaForTesting([&](bool success) {
                       result = success;
                       run_loop.Quit();
                     }));
  run_loop.Run();
  ASSERT_FALSE(result);
}

TEST_F(BruschettaServiceTest, RemoveVmFailedToRemoveDlc) {
  FakeDlcserviceClient()->set_uninstall_error("Error");
  base::RunLoop run_loop;
  bool result = false;
  service_->RemoveVm(MakeBruschettaId(kTestVmName),
                     base::BindLambdaForTesting([&](bool success) {
                       result = success;
                       run_loop.Quit();
                     }));
  run_loop.Run();
  ASSERT_FALSE(result);
}

}  // namespace bruschetta