blob: 29857063a3fd0cbe0ca49978df62a5b8402578dc [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/web_app_database.h"
#include <memory>
#include <random>
#include <string>
#include <utility>
#include <vector>
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/time/time.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_utils.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/test/test_web_app_database_factory.h"
#include "chrome/browser/web_applications/test/test_web_app_registry_controller.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_proto_utils.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "components/services/app_service/public/cpp/file_handler.h"
#include "components/services/app_service/public/cpp/protocol_handler_info.h"
#include "components/services/app_service/public/cpp/share_target.h"
#include "components/sync/model/model_type_store.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace web_app {
namespace {
const int kIconSize = 64;
class RandomHelper {
public:
explicit RandomHelper(const uint32_t seed)
: // Seed of 0 and 1 generate the same sequence, so skip 0.
generator_(seed + 1),
distribution_(0u, UINT32_MAX) {}
uint32_t next_uint() { return distribution_(generator_); }
// Return an unsigned int between 0 (inclusive) and bound (exclusive).
uint32_t next_uint(uint32_t bound) { return next_uint() % bound; }
bool next_bool() { return next_uint() & 1u; }
private:
std::default_random_engine generator_;
std::uniform_int_distribution<uint32_t> distribution_;
};
} // namespace
class WebAppDatabaseTest : public WebAppTest {
public:
void SetUp() override {
WebAppTest::SetUp();
test_registry_controller_ =
std::make_unique<TestWebAppRegistryController>();
test_registry_controller_->SetUp(profile());
}
static apps::FileHandlers CreateFileHandlers(uint32_t suffix) {
apps::FileHandlers file_handlers;
for (unsigned int i = 0; i < 5; ++i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
apps::FileHandler::AcceptEntry accept_entry1;
accept_entry1.mime_type = "application/" + suffix_str + "+foo";
accept_entry1.file_extensions.insert("." + suffix_str + "a");
accept_entry1.file_extensions.insert("." + suffix_str + "b");
apps::FileHandler::AcceptEntry accept_entry2;
accept_entry2.mime_type = "application/" + suffix_str + "+bar";
accept_entry2.file_extensions.insert("." + suffix_str + "a");
accept_entry2.file_extensions.insert("." + suffix_str + "b");
apps::FileHandler file_handler;
file_handler.action = GURL("https://example.com/open-" + suffix_str);
file_handler.accept.push_back(std::move(accept_entry1));
file_handler.accept.push_back(std::move(accept_entry2));
file_handlers.push_back(std::move(file_handler));
}
return file_handlers;
}
static apps::ShareTarget CreateShareTarget(uint32_t suffix) {
apps::ShareTarget share_target;
share_target.action =
GURL("https://example.com/path/target/" + base::NumberToString(suffix));
share_target.method = (suffix % 2 == 0) ? apps::ShareTarget::Method::kPost
: apps::ShareTarget::Method::kGet;
share_target.enctype = (suffix / 2 % 2 == 0)
? apps::ShareTarget::Enctype::kMultipartFormData
: apps::ShareTarget::Enctype::kFormUrlEncoded;
if (suffix % 3 != 0)
share_target.params.title = "title" + base::NumberToString(suffix);
if (suffix % 3 != 1)
share_target.params.text = "text" + base::NumberToString(suffix);
if (suffix % 3 != 2)
share_target.params.url = "url" + base::NumberToString(suffix);
for (uint32_t index = 0; index < suffix % 5; ++index) {
apps::ShareTarget::Files files;
files.name = "files" + base::NumberToString(index);
files.accept.push_back(".extension" + base::NumberToString(index));
files.accept.push_back("type/subtype" + base::NumberToString(index));
share_target.params.files.push_back(files);
}
return share_target;
}
static std::vector<apps::ProtocolHandlerInfo> CreateProtocolHandlers(
uint32_t suffix) {
std::vector<apps::ProtocolHandlerInfo> protocol_handlers;
for (unsigned int i = 0; i < 5; ++i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
apps::ProtocolHandlerInfo protocol_handler;
protocol_handler.protocol = "web+test" + suffix_str;
protocol_handler.url = GURL("https://example.com/%s");
protocol_handlers.push_back(std::move(protocol_handler));
}
return protocol_handlers;
}
static std::vector<WebApplicationShortcutsMenuItemInfo>
CreateShortcutsMenuItemInfos(const std::string& base_url, uint32_t suffix) {
std::vector<WebApplicationShortcutsMenuItemInfo> shortcuts_menu_item_infos;
for (unsigned int i = 0; i < 3; ++i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
WebApplicationShortcutsMenuItemInfo shortcut_info;
shortcut_info.url = GURL(base_url + "/shortcut" + suffix_str);
shortcut_info.name = base::UTF8ToUTF16("shortcut" + suffix_str);
for (unsigned int j = 0; j < i; ++j) {
std::string icon_suffix_str = suffix_str + base::NumberToString(j);
WebApplicationShortcutsMenuItemInfo::Icon shortcut_icon;
shortcut_icon.url =
GURL(base_url + "/shortcuts/icon" + icon_suffix_str);
shortcut_icon.square_size_px = kIconSize * (i + j);
shortcut_info.shortcut_icon_infos.emplace_back(
std::move(shortcut_icon));
}
shortcuts_menu_item_infos.emplace_back(std::move(shortcut_info));
}
return shortcuts_menu_item_infos;
}
static std::vector<std::vector<SquareSizePx>>
CreateDownloadedShortcutsMenuIconsSizes() {
std::vector<std::vector<SquareSizePx>> results;
for (unsigned int i = 0; i < 3; ++i) {
std::vector<SquareSizePx> result;
for (unsigned int j = 0; j < i; ++j) {
result.emplace_back(kIconSize * (i + j));
}
results.emplace_back(std::move(result));
}
return results;
}
static std::unique_ptr<WebApp> CreateWebApp(const std::string& base_url,
const uint32_t seed) {
RandomHelper random(seed);
const std::string seed_str = base::NumberToString(seed);
const auto start_url = base_url + seed_str;
const AppId app_id = GenerateAppIdFromURL(GURL(start_url));
const std::string name = "Name" + seed_str;
const std::string description = "Description" + seed_str;
const std::string scope = base_url + "/scope" + seed_str;
const base::Optional<SkColor> theme_color = random.next_uint();
const base::Optional<SkColor> background_color = random.next_uint();
const base::Optional<SkColor> synced_theme_color = random.next_uint();
auto app = std::make_unique<WebApp>(app_id);
// Generate all possible permutations of field values in a random way:
if (random.next_bool())
app->AddSource(Source::kSystem);
if (random.next_bool())
app->AddSource(Source::kPolicy);
if (random.next_bool())
app->AddSource(Source::kWebAppStore);
if (random.next_bool())
app->AddSource(Source::kSync);
if (random.next_bool())
app->AddSource(Source::kDefault);
// Must always be at least one source.
if (!app->HasAnySources())
app->AddSource(Source::kSync);
app->SetName(name);
app->SetDescription(description);
app->SetStartUrl(GURL(start_url));
app->SetScope(GURL(scope));
app->SetThemeColor(theme_color);
app->SetBackgroundColor(background_color);
app->SetIsLocallyInstalled(random.next_bool());
app->SetIsInSyncInstall(random.next_bool());
app->SetUserDisplayMode(random.next_bool() ? DisplayMode::kBrowser
: DisplayMode::kStandalone);
const base::Time last_launch_time =
base::Time::UnixEpoch() +
base::TimeDelta::FromMilliseconds(random.next_uint());
app->SetLastLaunchTime(last_launch_time);
const base::Time install_time =
base::Time::UnixEpoch() +
base::TimeDelta::FromMilliseconds(random.next_uint());
app->SetInstallTime(install_time);
const DisplayMode display_modes[4] = {
DisplayMode::kBrowser, DisplayMode::kMinimalUi,
DisplayMode::kStandalone, DisplayMode::kFullscreen};
app->SetDisplayMode(display_modes[random.next_uint(4)]);
// Add only unique display modes.
std::set<DisplayMode> display_mode_override;
int num_display_mode_override_tries = random.next_uint(5);
for (int i = 0; i < num_display_mode_override_tries; i++)
display_mode_override.insert(display_modes[random.next_uint(4)]);
app->SetDisplayModeOverride(std::vector<DisplayMode>(
display_mode_override.begin(), display_mode_override.end()));
const RunOnOsLoginMode run_on_os_login_modes[3] = {
RunOnOsLoginMode::kUndefined, RunOnOsLoginMode::kWindowed,
RunOnOsLoginMode::kMinimized};
app->SetRunOnOsLoginMode(run_on_os_login_modes[random.next_uint(3)]);
const SquareSizePx size = 256;
const int num_icons = random.next_uint(10);
std::vector<WebApplicationIconInfo> icon_infos(num_icons);
for (int i = 0; i < num_icons; i++) {
WebApplicationIconInfo icon;
icon.url =
GURL(base_url + "/icon" + base::NumberToString(random.next_uint()));
if (random.next_bool())
icon.square_size_px = size;
int purpose = random.next_uint(3);
if (purpose == 0)
icon.purpose = blink::Manifest::ImageResource::Purpose::ANY;
if (purpose == 1)
icon.purpose = blink::Manifest::ImageResource::Purpose::MASKABLE;
// if (purpose == 2), leave purpose unset. Should default to ANY.
icon_infos[i] = icon;
}
app->SetIconInfos(icon_infos);
if (random.next_bool())
app->SetDownloadedIconSizes(IconPurpose::ANY, {size});
if (random.next_bool())
app->SetDownloadedIconSizes(IconPurpose::MASKABLE, {size});
app->SetIsGeneratedIcon(random.next_bool());
app->SetFileHandlers(CreateFileHandlers(random.next_uint()));
if (random.next_bool())
app->SetShareTarget(CreateShareTarget(random.next_uint()));
app->SetProtocolHandlers(CreateProtocolHandlers(random.next_uint()));
const int num_additional_search_terms = random.next_uint(8);
std::vector<std::string> additional_search_terms(
num_additional_search_terms);
for (int i = 0; i < num_additional_search_terms; ++i) {
additional_search_terms[i] =
"Foo_" + seed_str + "_" + base::NumberToString(i);
}
app->SetAdditionalSearchTerms(std::move(additional_search_terms));
app->SetShortcutsMenuItemInfos(
CreateShortcutsMenuItemInfos(base_url, random.next_uint()));
app->SetDownloadedShortcutsMenuIconsSizes(
CreateDownloadedShortcutsMenuIconsSizes());
if (IsChromeOs()) {
auto chromeos_data = base::make_optional<WebAppChromeOsData>();
chromeos_data->show_in_launcher = random.next_bool();
chromeos_data->show_in_search = random.next_bool();
chromeos_data->show_in_management = random.next_bool();
chromeos_data->is_disabled = random.next_bool();
app->SetWebAppChromeOsData(std::move(chromeos_data));
}
WebApp::SyncFallbackData sync_fallback_data;
sync_fallback_data.name = "Sync" + name;
sync_fallback_data.theme_color = synced_theme_color;
sync_fallback_data.scope = app->scope();
sync_fallback_data.icon_infos = app->icon_infos();
app->SetSyncFallbackData(std::move(sync_fallback_data));
return app;
}
bool IsDatabaseRegistryEqualToRegistrar() {
Registry registry = database_factory().ReadRegistry();
return IsRegistryEqual(mutable_registrar().registry(), registry);
}
void WriteBatch(
std::unique_ptr<syncer::ModelTypeStore::WriteBatch> write_batch) {
base::RunLoop run_loop;
database_factory().store()->CommitWriteBatch(
std::move(write_batch),
base::BindLambdaForTesting(
[&](const base::Optional<syncer::ModelError>& error) {
EXPECT_FALSE(error);
run_loop.Quit();
}));
run_loop.Run();
}
Registry WriteWebApps(const std::string& base_url, int num_apps) {
Registry registry;
auto write_batch = database_factory().store()->CreateWriteBatch();
for (int i = 0; i < num_apps; ++i) {
auto app = CreateWebApp(base_url, i);
auto proto = WebAppDatabase::CreateWebAppProto(*app);
const auto app_id = app->app_id();
write_batch->WriteData(app_id, proto->SerializeAsString());
registry.emplace(app_id, std::move(app));
}
WriteBatch(std::move(write_batch));
return registry;
}
protected:
TestWebAppRegistryController& controller() {
return *test_registry_controller_;
}
TestWebAppDatabaseFactory& database_factory() {
return controller().database_factory();
}
WebAppRegistrar& registrar() { return controller().registrar(); }
WebAppRegistrarMutable& mutable_registrar() {
return controller().mutable_registrar();
}
WebAppSyncBridge& sync_bridge() { return controller().sync_bridge(); }
private:
std::unique_ptr<TestWebAppRegistryController> test_registry_controller_;
};
TEST_F(WebAppDatabaseTest, WriteAndReadRegistry) {
controller().Init();
EXPECT_TRUE(registrar().is_empty());
const int num_apps = 20;
const std::string base_url = "https://example.com/path";
auto app = CreateWebApp(base_url, 0);
auto app_id = app->app_id();
controller().RegisterApp(std::move(app));
EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar());
for (int i = 1; i <= num_apps; ++i) {
auto extra_app = CreateWebApp(base_url, i);
controller().RegisterApp(std::move(extra_app));
}
EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar());
controller().UnregisterApp(app_id);
EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar());
controller().UnregisterAll();
EXPECT_TRUE(IsDatabaseRegistryEqualToRegistrar());
}
TEST_F(WebAppDatabaseTest, WriteAndDeleteAppsWithCallbacks) {
controller().Init();
EXPECT_TRUE(registrar().is_empty());
const int num_apps = 10;
const std::string base_url = "https://example.com/path";
RegistryUpdateData::Apps apps_to_create;
std::vector<AppId> apps_to_delete;
Registry expected_registry;
for (int i = 0; i < num_apps; ++i) {
std::unique_ptr<WebApp> app = CreateWebApp(base_url, i);
apps_to_delete.push_back(app->app_id());
apps_to_create.push_back(std::move(app));
std::unique_ptr<WebApp> expected_app = CreateWebApp(base_url, i);
expected_registry.emplace(expected_app->app_id(), std::move(expected_app));
}
{
base::RunLoop run_loop;
std::unique_ptr<WebAppRegistryUpdate> update = sync_bridge().BeginUpdate();
for (std::unique_ptr<WebApp>& web_app : apps_to_create)
update->CreateApp(std::move(web_app));
sync_bridge().CommitUpdate(std::move(update),
base::BindLambdaForTesting([&](bool success) {
EXPECT_TRUE(success);
run_loop.Quit();
}));
run_loop.Run();
Registry registry_written = database_factory().ReadRegistry();
EXPECT_TRUE(IsRegistryEqual(registry_written, expected_registry));
}
{
base::RunLoop run_loop;
std::unique_ptr<WebAppRegistryUpdate> update = sync_bridge().BeginUpdate();
for (const AppId& app_id : apps_to_delete)
update->DeleteApp(app_id);
sync_bridge().CommitUpdate(std::move(update),
base::BindLambdaForTesting([&](bool success) {
EXPECT_TRUE(success);
run_loop.Quit();
}));
run_loop.Run();
Registry registry_deleted = database_factory().ReadRegistry();
EXPECT_TRUE(registry_deleted.empty());
}
}
TEST_F(WebAppDatabaseTest, OpenDatabaseAndReadRegistry) {
Registry registry = WriteWebApps("https://example.com/path", 20);
controller().Init();
EXPECT_TRUE(IsRegistryEqual(mutable_registrar().registry(), registry));
}
TEST_F(WebAppDatabaseTest, BackwardCompatibility_WebAppWithOnlyRequiredFields) {
const GURL start_url{"https://example.com/"};
const AppId app_id = GenerateAppIdFromURL(start_url);
const std::string name = "App Name";
const auto user_display_mode = DisplayMode::kBrowser;
const bool is_locally_installed = true;
std::vector<std::unique_ptr<WebAppProto>> protos;
// Create a proto with |required| only fields.
// Do not add new fields in this test: any new fields should be |optional|.
auto proto = std::make_unique<WebAppProto>();
{
sync_pb::WebAppSpecifics sync_proto;
sync_proto.set_start_url(start_url.spec());
sync_proto.set_user_display_mode(
ToWebAppSpecificsUserDisplayMode(user_display_mode));
*(proto->mutable_sync_data()) = std::move(sync_proto);
}
proto->set_name(name);
proto->set_is_locally_installed(is_locally_installed);
proto->mutable_sources()->set_system(false);
proto->mutable_sources()->set_policy(false);
proto->mutable_sources()->set_web_app_store(false);
proto->mutable_sources()->set_sync(true);
proto->mutable_sources()->set_default_(false);
if (IsChromeOs()) {
proto->mutable_chromeos_data()->set_show_in_launcher(false);
proto->mutable_chromeos_data()->set_show_in_search(false);
proto->mutable_chromeos_data()->set_show_in_management(false);
proto->mutable_chromeos_data()->set_is_disabled(true);
}
protos.push_back(std::move(proto));
database_factory().WriteProtos(protos);
// Read the registry: the proto parsing may fail while reading the proto
// above.
controller().Init();
const WebApp* app = registrar().GetAppById(app_id);
EXPECT_EQ(app_id, app->app_id());
EXPECT_EQ(start_url, app->start_url());
EXPECT_EQ(name, app->name());
EXPECT_EQ(user_display_mode, app->user_display_mode());
EXPECT_EQ(is_locally_installed, app->is_locally_installed());
EXPECT_TRUE(app->IsSynced());
EXPECT_FALSE(app->IsDefaultApp());
if (IsChromeOs()) {
EXPECT_FALSE(app->chromeos_data()->show_in_launcher);
EXPECT_FALSE(app->chromeos_data()->show_in_search);
EXPECT_FALSE(app->chromeos_data()->show_in_management);
EXPECT_TRUE(app->chromeos_data()->is_disabled);
} else {
EXPECT_FALSE(app->chromeos_data().has_value());
}
}
TEST_F(WebAppDatabaseTest, WebAppWithoutOptionalFields) {
controller().Init();
const auto start_url = GURL("https://example.com/");
const AppId app_id = GenerateAppIdFromURL(GURL(start_url));
const std::string name = "Name";
const auto user_display_mode = DisplayMode::kBrowser;
auto app = std::make_unique<WebApp>(app_id);
// Required fields:
app->SetStartUrl(start_url);
app->SetName(name);
app->SetUserDisplayMode(user_display_mode);
app->SetIsLocallyInstalled(false);
// chromeos_data should always be set on ChromeOS.
if (IsChromeOs())
app->SetWebAppChromeOsData(base::make_optional<WebAppChromeOsData>());
EXPECT_FALSE(app->HasAnySources());
for (int i = Source::kMinValue; i <= Source::kMaxValue; ++i) {
app->AddSource(static_cast<Source::Type>(i));
EXPECT_TRUE(app->HasAnySources());
}
// Let optional fields be empty:
EXPECT_EQ(app->display_mode(), DisplayMode::kUndefined);
EXPECT_TRUE(app->display_mode_override().empty());
EXPECT_TRUE(app->description().empty());
EXPECT_TRUE(app->scope().is_empty());
EXPECT_FALSE(app->theme_color().has_value());
EXPECT_FALSE(app->background_color().has_value());
EXPECT_TRUE(app->icon_infos().empty());
EXPECT_TRUE(app->downloaded_icon_sizes(IconPurpose::ANY).empty());
EXPECT_TRUE(app->downloaded_icon_sizes(IconPurpose::MASKABLE).empty());
EXPECT_FALSE(app->is_generated_icon());
EXPECT_FALSE(app->is_in_sync_install());
EXPECT_TRUE(app->sync_fallback_data().name.empty());
EXPECT_FALSE(app->sync_fallback_data().theme_color.has_value());
EXPECT_FALSE(app->sync_fallback_data().scope.is_valid());
EXPECT_TRUE(app->sync_fallback_data().icon_infos.empty());
EXPECT_TRUE(app->file_handlers().empty());
EXPECT_FALSE(app->share_target().has_value());
EXPECT_TRUE(app->additional_search_terms().empty());
EXPECT_TRUE(app->protocol_handlers().empty());
EXPECT_TRUE(app->last_launch_time().is_null());
EXPECT_TRUE(app->install_time().is_null());
EXPECT_TRUE(app->shortcuts_menu_item_infos().empty());
EXPECT_TRUE(app->downloaded_shortcuts_menu_icons_sizes().empty());
EXPECT_EQ(app->run_on_os_login_mode(), RunOnOsLoginMode::kUndefined);
controller().RegisterApp(std::move(app));
Registry registry = database_factory().ReadRegistry();
EXPECT_EQ(1UL, registry.size());
std::unique_ptr<WebApp>& app_copy = registry.at(app_id);
// Required fields were serialized:
EXPECT_EQ(app_id, app_copy->app_id());
EXPECT_EQ(start_url, app_copy->start_url());
EXPECT_EQ(name, app_copy->name());
EXPECT_EQ(user_display_mode, app_copy->user_display_mode());
EXPECT_FALSE(app_copy->is_locally_installed());
auto& chromeos_data = app_copy->chromeos_data();
if (IsChromeOs()) {
EXPECT_TRUE(chromeos_data->show_in_launcher);
EXPECT_TRUE(chromeos_data->show_in_search);
EXPECT_TRUE(chromeos_data->show_in_management);
EXPECT_FALSE(chromeos_data->is_disabled);
} else {
EXPECT_FALSE(chromeos_data.has_value());
}
for (int i = Source::kMinValue; i <= Source::kMaxValue; ++i) {
EXPECT_TRUE(app_copy->HasAnySources());
app_copy->RemoveSource(static_cast<Source::Type>(i));
}
EXPECT_FALSE(app_copy->HasAnySources());
// No optional fields.
EXPECT_EQ(app_copy->display_mode(), DisplayMode::kUndefined);
EXPECT_TRUE(app_copy->display_mode_override().empty());
EXPECT_TRUE(app_copy->description().empty());
EXPECT_TRUE(app_copy->scope().is_empty());
EXPECT_FALSE(app_copy->theme_color().has_value());
EXPECT_FALSE(app_copy->background_color().has_value());
EXPECT_TRUE(app_copy->last_launch_time().is_null());
EXPECT_TRUE(app_copy->install_time().is_null());
EXPECT_TRUE(app_copy->icon_infos().empty());
EXPECT_TRUE(app_copy->downloaded_icon_sizes(IconPurpose::ANY).empty());
EXPECT_TRUE(app_copy->downloaded_icon_sizes(IconPurpose::MASKABLE).empty());
EXPECT_FALSE(app_copy->is_generated_icon());
EXPECT_FALSE(app_copy->is_in_sync_install());
EXPECT_TRUE(app_copy->sync_fallback_data().name.empty());
EXPECT_FALSE(app_copy->sync_fallback_data().theme_color.has_value());
EXPECT_FALSE(app_copy->sync_fallback_data().scope.is_valid());
EXPECT_TRUE(app_copy->sync_fallback_data().icon_infos.empty());
EXPECT_TRUE(app_copy->file_handlers().empty());
EXPECT_FALSE(app_copy->share_target().has_value());
EXPECT_TRUE(app_copy->additional_search_terms().empty());
EXPECT_TRUE(app_copy->protocol_handlers().empty());
EXPECT_TRUE(app_copy->shortcuts_menu_item_infos().empty());
EXPECT_TRUE(app_copy->downloaded_shortcuts_menu_icons_sizes().empty());
EXPECT_EQ(app_copy->run_on_os_login_mode(), RunOnOsLoginMode::kUndefined);
}
TEST_F(WebAppDatabaseTest, WebAppWithManyIcons) {
controller().Init();
const int num_icons = 32;
const std::string base_url = "https://example.com/path";
auto app = CreateWebApp(base_url, 0);
auto app_id = app->app_id();
std::vector<WebApplicationIconInfo> icons;
std::vector<SquareSizePx> sizes;
for (int i = 1; i <= num_icons; ++i) {
WebApplicationIconInfo icon;
icon.url = GURL(base_url + "/icon" + base::NumberToString(num_icons));
// Let size equals the icon's number squared.
icon.square_size_px = i * i;
sizes.push_back(*icon.square_size_px);
icons.push_back(std::move(icon));
}
app->SetIconInfos(std::move(icons));
app->SetDownloadedIconSizes(IconPurpose::ANY, std::move(sizes));
app->SetIsGeneratedIcon(false);
controller().RegisterApp(std::move(app));
Registry registry = database_factory().ReadRegistry();
EXPECT_EQ(1UL, registry.size());
std::unique_ptr<WebApp>& app_copy = registry.at(app_id);
EXPECT_EQ(static_cast<unsigned>(num_icons), app_copy->icon_infos().size());
for (int i = 1; i <= num_icons; ++i) {
const int icon_size_in_px = i * i;
EXPECT_EQ(icon_size_in_px, app_copy->icon_infos()[i - 1].square_size_px);
}
EXPECT_FALSE(app_copy->is_generated_icon());
}
TEST_F(WebAppDatabaseTest, WebAppWithFileHandlersRoundTrip) {
controller().Init();
const std::string base_url = "https://example.com/path";
auto app = CreateWebApp(base_url, 0);
auto app_id = app->app_id();
apps::FileHandlers file_handlers;
apps::FileHandler file_handler1;
file_handler1.action = GURL("https://example.com/path/csv");
apps::FileHandler::AcceptEntry accept_csv;
accept_csv.mime_type = "text/csv";
accept_csv.file_extensions.insert(".csv");
accept_csv.file_extensions.insert(".txt");
file_handler1.accept.push_back(std::move(accept_csv));
file_handlers.push_back(std::move(file_handler1));
apps::FileHandler file_handler2;
file_handler2.action = GURL("https://example.com/path/svg");
apps::FileHandler::AcceptEntry accept_xml;
accept_xml.mime_type = "text/xml";
accept_xml.file_extensions.insert(".xml");
file_handler2.accept.push_back(std::move(accept_xml));
apps::FileHandler::AcceptEntry accept_svg;
accept_svg.mime_type = "text/xml+svg";
accept_svg.file_extensions.insert(".svg");
file_handler2.accept.push_back(std::move(accept_svg));
file_handlers.push_back(std::move(file_handler2));
app->SetFileHandlers(std::move(file_handlers));
controller().RegisterApp(std::move(app));
Registry registry = database_factory().ReadRegistry();
EXPECT_TRUE(IsRegistryEqual(mutable_registrar().registry(), registry));
}
TEST_F(WebAppDatabaseTest, WebAppWithShareTargetRoundTrip) {
controller().Init();
const std::string base_url = "https://example.com/path";
auto app = CreateWebApp(base_url, 0);
auto app_id = app->app_id();
apps::ShareTarget share_target;
share_target.action = GURL("https://example.com/path/target");
share_target.method = apps::ShareTarget::Method::kPost;
share_target.enctype = apps::ShareTarget::Enctype::kMultipartFormData;
share_target.params.title = "Title";
share_target.params.text = "Text";
share_target.params.url = "Url";
app->SetShareTarget(std::move(share_target));
controller().RegisterApp(std::move(app));
Registry registry = database_factory().ReadRegistry();
EXPECT_TRUE(IsRegistryEqual(mutable_registrar().registry(), registry));
}
} // namespace web_app