// Copyright 2018 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/file_manager/file_manager_test_util.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/test/bind.h"
#include "chrome/browser/apps/app_service/app_service_proxy_ash.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/file_manager/file_tasks.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/file_manager/volume_manager_observer.h"
#include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
#include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "components/services/app_service/public/cpp/intent_test_util.h"
#include "extensions/browser/entry_info.h"
#include "extensions/browser/extension_system.h"
#include "net/base/mime_util.h"
#include "testing/gtest/include/gtest/gtest.h"
using platform_util::OpenOperationResult;
namespace file_manager {
namespace test {
FolderInMyFiles::FolderInMyFiles(Profile* profile) : profile_(profile) {
const base::FilePath root = util::GetMyFilesFolderForProfile(profile);
VolumeManager::Get(profile)->RegisterDownloadsDirectoryForTesting(root);
base::ScopedAllowBlockingForTesting allow_blocking;
constexpr base::FilePath::CharType kPrefix[] = FILE_PATH_LITERAL("a_folder");
CHECK(CreateTemporaryDirInDir(root, kPrefix, &folder_));
}
FolderInMyFiles::~FolderInMyFiles() = default;
void FolderInMyFiles::Add(const std::vector<base::FilePath>& files) {
base::ScopedAllowBlockingForTesting allow_blocking;
for (const auto& file : files) {
files_.push_back(folder_.Append(file.BaseName()));
base::CopyFile(file, files_.back());
}
}
void FolderInMyFiles::AddWithName(const base::FilePath& file,
const base::FilePath& new_base_name) {
base::ScopedAllowBlockingForTesting allow_blocking;
files_.push_back(folder_.Append(new_base_name));
base::CopyFile(file, files_.back());
}
OpenOperationResult FolderInMyFiles::Open(const base::FilePath& file) {
const auto& it =
base::ranges::find(files_, file.BaseName(), &base::FilePath::BaseName);
EXPECT_FALSE(it == files_.end());
if (it == files_.end()) {
return platform_util::OPEN_FAILED_PATH_NOT_FOUND;
}
const base::FilePath& path = *it;
base::RunLoop run_loop;
OpenOperationResult open_result;
platform_util::OpenItem(
profile_, path, platform_util::OPEN_FILE,
base::BindLambdaForTesting([&](OpenOperationResult result) {
open_result = result;
run_loop.Quit();
}));
run_loop.Run();
return open_result;
}
void FolderInMyFiles::Refresh() {
constexpr bool kRecursive = false;
files_.clear();
base::ScopedAllowBlockingForTesting allow_blocking;
base::FileEnumerator e(folder_, kRecursive, base::FileEnumerator::FILES);
for (base::FilePath path = e.Next(); !path.empty(); path = e.Next()) {
files_.push_back(path);
}
std::sort(files_.begin(), files_.end(),
[](const base::FilePath& l, const base::FilePath& r) {
return l.BaseName() < r.BaseName();
});
}
std::vector<storage::FileSystemURL> CopyTestFilesIntoMyFiles(
Profile* profile,
std::vector<std::string> file_names) {
FolderInMyFiles folder(profile);
base::FilePath test_data_path;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
for (const auto& file_name : file_names) {
base::FilePath file_path =
test_data_path.AppendASCII("chromeos/file_manager/" + file_name);
{
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(file_path));
}
// Copy the file into MyFiles.
folder.Add({file_path});
}
std::vector<storage::FileSystemURL> files;
for (const auto& path_in_my_files : folder.files()) {
GURL url;
CHECK(util::ConvertAbsoluteFilePathToFileSystemUrl(
profile, path_in_my_files, util::GetFileManagerURL(), &url));
auto* file_system_context = util::GetFileManagerFileSystemContext(profile);
files.push_back(file_system_context->CrackURLInFirstPartyContext(url));
}
return files;
}
void AddDefaultComponentExtensionsOnMainThread(Profile* profile) {
CHECK(profile);
extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
extensions::ExtensionService* service =
extensions::ExtensionSystem::Get(profile)->extension_service();
service->component_loader()->AddDefaultComponentExtensions(false);
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// QuickOffice loads from rootfs at /usr/share/chromeos-assets/quickoffce
// which does not exist on bots for tests, so load test version.
base::FilePath data_dir;
CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &data_dir));
base::RunLoop run_loop;
service->component_loader()->AddComponentFromDirWithManifestFilename(
data_dir.Append("chromeos/file_manager/quickoffice"),
extension_misc::kQuickOfficeComponentExtensionId,
extensions::kManifestFilename, extensions::kManifestFilename,
run_loop.QuitClosure());
run_loop.Run();
#endif
// AddDefaultComponentExtensions() is normally invoked during
// ExtensionService::Init() which also invokes UninstallMigratedExtensions().
// Invoke it here as well, otherwise migrated extensions will remain installed
// for the duration of the test. Note this may result in immediately
// uninstalling an extension just installed above.
service->UninstallMigratedExtensionsForTest();
}
namespace {
// Helper to exit a RunLoop the next time OnVolumeMounted() is invoked.
class VolumeWaiter : public VolumeManagerObserver {
public:
VolumeWaiter(Profile* profile, const base::RepeatingClosure& on_mount)
: profile_(profile), on_mount_(on_mount) {
VolumeManager::Get(profile_)->AddObserver(this);
}
~VolumeWaiter() override {
VolumeManager::Get(profile_)->RemoveObserver(this);
}
void OnVolumeMounted(ash::MountError error_code,
const Volume& volume) override {
on_mount_.Run();
}
private:
raw_ptr<Profile> profile_;
base::RepeatingClosure on_mount_;
};
} // namespace
scoped_refptr<const extensions::Extension> InstallTestingChromeApp(
Profile* profile,
const char* test_path_ascii) {
base::ScopedAllowBlockingForTesting allow_io;
extensions::ChromeTestExtensionLoader loader(profile);
base::FilePath path;
CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &path));
path = path.AppendASCII(test_path_ascii);
// ChromeTestExtensionLoader waits for the background page to load before
// returning.
auto extension = loader.LoadExtension(path);
CHECK(extension);
return extension;
}
base::WeakPtr<file_manager::Volume> InstallFileSystemProviderChromeApp(
Profile* profile) {
return InstallFileSystemProviderChromeApp(
profile, base::BindOnce(&InstallTestingChromeApp, profile)
.Then(base::BindOnce(
[](scoped_refptr<const extensions::Extension>) {})));
}
base::WeakPtr<file_manager::Volume> InstallFileSystemProviderChromeApp(
Profile* profile,
base::OnceCallback<void(const char*)> install_fn) {
static constexpr char kFileSystemProviderFilesystemId[] =
"test-image-provider-fs";
base::RunLoop run_loop;
// Incognito profiles don't have VolumeManager events (so a
// file_manager::test::VolumeWaiter won't work with them). Switch to the
// original profile in that case. For regular (not incognito) profiles,
// p->GetOriginalProfile() returns p.
Profile* profile_with_volume_manager_events = profile->GetOriginalProfile();
file_manager::test::VolumeWaiter waiter(profile_with_volume_manager_events,
run_loop.QuitClosure());
std::move(install_fn).Run("extensions/api_test/file_browser/image_provider");
run_loop.Run();
auto* volume_manager =
file_manager::VolumeManager::Get(profile_with_volume_manager_events);
CHECK(volume_manager);
base::WeakPtr<file_manager::Volume> volume;
for (auto& v : volume_manager->GetVolumeList()) {
if (v->file_system_id() == kFileSystemProviderFilesystemId) {
volume = v;
}
}
CHECK(volume);
return volume;
}
std::vector<file_tasks::FullTaskDescriptor> GetTasksForFile(
Profile* profile,
const base::FilePath& file) {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string mime_type;
net::GetMimeTypeFromFile(file, &mime_type);
CHECK(!mime_type.empty());
std::vector<extensions::EntryInfo> entries;
entries.emplace_back(file, mime_type, false);
// Use fake URLs in this helper. This is needed because FindAppServiceTasks
// uses the file extension found in the URL to do file extension matching.
std::vector<GURL> file_urls;
GURL url = GURL(base::JoinString(
{"filesystem:https://site.com/isolated/foo", file.Extension()}, ""));
CHECK(url.is_valid());
file_urls.emplace_back(url);
std::vector<file_tasks::FullTaskDescriptor> result;
bool invoked_synchronously = false;
auto callback = base::BindLambdaForTesting(
[&](std::unique_ptr<file_tasks::ResultingTasks> resulting_tasks) {
result = std::move(resulting_tasks->tasks);
invoked_synchronously = true;
});
FindAllTypesOfTasks(profile, entries, file_urls, {""}, callback);
// MIME sniffing requires a run loop, but the mime type must be explicitly
// available, and is provided in this helper.
CHECK(invoked_synchronously);
return result;
}
void AddFakeAppWithIntentFilters(
const std::string& app_id,
std::vector<apps::IntentFilterPtr> intent_filters,
apps::AppType app_type,
std::optional<bool> handles_intents,
apps::AppServiceProxy* app_service_proxy) {
std::vector<apps::AppPtr> apps;
auto app = std::make_unique<apps::App>(app_type, app_id);
app->app_id = app_id;
app->app_type = app_type;
app->handles_intents = handles_intents;
app->readiness = apps::Readiness::kReady;
app->intent_filters = std::move(intent_filters);
apps.push_back(std::move(app));
app_service_proxy->OnApps(std::move(apps), app_type,
false /* should_notify_initialized */);
}
void AddFakeWebApp(const std::string& app_id,
const std::string& mime_type,
const std::string& file_extension,
const std::string& activity_label,
std::optional<bool> handles_intents,
apps::AppServiceProxy* app_service_proxy) {
std::vector<apps::IntentFilterPtr> filters;
filters.push_back(apps_util::MakeFileFilterForView(mime_type, file_extension,
activity_label));
AddFakeAppWithIntentFilters(app_id, std::move(filters), apps::AppType::kWeb,
handles_intents, app_service_proxy);
}
FakeSimpleDriveFs::FakeSimpleDriveFs(const base::FilePath& mount_path)
: drivefs::FakeDriveFs(mount_path) {}
FakeSimpleDriveFs::~FakeSimpleDriveFs() = default;
void FakeSimpleDriveFs::SetMetadata(const drivefs::FakeMetadata& metadata) {
alternate_urls_[metadata.path] = metadata.alternate_url;
}
void FakeSimpleDriveFs::GetMetadata(const base::FilePath& path,
GetMetadataCallback callback) {
if (!alternate_urls_.count(path)) {
std::move(callback).Run(drive::FILE_ERROR_NOT_FOUND, nullptr);
return;
}
auto metadata = drivefs::mojom::FileMetadata::New();
metadata->alternate_url = alternate_urls_[path];
// Fill the rest of `metadata` with default values.
metadata->content_mime_type = "";
const drivefs::mojom::Capabilities& capabilities = {};
metadata->capabilities = capabilities.Clone();
metadata->folder_feature = {};
metadata->available_offline = false;
metadata->shared = false;
std::move(callback).Run(drive::FILE_ERROR_OK, std::move(metadata));
}
FakeSimpleDriveFsHelper::FakeSimpleDriveFsHelper(
Profile* profile,
const base::FilePath& mount_path)
: drive::FakeDriveFsHelper(profile, mount_path),
mount_path_(mount_path),
fake_drivefs_(mount_path_) {}
base::RepeatingCallback<std::unique_ptr<drivefs::DriveFsBootstrapListener>()>
FakeSimpleDriveFsHelper::CreateFakeDriveFsListenerFactory() {
return base::BindRepeating(&drivefs::FakeDriveFs::CreateMojoListener,
base::Unretained(&fake_drivefs_));
}
FakeProvidedFileSystemOneDrive::FakeProvidedFileSystemOneDrive(
const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info)
: FakeProvidedFileSystem(file_system_info) {}
ash::file_system_provider::AbortCallback
FakeProvidedFileSystemOneDrive::CreateFile(
const base::FilePath& file_path,
storage::AsyncFileUtil::StatusCallback callback) {
if (create_file_callback_) {
std::move(create_file_callback_).Run();
}
if (create_file_error_ != base::File::Error::FILE_OK) {
std::move(callback).Run(create_file_error_);
return ash::file_system_provider::AbortCallback();
}
return FakeProvidedFileSystem::CreateFile(file_path, std::move(callback));
}
void FakeProvidedFileSystemOneDrive::SetCreateFileError(
base::File::Error error) {
create_file_error_ = error;
}
void FakeProvidedFileSystemOneDrive::SetCreateFileCallback(
base::OnceClosure callback) {
create_file_callback_ = std::move(callback);
}
void FakeProvidedFileSystemOneDrive::SetGetActionsError(
base::File::Error error) {
get_actions_error_ = error;
}
void FakeProvidedFileSystemOneDrive::SetReauthenticationRequired(
bool reauthentication_required) {
reauthentication_required_ = reauthentication_required;
}
ash::file_system_provider::AbortCallback
FakeProvidedFileSystemOneDrive::GetActions(
const std::vector<base::FilePath>& entry_paths,
GetActionsCallback callback) {
ash::file_system_provider::Actions actions;
// Expect only single entry requests.
if (entry_paths.size() != 1) {
std::move(callback).Run(actions, base::File::FILE_ERROR_NOT_FOUND);
return ash::file_system_provider::AbortCallback();
}
// If root requested, return ODFS metadata.
if (entry_paths[0].value() == ash::cloud_upload::kODFSMetadataQueryPath) {
actions.push_back(
{ash::cloud_upload::kUserEmailActionId, kSampleUserEmail1});
actions.push_back({ash::cloud_upload::kReauthenticationRequiredId,
reauthentication_required_ ? "true" : "false"});
actions.push_back(
{ash::cloud_upload::kAccountStateId,
reauthentication_required_ ? "REAUTHENTICATION_REQUIRED" : "NORMAL"});
std::move(callback).Run(actions, base::File::FILE_OK);
return ash::file_system_provider::AbortCallback();
}
// If `get_actions_error_` set, mock error for non-root entry.
if (get_actions_error_ != base::File::Error::FILE_OK) {
std::move(callback).Run(actions, get_actions_error_);
return ash::file_system_provider::AbortCallback();
}
ash::file_system_provider::FakeEntry* entry = GetEntry(entry_paths[0]);
// If no entry exists, return error.
if (!entry) {
std::move(callback).Run(actions, base::File::FILE_ERROR_NOT_FOUND);
return ash::file_system_provider::AbortCallback();
}
// Otherwise, return `kODFSSampleUrl`.
actions.push_back({ash::cloud_upload::kOneDriveUrlActionId, kODFSSampleUrl});
std::move(callback).Run(actions, base::File::FILE_OK);
return ash::file_system_provider::AbortCallback();
}
std::unique_ptr<ash::file_system_provider::ProviderInterface>
FakeExtensionProviderOneDrive::Create(
const extensions::ExtensionId& extension_id) {
ash::file_system_provider::Capabilities default_capabilities(
false, false, false, extensions::SOURCE_NETWORK);
return std::unique_ptr<ProviderInterface>(
new FakeExtensionProviderOneDrive(extension_id, default_capabilities));
}
std::unique_ptr<ash::file_system_provider::ProvidedFileSystemInterface>
FakeExtensionProviderOneDrive::CreateProvidedFileSystem(
Profile* profile,
const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
ash::file_system_provider::CacheManager* cache_manager) {
DCHECK(profile);
std::unique_ptr<FakeProvidedFileSystemOneDrive> fake_provided_file_system =
std::make_unique<FakeProvidedFileSystemOneDrive>(file_system_info);
return fake_provided_file_system;
}
bool FakeExtensionProviderOneDrive::RequestMount(
Profile* profile,
ash::file_system_provider::RequestMountCallback callback) {
if (request_mount_impl_) {
std::move(request_mount_impl_).Run(std::move(callback));
return true;
}
return ash::file_system_provider::FakeExtensionProvider::RequestMount(
profile, std::move(callback));
}
void FakeExtensionProviderOneDrive::SetRequestMountImpl(
base::OnceCallback<void(ash::file_system_provider::RequestMountCallback)>
callback) {
request_mount_impl_ = std::move(callback);
}
FakeExtensionProviderOneDrive::FakeExtensionProviderOneDrive(
const extensions::ExtensionId& extension_id,
const ash::file_system_provider::Capabilities& capabilities)
: FakeExtensionProvider(extension_id, capabilities) {}
FakeExtensionProviderOneDrive::~FakeExtensionProviderOneDrive() = default;
FakeProvidedFileSystemOneDrive::~FakeProvidedFileSystemOneDrive() = default;
ash::file_system_provider::ProvidedFileSystemInterface* MountProvidedFileSystem(
Profile* profile,
const extensions::ExtensionId& extension_id,
ash::file_system_provider::MountOptions options,
std::unique_ptr<ash::file_system_provider::ProviderInterface> provider) {
// Create a fake provided file system.
ash::file_system_provider::Service* service =
ash::file_system_provider::Service::Get(profile);
service->RegisterProvider(std::move(provider));
ash::file_system_provider::ProviderId provider_id =
ash::file_system_provider::ProviderId::CreateFromExtensionId(
extension_id);
EXPECT_EQ(base::File::FILE_OK,
service->MountFileSystem(provider_id, options));
// Get a pointer to the provided file system.
std::vector<ash::file_system_provider::ProvidedFileSystemInfo> file_systems =
service->GetProvidedFileSystemInfoList(provider_id);
return service->GetProvidedFileSystem(provider_id, options.file_system_id);
}
ash::file_system_provider::ProviderInterface* GetProvider(
Profile* profile,
const extensions::ExtensionId& extension_id) {
ash::file_system_provider::Service* service =
ash::file_system_provider::Service::Get(profile);
ash::file_system_provider::ProviderId provider_id =
ash::file_system_provider::ProviderId::CreateFromExtensionId(
extension_id);
return service->GetProvider(provider_id);
}
FakeProvidedFileSystemOneDrive* MountFakeProvidedFileSystemOneDrive(
Profile* profile) {
ash::file_system_provider::MountOptions options(/*file_system_id=*/"odfs",
/*display_name=*/"ODFS");
std::unique_ptr<ash::file_system_provider::ProviderInterface> provider =
FakeExtensionProviderOneDrive::Create(extension_misc::kODFSExtensionId);
return static_cast<test::FakeProvidedFileSystemOneDrive*>(
MountProvidedFileSystem(profile, extension_misc::kODFSExtensionId,
options, std::move(provider)));
}
FakeExtensionProviderOneDrive* GetFakeProviderOneDrive(Profile* profile) {
return static_cast<FakeExtensionProviderOneDrive*>(
GetProvider(profile, extension_misc::kODFSExtensionId));
}
} // namespace test
} // namespace file_manager