1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
|
// Copyright 2023 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/apps/app_service/publishers/guest_os_apps.h"
#include <memory>
#include <vector>
#include "base/test/simple_test_clock.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/ash/guest_os/public/types.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/test/base/testing_profile.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace apps {
namespace {
// An app publisher (in the App Service sense) that inherits GuestOSApps and
// implements the necessary virtual functions.
class TestPublisher : public GuestOSApps {
public:
explicit TestPublisher(AppServiceProxy* proxy) : GuestOSApps(proxy) {}
private:
bool CouldBeAllowed() const override { return true; }
apps::AppType AppType() const override { return AppType::kBruschetta; }
guest_os::VmType VmType() const override {
return guest_os::VmType::BRUSCHETTA;
}
// apps::AppPublisher overrides.
void Launch(const std::string& app_id,
int32_t event_flags,
LaunchSource launch_source,
WindowInfoPtr window_info) override {}
void CreateAppOverrides(
const guest_os::GuestOsRegistryService::Registration& registration,
App* app) override {
app->name = "override_name";
}
};
} // namespace
class GuestOSAppsTest : public testing::Test {
public:
GuestOSAppsTest()
: task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD) {}
TestingProfile* profile() { return profile_.get(); }
AppServiceProxy* app_service_proxy() { return app_service_proxy_; }
guest_os::GuestOsRegistryService* registry() { return registry_.get(); }
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
app_service_proxy_ = AppServiceProxyFactory::GetForProfile(profile_.get());
web_app::test::AwaitStartWebAppProviderAndSubsystems(profile_.get());
registry_ =
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_.get());
registry_->SetClockForTesting(&test_clock_);
publisher_ = std::make_unique<TestPublisher>(app_service_proxy());
publisher_->InitializeForTesting();
}
void TearDown() override {
// TestPublisher must be torn down before TestingProfile because the latter
// destroys the GuestOsRegistryService.
publisher_.reset();
profile_.reset();
}
// Adds the app to the registry and returns its app_id.
std::string AddApp(const vm_tools::apps::App& app) {
// Update the ApplicationList, this calls through GuestOSApps::CreateApp.
vm_tools::apps::ApplicationList app_list;
app_list.set_vm_name(bruschetta::kBruschettaVmName);
app_list.set_container_name("test_container");
*app_list.add_apps() = app;
registry()->UpdateApplicationList(app_list);
return registry()->GenerateAppId(app.desktop_file_id(),
bruschetta::kBruschettaVmName,
app_list.container_name());
}
// Adds an app with the specified mime_types and returns its app_id.
std::string AddAppWithMimeTypes(const std::string& desktop_file_id,
const std::string& app_name,
const std::vector<std::string>& mime_types) {
vm_tools::apps::App app;
for (std::string mime_type : mime_types) {
app.add_mime_types(mime_type);
}
app.set_desktop_file_id(desktop_file_id);
vm_tools::apps::App::LocaleString::Entry* entry =
app.mutable_name()->add_values();
entry->set_locale(std::string());
entry->set_value(app_name);
return AddApp(app);
}
// Get the registered intent filters for the given app_id.
std::vector<std::unique_ptr<IntentFilter>> GetIntentFiltersForApp(
const std::string& app_id) {
std::vector<std::unique_ptr<IntentFilter>> intent_filters;
app_service_proxy()->AppRegistryCache().ForOneApp(
app_id, [&intent_filters](const AppUpdate& update) {
for (auto& intent_filter : update.IntentFilters()) {
intent_filters.push_back(std::move(intent_filter));
}
});
return intent_filters;
}
void UpdateMimeTypes(std::string container_name,
std::string extension,
std::string mime_type) {
vm_tools::apps::MimeTypes mime_types_list;
mime_types_list.set_vm_name(bruschetta::kBruschettaVmName);
mime_types_list.set_container_name(container_name);
(*mime_types_list.mutable_mime_type_mappings())[extension] = mime_type;
auto* mime_types_service =
guest_os::GuestOsMimeTypesServiceFactory::GetForProfile(profile());
mime_types_service->UpdateMimeTypes(mime_types_list);
task_environment_.RunUntilIdle();
}
base::SimpleTestClock& test_clock() { return test_clock_; }
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfile> profile_;
raw_ptr<AppServiceProxy, DanglingUntriaged> app_service_proxy_ = nullptr;
raw_ptr<guest_os::GuestOsRegistryService, DanglingUntriaged> registry_ =
nullptr;
std::unique_ptr<TestPublisher> publisher_;
base::SimpleTestClock test_clock_;
};
TEST_F(GuestOSAppsTest, CreateApp) {
// Create a test app.
vm_tools::apps::App app;
app.add_mime_types("text/plain");
app.set_desktop_file_id("desktop_file_id");
vm_tools::apps::App::LocaleString::Entry* entry =
app.mutable_name()->add_values();
entry->set_value("app_name");
const std::string app_id = AddApp(app);
// Get the AppUpdate from the registry and check its contents.
bool seen = false;
app_service_proxy()->AppRegistryCache().ForOneApp(
app_id, [&seen](const AppUpdate& update) {
seen = true;
EXPECT_EQ(update.AppType(), AppType::kBruschetta);
EXPECT_EQ(update.Readiness(), Readiness::kReady);
EXPECT_EQ(update.Name(), "override_name")
<< "CreateAppOverrides should have changed the name.";
EXPECT_EQ(update.InstallReason(), InstallReason::kUser);
EXPECT_EQ(update.InstallSource(), InstallSource::kUnknown);
EXPECT_TRUE(update.IconKey().has_value());
EXPECT_TRUE(update.ShowInLauncher());
EXPECT_TRUE(update.ShowInSearch());
EXPECT_TRUE(update.ShowInShelf());
EXPECT_EQ(bruschetta::kBruschettaVmName,
*update.Extra()->FindString("vm_name"));
EXPECT_EQ("test_container",
*update.Extra()->FindString("container_name"));
EXPECT_EQ("desktop_file_id",
*update.Extra()->FindString("desktop_file_id"));
EXPECT_EQ("", *update.Extra()->FindString("exec"));
EXPECT_EQ("", *update.Extra()->FindString("executable_file_name"));
EXPECT_FALSE(update.Extra()->FindBool("no_display").value());
EXPECT_FALSE(update.Extra()->FindBool("terminal").value());
EXPECT_FALSE(update.Extra()->FindBool("scaled").value());
EXPECT_EQ("", *update.Extra()->FindString("package_id"));
EXPECT_EQ("", *update.Extra()->FindString("startup_wm_class"));
EXPECT_FALSE(update.Extra()->FindBool("startup_notify").value());
});
EXPECT_TRUE(seen) << "Couldn't find test app in registry.";
}
TEST_F(GuestOSAppsTest, OnAppLastLaunchTimeUpdated) {
// Create a test app.
vm_tools::apps::App app;
app.add_mime_types("text/plain");
app.set_desktop_file_id("desktop_file_id");
vm_tools::apps::App::LocaleString::Entry* entry =
app.mutable_name()->add_values();
entry->set_value("app_name");
const std::string app_id = AddApp(app);
// Get the AppUpdate from the registry and check its contents.
app_service_proxy()->AppRegistryCache().ForOneApp(
app_id, [](const AppUpdate& update) {
EXPECT_EQ(update.LastLaunchTime(), base::Time());
});
test_clock().Advance(base::Hours(1));
registry()->AppLaunched(app_id);
// Get LastLaunchTime from the registry and check its contents.
app_service_proxy()->AppRegistryCache().ForOneApp(
app_id, [](const AppUpdate& update) {
EXPECT_EQ(update.LastLaunchTime(), base::Time() + base::Hours(1));
});
}
TEST_F(GuestOSAppsTest, AppServiceHasIntentFilters) {
const std::vector<std::string> mime_types = {"text/csv", "text/html"};
const std::string app_id =
AddAppWithMimeTypes("desktop_file_id", "app_name", mime_types);
const std::vector<std::unique_ptr<IntentFilter>> intent_filters =
GetIntentFiltersForApp(app_id);
ASSERT_EQ(intent_filters.size(), 1U);
EXPECT_EQ(intent_filters[0]->conditions.size(), 2U);
// Check that the filter has the correct action type.
{
const Condition* condition = intent_filters[0]->conditions[0].get();
ASSERT_EQ(condition->condition_type, ConditionType::kAction);
EXPECT_EQ(condition->condition_values.size(), 1U);
ASSERT_EQ(condition->condition_values[0]->match_type,
PatternMatchType::kLiteral);
ASSERT_EQ(condition->condition_values[0]->value,
apps_util::kIntentActionView);
}
// Check that the filter has all our mime types. Realistically, the filter
// would also have the extension equivalents of the mime types too if there
// were mime/ extension mappings in prefs.
{
const Condition* condition = intent_filters[0]->conditions[1].get();
ASSERT_EQ(condition->condition_type, ConditionType::kFile);
EXPECT_EQ(condition->condition_values.size(), 2U);
ASSERT_EQ(condition->condition_values[0]->value, mime_types[0]);
ASSERT_EQ(condition->condition_values[1]->value, mime_types[1]);
}
}
TEST_F(GuestOSAppsTest, IntentFilterWithTextPlainAddsTextWildcardMimeType) {
{
const std::string normal_app_id = AddAppWithMimeTypes(
"normal_desktop_file_id", "normal_app_name", {"text/csv"});
const std::vector<std::unique_ptr<IntentFilter>> intent_filters =
GetIntentFiltersForApp(normal_app_id);
EXPECT_EQ(intent_filters.size(), 1U);
EXPECT_EQ(intent_filters[0]->conditions.size(), 2U);
// Check that the filter is unchanged because there isn't a "text/plain"
// mime type condition.
const Condition* condition = intent_filters[0]->conditions[1].get();
ASSERT_EQ(condition->condition_type, ConditionType::kFile);
EXPECT_EQ(condition->condition_values.size(), 1U);
ASSERT_EQ(condition->condition_values[0]->value, "text/csv");
}
{
const std::string many_mimes_app_id = AddAppWithMimeTypes(
"many_mimes_desktop_file_id", "many_mimes_app_name",
{"text/plain", "text/csv", "text/javascript", "video/mp4"});
const std::vector<std::unique_ptr<IntentFilter>> intent_filters =
GetIntentFiltersForApp(many_mimes_app_id);
EXPECT_EQ(intent_filters.size(), 1U);
EXPECT_EQ(intent_filters[0]->conditions.size(), 2U);
// Check that the filter has "text/*" to replace all the text mime types.
const Condition* condition = intent_filters[0]->conditions[1].get();
ASSERT_EQ(condition->condition_type, ConditionType::kFile);
EXPECT_EQ(condition->condition_values.size(), 2U);
ASSERT_EQ(condition->condition_values[0]->value, "video/mp4");
ASSERT_EQ(condition->condition_values[1]->value, "text/*");
}
}
TEST_F(GuestOSAppsTest, IntentFilterHasExtensionsFromPrefs) {
const std::string mime_type = "test/mime1";
const std::string extension = "test_extension";
UpdateMimeTypes("test_container", extension, mime_type);
// Create app and get its registered intent filters.
const std::string app_id =
AddAppWithMimeTypes("desktop_file_id", "app_name", {mime_type});
std::vector<std::unique_ptr<IntentFilter>> intent_filters =
GetIntentFiltersForApp(app_id);
ASSERT_EQ(intent_filters.size(), 1U);
std::unique_ptr<IntentFilter> intent_filter = std::move(intent_filters[0]);
ASSERT_EQ(intent_filter->conditions.size(), 2U);
// Check that the filter has the correct action type.
{
const Condition* condition = intent_filter->conditions[0].get();
EXPECT_EQ(condition->condition_type, ConditionType::kAction);
ASSERT_EQ(condition->condition_values.size(), 1U);
EXPECT_EQ(condition->condition_values[0]->match_type,
PatternMatchType::kLiteral);
EXPECT_EQ(condition->condition_values[0]->value,
apps_util::kIntentActionView);
}
// Check that the filter has both mime type and its extension equivalent based
// on what is recorded in prefs for GuestOS mime types.
{
const Condition* condition = intent_filter->conditions[1].get();
EXPECT_EQ(condition->condition_type, ConditionType::kFile);
ASSERT_EQ(condition->condition_values.size(), 2U);
EXPECT_EQ(condition->condition_values[0]->value, mime_type);
EXPECT_EQ(condition->condition_values[0]->match_type,
PatternMatchType::kMimeType);
EXPECT_EQ(condition->condition_values[1]->value, extension);
EXPECT_EQ(condition->condition_values[1]->match_type,
PatternMatchType::kFileExtension);
}
}
} // namespace apps
|