chromium/chrome/browser/ash/plugin_vm/plugin_vm_test_helper.cc

// Copyright 2019 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/plugin_vm/plugin_vm_test_helper.h"

#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "base/system/sys_info.h"
#include "base/test/scoped_running_on_chromeos.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_features.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/scoped_user_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace plugin_vm {

namespace {

const char kDiskImageImportCommandUuid[] = "3922722bd7394acf85bf4d5a330d4a47";
const char kDomain[] = "example.com";
const char kDeviceId[] = "device_id";

// Arbitrary container value used for updating the GuestOsRegistryService.
const char kPluginVmContainer[] = "PluginVmContainer";

// For adding a fake shelf item without requiring opening an actual window.
class FakeShelfItemDelegate : public ash::ShelfItemDelegate {
 public:
  explicit FakeShelfItemDelegate(const ash::ShelfID& shelf_id)
      : ShelfItemDelegate(shelf_id) {}

  void ExecuteCommand(bool from_context_menu,
                      int64_t command_id,
                      int32_t event_flags,
                      int64_t display_id) override {}
  void Close() override {
    ChromeShelfController::instance()->ReplaceWithAppShortcutOrRemove(
        ash::ShelfID(kPluginVmShelfAppId));
  }
};

}  // namespace

void SetupConciergeForSuccessfulDiskImageImport(
    ash::FakeConciergeClient* fake_concierge_client_) {
  // Set immediate response for the ImportDiskImage call: will be that "image is
  // in progress":
  vm_tools::concierge::ImportDiskImageResponse import_disk_image_response;
  import_disk_image_response.set_status(
      vm_tools::concierge::DISK_STATUS_IN_PROGRESS);
  import_disk_image_response.set_command_uuid(kDiskImageImportCommandUuid);
  fake_concierge_client_->set_import_disk_image_response(
      import_disk_image_response);

  // Set a series of signals: one at 50% (in progress) and one at 100%
  // (created):
  std::vector<vm_tools::concierge::DiskImageStatusResponse> signals;
  vm_tools::concierge::DiskImageStatusResponse signal1;
  signal1.set_status(vm_tools::concierge::DISK_STATUS_IN_PROGRESS);
  signal1.set_progress(50);
  signal1.set_command_uuid(kDiskImageImportCommandUuid);
  vm_tools::concierge::DiskImageStatusResponse signal2;
  signal2.set_status(vm_tools::concierge::DISK_STATUS_CREATED);
  signal2.set_progress(100);
  signal2.set_command_uuid(kDiskImageImportCommandUuid);
  fake_concierge_client_->set_disk_image_status_signals({signal1, signal2});

  // Finally, set a success response for any eventual final call to
  // DiskImageStatus:
  vm_tools::concierge::DiskImageStatusResponse disk_image_status_response;
  disk_image_status_response.set_status(
      vm_tools::concierge::DISK_STATUS_CREATED);
  disk_image_status_response.set_command_uuid(kDiskImageImportCommandUuid);
  fake_concierge_client_->set_disk_image_status_response(
      disk_image_status_response);
}

void SetupConciergeForFailedDiskImageImport(
    ash::FakeConciergeClient* fake_concierge_client_,
    vm_tools::concierge::DiskImageStatus status) {
  // Set immediate response for the ImportDiskImage call: will be that "image is
  // in progress":
  vm_tools::concierge::ImportDiskImageResponse import_disk_image_response;
  import_disk_image_response.set_status(
      vm_tools::concierge::DISK_STATUS_IN_PROGRESS);
  import_disk_image_response.set_command_uuid(kDiskImageImportCommandUuid);
  fake_concierge_client_->set_import_disk_image_response(
      import_disk_image_response);

  // Set a series of signals: one at 50% (in progress) and one at 75%
  // (failed):
  std::vector<vm_tools::concierge::DiskImageStatusResponse> signals;
  vm_tools::concierge::DiskImageStatusResponse signal1;
  signal1.set_status(vm_tools::concierge::DISK_STATUS_IN_PROGRESS);
  signal1.set_progress(50);
  signal1.set_command_uuid(kDiskImageImportCommandUuid);
  vm_tools::concierge::DiskImageStatusResponse signal2;
  signal2.set_status(status);
  signal2.set_progress(75);
  signal2.set_command_uuid(kDiskImageImportCommandUuid);
  fake_concierge_client_->set_disk_image_status_signals({signal1, signal2});

  // Finally, set a failure response for any eventual final call to
  // DiskImageStatus:
  vm_tools::concierge::DiskImageStatusResponse disk_image_status_response;
  disk_image_status_response.set_status(status);
  disk_image_status_response.set_command_uuid(kDiskImageImportCommandUuid);
  fake_concierge_client_->set_disk_image_status_response(
      disk_image_status_response);
}

void SetupConciergeForCancelDiskImageOperation(
    ash::FakeConciergeClient* fake_concierge_client_,
    bool success) {
  vm_tools::concierge::CancelDiskImageResponse cancel_disk_image_response;
  cancel_disk_image_response.set_success(success);
  fake_concierge_client_->set_cancel_disk_image_response(
      cancel_disk_image_response);
}

PluginVmTestHelper::PluginVmTestHelper(TestingProfile* testing_profile)
    : testing_profile_(testing_profile) {
  testing_profile_->ScopedCrosSettingsTestHelper()
      ->ReplaceDeviceSettingsProviderWithStub();

  current_apps_.set_vm_name(kPluginVmName);
  current_apps_.set_container_name(kPluginVmContainer);
  current_apps_.set_vm_type(guest_os::VmType::PLUGIN_VM);
}

PluginVmTestHelper::~PluginVmTestHelper() = default;

void PluginVmTestHelper::SetPolicyRequirementsToAllowPluginVm() {
  testing_profile_->GetPrefs()->SetBoolean(plugin_vm::prefs::kPluginVmAllowed,
                                           true);
  testing_profile_->GetPrefs()->SetString(plugin_vm::prefs::kPluginVmUserId,
                                          "fake-id");
  testing_profile_->ScopedCrosSettingsTestHelper()->SetBoolean(
      ash::kPluginVmAllowed, true);
}

void PluginVmTestHelper::SetUserRequirementsToAllowPluginVm() {
  // User for the profile should be affiliated with the device.
  const AccountId account_id(AccountId::FromUserEmailGaiaId(
      testing_profile_->GetProfileUserName(), "id"));
  auto user_manager = std::make_unique<ash::FakeChromeUserManager>();
  auto* user = user_manager->AddUserWithAffiliationAndTypeAndProfile(
      account_id, true, user_manager::UserType::kRegular, testing_profile_);
  user_manager->UserLoggedIn(user->GetAccountId(), user->username_hash(),
                             /*browser_restart=*/false,
                             /*is_child=*/false);
  scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
      std::move(user_manager));
  running_on_chromeos_ =
      std::make_unique<base::test::ScopedRunningOnChromeOS>();
}

void PluginVmTestHelper::EnablePluginVmFeature() {
  scoped_feature_list_.InitAndEnableFeature(features::kPluginVm);
}

void PluginVmTestHelper::EnterpriseEnrollDevice() {
  testing_profile_->ScopedCrosSettingsTestHelper()
      ->InstallAttributes()
      ->SetCloudManaged(kDomain, kDeviceId);
}

void PluginVmTestHelper::AllowPluginVm() {
  ASSERT_FALSE(PluginVmFeatures::Get()->IsAllowed(testing_profile_));
  SetUserRequirementsToAllowPluginVm();
  EnablePluginVmFeature();
  EnterpriseEnrollDevice();
  SetPolicyRequirementsToAllowPluginVm();
  ASSERT_TRUE(PluginVmFeatures::Get()->IsAllowed(testing_profile_));
}

void PluginVmTestHelper::EnablePluginVm() {
  testing_profile_->GetPrefs()->SetBoolean(
      plugin_vm::prefs::kPluginVmImageExists, true);
}

void PluginVmTestHelper::OpenShelfItem() {
  ash::ShelfID shelf_id(kPluginVmShelfAppId);
  std::unique_ptr<ash::ShelfItemDelegate> delegate =
      std::make_unique<FakeShelfItemDelegate>(shelf_id);
  ChromeShelfController* shelf_controller = ChromeShelfController::instance();
  // Similar logic to AppServiceAppWindowShelfController, for handling pins
  // and spinners.
  if (shelf_controller->GetItem(shelf_id)) {
    shelf_controller->shelf_model()->ReplaceShelfItemDelegate(
        shelf_id, std::move(delegate));
    shelf_controller->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
  } else {
    shelf_controller->CreateAppItem(std::move(delegate), ash::STATUS_RUNNING,
                                    /*pinned=*/false);
  }
}

void PluginVmTestHelper::CloseShelfItem() {
  ChromeShelfController::instance()->Close(ash::ShelfID(kPluginVmShelfAppId));
}

void PluginVmTestHelper::AddApp(const vm_tools::apps::App& app) {
  for (int i = 0; i < current_apps_.apps_size(); ++i) {
    if (current_apps_.apps(i).desktop_file_id() == app.desktop_file_id()) {
      *current_apps_.mutable_apps(i) = app;
      UpdateRegistry();
      return;
    }
  }
  *current_apps_.add_apps() = app;
  UpdateRegistry();
}

// static
std::string PluginVmTestHelper::GenerateAppId(const std::string& app_name) {
  return guest_os::GuestOsRegistryService::GenerateAppId(
      app_name, /*vm_name=*/kPluginVmName,
      /*container_name=*/kPluginVmContainer);
}

void PluginVmTestHelper::UpdateRegistry() {
  guest_os::GuestOsRegistryServiceFactory::GetForProfile(testing_profile_)
      ->UpdateApplicationList(current_apps_);
}

}  // namespace plugin_vm