chromium/chrome/browser/ash/plugin_vm/plugin_vm_installer_unittest.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_installer.h"

#include <stdint.h>
#include <string.h>

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_drive_image_download_service.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_image_download_client.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_installer_factory.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_test_helper.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/ash/components/dbus/dlcservice/fake_dlcservice_client.h"
#include "chromeos/ash/components/dbus/spaced/fake_spaced_client.h"
#include "chromeos/ash/components/dbus/vm_plugin_dispatcher/vm_plugin_dispatcher_client.h"
#include "components/account_id/account_id.h"
#include "components/download/public/background_service/test/test_download_service.h"
#include "components/drive/service/dummy_drive_service.h"
#include "components/drive/service/fake_drive_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/common/api_error_codes.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace plugin_vm {

namespace {

using ::base::test::RunClosure;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::DoubleEq;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
using ::testing::StrictMock;

using FailureReason = PluginVmInstaller::FailureReason;
using InstallingState = PluginVmInstaller::InstallingState;

const char kProfileName[] = "p1";
const char kUrl[] = "http://example.com";
const char kDriveUrl[] = "https://drive.google.com/open?id=fakedriveid";
const char kDriveId[] = "fakedriveid";
const char kDriveUrl2[] = "https://drive.google.com/open?id=nonexistantdriveid";
const char kDriveContentType[] = "application/zip";
const char kPluginVmImageFile[] = "plugin_vm_image_file_1.zip";
const char kContent[] = "This is zipped content.";
const char kHash[] =
    "c80344fd4a0e9ee9f803f64edb3ea3ed8b11fe300869817e8fd50898d0663c35";
const char kHashUppercase[] =
    "C80344FD4A0E9EE9F803F64EDB3EA3ED8B11FE300869817E8FD50898D0663C35";
const char kHash2[] =
    "02f06421ae27144aacdc598aebcd345a5e2e634405e8578300173628fe1574bd";
// File size set in test_download_service.
const int64_t kDefaultRequiredFreeDiskSpaceGB = 20LL;
const int kRequiredFreeDiskSpaceGB = 40;
const int64_t kBytesPerGigabyte = 1024 * 1024 * 1024;

constexpr char kFailureReasonHistogram[] = "PluginVm.SetupFailureReason";

}  // namespace

class MockObserver : public PluginVmInstaller::Observer {
 public:
  MOCK_METHOD1(OnStateUpdated, void(InstallingState));
  MOCK_METHOD1(OnProgressUpdated, void(double));
  MOCK_METHOD2(OnDownloadProgressUpdated,
               void(uint64_t bytes_downloaded, int64_t content_length));
  MOCK_METHOD0(OnVmExists, void());
  MOCK_METHOD0(OnCreated, void());
  MOCK_METHOD0(OnImported, void());
  MOCK_METHOD1(OnError, void(FailureReason));
  MOCK_METHOD0(OnCancelFinished, void());
};

// We are inheriting from DummyDriveService instead of DriveServiceInterface
// here since we are only interested in a couple of methods and don't need to
// define the rest.
class SimpleFakeDriveService : public drive::DummyDriveService {
 public:
  using DownloadActionCallback = google_apis::DownloadActionCallback;
  using GetContentCallback = google_apis::GetContentCallback;
  using ProgressCallback = google_apis::ProgressCallback;

  void RunDownloadActionCallback(google_apis::ApiErrorCode error,
                                 const base::FilePath& temp_file) {
    std::move(download_action_callback_).Run(error, temp_file);
  }

  void RunGetContentCallback(google_apis::ApiErrorCode error,
                             std::unique_ptr<std::string> content) {
    get_content_callback_.Run(error, std::move(content),
                              !get_content_callback_called_);
    get_content_callback_called_ = true;
  }

  void RunProgressCallback(int64_t progress, int64_t total) {
    progress_callback_.Run(progress, total);
  }

  bool cancel_callback_called() const { return cancel_callback_called_; }

  // DriveServiceInterface override.
  google_apis::CancelCallbackOnce DownloadFile(
      const base::FilePath& /*cache_path*/,
      const std::string& /*resource_id*/,
      DownloadActionCallback download_action_callback,
      const GetContentCallback& get_content_callback,
      ProgressCallback progress_callback) override {
    download_action_callback_ = std::move(download_action_callback);
    get_content_callback_ = get_content_callback;
    progress_callback_ = std::move(progress_callback);

    // It is safe to use base::Unretained as this object will not get deleted
    // before the end of the test.
    return base::BindOnce(&SimpleFakeDriveService::CancelCallback,
                          base::Unretained(this));
  }

 private:
  void CancelCallback() { cancel_callback_called_ = true; }

  bool cancel_callback_called_{false};

  DownloadActionCallback download_action_callback_;
  GetContentCallback get_content_callback_;
  ProgressCallback progress_callback_;

  bool get_content_callback_called_{false};
};

class PluginVmInstallerTestBase : public testing::Test {
 public:
  PluginVmInstallerTestBase() = default;

  PluginVmInstallerTestBase(const PluginVmInstallerTestBase&) = delete;
  PluginVmInstallerTestBase& operator=(const PluginVmInstallerTestBase&) =
      delete;

  ~PluginVmInstallerTestBase() override = default;

 protected:
  void SetUp() override {
    ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
    ash::DebugDaemonClient::InitializeFake();
    ash::VmPluginDispatcherClient::InitializeFake();

    ASSERT_TRUE(profiles_dir_.CreateUniqueTempDir());
    CreateProfile();
    plugin_vm_test_helper_ =
        std::make_unique<PluginVmTestHelper>(profile_.get());
    plugin_vm_test_helper_->AllowPluginVm();
    // Sets new PluginVmImage pref for this test.
    SetPluginVmImagePref(kUrl, kHash);

    installer_ = PluginVmInstallerFactory::GetForProfile(profile_.get());
    observer_ = std::make_unique<StrictMock<MockObserver>>();
    installer_->SetObserver(observer_.get());
    installer_->SkipLicenseCheckForTesting();
    installer_->SetFreeDiskSpaceForTesting(std::numeric_limits<int64_t>::max());
    installer_->SetDownloadedImageForTesting(CreateZipFile());

    SetDefaultExpectations();

    fake_concierge_client_ = ash::FakeConciergeClient::Get();

    ash::FakeSpacedClient::InitializeFake();
  }

  void TearDown() override {
    observer_.reset();
    plugin_vm_test_helper_.reset();
    profile_.reset();
    observer_.reset();

    ash::VmPluginDispatcherClient::Shutdown();
    ash::DebugDaemonClient::Shutdown();
    ash::ConciergeClient::Shutdown();
    ash::FakeSpacedClient::Shutdown();
  }

  void SetPluginVmImagePref(std::string url, std::string hash) {
    ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kPluginVmImage);
    base::Value::Dict& plugin_vm_image = update.Get();
    plugin_vm_image.Set("url", url);
    plugin_vm_image.Set("hash", hash);
  }

  void SetRequiredFreeDiskSpaceGBPref(int required_free_disk_space) {
    profile_->GetPrefs()->SetInteger(prefs::kPluginVmRequiredFreeDiskSpaceGB,
                                     required_free_disk_space);
  }

  base::FilePath CreateZipFile() {
    base::FilePath zip_file_path =
        profile_->GetPath().AppendASCII(kPluginVmImageFile);
    base::WriteFile(zip_file_path, kContent);
    return zip_file_path;
  }

  void VerifyExpectations() {
    Mock::VerifyAndClearExpectations(observer_.get());
    SetDefaultExpectations();
  }

  // Set expectations for observer events up to and including |end_state|.
  void ExpectObserverEventsUntil(InstallingState end_state) {
    InstallingState states[] = {
        InstallingState::kCheckingLicense,
        InstallingState::kCheckingForExistingVm,
        InstallingState::kCheckingDiskSpace,
        InstallingState::kDownloadingDlc,
        InstallingState::kStartingDispatcher,
        InstallingState::kDownloadingImage,
        InstallingState::kImporting,
    };

    for (InstallingState state : states) {
      EXPECT_CALL(*observer_, OnStateUpdated(state));
      if (state == end_state) {
        return;
      }
    }

    NOTREACHED_IN_MIGRATION();
  }

  // Helper functions for starting and progressing the installer.

  void RunUntil(InstallingState state) {
    base::RunLoop run_loop;
    ON_CALL(*observer_, OnStateUpdated(state))
        .WillByDefault(RunClosure(run_loop.QuitClosure()));
    run_loop.Run();
  }

  void StartAndRunUntil(InstallingState state) {
    installer_->Start();
    RunUntil(state);
  }

  void StartAndRunToCompletion() {
    installer_->Start();
    task_environment_.RunUntilIdle();
  }

  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<PluginVmTestHelper> plugin_vm_test_helper_;
  raw_ptr<PluginVmInstaller, DanglingUntriaged> installer_;
  std::unique_ptr<MockObserver> observer_;

  // A pointer to a singleton object which is valid until
  // ConciergeClient::Shutdown() is called.
  raw_ptr<ash::FakeConciergeClient, DanglingUntriaged> fake_concierge_client_;
  ash::FakeDlcserviceClient fake_dlcservice_client_;

 private:
  void CreateProfile() {
    TestingProfile::Builder profile_builder;
    profile_builder.SetProfileName(kProfileName);
    profile_builder.SetPath(profiles_dir_.GetPath().AppendASCII(kProfileName));
    std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service =
        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
    RegisterUserProfilePrefs(pref_service->registry());
    profile_builder.SetPrefService(std::move(pref_service));
    profile_ = profile_builder.Build();
  }

  void SetDefaultExpectations() {
    // Suppress progress updates.
    EXPECT_CALL(*observer_, OnProgressUpdated(_)).Times(AnyNumber());
  }

  base::ScopedTempDir profiles_dir_;
};

class PluginVmInstallerDownloadServiceTest : public PluginVmInstallerTestBase {
 public:
  PluginVmInstallerDownloadServiceTest() = default;

  PluginVmInstallerDownloadServiceTest(
      const PluginVmInstallerDownloadServiceTest&) = delete;
  PluginVmInstallerDownloadServiceTest& operator=(
      const PluginVmInstallerDownloadServiceTest&) = delete;

  ~PluginVmInstallerDownloadServiceTest() override = default;

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

    download_service_ = std::make_unique<download::test::TestDownloadService>();
    download_service_->SetIsReady(true);
    download_service_->SetHash256(kHash);
    client_ = std::make_unique<PluginVmImageDownloadClient>(profile_.get());
    download_service_->set_client(client_.get());
    installer_->SetDownloadServiceForTesting(download_service_.get());
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

  void TearDown() override {
    histogram_tester_.reset();
    download_service_.reset();
    client_.reset();

    PluginVmInstallerTestBase::TearDown();
  }

  std::unique_ptr<download::test::TestDownloadService> download_service_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;

 private:
  std::unique_ptr<PluginVmImageDownloadClient> client_;
};

class PluginVmInstallerDriveTest : public PluginVmInstallerTestBase {
 public:
  PluginVmInstallerDriveTest() = default;

  PluginVmInstallerDriveTest(const PluginVmInstallerDriveTest&) = delete;
  PluginVmInstallerDriveTest& operator=(const PluginVmInstallerDriveTest&) =
      delete;

  ~PluginVmInstallerDriveTest() override = default;

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

    google_apis::ApiErrorCode error = google_apis::OTHER_ERROR;
    std::unique_ptr<google_apis::FileResource> entry;
    auto fake_drive_service = std::make_unique<drive::FakeDriveService>();
    // We will need to access this object for some tests in the future.
    fake_drive_service_ = fake_drive_service.get();
    fake_drive_service->AddNewFileWithResourceId(
        kDriveId, kDriveContentType, kContent,
        "",  // parent_resource_id
        kPluginVmImageFile,
        true,  // shared_with_me
        base::BindLambdaForTesting(
            [&](google_apis::ApiErrorCode drive_error,
                std::unique_ptr<google_apis::FileResource> drive_entry) {
              error = drive_error;
              entry = std::move(drive_entry);
            }));
    base::RunLoop().RunUntilIdle();
    ASSERT_EQ(google_apis::HTTP_CREATED, error);
    ASSERT_TRUE(entry);

    auto drive_download_service =
        std::make_unique<PluginVmDriveImageDownloadService>(installer_,
                                                            profile_.get());
    // We will need to access this object for some tests in the future.
    drive_download_service_ = drive_download_service.get();

    base::ScopedTempDir temp_dir;
    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    drive_download_service->SetDownloadDirectoryForTesting(temp_dir.Take());
    drive_download_service->SetDriveServiceForTesting(
        std::move(fake_drive_service));

    installer_->SetDriveDownloadServiceForTesting(
        std::move(drive_download_service));
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

  void TearDown() override {
    histogram_tester_.reset();

    PluginVmInstallerTestBase::TearDown();
  }

  SimpleFakeDriveService* SetUpSimpleFakeDriveService() {
    auto fake_drive_service = std::make_unique<SimpleFakeDriveService>();
    SimpleFakeDriveService* fake_drive_service_ptr = fake_drive_service.get();

    drive_download_service_->SetDriveServiceForTesting(
        std::move(fake_drive_service));

    return fake_drive_service_ptr;
  }

  raw_ptr<PluginVmDriveImageDownloadService, DanglingUntriaged>
      drive_download_service_;
  raw_ptr<drive::FakeDriveService, DanglingUntriaged> fake_drive_service_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
};

TEST_F(PluginVmInstallerDownloadServiceTest, ProgressUpdates) {
  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);

  // Override default expectation so unexpected calls will fail the test.
  EXPECT_CALL(*observer_, OnProgressUpdated(_)).Times(0);

  EXPECT_CALL(*observer_, OnProgressUpdated(DoubleEq(0.01)));
  EXPECT_CALL(*observer_, OnProgressUpdated(DoubleEq(0.45)));
  EXPECT_CALL(*observer_, OnProgressUpdated(DoubleEq(0.725)));

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnImported());
  StartAndRunToCompletion();
}

TEST_F(PluginVmInstallerDownloadServiceTest, InsufficientDisk) {
  installer_->SetFreeDiskSpaceForTesting(
      kDefaultRequiredFreeDiskSpaceGB * kBytesPerGigabyte - 1);
  ExpectObserverEventsUntil(InstallingState::kCheckingDiskSpace);
  EXPECT_CALL(*observer_, OnError(FailureReason::INSUFFICIENT_DISK_SPACE));
  StartAndRunToCompletion();
  histogram_tester_->ExpectUniqueSample(
      kFailureReasonHistogram, FailureReason::INSUFFICIENT_DISK_SPACE, 1);
  histogram_tester_->ExpectUniqueSample(kPluginVmSetupResultHistogram,
                                        PluginVmSetupResult::kError, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, InsufficientDiskWhenSetInPolicy) {
  SetRequiredFreeDiskSpaceGBPref(kRequiredFreeDiskSpaceGB);
  int64_t requred_free_disk_space_bytes =
      kRequiredFreeDiskSpaceGB * kBytesPerGigabyte;
  installer_->SetFreeDiskSpaceForTesting(requred_free_disk_space_bytes - 1);
  ExpectObserverEventsUntil(InstallingState::kCheckingDiskSpace);
  EXPECT_CALL(*observer_, OnError(FailureReason::INSUFFICIENT_DISK_SPACE));
  StartAndRunToCompletion();
  histogram_tester_->ExpectUniqueSample(
      kFailureReasonHistogram, FailureReason::INSUFFICIENT_DISK_SPACE, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, VmExists) {
  // This flow works even if the image url is not set.
  SetPluginVmImagePref("", kHash);

  vm_tools::concierge::ListVmDisksResponse list_vm_disks_response;
  list_vm_disks_response.set_success(true);
  auto* image = list_vm_disks_response.add_images();
  image->set_name(kPluginVmName);
  image->set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_PLUGINVM);
  fake_concierge_client_->set_list_vm_disks_response(list_vm_disks_response);

  ExpectObserverEventsUntil(InstallingState::kCheckingForExistingVm);
  EXPECT_CALL(*observer_, OnVmExists());
  StartAndRunToCompletion();

  histogram_tester_->ExpectUniqueSample(
      kPluginVmSetupResultHistogram, PluginVmSetupResult::kVmAlreadyExists, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, InvalidVmExists) {
  // This flow works even if the image url is not set.
  SetPluginVmImagePref("", kHash);

  vm_tools::concierge::ListVmDisksResponse list_vm_disks_response;
  list_vm_disks_response.set_success(true);
  auto* image = list_vm_disks_response.add_images();
  // Pretend we have a VM with the right name in a wrong location.
  image->set_name(kPluginVmName);
  image->set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT);
  fake_concierge_client_->set_list_vm_disks_response(list_vm_disks_response);

  ExpectObserverEventsUntil(InstallingState::kCheckingForExistingVm);
  EXPECT_CALL(*observer_, OnError(FailureReason::EXISTING_IMAGE_INVALID));
  StartAndRunToCompletion();

  histogram_tester_->ExpectUniqueSample(
      kFailureReasonHistogram, FailureReason::EXISTING_IMAGE_INVALID, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, CancelOnVmExistsCheck) {
  base::RunLoop run_loop;
  ExpectObserverEventsUntil(InstallingState::kCheckingForExistingVm);
  ON_CALL(*observer_, OnStateUpdated(InstallingState::kCheckingForExistingVm))
      .WillByDefault(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(*observer_, OnCancelFinished());

  installer_->Start();
  run_loop.Run();
  installer_->Cancel();
  task_environment_.RunUntilIdle();

  histogram_tester_->ExpectUniqueSample(
      kPluginVmSetupResultHistogram,
      PluginVmSetupResult::kUserCancelledCheckingForExistingVm, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, DownloadPluginVmImageParamsTest) {
  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnImported());

  StartAndRunUntil(InstallingState::kDownloadingImage);

  std::string guid = installer_->GetCurrentDownloadGuid();
  const std::optional<download::DownloadParams>& params =
      download_service_->GetDownload(guid);
  ASSERT_TRUE(params.has_value());
  EXPECT_EQ(guid, params->guid);
  EXPECT_EQ(download::DownloadClient::PLUGIN_VM_IMAGE, params->client);
  EXPECT_EQ(GURL(kUrl), params->request_params.url);

  // Finishing image processing.
  task_environment_.RunUntilIdle();
}

TEST_F(PluginVmInstallerDownloadServiceTest, OnlyOneImageIsProcessedTest) {
  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnImported());

  StartAndRunUntil(InstallingState::kDownloadingImage);

  EXPECT_TRUE(installer_->IsProcessing());

  RunUntil(InstallingState::kImporting);

  EXPECT_TRUE(installer_->IsProcessing());

  task_environment_.RunUntilIdle();

  EXPECT_FALSE(installer_->IsProcessing());

  histogram_tester_->ExpectUniqueSample(kPluginVmSetupResultHistogram,
                                        PluginVmSetupResult::kSuccess, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest,
       CanProceedWithANewImageWhenSucceededTest) {
  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnImported());
  StartAndRunToCompletion();
  VerifyExpectations();

  EXPECT_FALSE(installer_->IsProcessing());
  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnImported());

  // As it is deleted after successful importing.
  installer_->SetDownloadedImageForTesting(CreateZipFile());
  StartAndRunToCompletion();

  histogram_tester_->ExpectUniqueSample(kPluginVmSetupResultHistogram,
                                        PluginVmSetupResult::kSuccess, 2);
}

TEST_F(PluginVmInstallerDownloadServiceTest,
       CanProceedWithANewImageWhenFailedTest) {
  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);

  ExpectObserverEventsUntil(InstallingState::kDownloadingImage);
  EXPECT_CALL(*observer_, OnError(FailureReason::DOWNLOAD_FAILED_ABORTED));

  StartAndRunUntil(InstallingState::kDownloadingImage);
  std::string guid = installer_->GetCurrentDownloadGuid();
  download_service_->SetFailedDownload(guid, false);
  task_environment_.RunUntilIdle();
  VerifyExpectations();

  EXPECT_FALSE(installer_->IsProcessing());

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnImported());

  StartAndRunToCompletion();

  histogram_tester_->ExpectBucketCount(kPluginVmSetupResultHistogram,
                                       PluginVmSetupResult::kError, 1);
  histogram_tester_->ExpectBucketCount(kPluginVmSetupResultHistogram,
                                       PluginVmSetupResult::kSuccess, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, CancelledDownloadTest) {
  ExpectObserverEventsUntil(InstallingState::kDownloadingImage);
  EXPECT_CALL(*observer_, OnCancelFinished());

  StartAndRunUntil(InstallingState::kDownloadingImage);
  installer_->Cancel();
  task_environment_.RunUntilIdle();

  histogram_tester_->ExpectTotalCount(kFailureReasonHistogram, 0);
  histogram_tester_->ExpectUniqueSample(
      kPluginVmSetupResultHistogram,
      PluginVmSetupResult::kUserCancelledDownloadingPluginVmImage, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, ImportNonExistingImageTest) {
  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnError(FailureReason::COULD_NOT_OPEN_IMAGE));

  installer_->SetDownloadedImageForTesting(base::FilePath());
  StartAndRunToCompletion();
}

TEST_F(PluginVmInstallerDownloadServiceTest, ImportFailedOutOfSpaceTest) {
  SetupConciergeForFailedDiskImageImport(
      fake_concierge_client_,
      vm_tools::concierge::DISK_STATUS_NOT_ENOUGH_SPACE);

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnError(FailureReason::OUT_OF_DISK_SPACE));
  StartAndRunToCompletion();

  histogram_tester_->ExpectBucketCount(kPluginVmSetupResultHistogram,
                                       PluginVmSetupResult::kError, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, CancelledImportTest) {
  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
  SetupConciergeForCancelDiskImageOperation(fake_concierge_client_,
                                            true /* success */);

  ExpectObserverEventsUntil(InstallingState::kImporting);
  StartAndRunUntil(InstallingState::kImporting);

  EXPECT_CALL(*observer_, OnCancelFinished());
  installer_->Cancel();
  task_environment_.RunUntilIdle();

  histogram_tester_->ExpectUniqueSample(
      kPluginVmSetupResultHistogram,
      PluginVmSetupResult::kUserCancelledImportingPluginVmImage, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, EmptyPluginVmImageUrlTest) {
  SetPluginVmImagePref("", kHash);
  ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
  EXPECT_CALL(*observer_, OnError(FailureReason::INVALID_IMAGE_URL));
  StartAndRunToCompletion();

  histogram_tester_->ExpectUniqueSample(kFailureReasonHistogram,
                                        FailureReason::INVALID_IMAGE_URL, 1);
}

TEST_F(PluginVmInstallerDownloadServiceTest, VerifyDownloadTest) {
  EXPECT_FALSE(installer_->VerifyDownload(kHash2));
  EXPECT_TRUE(installer_->VerifyDownload(kHashUppercase));
  EXPECT_TRUE(installer_->VerifyDownload(kHash));
  EXPECT_FALSE(installer_->VerifyDownload(std::string()));
}

TEST_F(PluginVmInstallerDownloadServiceTest, CannotStartIfPluginVmIsDisabled) {
  profile_->ScopedCrosSettingsTestHelper()->SetBoolean(ash::kPluginVmAllowed,
                                                       false);
  EXPECT_EQ(FailureReason::NOT_ALLOWED, installer_->Start());
  task_environment_.RunUntilIdle();
}

TEST_F(PluginVmInstallerDriveTest, InvalidDriveUrlTest) {
  SetPluginVmImagePref(kDriveUrl2, kHash);

  ExpectObserverEventsUntil(InstallingState::kDownloadingImage);
  EXPECT_CALL(*observer_, OnError(FailureReason::INVALID_IMAGE_URL));
  StartAndRunToCompletion();
}

TEST_F(PluginVmInstallerDriveTest, NoConnectionDriveTest) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  fake_drive_service_->set_offline(true);

  ExpectObserverEventsUntil(InstallingState::kDownloadingImage);
  EXPECT_CALL(*observer_, OnError(FailureReason::DOWNLOAD_FAILED_NETWORK));
  StartAndRunToCompletion();
}

TEST_F(PluginVmInstallerDriveTest, WrongHashDriveTest) {
  SetPluginVmImagePref(kDriveUrl, kHash2);

  ExpectObserverEventsUntil(InstallingState::kDownloadingImage);
  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(_, _)).Times(2);
  EXPECT_CALL(*observer_, OnError(FailureReason::HASH_MISMATCH));

  StartAndRunToCompletion();
}

TEST_F(PluginVmInstallerDriveTest, DriveDownloadFailedAfterStartingTest) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  SimpleFakeDriveService* fake_drive_service = SetUpSimpleFakeDriveService();

  ExpectObserverEventsUntil(InstallingState::kDownloadingImage);
  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(5, 100));
  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(10, 100));
  EXPECT_CALL(*observer_, OnError(FailureReason::DOWNLOAD_FAILED_NETWORK));

  StartAndRunToCompletion();

  fake_drive_service->RunGetContentCallback(
      google_apis::HTTP_SUCCESS, std::make_unique<std::string>("Part1"));
  fake_drive_service->RunProgressCallback(5, 100);
  fake_drive_service->RunGetContentCallback(
      google_apis::HTTP_SUCCESS, std::make_unique<std::string>("Part2"));
  fake_drive_service->RunProgressCallback(10, 100);
  fake_drive_service->RunGetContentCallback(google_apis::NO_CONNECTION,
                                            std::unique_ptr<std::string>());
}

TEST_F(PluginVmInstallerDriveTest, CancelledDriveDownloadTest) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  SimpleFakeDriveService* fake_drive_service = SetUpSimpleFakeDriveService();

  ExpectObserverEventsUntil(InstallingState::kDownloadingImage);
  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(5, 100));
  EXPECT_CALL(*observer_, OnCancelFinished());

  StartAndRunToCompletion();

  fake_drive_service->RunGetContentCallback(
      google_apis::HTTP_SUCCESS, std::make_unique<std::string>("Part1"));
  fake_drive_service->RunProgressCallback(5, 100);
  installer_->Cancel();
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(fake_drive_service->cancel_callback_called());
}

TEST_F(PluginVmInstallerDriveTest, SuccessfulDriveDownloadTest) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  fake_dlcservice_client_.set_install_error(dlcservice::kErrorNone);

  ExpectObserverEventsUntil(InstallingState::kImporting);
  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(_, std::strlen(kContent)))
      .Times(AtLeast(1));
  EXPECT_CALL(*observer_, OnError(_));

  StartAndRunToCompletion();
  histogram_tester_->ExpectUniqueSample(kPluginVmDlcUseResultHistogram,
                                        PluginVmDlcUseResult::kDlcSuccess, 1);
}

TEST_F(PluginVmInstallerDriveTest, InstallingPluginVmDlcNeedReboot) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  fake_dlcservice_client_.set_install_error(dlcservice::kErrorNeedReboot);

  ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_NEED_REBOOT));

  StartAndRunToCompletion();
  histogram_tester_->ExpectUniqueSample(
      kPluginVmDlcUseResultHistogram, PluginVmDlcUseResult::kNeedRebootDlcError,
      1);
}

TEST_F(PluginVmInstallerDriveTest, InstallingPluginVmDlcNeedSpace) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  fake_dlcservice_client_.set_install_error(dlcservice::kErrorAllocation);

  ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_NEED_SPACE));

  StartAndRunToCompletion();
  histogram_tester_->ExpectUniqueSample(
      kPluginVmDlcUseResultHistogram, PluginVmDlcUseResult::kNeedSpaceDlcError,
      1);
}

TEST_F(PluginVmInstallerDriveTest, InstallingPluginVmDlcWhenUnsupported) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  fake_dlcservice_client_.set_install_error(dlcservice::kErrorInvalidDlc);

  ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_UNSUPPORTED));

  StartAndRunToCompletion();
  histogram_tester_->ExpectUniqueSample(kPluginVmDlcUseResultHistogram,
                                        PluginVmDlcUseResult::kInvalidDlcError,
                                        1);
}

TEST_F(PluginVmInstallerDriveTest, InstallingPluginVmDlcWhenNoImageFound) {
  SetPluginVmImagePref(kDriveUrl, kHash);
  fake_dlcservice_client_.set_install_error(dlcservice::kErrorNoImageFound);

  ExpectObserverEventsUntil(InstallingState::kDownloadingDlc);
  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_INTERNAL));

  StartAndRunToCompletion();
  histogram_tester_->ExpectUniqueSample(
      kPluginVmDlcUseResultHistogram,
      PluginVmDlcUseResult::kNoImageFoundDlcError, 1);
}

}  // namespace plugin_vm