| // 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/extensions/file_manager/event_router.h" |
| |
| #include <memory> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/rand_util.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/extensions/file_manager/event_router_factory.h" |
| #include "chrome/browser/ash/file_manager/io_task.h" |
| #include "chrome/browser/ash/file_manager/path_util.h" |
| #include "chrome/browser/ash/file_manager/volume_manager_factory.h" |
| #include "chrome/browser/ash/fileapi/file_system_backend.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chromeos/ash/components/disks/fake_disk_mount_manager.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/test_event_router.h" |
| #include "extensions/common/extension.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "storage/browser/quota/quota_manager_proxy.h" |
| #include "storage/browser/test/test_file_system_context.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "ui/display/test/test_screen.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace file_manager { |
| |
| TEST(EventRouterTest, PopulateCrostiniEvent) { |
| extensions::api::file_manager_private::CrostiniEvent ext_event; |
| url::Origin ext_origin = url::Origin::Create( |
| extensions::Extension::GetBaseURLFromExtensionId("extensionid")); |
| EventRouter::PopulateCrostiniEvent( |
| ext_event, |
| extensions::api::file_manager_private::CrostiniEventType::kUnshare, |
| "vmname", ext_origin, "mountname", "filesystemname", "/full/path"); |
| |
| EXPECT_EQ(ext_event.event_type, |
| extensions::api::file_manager_private::CrostiniEventType::kUnshare); |
| EXPECT_EQ(ext_event.vm_name, "vmname"); |
| EXPECT_EQ(ext_event.entries.size(), 1u); |
| base::Value::Dict ext_props; |
| ext_props.Set( |
| "fileSystemRoot", |
| "filesystem:chrome-extension://extensionid/external/mountname/"); |
| ext_props.Set("fileSystemName", "filesystemname"); |
| ext_props.Set("fileFullPath", "/full/path"); |
| ext_props.Set("fileIsDirectory", true); |
| EXPECT_EQ(ext_event.entries[0].additional_properties, ext_props); |
| |
| extensions::api::file_manager_private::CrostiniEvent swa_event; |
| url::Origin swa_origin = url::Origin::Create( |
| GURL("chrome://file-manager/this-part-should-not-be-in?the=event")); |
| EventRouter::PopulateCrostiniEvent( |
| swa_event, |
| extensions::api::file_manager_private::CrostiniEventType::kShare, |
| "vmname", swa_origin, "mountname", "filesystemname", "/full/path"); |
| |
| EXPECT_EQ(swa_event.event_type, |
| extensions::api::file_manager_private::CrostiniEventType::kShare); |
| EXPECT_EQ(swa_event.vm_name, "vmname"); |
| EXPECT_EQ(swa_event.entries.size(), 1u); |
| base::Value::Dict swa_props; |
| swa_props.Set("fileSystemRoot", |
| "filesystem:chrome://file-manager/external/mountname/"); |
| swa_props.Set("fileSystemName", "filesystemname"); |
| swa_props.Set("fileFullPath", "/full/path"); |
| swa_props.Set("fileIsDirectory", true); |
| EXPECT_EQ(swa_event.entries[0].additional_properties, swa_props); |
| } |
| |
| namespace { |
| |
| using ::base::test::RunClosure; |
| using ::testing::_; |
| using ::testing::AllOf; |
| using ::testing::Field; |
| |
| // Observes the `BroadcastEvent` operation that is emitted by the event router. |
| // The mock methods are used to assert expectations on the return results. |
| class TestEventRouterObserver |
| : public extensions::TestEventRouter::EventObserver { |
| public: |
| explicit TestEventRouterObserver(extensions::TestEventRouter* event_router) |
| : event_router_(event_router) { |
| event_router_->AddEventObserver(this); |
| } |
| ~TestEventRouterObserver() override { |
| event_router_->RemoveEventObserver(this); |
| } |
| TestEventRouterObserver(const TestEventRouterObserver&) = delete; |
| TestEventRouterObserver& operator=(const TestEventRouterObserver&) = delete; |
| |
| // TestEventRouter::EventObserver: |
| MOCK_METHOD(void, OnBroadcastEvent, (const extensions::Event&), (override)); |
| MOCK_METHOD(void, |
| OnDispatchEventToExtension, |
| (const std::string&, const extensions::Event&), |
| (override)); |
| |
| private: |
| raw_ptr<extensions::TestEventRouter> event_router_; |
| }; |
| |
| class FileManagerEventRouterTest : public testing::Test { |
| public: |
| FileManagerEventRouterTest() = default; |
| FileManagerEventRouterTest(const FileManagerEventRouterTest&) = delete; |
| FileManagerEventRouterTest& operator=(const FileManagerEventRouterTest&) = |
| delete; |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| |
| profile_ = |
| std::make_unique<TestingProfile>(base::FilePath(temp_dir_.GetPath())); |
| file_system_context_ = storage::CreateFileSystemContextForTesting( |
| nullptr, temp_dir_.GetPath()); |
| |
| VolumeManagerFactory::GetInstance()->SetTestingFactory( |
| profile_.get(), |
| base::BindLambdaForTesting([this](content::BrowserContext* context) { |
| return std::unique_ptr<KeyedService>(std::make_unique<VolumeManager>( |
| Profile::FromBrowserContext(context), nullptr, nullptr, |
| &disk_mount_manager_, nullptr, |
| VolumeManager::GetMtpStorageInfoCallback())); |
| })); |
| |
| storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( |
| file_manager::util::GetDownloadsMountPointName(profile_.get()), |
| storage::kFileSystemTypeLocal, storage::FileSystemMountOption(), |
| temp_dir_.GetPath()); |
| |
| auto* context = file_manager::util::GetFileSystemContextForSourceURL( |
| profile_.get(), GURL("chrome-extension://abc")); |
| ash::FileSystemBackend::Get(*context)->GrantFileAccessToOrigin( |
| url::Origin::Create(GURL("chrome-extension://abc")), |
| base::FilePath( |
| file_manager::util::GetDownloadsMountPointName(profile_.get()))); |
| } |
| |
| const io_task::EntryStatus CreateSuccessfulEntryStatusForFileName( |
| const std::string& file_name) { |
| const base::FilePath file_path = temp_dir_.GetPath().Append(file_name); |
| EXPECT_TRUE(base::WriteFile(file_path, base::RandBytesAsString(32))); |
| |
| storage::FileSystemURL url = |
| file_system_context_->CreateCrackedFileSystemURL( |
| kTestStorageKey, storage::kFileSystemTypeTest, file_path); |
| |
| return io_task::EntryStatus(std::move(url), base::File::FILE_OK); |
| } |
| |
| content::BrowserTaskEnvironment task_environment_; |
| display::test::TestScreen test_screen_{/*create_dispay=*/true, |
| /*register_screen=*/true}; |
| base::ScopedTempDir temp_dir_; |
| std::unique_ptr<TestingProfile> profile_; |
| const blink::StorageKey kTestStorageKey = |
| blink::StorageKey::CreateFromStringForTesting("chrome-extension://abc"); |
| scoped_refptr<storage::FileSystemContext> file_system_context_; |
| ash::disks::FakeDiskMountManager disk_mount_manager_; |
| }; |
| |
| // A matcher that matches an `extensions::Event::event_args` and attempts to |
| // extract the "outputs" key. It then looks at the output at `index` and matches |
| // the `field` against the `expected_value`. |
| MATCHER_P3(ExpectEventArgString, index, field, expected_value, "") { |
| EXPECT_GE(arg.size(), 1u); |
| const base::Value::List* outputs = arg[0].GetDict().FindList("outputs"); |
| EXPECT_TRUE(outputs) << "The outputs field is not available on the event"; |
| EXPECT_GT(outputs->size(), index) |
| << "The supplied index on outputs is not available, size: " |
| << outputs->size() << ", index: " << index; |
| const std::string* actual_value = |
| (*outputs)[index].GetDict().FindString(field); |
| EXPECT_TRUE(actual_value) << "Could not find the string with key: " << field; |
| return testing::ExplainMatchResult(expected_value, *actual_value, |
| result_listener); |
| } |
| |
| // A matcher that matches an `extensions::Event::event_args` and attempts to |
| // extract the "conflictParams" and "pauseParams" keys. It expects |
| // "conflictParams" to be empty, and then matches the "policyParams" values |
| // against the expected ones. |
| MATCHER_P4(ExpectEventArgPauseParams, |
| expected_type, |
| expected_count, |
| expected_file_name, |
| expected_always_show_review, |
| "") { |
| EXPECT_GE(arg.size(), 1u); |
| const base::Value::Dict* pause_params = |
| arg[0].GetDict().FindDict("pauseParams"); |
| EXPECT_TRUE(pause_params) |
| << "The pause_params field is not available on the event"; |
| |
| const base::Value::Dict* conflict_pause_params = |
| pause_params->FindDict("conflictParams"); |
| EXPECT_FALSE(conflict_pause_params) |
| << "The conflictParams field should not be available on the event"; |
| |
| const base::Value::Dict* policy_pause_params = |
| pause_params->FindDict("policyParams"); |
| EXPECT_TRUE(policy_pause_params) |
| << "The policyParams field is not available on the event"; |
| const std::string* actual_type = policy_pause_params->FindString("type"); |
| EXPECT_TRUE(actual_type) << "Could not find the string with key: type"; |
| const std::optional<int> actual_count = |
| policy_pause_params->FindInt("policyFileCount"); |
| EXPECT_TRUE(actual_count.has_value()) |
| << "Could not find the number with key: type"; |
| const std::string* actual_file_name = |
| policy_pause_params->FindString("fileName"); |
| EXPECT_TRUE(actual_file_name) |
| << "Could not find the string with key: fileName"; |
| const std::optional<bool> actual_always_show_review = |
| policy_pause_params->FindBool("alwaysShowReview"); |
| EXPECT_TRUE(actual_always_show_review.has_value()) |
| << "Could not find the string with key: alwaysShowReview"; |
| return testing::ExplainMatchResult(expected_type, *actual_type, |
| result_listener) && |
| testing::ExplainMatchResult(expected_count, actual_count.value(), |
| result_listener) && |
| testing::ExplainMatchResult(expected_file_name, *actual_file_name, |
| result_listener) && |
| testing::ExplainMatchResult(expected_always_show_review, |
| actual_always_show_review.value(), |
| result_listener); |
| } |
| |
| // A matcher that matches an `extensions::Event::event_args` and attempts to |
| // extract the "policyError" key. It then matches the "policyError" values |
| // against the expected ones. |
| MATCHER_P4(ExpectEventArgPolicyError, |
| expected_type, |
| expected_count, |
| expected_file_name, |
| expected_always_show_review, |
| "") { |
| EXPECT_GE(arg.size(), 1u); |
| const base::Value::Dict* policy_error = |
| arg[0].GetDict().FindDict("policyError"); |
| EXPECT_TRUE(policy_error) |
| << "The policyError field is not available on the event"; |
| |
| const std::string* actual_type = policy_error->FindString("type"); |
| EXPECT_TRUE(actual_type) << "Could not find the string with key: type"; |
| const std::optional<int> actual_count = |
| policy_error->FindInt("policyFileCount"); |
| EXPECT_TRUE(actual_count.has_value()) |
| << "Could not find the string with key: type"; |
| const std::string* actual_file_name = policy_error->FindString("fileName"); |
| EXPECT_TRUE(actual_file_name) |
| << "Could not find the string with key: fileName"; |
| const std::optional<bool> actual_always_show_review = |
| policy_error->FindBool("alwaysShowReview"); |
| EXPECT_TRUE(actual_always_show_review.has_value()) |
| << "Could not find the string with key: alwaysShowReview"; |
| return testing::ExplainMatchResult(expected_type, *actual_type, |
| result_listener) && |
| testing::ExplainMatchResult(expected_count, actual_count.value(), |
| result_listener) && |
| testing::ExplainMatchResult(expected_file_name, *actual_file_name, |
| result_listener) && |
| testing::ExplainMatchResult(expected_always_show_review, |
| actual_always_show_review.value(), |
| result_listener); |
| } |
| |
| TEST_F(FileManagerEventRouterTest, OnIOTaskStatusForTrash) { |
| // Setup event routers. |
| extensions::TestEventRouter* test_event_router = |
| extensions::CreateAndUseTestEventRouter(profile_.get()); |
| TestEventRouterObserver observer(test_event_router); |
| auto event_router = std::make_unique<EventRouter>(profile_.get()); |
| event_router->ForceBroadcastingForTesting(true); |
| |
| io_task::EntryStatus source_entry = |
| CreateSuccessfulEntryStatusForFileName("foo.txt"); |
| io_task::EntryStatus output_entry = |
| CreateSuccessfulEntryStatusForFileName("bar.txt"); |
| |
| std::vector<io_task::EntryStatus> source_entries; |
| source_entries.push_back(std::move(source_entry)); |
| std::vector<io_task::EntryStatus> output_entries; |
| output_entries.push_back(std::move(output_entry)); |
| |
| // Setup the ProgressStatus event that expects |
| file_manager::io_task::ProgressStatus status; |
| status.type = file_manager::io_task::OperationType::kTrash; |
| status.state = file_manager::io_task::State::kSuccess; |
| status.sources = std::move(source_entries); |
| status.outputs = std::move(output_entries); |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL( |
| observer, |
| OnBroadcastEvent(Field( |
| &extensions::Event::event_args, |
| AllOf(ExpectEventArgString(0u, "fileFullPath", "/bar.txt"), |
| ExpectEventArgString(0u, "fileSystemName", "Downloads"), |
| ExpectEventArgString( |
| 0u, "fileSystemRoot", |
| "filesystem:chrome-extension://abc/external/Downloads/"))))) |
| .WillOnce(RunClosure(run_loop.QuitClosure())); |
| |
| event_router->OnIOTaskStatus(status); |
| run_loop.Run(); |
| } |
| |
| TEST_F(FileManagerEventRouterTest, OnIOTaskStatusForCopyPause) { |
| // Setup event routers. |
| extensions::TestEventRouter* test_event_router = |
| extensions::CreateAndUseTestEventRouter(profile_.get()); |
| TestEventRouterObserver observer(test_event_router); |
| auto event_router = std::make_unique<EventRouter>(profile_.get()); |
| event_router->ForceBroadcastingForTesting(true); |
| |
| io_task::EntryStatus source_entry = |
| CreateSuccessfulEntryStatusForFileName("foo.txt"); |
| |
| std::vector<io_task::EntryStatus> source_entries; |
| source_entries.push_back(std::move(source_entry)); |
| |
| // Setup the ProgressStatus event. |
| file_manager::io_task::ProgressStatus status; |
| status.type = file_manager::io_task::OperationType::kCopy; |
| status.state = file_manager::io_task::State::kPaused; |
| status.sources = std::move(source_entries); |
| status.pause_params.policy_params = io_task::PolicyPauseParams( |
| policy::Policy::kDlp, /*warning_files_count*/ 2u, "foo.txt", |
| /*always_show_review=*/false); |
| |
| // Expect the event to have dlp as policy pause params. |
| base::RunLoop run_loop; |
| EXPECT_CALL(observer, |
| OnBroadcastEvent(Field(&extensions::Event::event_args, |
| AllOf(ExpectEventArgPauseParams( |
| "dlp", 2, "foo.txt", false))))) |
| .WillOnce(RunClosure(run_loop.QuitClosure())); |
| |
| event_router->OnIOTaskStatus(status); |
| run_loop.Run(); |
| } |
| |
| TEST_F(FileManagerEventRouterTest, OnIOTaskStatusForPolicyError) { |
| // Setup event routers. |
| extensions::TestEventRouter* test_event_router = |
| extensions::CreateAndUseTestEventRouter(profile_.get()); |
| TestEventRouterObserver observer(test_event_router); |
| auto event_router = std::make_unique<EventRouter>(profile_.get()); |
| event_router->ForceBroadcastingForTesting(true); |
| |
| io_task::EntryStatus source_entry = |
| CreateSuccessfulEntryStatusForFileName("foo.txt"); |
| |
| std::vector<io_task::EntryStatus> source_entries; |
| source_entries.push_back(std::move(source_entry)); |
| |
| // Setup the ProgressStatus event. |
| file_manager::io_task::ProgressStatus status; |
| status.type = file_manager::io_task::OperationType::kCopy; |
| status.state = file_manager::io_task::State::kError; |
| status.sources = std::move(source_entries); |
| status.policy_error.emplace(io_task::PolicyErrorType::kDlp, |
| /*blocked_files=*/1, "foo.txt", |
| /*always_show_review=*/true); |
| |
| // Expect the event to have dlp as policy error. |
| base::RunLoop run_loop; |
| EXPECT_CALL(observer, |
| OnBroadcastEvent(Field( |
| &extensions::Event::event_args, |
| AllOf(ExpectEventArgPolicyError("dlp", 1, "foo.txt", true))))) |
| .WillOnce(RunClosure(run_loop.QuitClosure())); |
| |
| event_router->OnIOTaskStatus(status); |
| run_loop.Run(); |
| } |
| |
| } // namespace |
| } // namespace file_manager |