// 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/crostini/crostini_package_service.h"
#include <memory>
#include <string>
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/crostini/crostini_simple_types.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/file_manager/path_util.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/notifications/notification_display_service_factory.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/chunneld/chunneld_client.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/cicerone/fake_cicerone_client.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/seneschal/fake_seneschal_client.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "chromeos/ash/components/dbus/vm_applications/apps.pb.h"
#include "content/public/test/browser_task_environment.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
namespace crostini {
namespace {
using ::ash::FakeCiceroneClient;
using ::ash::FakeConciergeClient;
using ::ash::FakeSeneschalClient;
using ::base::test::TestFuture;
using ::chromeos::DBusMethodCallback;
using ::testing::_;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::MakeMatcher;
using ::testing::Matcher;
using ::testing::MatcherInterface;
using ::testing::MatchResultListener;
using ::testing::UnorderedElementsAre;
using ::vm_tools::cicerone::InstallLinuxPackageProgressSignal;
using ::vm_tools::cicerone::InstallLinuxPackageRequest;
using ::vm_tools::cicerone::InstallLinuxPackageResponse;
using ::vm_tools::cicerone::LinuxPackageInfoRequest;
using ::vm_tools::cicerone::LinuxPackageInfoResponse;
using ::vm_tools::cicerone::PendingAppListUpdatesSignal;
using ::vm_tools::cicerone::UninstallPackageOwningFileRequest;
using ::vm_tools::cicerone::UninstallPackageOwningFileResponse;
using ::vm_tools::cicerone::UninstallPackageProgressSignal;
using ::vm_tools::seneschal::SharePathResponse;
// IDs, etc of apps that are always registered during tests.
// These are on the default VM / default container.
constexpr char kDefaultAppFileId[] = "default_file_id";
constexpr char kDefaultAppName[] = "The Default";
constexpr char16_t kDefaultAppName16[] = u"The Default";
constexpr char kSecondAppFileId[] = "second_file_id";
constexpr char kSecondAppName[] = "Another Fine App";
constexpr char16_t kSecondAppName16[] = u"Another Fine App";
constexpr char kThirdAppFileId[] = "third_file_id";
constexpr char kThirdAppName[] = "Yet Another App";
constexpr char16_t kThirdAppName16[] = u"Yet Another App";
// Different VM name, but container name is the default.
constexpr char kDifferentVmAppFileId[] = "different_vm_app";
constexpr char kDifferentVmAppName[] = "I'm in a VM!";
constexpr char16_t kDifferentVmAppName16[] = u"I'm in a VM!";
constexpr char kDifferentVmApp2FileId[] = "different_vm_app_2";
constexpr char kDifferentVmApp2Name[] = "I'm in a VM also";
constexpr char16_t kDifferentVmApp2Name16[] = u"I'm in a VM also";
constexpr char kDifferentVmVmName[] = "second_vm_name";
// Default VM name, but container name is different.
constexpr char kDifferentContainerAppFileId[] = "different_container_app";
constexpr char kDifferentContainerAppName[] =
"Just Over The Container Boundary";
constexpr char16_t kDifferentContainerAppName16[] =
u"Just Over The Container Boundary";
constexpr char kDifferentContainerApp2FileId[] = "different_container_app_2";
constexpr char kDifferentContainerApp2Name[] = "Severe Lack of Containers";
constexpr char16_t kDifferentContainerApp2Name16[] =
u"Severe Lack of Containers";
constexpr char kDifferentContainerContainerName[] = "second_container_name";
constexpr char kPackageFilePath[] = "/tmp/nethack.deb";
constexpr char kPackageFileContainerPath[] =
"/mnt/chromeos/MyFiles/tmp/nethack.deb";
// Run until |fake_cicerone_client_|'s UninstallPackageOwningFile is called.
// |request| and |callback| are filled in with the parameters to
// CiceroneClient::UninstallPackageOwningFile.
void RunUntilUninstallRequestMade(
FakeCiceroneClient* fake_cicerone_client,
UninstallPackageOwningFileRequest* request,
DBusMethodCallback<UninstallPackageOwningFileResponse>* callback) {
TestFuture<const UninstallPackageOwningFileRequest&,
DBusMethodCallback<UninstallPackageOwningFileResponse>>
result_future;
fake_cicerone_client->SetOnUninstallPackageOwningFileCallback(
result_future.GetRepeatingCallback());
auto result = result_future.Take();
*request = std::get<0>(result);
if (callback != nullptr) {
*callback = std::move(std::get<1>(result));
}
// Callback isn't valid after end of function.
fake_cicerone_client->SetOnUninstallPackageOwningFileCallback(
base::NullCallback());
}
class CrostiniPackageServiceTest : public testing::Test {
public:
CrostiniPackageServiceTest()
: kDefaultAppId(CrostiniTestHelper::GenerateAppId(kDefaultAppFileId)),
kSecondAppId(CrostiniTestHelper::GenerateAppId(kSecondAppFileId)),
kThirdAppId(CrostiniTestHelper::GenerateAppId(kThirdAppFileId)),
kDifferentVmAppId(
CrostiniTestHelper::GenerateAppId(kDifferentVmAppFileId,
kDifferentVmVmName,
kCrostiniDefaultContainerName)),
kDifferentVmApp2Id(
CrostiniTestHelper::GenerateAppId(kDifferentVmApp2FileId,
kDifferentVmVmName,
kCrostiniDefaultContainerName)),
kDifferentContainerAppId(CrostiniTestHelper::GenerateAppId(
kDifferentContainerAppFileId,
kCrostiniDefaultVmName,
kDifferentContainerContainerName)),
kDifferentContainerApp2Id(CrostiniTestHelper::GenerateAppId(
kDifferentContainerApp2FileId,
kCrostiniDefaultVmName,
kDifferentContainerContainerName)) {}
void SetUp() override {
ash::ChunneldClient::InitializeFake();
ash::CiceroneClient::InitializeFake();
ash::ConciergeClient::InitializeFake();
ash::SeneschalClient::InitializeFake();
fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
ASSERT_TRUE(fake_cicerone_client_);
fake_seneschal_client_ = FakeSeneschalClient::Get();
ASSERT_TRUE(fake_seneschal_client_);
task_environment_ = std::make_unique<content::BrowserTaskEnvironment>(
base::test::TaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC,
content::BrowserTaskEnvironment::REAL_IO_THREAD);
profile_ = std::make_unique<TestingProfile>(
base::FilePath("/home/chronos/u-0123456789abcdef"));
crostini_test_helper_ =
std::make_unique<CrostiniTestHelper>(profile_.get());
notification_display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(profile_.get());
notification_display_service_ =
static_cast<StubNotificationDisplayService*>(
NotificationDisplayServiceFactory::GetForProfile(profile_.get()));
ASSERT_TRUE(notification_display_service_);
service_ = std::make_unique<CrostiniPackageService>(profile_.get());
storage::ExternalMountPoints* mount_points =
storage::ExternalMountPoints::GetSystemInstance();
ASSERT_TRUE(mount_points);
std::string mount_point_name =
file_manager::util::GetDownloadsMountPointName(profile_.get());
mount_points->RegisterFileSystem(
mount_point_name, storage::kFileSystemTypeLocal,
storage::FileSystemMountOption(),
file_manager::util::GetDownloadsFolderForProfile(profile_.get()));
package_file_url_ = mount_points->CreateExternalFileSystemURL(
blink::StorageKey(), mount_point_name,
base::FilePath(kPackageFilePath));
auto* crostini_manager = CrostiniManager::GetForProfile(profile_.get());
ASSERT_TRUE(crostini_manager);
crostini_manager->set_skip_restart_for_testing();
crostini_manager->AddRunningVmForTesting(kDifferentVmVmName);
CreateDefaultAppRegistration();
CreateSecondAppRegistration();
CreateThirdAppRegistration();
CreateDifferentVmAppRegistration();
CreateDifferentContainerAppRegistration();
}
void TearDown() override {
storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
file_manager::util::GetDownloadsMountPointName(profile_.get()));
service_.reset();
notification_display_service_tester_.reset();
crostini_test_helper_.reset();
profile_.reset();
task_environment_.reset();
ash::SeneschalClient::Shutdown();
ash::ConciergeClient::Shutdown();
ash::CiceroneClient::Shutdown();
ash::ChunneldClient::Shutdown();
}
void InstallLinuxPackageAndWait() {
TestFuture<CrostiniResult> ready_waiter;
service_->QueueInstallLinuxPackage(DefaultContainerId(), package_file_url_,
ready_waiter.GetCallback());
ASSERT_TRUE(ready_waiter.Wait());
}
LinuxPackageInfo GetLinuxPackageInfo() {
TestFuture<const LinuxPackageInfo&> result_future;
service_->GetLinuxPackageInfo(kDifferentContainerId, package_file_url_,
result_future.GetCallback());
return result_future.Get();
}
protected:
const std::string kDefaultAppId; // App_id for app with kDefaultAppFileId.
const std::string kSecondAppId; // App_id for app with kSecondAppFileId.
const std::string kThirdAppId; // App_id for app with kThirdAppFileId.
const std::string kDifferentVmAppId; // App_id for app with
// kDifferentVmAppFileId.
const std::string kDifferentVmApp2Id; // App_id for app with
// kDifferentVmApp2FileId.
const std::string kDifferentContainerAppId; // App_id for app with
// kDifferentContainerAppFileId.
const std::string kDifferentContainerApp2Id; // App_id for app with
// kDifferentContainerApp2FileId
const guest_os::GuestId kDifferentContainerId =
guest_os::GuestId(kCrostiniDefaultVmType,
kDifferentVmVmName,
kDifferentContainerContainerName);
storage::FileSystemURL package_file_url_;
UninstallPackageProgressSignal MakeUninstallSignal(
const UninstallPackageOwningFileRequest& request) {
UninstallPackageProgressSignal signal;
signal.set_vm_name(request.vm_name());
signal.set_container_name(request.container_name());
signal.set_owner_id(request.owner_id());
return signal;
}
InstallLinuxPackageProgressSignal MakeInstallSignal(
const InstallLinuxPackageRequest& request) {
InstallLinuxPackageProgressSignal signal;
signal.set_vm_name(request.vm_name());
signal.set_container_name(request.container_name());
signal.set_owner_id(request.owner_id());
return signal;
}
void SendAppListUpdateSignal(const guest_os::GuestId& container_id,
int count) {
PendingAppListUpdatesSignal signal;
signal.set_vm_name(container_id.vm_name);
signal.set_container_name(container_id.container_name);
signal.set_count(count);
fake_cicerone_client_->NotifyPendingAppListUpdates(signal);
}
// Closes the notification as if the user had clicked 'close'.
void CloseNotification(const message_center::Notification& notification) {
notification_display_service_->RemoveNotification(
NotificationHandler::Type::TRANSIENT, notification.id(),
true /*by_user*/, false /*silent*/);
}
// Start an uninstall and then sent a single uninstall signal with the given
// status and (optional) progress. If |request_out| is not nullptr, the
// request sent by the service will be copied to |*request_out|.
void StartAndSignalUninstall(
UninstallPackageProgressSignal::Status signal_status,
int progress_percent = 0,
const char* const expected_desktop_file_id = kDefaultAppFileId,
UninstallPackageOwningFileRequest* request_out = nullptr) {
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
EXPECT_EQ(request.desktop_file_id(), expected_desktop_file_id);
// It's illegal to send a signal unless the response was STARTED. So assume
// that any test calling this function wants us to give a STARTED response.
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback).Run(response);
UninstallPackageProgressSignal signal = MakeUninstallSignal(request);
signal.set_status(signal_status);
switch (signal_status) {
case UninstallPackageProgressSignal::UNINSTALLING:
signal.set_progress_percent(progress_percent);
break;
case UninstallPackageProgressSignal::FAILED:
signal.set_failure_details("Oh no not again");
break;
case UninstallPackageProgressSignal::SUCCEEDED:
break;
default:
NOTREACHED_IN_MIGRATION();
}
fake_cicerone_client_->UninstallPackageProgress(signal);
if (request_out != nullptr) {
*request_out = request;
}
}
// Start an install and then sent a single install signal with the given
// status and (optional) progress.
void StartAndSignalInstall(
InstallLinuxPackageProgressSignal::Status signal_status,
int progress_percent = 0) {
InstallLinuxPackageProgressSignal signal = MakeInstallSignal(
fake_cicerone_client_->get_most_recent_install_linux_package_request());
signal.set_status(signal_status);
switch (signal_status) {
case InstallLinuxPackageProgressSignal::DOWNLOADING:
case InstallLinuxPackageProgressSignal::INSTALLING:
signal.set_progress_percent(progress_percent);
break;
case InstallLinuxPackageProgressSignal::FAILED:
signal.set_failure_details("Wouldn't be prudent");
break;
case InstallLinuxPackageProgressSignal::SUCCEEDED:
break;
default:
NOTREACHED_IN_MIGRATION();
}
fake_cicerone_client_->InstallLinuxPackageProgress(signal);
}
raw_ptr<FakeCiceroneClient, DanglingUntriaged> fake_cicerone_client_ =
nullptr;
raw_ptr<FakeSeneschalClient, DanglingUntriaged> fake_seneschal_client_ =
nullptr;
std::unique_ptr<content::BrowserTaskEnvironment> task_environment_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<CrostiniTestHelper> crostini_test_helper_;
std::unique_ptr<NotificationDisplayServiceTester>
notification_display_service_tester_;
raw_ptr<StubNotificationDisplayService, DanglingUntriaged>
notification_display_service_;
std::unique_ptr<CrostiniPackageService> service_;
private:
// Helper to set up an App proto with a single name.
vm_tools::apps::App BasicApp(const std::string& desktop_file_id,
const std::string& name,
const std::string& package_id) {
vm_tools::apps::App app;
app.set_desktop_file_id(desktop_file_id);
app.mutable_name()->add_values()->set_value(name);
app.set_no_display(false);
app.set_package_id(package_id);
return app;
}
// Create a registration in GuestOsRegistryService for an app with app_id
// kDefaultAppId and desktop file ID kDefaultAppFileId.
void CreateDefaultAppRegistration() {
auto app = BasicApp(kDefaultAppFileId, kDefaultAppName, "123-thing");
crostini_test_helper_->AddApp(app);
}
// Create a registration in GuestOsRegistryService for an app with app_id
// kSecondAppId and desktop file ID kSecondAppFileId.
void CreateSecondAppRegistration() {
auto app = BasicApp(kSecondAppFileId, kSecondAppName, "abc-another");
crostini_test_helper_->AddApp(app);
}
// Create a registration in GuestOsRegistryService for an app with app_id
// kThirdAppId and desktop file ID kThirdAppFileId.
void CreateThirdAppRegistration() {
auto app = BasicApp(kThirdAppFileId, kThirdAppName, "yanpi");
crostini_test_helper_->AddApp(app);
}
// Create a registration in GuestOsRegistryService for apps with app_id
// kDifferentVmAppId and kDifferentVmApp2Id inside kDifferentVmVmName.
void CreateDifferentVmAppRegistration() {
// CrostiniTestHelper doesn't directly allow apps to be added for VMs other
// than the default VM.
vm_tools::apps::ApplicationList app_list;
app_list.set_vm_name(kDifferentVmVmName);
app_list.set_container_name(kCrostiniDefaultContainerName);
*app_list.add_apps() =
BasicApp(kDifferentVmAppFileId, kDifferentVmAppName, "pack5");
*app_list.add_apps() =
BasicApp(kDifferentVmApp2FileId, kDifferentVmApp2Name, "pack5-2");
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_.get())
->UpdateApplicationList(app_list);
}
// Create a registration in GuestOsRegistryService for apps with app_id
// kDifferentContainerAppId and kDifferentContainerApp2Id inside
// kDifferentContainerContainerName.
void CreateDifferentContainerAppRegistration() {
// CrostiniTestHelper doesn't directly allow apps to be added for containers
// other than the default container.
vm_tools::apps::ApplicationList app_list;
app_list.set_vm_name(kCrostiniDefaultVmName);
app_list.set_container_name(kDifferentContainerContainerName);
*app_list.add_apps() = BasicApp(kDifferentContainerAppFileId,
kDifferentContainerAppName, "pack7");
*app_list.add_apps() = BasicApp(kDifferentContainerApp2FileId,
kDifferentContainerApp2Name, "pack7-2");
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_.get())
->UpdateApplicationList(app_list);
}
};
// A way of referring to one of the various app ids in parameters.
enum KnownApp {
DEFAULT_APP,
SECOND_APP,
THIRD_APP,
DIFFERENT_VM,
DIFFERENT_VM_2,
DIFFERENT_CONTAINER,
DIFFERENT_CONTAINER_2
};
// Returns the app name for one of the known apps.
std::u16string GetAppName(KnownApp app) {
switch (app) {
case DEFAULT_APP:
return kDefaultAppName16;
case SECOND_APP:
return kSecondAppName16;
case THIRD_APP:
return kThirdAppName16;
case DIFFERENT_VM:
return kDifferentVmAppName16;
case DIFFERENT_VM_2:
return kDifferentVmApp2Name16;
case DIFFERENT_CONTAINER:
return kDifferentContainerAppName16;
case DIFFERENT_CONTAINER_2:
return kDifferentContainerApp2Name16;
default:
NOTREACHED_IN_MIGRATION();
}
}
// This is a wrapper around message_center::Notification, allowing us to print
// them nicely. Otherwise, UnorderedElementsAre has failures that look like
// Actual: { 680-byte object <68-98 31-13 AC-7F 00-00 00-00 00-00 AB-AB AB-AB
// 80-AC 97-F1 06-23 00-00 1C-00 00-00 00-00 00-00 20-00 00-00 00-00
// 00-80 80-49 9F-F1 06-23 00-00 0B-00 00-00 00-00 00-00 10-00 00-00
// 00-00 00-80 ... 00-00 00-00 00-00 00-00 D2-67 19-FF 00-00 00-00
// 00-75 59-F1 00-00 00-00 03-00 00-00 AB-AB AB-AB E0-40 92-F1 06-23
// 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00
// 00-00 00-00> }
class PrintableNotification {
public:
explicit PrintableNotification(const message_center::Notification& base)
: display_source_(base.display_source()),
title_(base.title()),
message_(base.message()),
progress_(base.progress()) {}
const std::u16string& display_source() const { return display_source_; }
const std::u16string& title() const { return title_; }
const std::u16string& message() const { return message_; }
int progress() const { return progress_; }
private:
const std::u16string display_source_;
const std::u16string title_;
const std::u16string message_;
const int progress_;
};
std::ostream& operator<<(std::ostream& os,
const PrintableNotification& notification) {
os << "source: \"" << notification.display_source() << "\", title: \""
<< notification.title() << "\", message: \"" << notification.message()
<< "\", progress: " << notification.progress();
return os;
}
// Short-named conversion functions to avoid adding more noise than necessary
// to EXPECT_THAT calls.
PrintableNotification Printable(const message_center::Notification& base) {
return PrintableNotification(base);
}
std::vector<PrintableNotification> Printable(
const std::vector<message_center::Notification>& base) {
std::vector<PrintableNotification> result;
result.reserve(base.size());
for (const message_center::Notification& base_notification : base) {
result.push_back(PrintableNotification(base_notification));
}
return result;
}
class NotificationMatcher : public MatcherInterface<PrintableNotification> {
public:
NotificationMatcher(const std::u16string& expected_source,
const std::u16string& expected_title,
const std::u16string& expected_message)
: expected_source_(expected_source),
expected_title_(expected_title),
check_message_(true),
expected_message_(expected_message),
check_progress_(false),
expected_progress_(-1) {}
NotificationMatcher(const std::u16string& expected_source,
const std::u16string& expected_title,
int expected_progress)
: expected_source_(expected_source),
expected_title_(expected_title),
check_message_(false),
expected_message_(),
check_progress_(true),
expected_progress_(expected_progress) {}
bool MatchAndExplain(PrintableNotification notification,
MatchResultListener* listener) const override {
bool has_mismatch = false;
if (notification.display_source() != expected_source_) {
*listener << "notification source: " << notification.display_source()
<< "\ndoes not equal expected source: " << expected_source_;
has_mismatch = true;
}
if (notification.title() != expected_title_) {
if (has_mismatch) {
*listener << "\nand\n";
}
*listener << "notification title: " << notification.title()
<< "\ndoes not equal expected title: " << expected_title_;
has_mismatch = true;
}
if (check_message_ && (notification.message() != expected_message_)) {
if (has_mismatch) {
*listener << "\nand\n";
}
*listener << "notification message: " << notification.message()
<< "\ndoes not equal expected message: " << expected_message_;
has_mismatch = true;
}
if (check_progress_ && (notification.progress() != expected_progress_)) {
if (has_mismatch) {
*listener << "\nand\n";
}
*listener << "notification progress: " << notification.progress()
<< "\ndoes not equal expected progress: " << expected_progress_;
has_mismatch = true;
}
return !has_mismatch;
}
void DescribeTo(std::ostream* os) const override {
*os << "has notification source \"" << expected_source_ << "\" and title \""
<< expected_title_ << "\"";
if (check_message_) {
*os << " and message \"" << expected_message_ << "\"";
}
if (check_progress_) {
*os << " and progress " << expected_progress_;
}
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "does not have notification source \"" << expected_source_
<< "\" or does not have title \"" << expected_title_ << "\"";
if (check_message_) {
*os << " or does not have message \"" << expected_message_ << "\"";
}
if (check_progress_) {
*os << " or does not have progress " << expected_progress_;
}
}
private:
const std::u16string expected_source_;
const std::u16string expected_title_;
const bool check_message_;
const std::u16string expected_message_;
const bool check_progress_;
const int expected_progress_;
};
Matcher<PrintableNotification> IsUninstallSuccessNotification(
KnownApp app = DEFAULT_APP) {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringFUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_COMPLETED_TITLE,
GetAppName(app)),
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_COMPLETED_MESSAGE)));
}
Matcher<PrintableNotification> IsUninstallFailedNotification(
KnownApp app = DEFAULT_APP) {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringFUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_ERROR_TITLE,
GetAppName(app)),
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_ERROR_MESSAGE)));
}
Matcher<PrintableNotification> IsUninstallProgressNotification(
int expected_progress,
KnownApp app = DEFAULT_APP) {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringFUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_IN_PROGRESS_TITLE,
GetAppName(app)),
expected_progress));
}
Matcher<PrintableNotification> IsUninstallWaitingForAppListNotification(
KnownApp app = DEFAULT_APP) {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringFUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_IN_PROGRESS_TITLE,
GetAppName(app)),
-1));
}
Matcher<PrintableNotification> IsUninstallQueuedNotification(
KnownApp app = DEFAULT_APP) {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringFUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_QUEUED_TITLE,
GetAppName(app)),
l10n_util::GetStringUTF16(
IDS_CROSTINI_APPLICATION_UNINSTALL_NOTIFICATION_QUEUED_MESSAGE)));
}
Matcher<PrintableNotification> IsInstallProgressNotification(
int expected_progress) {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_IN_PROGRESS_TITLE),
expected_progress));
}
Matcher<PrintableNotification> IsInstallWaitingForAppListNotification() {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_IN_PROGRESS_TITLE),
-1));
}
Matcher<PrintableNotification> IsInstallSuccessNotification() {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_COMPLETED_TITLE),
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_COMPLETED_MESSAGE)));
}
Matcher<PrintableNotification> IsInstallFailedNotification() {
return MakeMatcher(new NotificationMatcher(
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_DISPLAY_SOURCE),
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_ERROR_TITLE),
l10n_util::GetStringUTF16(
IDS_CROSTINI_PACKAGE_INSTALL_NOTIFICATION_ERROR_MESSAGE)));
}
TEST_F(CrostiniPackageServiceTest, BasicUninstallMakesValidUninstallRequest) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, nullptr);
EXPECT_EQ(request.vm_name(), kCrostiniDefaultVmName);
EXPECT_EQ(request.container_name(), kCrostiniDefaultContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
}
TEST_F(CrostiniPackageServiceTest, DifferentVmMakesValidUninstallRequest) {
service_->QueueUninstallApplication(kDifferentVmAppId);
UninstallPackageOwningFileRequest request;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, nullptr);
EXPECT_EQ(request.vm_name(), kDifferentVmVmName);
EXPECT_EQ(request.container_name(), kCrostiniDefaultContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.desktop_file_id(), kDifferentVmAppFileId);
}
TEST_F(CrostiniPackageServiceTest,
DifferentContainerMakesValidUninstallRequest) {
service_->QueueUninstallApplication(kDifferentContainerAppId);
UninstallPackageOwningFileRequest request;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, nullptr);
EXPECT_EQ(request.vm_name(), kCrostiniDefaultVmName);
EXPECT_EQ(request.container_name(), kDifferentContainerContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.desktop_file_id(), kDifferentContainerAppFileId);
}
TEST_F(CrostiniPackageServiceTest, BasicUninstallDisplaysNotification) {
service_->QueueUninstallApplication(kDefaultAppId);
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification()));
}
TEST_F(CrostiniPackageServiceTest,
FailedUninstallResponseDisplaysFailedNotification) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
// Tell service the uninstall failed.
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::FAILED);
response.set_failure_reason("I prefer not to");
std::move(callback).Run(response);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification()));
}
TEST_F(CrostiniPackageServiceTest,
BlockedUninstallResponseDisplaysFailedNotification) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
// Tell service the uninstall failed.
UninstallPackageOwningFileResponse response;
response.set_status(
UninstallPackageOwningFileResponse::BLOCKING_OPERATION_IN_PROGRESS);
response.set_failure_reason("Hahaha NO");
std::move(callback).Run(response);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification()));
}
TEST_F(CrostiniPackageServiceTest,
FailedUninstallSignalDisplaysFailedNotification) {
service_->QueueUninstallApplication(kDefaultAppId);
StartAndSignalUninstall(UninstallPackageProgressSignal::FAILED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification()));
}
TEST_F(CrostiniPackageServiceTest,
UninstallDisplaysProgressNotificationBeforeResponse) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, nullptr);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallDisplaysProgressNotificationBeforeSignal) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback).Run(response);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallDisplaysProgressNotificationAfterProgressSignal) {
service_->QueueUninstallApplication(kDefaultAppId);
StartAndSignalUninstall(UninstallPackageProgressSignal::UNINSTALLING,
23 /*progress_percent*/);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(23)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallDisplaysSuccessNotificationAfterProgressThenSuccess) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
StartAndSignalUninstall(UninstallPackageProgressSignal::UNINSTALLING,
50 /*progress_percent*/, kDefaultAppFileId, &request);
UninstallPackageProgressSignal signal_success = MakeUninstallSignal(request);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification()));
}
TEST_F(CrostiniPackageServiceTest,
UninstallDisplaysFailureNotificationAfterProgressThenFailure) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
StartAndSignalUninstall(UninstallPackageProgressSignal::UNINSTALLING,
50 /*progress_percent*/, kDefaultAppFileId, &request);
UninstallPackageProgressSignal signal_failure = MakeUninstallSignal(request);
signal_failure.set_status(UninstallPackageProgressSignal::FAILED);
signal_failure.set_failure_details("I prefer not to");
fake_cicerone_client_->UninstallPackageProgress(signal_failure);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification()));
}
TEST_F(CrostiniPackageServiceTest, SecondUninstallDisplaysQueuedNotification) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallQueuedNotification(SECOND_APP)));
}
TEST_F(CrostiniPackageServiceTest, SecondUninstallStartsWhenFirstCompletes) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallProgressNotification(0, SECOND_APP)));
}
TEST_F(CrostiniPackageServiceTest, SecondUninstallStartsWhenFirstFails) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
StartAndSignalUninstall(UninstallPackageProgressSignal::FAILED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification(DEFAULT_APP),
IsUninstallProgressNotification(0, SECOND_APP)));
}
TEST_F(CrostiniPackageServiceTest, DuplicateUninstallSucceeds) {
// Use three uninstalls as a regression test for crbug.com/1015341
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
StartAndSignalUninstall(UninstallPackageProgressSignal::UNINSTALLING,
50 /*progress_percent*/, kDefaultAppFileId, &request);
crostini_test_helper_->RemoveApp(0);
UninstallPackageProgressSignal signal_success = MakeUninstallSignal(request);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallSuccessNotification(DEFAULT_APP)));
}
TEST_F(CrostiniPackageServiceTest,
AfterSecondInstallStartsProgressAppliesToSecond) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
// Uninstall the first.
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
// Start uninstalling the second
StartAndSignalUninstall(UninstallPackageProgressSignal::UNINSTALLING,
46 /*progress_percent*/, kSecondAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallProgressNotification(46, SECOND_APP)));
}
TEST_F(CrostiniPackageServiceTest, BothUninstallsEventuallyComplete) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
// Uninstall the first.
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
// Uninstall the second.
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED,
0 /*progress_percent*/, kSecondAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallSuccessNotification(SECOND_APP)));
}
TEST_F(CrostiniPackageServiceTest, QueuedUninstallsProcessedInFifoOrder) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
service_->QueueUninstallApplication(kThirdAppId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallQueuedNotification(THIRD_APP)));
// Finish the first; second should start.
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallProgressNotification(0, SECOND_APP),
IsUninstallQueuedNotification(THIRD_APP)));
// Finish the second, third should start.
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED,
0 /*progress_percent*/, kSecondAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallSuccessNotification(SECOND_APP),
IsUninstallProgressNotification(0, THIRD_APP)));
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED,
0 /*progress_percent*/, kThirdAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallSuccessNotification(SECOND_APP),
IsUninstallSuccessNotification(THIRD_APP)));
}
TEST_F(CrostiniPackageServiceTest, UninstallNotificationWaitsForAppListUpdate) {
service_->QueueUninstallApplication(kDefaultAppId);
SendAppListUpdateSignal(DefaultContainerId(), 1);
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallWaitingForAppListNotification(DEFAULT_APP)));
SendAppListUpdateSignal(DefaultContainerId(), 0);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallNotificationDoesntWaitForAppListUpdate) {
service_->QueueUninstallApplication(kDefaultAppId);
SendAppListUpdateSignal(DefaultContainerId(), 0);
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP)));
SendAppListUpdateSignal(DefaultContainerId(), 1);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallNotificationAppListUpdatesAreVmSpecific) {
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
service_->QueueUninstallApplication(kDefaultAppId);
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
UninstallPackageProgressSignal signal_progress = MakeUninstallSignal(request);
signal_progress.set_status(UninstallPackageProgressSignal::SUCCEEDED);
service_->QueueUninstallApplication(kDifferentVmAppId);
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
UninstallPackageProgressSignal signal_progress2 =
MakeUninstallSignal(request);
signal_progress2.set_status(UninstallPackageProgressSignal::SUCCEEDED);
SendAppListUpdateSignal(
guest_os::GuestId(kCrostiniDefaultVmType, kDifferentVmVmName,
kCrostiniDefaultContainerName),
1);
fake_cicerone_client_->UninstallPackageProgress(signal_progress);
fake_cicerone_client_->UninstallPackageProgress(signal_progress2);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallWaitingForAppListNotification(DIFFERENT_VM)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallNotificationAppListUpdatesFromUnknownContainersAreIgnored) {
service_->QueueUninstallApplication(kDefaultAppId);
SendAppListUpdateSignal(
guest_os::GuestId(kCrostiniDefaultVmType, kDifferentVmVmName,
kCrostiniDefaultContainerName),
1);
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP)));
}
TEST_F(CrostiniPackageServiceTest, UninstallNotificationFailsOnVmShutdown) {
// Use two apps to ensure one is queued up.
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
auto* crostini_manager = CrostiniManager::GetForProfile(profile_.get());
crostini_manager->AddRunningVmForTesting(kCrostiniDefaultVmName);
TestFuture<CrostiniResult> stop_waiter;
crostini_manager->StopVm(kCrostiniDefaultVmName, stop_waiter.GetCallback());
ASSERT_TRUE(stop_waiter.Wait());
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification(DEFAULT_APP),
IsUninstallFailedNotification(SECOND_APP)));
}
TEST_F(CrostiniPackageServiceTest, ClosingSuccessNotificationWorks) {
service_->QueueUninstallApplication(kDefaultAppId);
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 1U);
EXPECT_THAT(Printable(notifications[0]), IsUninstallSuccessNotification());
CloseNotification(notifications[0]);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
IsEmpty());
}
TEST_F(CrostiniPackageServiceTest, ClosingFailureNotificationWorks) {
service_->QueueUninstallApplication(kDefaultAppId);
StartAndSignalUninstall(UninstallPackageProgressSignal::FAILED);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 1U);
EXPECT_THAT(Printable(notifications[0]), IsUninstallFailedNotification());
CloseNotification(notifications[0]);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
IsEmpty());
}
TEST_F(CrostiniPackageServiceTest,
ClosedInProgressNotificationDoesNotReopenOnProgress) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback).Run(response);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 1U);
EXPECT_THAT(Printable(notifications[0]), IsUninstallProgressNotification(0));
CloseNotification(notifications[0]);
UninstallPackageProgressSignal signal_progress = MakeUninstallSignal(request);
signal_progress.set_status(UninstallPackageProgressSignal::UNINSTALLING);
signal_progress.set_progress_percent(50);
fake_cicerone_client_->UninstallPackageProgress(signal_progress);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
IsEmpty());
}
TEST_F(CrostiniPackageServiceTest,
ClosedInProgressNotificationReopensOnSuccess) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback).Run(response);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 1U);
EXPECT_THAT(Printable(notifications[0]), IsUninstallProgressNotification(0));
CloseNotification(notifications[0]);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
IsEmpty());
UninstallPackageProgressSignal signal_success = MakeUninstallSignal(request);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification()));
}
TEST_F(CrostiniPackageServiceTest,
ClosedInProgressNotificationReopensOnFailure) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback).Run(response);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 1U);
EXPECT_THAT(Printable(notifications[0]), IsUninstallProgressNotification(0));
CloseNotification(notifications[0]);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
IsEmpty());
UninstallPackageProgressSignal signal_failure = MakeUninstallSignal(request);
signal_failure.set_status(UninstallPackageProgressSignal::FAILED);
signal_failure.set_failure_details("I prefer not to");
fake_cicerone_client_->UninstallPackageProgress(signal_failure);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification()));
}
TEST_F(CrostiniPackageServiceTest,
ClosedQueuedNotificationDoesNotReopenOnProgress) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 2U);
EXPECT_THAT(Printable(notifications[1]),
IsUninstallQueuedNotification(SECOND_APP));
CloseNotification(notifications[1]);
// Complete Uninstall 1
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
// Uninstall 2 is now started, but we don't see a notification.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP)));
}
TEST_F(CrostiniPackageServiceTest,
ClosedQueuedNotificationDoesNotReopenOnFurtherProgress) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 2U);
EXPECT_THAT(Printable(notifications[1]),
IsUninstallQueuedNotification(SECOND_APP));
CloseNotification(notifications[1]);
// Complete Uninstall 1
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
// Start Uninstall 2
StartAndSignalUninstall(UninstallPackageProgressSignal::UNINSTALLING,
50 /*progress_percent*/, kSecondAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP)));
}
TEST_F(CrostiniPackageServiceTest,
ClosedQueuedNotificationReopensOnCompletion) {
service_->QueueUninstallApplication(kDefaultAppId);
service_->QueueUninstallApplication(kSecondAppId);
std::vector<message_center::Notification> notifications =
notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT);
ASSERT_EQ(notifications.size(), 2U);
EXPECT_THAT(Printable(notifications[1]),
IsUninstallQueuedNotification(SECOND_APP));
CloseNotification(notifications[1]);
// Complete Uninstall 1
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED);
// Complete Uninstall 2
StartAndSignalUninstall(UninstallPackageProgressSignal::SUCCEEDED,
0 /*progress_percent*/, kSecondAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallSuccessNotification(SECOND_APP)));
}
TEST_F(CrostiniPackageServiceTest, UninstallsOnDifferentVmsDoNotInterfere) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
EXPECT_EQ(request.vm_name(), kCrostiniDefaultVmName);
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
service_->QueueUninstallApplication(kDifferentVmAppId);
UninstallPackageOwningFileRequest request_different_vm;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback_different_vm;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request_different_vm,
&callback_different_vm);
EXPECT_EQ(request_different_vm.vm_name(), kDifferentVmVmName);
EXPECT_EQ(request_different_vm.desktop_file_id(), kDifferentVmAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallProgressNotification(0, DIFFERENT_VM)));
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback).Run(response);
UninstallPackageProgressSignal signal_progress = MakeUninstallSignal(request);
signal_progress.set_status(UninstallPackageProgressSignal::UNINSTALLING);
signal_progress.set_progress_percent(60);
fake_cicerone_client_->UninstallPackageProgress(signal_progress);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(60, DEFAULT_APP),
IsUninstallProgressNotification(0, DIFFERENT_VM)));
std::move(callback_different_vm).Run(response);
signal_progress = MakeUninstallSignal(request_different_vm);
signal_progress.set_status(UninstallPackageProgressSignal::UNINSTALLING);
signal_progress.set_progress_percent(40);
fake_cicerone_client_->UninstallPackageProgress(signal_progress);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(60, DEFAULT_APP),
IsUninstallProgressNotification(40, DIFFERENT_VM)));
UninstallPackageProgressSignal signal_success =
MakeUninstallSignal(request_different_vm);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(60, DEFAULT_APP),
IsUninstallSuccessNotification(DIFFERENT_VM)));
UninstallPackageProgressSignal signal_failure = MakeUninstallSignal(request);
signal_failure.set_status(UninstallPackageProgressSignal::FAILED);
signal_failure.set_failure_details("Nope");
fake_cicerone_client_->UninstallPackageProgress(signal_failure);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallFailedNotification(DEFAULT_APP),
IsUninstallSuccessNotification(DIFFERENT_VM)));
}
TEST_F(CrostiniPackageServiceTest, UninstallsOnDifferentVmsHaveSeparateQueues) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request_default;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback_default;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request_default,
&callback_default);
EXPECT_EQ(request_default.vm_name(), kCrostiniDefaultVmName);
EXPECT_EQ(request_default.desktop_file_id(), kDefaultAppFileId);
service_->QueueUninstallApplication(kDifferentVmAppId);
UninstallPackageOwningFileRequest request_different_vm;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback_different_vm;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request_different_vm,
&callback_different_vm);
EXPECT_EQ(request_different_vm.vm_name(), kDifferentVmVmName);
EXPECT_EQ(request_different_vm.desktop_file_id(), kDifferentVmAppFileId);
service_->QueueUninstallApplication(kSecondAppId);
service_->QueueUninstallApplication(kDifferentVmApp2Id);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallProgressNotification(0, DIFFERENT_VM),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallQueuedNotification(DIFFERENT_VM_2)));
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::FAILED);
std::move(callback_different_vm).Run(response);
// Even though kSecondAppId was queued first, kDifferentVmApp2Id is moved
// to progress state, because it's on a different queue.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_VM),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallProgressNotification(0, DIFFERENT_VM_2)));
UninstallPackageOwningFileRequest request_different_vm_2;
DBusMethodCallback<UninstallPackageOwningFileResponse>
callback_different_vm_2;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request_different_vm_2,
&callback_different_vm_2);
EXPECT_EQ(request_different_vm_2.vm_name(), kDifferentVmVmName);
EXPECT_EQ(request_different_vm_2.desktop_file_id(), kDifferentVmApp2FileId);
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback_different_vm_2).Run(response);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_VM),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallProgressNotification(0, DIFFERENT_VM_2)));
UninstallPackageProgressSignal signal_success =
MakeUninstallSignal(request_different_vm_2);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
// Even though the task finished on VM 2, SECOND_APP does not start
// uninstalling because it is on a different queue.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_VM),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallSuccessNotification(DIFFERENT_VM_2)));
std::move(callback_default).Run(response);
signal_success = MakeUninstallSignal(request_default);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
// Only when the uninstall on the default VM is done does SECOND_APP start
// uninstalling.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_VM),
IsUninstallProgressNotification(0, SECOND_APP),
IsUninstallSuccessNotification(DIFFERENT_VM_2)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallsOnDifferentContainersDoNotInterfere) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, &callback);
EXPECT_EQ(request.container_name(), kCrostiniDefaultContainerName);
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
service_->QueueUninstallApplication(kDifferentContainerAppId);
UninstallPackageOwningFileRequest request_different_container;
DBusMethodCallback<UninstallPackageOwningFileResponse>
callback_different_container;
RunUntilUninstallRequestMade(fake_cicerone_client_,
&request_different_container,
&callback_different_container);
EXPECT_EQ(request_different_container.container_name(),
kDifferentContainerContainerName);
EXPECT_EQ(request_different_container.desktop_file_id(),
kDifferentContainerAppFileId);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallProgressNotification(0, DIFFERENT_CONTAINER)));
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback).Run(response);
UninstallPackageProgressSignal signal_progress = MakeUninstallSignal(request);
signal_progress.set_status(UninstallPackageProgressSignal::UNINSTALLING);
signal_progress.set_progress_percent(60);
fake_cicerone_client_->UninstallPackageProgress(signal_progress);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(60, DEFAULT_APP),
IsUninstallProgressNotification(0, DIFFERENT_CONTAINER)));
std::move(callback_different_container).Run(response);
signal_progress = MakeUninstallSignal(request_different_container);
signal_progress.set_status(UninstallPackageProgressSignal::UNINSTALLING);
signal_progress.set_progress_percent(40);
fake_cicerone_client_->UninstallPackageProgress(signal_progress);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(60, DEFAULT_APP),
IsUninstallProgressNotification(40, DIFFERENT_CONTAINER)));
UninstallPackageProgressSignal signal_success =
MakeUninstallSignal(request_different_container);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(60, DEFAULT_APP),
IsUninstallSuccessNotification(DIFFERENT_CONTAINER)));
UninstallPackageProgressSignal signal_failure = MakeUninstallSignal(request);
signal_failure.set_status(UninstallPackageProgressSignal::FAILED);
signal_failure.set_failure_details("Nope");
fake_cicerone_client_->UninstallPackageProgress(signal_failure);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallFailedNotification(DEFAULT_APP),
IsUninstallSuccessNotification(DIFFERENT_CONTAINER)));
}
TEST_F(CrostiniPackageServiceTest,
UninstallsOnDifferentContainersHaveSeparateQueues) {
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request_default;
DBusMethodCallback<UninstallPackageOwningFileResponse> callback_default;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request_default,
&callback_default);
EXPECT_EQ(request_default.container_name(), kCrostiniDefaultContainerName);
EXPECT_EQ(request_default.desktop_file_id(), kDefaultAppFileId);
service_->QueueUninstallApplication(kDifferentContainerAppId);
UninstallPackageOwningFileRequest request_different_container;
DBusMethodCallback<UninstallPackageOwningFileResponse>
callback_different_container;
RunUntilUninstallRequestMade(fake_cicerone_client_,
&request_different_container,
&callback_different_container);
EXPECT_EQ(request_different_container.container_name(),
kDifferentContainerContainerName);
EXPECT_EQ(request_different_container.desktop_file_id(),
kDifferentContainerAppFileId);
service_->QueueUninstallApplication(kSecondAppId);
service_->QueueUninstallApplication(kDifferentContainerApp2Id);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallProgressNotification(0, DIFFERENT_CONTAINER),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallQueuedNotification(DIFFERENT_CONTAINER_2)));
UninstallPackageOwningFileResponse response;
response.set_status(UninstallPackageOwningFileResponse::FAILED);
std::move(callback_different_container).Run(response);
// Even though kSecondAppId was queued first, kDifferentContainerApp2Id is
// moved to progress state, because it's on a different queue.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_CONTAINER),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallProgressNotification(0, DIFFERENT_CONTAINER_2)));
UninstallPackageOwningFileRequest request_different_container_2;
DBusMethodCallback<UninstallPackageOwningFileResponse>
callback_different_container_2;
RunUntilUninstallRequestMade(fake_cicerone_client_,
&request_different_container_2,
&callback_different_container_2);
EXPECT_EQ(request_different_container_2.container_name(),
kDifferentContainerContainerName);
EXPECT_EQ(request_different_container_2.desktop_file_id(),
kDifferentContainerApp2FileId);
response.set_status(UninstallPackageOwningFileResponse::STARTED);
std::move(callback_different_container_2).Run(response);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_CONTAINER),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallProgressNotification(0, DIFFERENT_CONTAINER_2)));
UninstallPackageProgressSignal signal_success =
MakeUninstallSignal(request_different_container_2);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
// Even though the task finished on container 2, SECOND_APP does not start
// uninstalling because it is on a different queue.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallProgressNotification(0, DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_CONTAINER),
IsUninstallQueuedNotification(SECOND_APP),
IsUninstallSuccessNotification(DIFFERENT_CONTAINER_2)));
std::move(callback_default).Run(response);
signal_success = MakeUninstallSignal(request_default);
signal_success.set_status(UninstallPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->UninstallPackageProgress(signal_success);
// Only when the uninstall on the default container is done does SECOND_APP
// start uninstalling.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(
IsUninstallSuccessNotification(DEFAULT_APP),
IsUninstallFailedNotification(DIFFERENT_CONTAINER),
IsUninstallProgressNotification(0, SECOND_APP),
IsUninstallSuccessNotification(DIFFERENT_CONTAINER_2)));
}
TEST_F(CrostiniPackageServiceTest, InstallSendsValidRequest) {
TestFuture<CrostiniResult> result_future;
service_->QueueInstallLinuxPackage(kDifferentContainerId, package_file_url_,
result_future.GetCallback());
EXPECT_EQ(CrostiniResult::SUCCESS, result_future.Get());
const vm_tools::cicerone::InstallLinuxPackageRequest& request =
fake_cicerone_client_->get_most_recent_install_linux_package_request();
EXPECT_EQ(request.vm_name(), kDifferentVmVmName);
EXPECT_EQ(request.container_name(), kDifferentContainerContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.file_path(), kPackageFileContainerPath);
}
TEST_F(CrostiniPackageServiceTest, InstallConvertPathFailure) {
TestFuture<CrostiniResult> result_future;
service_->QueueInstallLinuxPackage(
kDifferentContainerId,
storage::FileSystemURL::CreateForTest(GURL("invalid")),
result_future.GetCallback());
EXPECT_EQ(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED, result_future.Get());
}
TEST_F(CrostiniPackageServiceTest, InstallDisplaysProgressNotificationOnStart) {
TestFuture<CrostiniResult> result_future;
service_->QueueInstallLinuxPackage(DefaultContainerId(), package_file_url_,
result_future.GetCallback());
EXPECT_EQ(CrostiniResult::SUCCESS, result_future.Get());
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallProgressNotification(0)));
}
TEST_F(CrostiniPackageServiceTest,
InstallUpdatesProgressNotificationOnDownloadingSignal) {
InstallLinuxPackageAndWait();
StartAndSignalInstall(InstallLinuxPackageProgressSignal::DOWNLOADING,
44 /*progress_percent*/);
// 22 = 44/2
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallProgressNotification(22)));
}
TEST_F(CrostiniPackageServiceTest,
InstallUpdatesProgressNotificationOnInstallingSignal) {
InstallLinuxPackageAndWait();
StartAndSignalInstall(InstallLinuxPackageProgressSignal::INSTALLING,
44 /*progress_percent*/);
// 72 = 44/2 + 50
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallProgressNotification(72)));
}
TEST_F(CrostiniPackageServiceTest,
InstallDisplaysSuccessNotificationOnSuccessSignal) {
InstallLinuxPackageAndWait();
StartAndSignalInstall(InstallLinuxPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallSuccessNotification()));
}
TEST_F(CrostiniPackageServiceTest,
InstallDisplaysFailureNotificationOnFailedSignal) {
InstallLinuxPackageAndWait();
StartAndSignalInstall(InstallLinuxPackageProgressSignal::FAILED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallFailedNotification()));
}
TEST_F(CrostiniPackageServiceTest, InstallNotificationWaitsForAppListUpdate) {
InstallLinuxPackageAndWait();
SendAppListUpdateSignal(DefaultContainerId(), 1);
StartAndSignalInstall(InstallLinuxPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallWaitingForAppListNotification()));
SendAppListUpdateSignal(DefaultContainerId(), 0);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallSuccessNotification()));
}
TEST_F(CrostiniPackageServiceTest,
InstallNotificationDoesntWaitForAppListUpdate) {
InstallLinuxPackageAndWait();
SendAppListUpdateSignal(DefaultContainerId(), 0);
StartAndSignalInstall(InstallLinuxPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallSuccessNotification()));
SendAppListUpdateSignal(DefaultContainerId(), 1);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallSuccessNotification()));
}
TEST_F(CrostiniPackageServiceTest,
InstallNotificationAppListUpdatesAreVmSpecific) {
InstallLinuxPackageRequest request;
InstallLinuxPackageAndWait();
request =
fake_cicerone_client_->get_most_recent_install_linux_package_request();
InstallLinuxPackageProgressSignal signal_progress =
MakeInstallSignal(request);
signal_progress.set_status(InstallLinuxPackageProgressSignal::SUCCEEDED);
service_->QueueInstallLinuxPackage(
guest_os::GuestId(kCrostiniDefaultVmType, kDifferentVmVmName,
kCrostiniDefaultContainerName),
package_file_url_, base::DoNothing());
request =
fake_cicerone_client_->get_most_recent_install_linux_package_request();
InstallLinuxPackageProgressSignal signal_progress2 =
MakeInstallSignal(request);
signal_progress2.set_status(InstallLinuxPackageProgressSignal::SUCCEEDED);
SendAppListUpdateSignal(
guest_os::GuestId(kCrostiniDefaultVmType, kDifferentVmVmName,
kCrostiniDefaultContainerName),
1);
fake_cicerone_client_->InstallLinuxPackageProgress(signal_progress);
fake_cicerone_client_->InstallLinuxPackageProgress(signal_progress2);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallSuccessNotification(),
IsInstallWaitingForAppListNotification()));
}
TEST_F(CrostiniPackageServiceTest,
InstallNotificationAppListUpdatesFromUnknownContainersAreIgnored) {
InstallLinuxPackageAndWait();
SendAppListUpdateSignal(
guest_os::GuestId(kCrostiniDefaultVmType, kDifferentVmVmName,
kCrostiniDefaultContainerName),
1);
StartAndSignalInstall(InstallLinuxPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallSuccessNotification()));
}
TEST_F(CrostiniPackageServiceTest, InstallNotificationFailsOnVmShutdown) {
InstallLinuxPackageAndWait();
StartAndSignalInstall(InstallLinuxPackageProgressSignal::INSTALLING);
auto* crostini_manager = CrostiniManager::GetForProfile(profile_.get());
crostini_manager->AddRunningVmForTesting(kCrostiniDefaultVmName);
TestFuture<CrostiniResult> stop_waiter;
crostini_manager->StopVm(kCrostiniDefaultVmName, stop_waiter.GetCallback());
ASSERT_TRUE(stop_waiter.Wait());
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallFailedNotification()));
}
TEST_F(CrostiniPackageServiceTest, UninstallsQueuesBehindStartingUpInstall) {
InstallLinuxPackageAndWait();
service_->QueueUninstallApplication(kDefaultAppId);
// Install doesn't show a notification until it gets a response, but uninstall
// still shows a queued notification.
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallProgressNotification(0),
IsUninstallQueuedNotification()));
}
TEST_F(CrostiniPackageServiceTest, InstallRunsInFrontOfQueuedUninstall) {
TestFuture<CrostiniResult> result_future;
service_->QueueInstallLinuxPackage(DefaultContainerId(), package_file_url_,
result_future.GetCallback());
service_->QueueUninstallApplication(kDefaultAppId);
EXPECT_EQ(CrostiniResult::SUCCESS, result_future.Get());
// Ensure the install started, not the uninstall.
const vm_tools::cicerone::InstallLinuxPackageRequest& request =
fake_cicerone_client_->get_most_recent_install_linux_package_request();
EXPECT_EQ(request.file_path(), kPackageFileContainerPath);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallProgressNotification(0),
IsUninstallQueuedNotification()));
}
TEST_F(CrostiniPackageServiceTest, QueuedUninstallRunsAfterCompletedInstall) {
InstallLinuxPackageAndWait();
service_->QueueUninstallApplication(kDefaultAppId);
StartAndSignalInstall(InstallLinuxPackageProgressSignal::SUCCEEDED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallSuccessNotification(),
IsUninstallProgressNotification(0)));
UninstallPackageOwningFileRequest request;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, nullptr);
EXPECT_EQ(request.vm_name(), kCrostiniDefaultVmName);
EXPECT_EQ(request.container_name(), kCrostiniDefaultContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
}
TEST_F(CrostiniPackageServiceTest,
QueuedUninstallRunsAfterFailedToStartInstall) {
InstallLinuxPackageResponse response;
response.set_status(InstallLinuxPackageResponse::FAILED);
response.set_failure_reason("No such file");
fake_cicerone_client_->set_install_linux_package_response(response);
InstallLinuxPackageAndWait();
service_->QueueUninstallApplication(kDefaultAppId);
UninstallPackageOwningFileRequest request;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, nullptr);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallFailedNotification(),
IsUninstallProgressNotification(0)));
EXPECT_EQ(request.vm_name(), kCrostiniDefaultVmName);
EXPECT_EQ(request.container_name(), kCrostiniDefaultContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
}
TEST_F(CrostiniPackageServiceTest,
QueuedUninstallRunsAfterFailedInstallSignal) {
InstallLinuxPackageAndWait();
service_->QueueUninstallApplication(kDefaultAppId);
StartAndSignalInstall(InstallLinuxPackageProgressSignal::FAILED);
EXPECT_THAT(
Printable(notification_display_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::TRANSIENT)),
UnorderedElementsAre(IsInstallFailedNotification(),
IsUninstallProgressNotification(0)));
UninstallPackageOwningFileRequest request;
RunUntilUninstallRequestMade(fake_cicerone_client_, &request, nullptr);
EXPECT_EQ(request.vm_name(), kCrostiniDefaultVmName);
EXPECT_EQ(request.container_name(), kCrostiniDefaultContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.desktop_file_id(), kDefaultAppFileId);
}
TEST_F(CrostiniPackageServiceTest, GetLinuxPackageInfoSendsCorrectRequest) {
GetLinuxPackageInfo();
const LinuxPackageInfoRequest& request =
fake_cicerone_client_->get_most_recent_linux_package_info_request();
EXPECT_EQ(request.vm_name(), kDifferentVmVmName);
EXPECT_EQ(request.container_name(), kDifferentContainerContainerName);
EXPECT_EQ(request.owner_id(), CryptohomeIdForProfile(profile_.get()));
EXPECT_EQ(request.file_path(), kPackageFileContainerPath);
}
TEST_F(CrostiniPackageServiceTest, GetLinuxPackageInfoReturnsInfoOnSuccess) {
LinuxPackageInfoResponse response;
response.set_success(true);
response.set_package_id("nethack;3.6.1;x64;some data");
response.set_license("ngpl");
response.set_description("Explore the Dungeon!");
response.set_project_url("https://www.nethack.org/");
response.set_size(548422342432);
response.set_summary("Fight! Run! Win!");
fake_cicerone_client_->set_linux_package_info_response(response);
auto result = GetLinuxPackageInfo();
EXPECT_TRUE(result.success);
EXPECT_EQ(result.name, "nethack");
EXPECT_EQ(result.version, "3.6.1");
EXPECT_EQ(result.summary, response.summary());
EXPECT_EQ(result.description, response.description());
}
TEST_F(CrostiniPackageServiceTest, GetLinuxPackageInfoConvertPathFailure) {
SharePathResponse response;
response.set_success(false);
fake_seneschal_client_->set_share_path_response(response);
TestFuture<const LinuxPackageInfo&> result_future;
service_->GetLinuxPackageInfo(
kDifferentContainerId,
storage::FileSystemURL::CreateForTest(GURL("invalid")),
result_future.GetCallback());
auto result = result_future.Get();
EXPECT_FALSE(result.success);
EXPECT_TRUE(base::StartsWith(result.failure_reason, "Invalid package url:",
base::CompareCase::SENSITIVE));
}
TEST_F(CrostiniPackageServiceTest, GetLinuxPackageInfoSharePathFailure) {
SharePathResponse response;
response.set_success(false);
fake_seneschal_client_->set_share_path_response(response);
TestFuture<const LinuxPackageInfo&> result_future;
service_->GetLinuxPackageInfo(kDifferentContainerId, package_file_url_,
result_future.GetCallback());
CrostiniManager::GetForProfile(profile_.get())
->CallRestarterStartLxdContainerFinishedForTesting(
service_->GetRestartIdForTesting(),
CrostiniResult::CONTAINER_START_FAILED);
auto result = result_future.Get();
EXPECT_FALSE(result.success);
EXPECT_TRUE(base::StartsWith(result.failure_reason, "Error sharing package",
base::CompareCase::SENSITIVE));
}
TEST_F(CrostiniPackageServiceTest, GetLinuxPackageInfoReturnsFailureOnFailure) {
LinuxPackageInfoResponse response;
response.set_success(false);
response.set_failure_reason("test failure reason");
fake_cicerone_client_->set_linux_package_info_response(response);
auto result = GetLinuxPackageInfo();
EXPECT_FALSE(result.success);
EXPECT_EQ(result.failure_reason, "test failure reason");
}
} // namespace
} // namespace crostini