blob: 4ad1f011c7b4bda4c36572d087c65271725a5f27 [file] [log] [blame]
// Copyright 2014 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/chromeos/file_manager/fileapi_util.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/file_system_provider/fake_extension_provider.h"
#include "chrome/browser/chromeos/file_system_provider/service.h"
#include "chrome/browser/chromeos/file_system_provider/service_factory.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/test/async_file_test_helper.h"
#include "storage/browser/test/test_file_system_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "ui/shell_dialogs/selected_file_info.h"
namespace file_manager {
namespace util {
namespace {
// Helper class that sets up a temporary file system.
class TempFileSystem {
public:
TempFileSystem(Profile* profile, const std::string& extension_id)
: name_(base::UnguessableToken::Create().ToString()),
extension_id_(extension_id),
origin_(url::Origin::Create(
extensions::Extension::GetBaseURLFromExtensionId(extension_id))),
file_system_context_(
GetFileSystemContextForExtensionId(profile, extension_id)) {}
~TempFileSystem() {
storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(name_);
}
// Finishes setting up the temporary file system. Must be called before use.
bool SetUp() {
if (!temp_dir_.CreateUniqueTempDir()) {
return false;
}
if (!storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
name_, storage::kFileSystemTypeLocal,
storage::FileSystemMountOption(), temp_dir_.GetPath())) {
return false;
}
// Grant the test extension the ability to access the just created
// file system.
file_system_context_->external_backend()->GrantFileAccessToExtension(
extension_id_, base::FilePath(name_));
return true;
}
// For the given FileSystemURL creates a file.
base::File::Error CreateFile(const storage::FileSystemURL& url) {
return storage::AsyncFileTestHelper::CreateFile(file_system_context_, url);
}
// Creates an external file system URL for the given path.
storage::FileSystemURL CreateFileSystemURL(const std::string& path) {
return file_system_context_->CreateCrackedFileSystemURL(
origin_, storage::kFileSystemTypeExternal,
base::FilePath().Append(name_).Append(
base::FilePath::FromUTF8Unsafe(path)));
}
const url::Origin origin() const { return origin_; }
private:
const std::string name_;
const std::string extension_id_;
const url::Origin origin_;
storage::FileSystemContext* const file_system_context_;
base::ScopedTempDir temp_dir_;
};
class FileManagerFileAPIUtilTest : public ::testing::Test {
public:
FileManagerFileAPIUtilTest() {}
void SetUp() override {
testing::Test::SetUp();
profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(profile_manager_->SetUp());
profile_ = profile_manager_->CreateTestingProfile("testing_profile");
}
void TearDown() override {
profile_manager_->DeleteAllTestingProfiles();
profile_ = nullptr;
profile_manager_.reset();
}
TestingProfile* GetProfile() { return profile_; }
protected:
const std::string extension_id_ = "abc";
const std::string file_system_id_ = "test-filesystem";
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfileManager> profile_manager_;
TestingProfile* profile_;
};
// Passes the |result| to the |output| pointer.
void PassFileChooserFileInfoList(FileChooserFileInfoList* output,
FileChooserFileInfoList result) {
for (const auto& file : result)
output->push_back(file->Clone());
}
TEST_F(FileManagerFileAPIUtilTest,
ConvertSelectedFileInfoListToFileChooserFileInfoList) {
Profile* const profile = GetProfile();
auto fake_provider =
chromeos::file_system_provider::FakeExtensionProvider::Create(
extension_id_);
const auto kProviderId = fake_provider->GetId();
auto* service = chromeos::file_system_provider::Service::Get(profile);
service->RegisterProvider(std::move(fake_provider));
service->MountFileSystem(kProviderId,
chromeos::file_system_provider::MountOptions(
file_system_id_, "Test FileSystem"));
// Obtain the file system context.
content::StoragePartition* const partition =
content::BrowserContext::GetStoragePartitionForUrl(
profile, GURL("http://example.com"));
ASSERT_TRUE(partition);
storage::FileSystemContext* const context = partition->GetFileSystemContext();
ASSERT_TRUE(context);
// Prepare the test input.
SelectedFileInfoList selected_info_list;
// Native file.
{
ui::SelectedFileInfo info;
info.file_path = base::FilePath(FILE_PATH_LITERAL("/native/File 1.txt"));
info.local_path = base::FilePath(FILE_PATH_LITERAL("/native/File 1.txt"));
info.display_name = "display_name";
selected_info_list.push_back(info);
}
const std::string path = FILE_PATH_LITERAL(base::StrCat(
{"/provided/", extension_id_, ":", file_system_id_, ":/hello.txt"}));
// Non-native file with cache.
{
ui::SelectedFileInfo info;
info.file_path = base::FilePath(path);
info.local_path = base::FilePath(FILE_PATH_LITERAL("/native/cache/xxx"));
info.display_name = "display_name";
selected_info_list.push_back(info);
}
// Non-native file without.
{
ui::SelectedFileInfo info;
info.file_path = base::FilePath(path);
selected_info_list.push_back(info);
}
// Run the test target.
FileChooserFileInfoList result;
ConvertSelectedFileInfoListToFileChooserFileInfoList(
context, GURL("http://example.com"), selected_info_list,
base::BindOnce(&PassFileChooserFileInfoList, &result));
content::RunAllTasksUntilIdle();
// Check the result.
ASSERT_EQ(3u, result.size());
EXPECT_TRUE(result[0]->is_native_file());
EXPECT_EQ(FILE_PATH_LITERAL("/native/File 1.txt"),
result[0]->get_native_file()->file_path.value());
EXPECT_EQ(u"display_name", result[0]->get_native_file()->display_name);
EXPECT_TRUE(result[1]->is_native_file());
EXPECT_EQ(FILE_PATH_LITERAL("/native/cache/xxx"),
result[1]->get_native_file()->file_path.value());
EXPECT_EQ(u"display_name", result[1]->get_native_file()->display_name);
EXPECT_TRUE(result[2]->is_file_system());
EXPECT_TRUE(result[2]->get_file_system()->url.is_valid());
const storage::FileSystemURL url =
context->CrackURL(result[2]->get_file_system()->url);
EXPECT_EQ(GURL("http://example.com"), url.origin().GetURL());
EXPECT_EQ(storage::kFileSystemTypeIsolated, url.mount_type());
EXPECT_EQ(storage::kFileSystemTypeProvided, url.type());
EXPECT_EQ(55u, result[2]->get_file_system()->length);
}
TEST_F(FileManagerFileAPIUtilTest,
ConvertFileDefinitionListToEntryDefinitionListSuccess) {
base::RunLoop run_loop;
EntryDefinitionListCallback callback = base::BindOnce(
[](base::OnceClosure quit_closure,
std::unique_ptr<EntryDefinitionList> entries) {
ASSERT_EQ(2, entries->size());
EXPECT_EQ(base::File::FILE_OK, entries->at(0).error);
EXPECT_EQ(base::File::FILE_OK, entries->at(1).error);
std::move(quit_closure).Run();
},
run_loop.QuitClosure());
Profile* const profile = GetProfile();
TempFileSystem temp_file_system(profile, extension_id_);
ASSERT_TRUE(temp_file_system.SetUp());
// Create two external FileSystemURL objects for the test extension that
// reference actual files created on the temporary file system.
storage::FileSystemURL x_file_url =
temp_file_system.CreateFileSystemURL("x.txt");
storage::FileSystemURL y_file_url =
temp_file_system.CreateFileSystemURL("y.txt");
// Create the underlying files referenced by the above created FileSystemURLs.
ASSERT_EQ(base::File::FILE_OK, temp_file_system.CreateFile(x_file_url));
ASSERT_EQ(base::File::FILE_OK, temp_file_system.CreateFile(y_file_url));
FileDefinition x_fd = {.virtual_path = x_file_url.virtual_path()},
y_fd = {.virtual_path = y_file_url.virtual_path()};
ConvertFileDefinitionListToEntryDefinitionList(
file_manager::util::GetFileSystemContextForExtensionId(profile,
extension_id_),
temp_file_system.origin(), {x_fd, y_fd}, std::move(callback));
run_loop.Run();
}
TEST_F(FileManagerFileAPIUtilTest,
ConvertFileDefinitionListToEntryDefinitionListNotFound) {
base::RunLoop run_loop;
EntryDefinitionListCallback callback = base::BindOnce(
[](base::OnceClosure quit_closure,
std::unique_ptr<EntryDefinitionList> entries) {
ASSERT_EQ(1, entries->size());
EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, entries->at(0).error);
std::move(quit_closure).Run();
},
run_loop.QuitClosure());
TempFileSystem temp_file_system(GetProfile(), extension_id_);
storage::FileSystemURL x_file_url = storage::FileSystemURL::CreateForTest(
temp_file_system.origin(), storage::kFileSystemTypeExternal,
base::FilePath().Append("not-found"));
FileDefinition x_fd = {.virtual_path = x_file_url.virtual_path()};
ConvertFileDefinitionListToEntryDefinitionList(
file_manager::util::GetFileSystemContextForExtensionId(GetProfile(),
extension_id_),
temp_file_system.origin(), {x_fd}, std::move(callback));
run_loop.Run();
}
TEST_F(FileManagerFileAPIUtilTest,
ConvertFileDefinitionListToEntryDefinitionNullContext) {
base::RunLoop run_loop;
EntryDefinitionListCallback callback = base::BindOnce(
[](base::OnceClosure quit_closure,
std::unique_ptr<EntryDefinitionList> entries) {
ASSERT_EQ(1, entries->size());
EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
entries->at(0).error);
std::move(quit_closure).Run();
},
run_loop.QuitClosure());
TempFileSystem temp_file_system(GetProfile(), extension_id_);
ASSERT_TRUE(temp_file_system.SetUp());
storage::FileSystemURL x_file_url = temp_file_system.CreateFileSystemURL(".");
FileDefinition x_fd = {.virtual_path = x_file_url.virtual_path()};
// Check a simple case where the context is already null before we have
// a chance to call the conversion function.
ConvertFileDefinitionListToEntryDefinitionList(
nullptr, temp_file_system.origin(), {x_fd}, std::move(callback));
run_loop.Run();
}
TEST_F(FileManagerFileAPIUtilTest,
ConvertFileDefinitionListToEntryDefinitionContextReset) {
base::RunLoop run_loop;
EntryDefinitionListCallback callback = base::BindOnce(
[](base::OnceClosure quit_closure,
std::unique_ptr<EntryDefinitionList> entries) {
ASSERT_EQ(1, entries->size());
EXPECT_EQ(base::File::FILE_OK, entries->at(0).error);
std::move(quit_closure).Run();
},
run_loop.QuitClosure());
TempFileSystem temp_file_system(GetProfile(), extension_id_);
ASSERT_TRUE(temp_file_system.SetUp());
storage::FileSystemURL x_file_url = temp_file_system.CreateFileSystemURL(".");
FileDefinition x_fd = {.virtual_path = x_file_url.virtual_path()};
scoped_refptr<storage::FileSystemContext> file_system_context =
file_manager::util::GetFileSystemContextForExtensionId(GetProfile(),
extension_id_);
// Check the case where the context is not null, but is reset to null as
// soon as function call is completed. Conversion takes place on a
// different thread, after the function call returns. However, since
// it holds to a copy of a scoped pointer we expect it to succeed.
ConvertFileDefinitionListToEntryDefinitionList(file_system_context,
temp_file_system.origin(),
{x_fd}, std::move(callback));
file_system_context.reset();
run_loop.Run();
}
} // namespace
} // namespace util
} // namespace file_manager