// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/ash/crosapi/test/ash_crosapi_tests_env.h"
#include "chrome/browser/ash/crosapi/test/crosapi_test_base.h"
#include "chromeos/crosapi/mojom/app_service.mojom.h"
#include "chromeos/crosapi/mojom/app_service_types.mojom.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/crosapi/mojom/file_manager.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
namespace crosapi {
namespace {
constexpr char kFakeChromeAppName[] = "fake-chrome-app";
class FileManagerCrosapiTestCommandLineModifier
: public AshCrosapiTestCommandLineModifierDelegate {
public:
FileManagerCrosapiTestCommandLineModifier() = default;
FileManagerCrosapiTestCommandLineModifier(
const FileManagerCrosapiTestCommandLineModifier&) = delete;
FileManagerCrosapiTestCommandLineModifier& operator=(
const FileManagerCrosapiTestCommandLineModifier&) = delete;
~FileManagerCrosapiTestCommandLineModifier() override = default;
void AddExtraCommandLine(base::CommandLine* command_line) override {
// This switch allows app installation.
command_line->AppendSwitchASCII(switches::kEnableFeatures,
"WebAppsCrosapi");
}
};
class FileManagerCrosapiTest : public CrosapiTestBase {
public:
FileManagerCrosapiTest()
: CrosapiTestBase(
std::make_unique<FileManagerCrosapiTestCommandLineModifier>()) {}
protected:
void SetUp() override {
app_publisher_ = BindCrosapiInterface(&mojom::Crosapi::BindWebAppPublisher);
file_manager_ = BindCrosapiInterface(&mojom::Crosapi::BindFileManager);
}
const base::FilePath GetMyFilesPath() {
return GetUserDataDir().Append("test-user").Append("MyFiles");
}
mojo::Remote<mojom::AppPublisher> app_publisher_;
mojo::Remote<mojom::FileManager> file_manager_;
};
TEST_F(FileManagerCrosapiTest, ShowItemInFolder) {
// A non-existent path.
const base::FilePath bad_path("/does/not/exist");
base::test::TestFuture<mojom::OpenResult> future1;
file_manager_->ShowItemInFolder(bad_path, future1.GetCallback());
EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedPathNotFound);
// A valid folder.
base::test::TestFuture<mojom::OpenResult> future2;
file_manager_->ShowItemInFolder(GetMyFilesPath(), future2.GetCallback());
EXPECT_EQ(future2.Get(), mojom::OpenResult::kSucceeded);
// A valid file.
const base::FilePath file_path = GetMyFilesPath().Append("test_file.txt");
{
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
EXPECT_TRUE(base::WriteFile(file_path, ""));
}
absl::Cleanup scoped_cleanup = [&] {
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
EXPECT_TRUE(base::DeleteFile(file_path));
};
base::test::TestFuture<mojom::OpenResult> future3;
file_manager_->ShowItemInFolder(file_path, future3.GetCallback());
EXPECT_EQ(future3.Get(), mojom::OpenResult::kSucceeded);
}
TEST_F(FileManagerCrosapiTest, OpenFolder) {
// A non-existent path.
const base::FilePath bad_path("/does/not/exist");
base::test::TestFuture<mojom::OpenResult> future1;
file_manager_->OpenFolder(bad_path, future1.GetCallback());
EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedPathNotFound);
// A valid folder.
base::test::TestFuture<mojom::OpenResult> future2;
file_manager_->OpenFolder(GetMyFilesPath(), future2.GetCallback());
EXPECT_EQ(future2.Get(), mojom::OpenResult::kSucceeded);
// A valid file.
const base::FilePath file_path = GetMyFilesPath().Append("test_file.txt");
{
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
EXPECT_TRUE(base::WriteFile(file_path, ""));
}
absl::Cleanup scoped_cleanup = [&] {
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
EXPECT_TRUE(base::DeleteFile(file_path));
};
base::test::TestFuture<mojom::OpenResult> future3;
file_manager_->OpenFolder(file_path, future3.GetCallback());
EXPECT_EQ(future3.Get(), mojom::OpenResult::kFailedInvalidType);
}
TEST_F(FileManagerCrosapiTest, OpenFile) {
// A non-existent path.
const base::FilePath bad_path("/does/not/exist");
base::test::TestFuture<mojom::OpenResult> future1;
file_manager_->OpenFile(bad_path, future1.GetCallback());
EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedPathNotFound);
// A valid folder.
base::test::TestFuture<mojom::OpenResult> future2;
file_manager_->OpenFile(GetMyFilesPath(), future2.GetCallback());
EXPECT_EQ(future2.Get(), mojom::OpenResult::kFailedInvalidType);
}
// TODO(crbug.com/40922738): Re-enable this test
TEST_F(FileManagerCrosapiTest, DISABLED_OpenFileWithAppInstalled) {
const base::FilePath pakfile_path = GetMyFilesPath().Append("test_file.pak");
{
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
EXPECT_TRUE(base::WriteFile(pakfile_path, ""));
}
absl::Cleanup scoped_cleanup = [&] {
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
EXPECT_TRUE(base::DeleteFile(pakfile_path));
};
// A valid file but there is no application to open .pak file.
base::test::TestFuture<mojom::OpenResult> future1;
file_manager_->OpenFile(pakfile_path, future1.GetCallback());
EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedNoHandlerForFileType);
// Register a stub AppController here, so that the registered apps will be
// published to the subscribers. See WebAppServiceAsh::OnApps for more detail.
mojo::PendingReceiver<crosapi::mojom::AppController> pending_receiver;
app_publisher_->RegisterAppController(
pending_receiver.InitWithNewPipeAndPassRemote());
// Install an application to open .pak file.
std::vector<apps::AppPtr> apps1;
apps::AppPtr app =
std::make_unique<apps::App>(apps::AppType::kWeb, kFakeChromeAppName);
apps::ConditionValues values;
values.push_back(std::make_unique<apps::ConditionValue>(
"pak", apps::PatternMatchType::kFileExtension));
apps::IntentFilterPtr intent_filter = std::make_unique<apps::IntentFilter>();
intent_filter->conditions.push_back(std::make_unique<apps::Condition>(
apps::ConditionType::kFile, std::move(values)));
apps::IntentFilters filters;
filters.push_back(std::move(intent_filter));
app->intent_filters = std::move(filters);
app->readiness = apps::Readiness::kReady;
app->handles_intents = true;
apps1.push_back(std::move(app));
app_publisher_->OnApps(std::move(apps1));
// A valid .pak file and the app which can handle .pak file exists.
base::test::TestFuture<mojom::OpenResult> future2;
file_manager_->OpenFile(pakfile_path, future2.GetCallback());
EXPECT_EQ(future2.Get(), mojom::OpenResult::kSucceeded);
// Remove the installed app.
std::vector<apps::AppPtr> apps2;
// Uninstall the app before removed.
apps::AppPtr app_uninstall =
std::make_unique<apps::App>(apps::AppType::kWeb, kFakeChromeAppName);
app_uninstall->readiness = apps::Readiness::kUninstalledByUser;
apps2.push_back(std::move(app_uninstall));
apps::AppPtr app_remove =
std::make_unique<apps::App>(apps::AppType::kWeb, kFakeChromeAppName);
app_remove->readiness = apps::Readiness::kRemoved;
apps2.push_back(std::move(app_remove));
app_publisher_->OnApps(std::move(apps2));
// A valid file but there is no application to open .pak file.
base::test::TestFuture<mojom::OpenResult> future3;
file_manager_->OpenFile(pakfile_path, future3.GetCallback());
EXPECT_EQ(future3.Get(), mojom::OpenResult::kFailedNoHandlerForFileType);
}
} // namespace
} // namespace crosapi