| // Copyright 2020 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/ui/ash/holding_space/holding_space_keyed_service.h" |
| |
| #include <vector> |
| |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/public/cpp/file_icon_util.h" |
| #include "ash/public/cpp/holding_space/holding_space_constants.h" |
| #include "ash/public/cpp/holding_space/holding_space_controller.h" |
| #include "ash/public/cpp/holding_space/holding_space_controller_observer.h" |
| #include "ash/public/cpp/holding_space/holding_space_image.h" |
| #include "ash/public/cpp/holding_space/holding_space_item.h" |
| #include "ash/public/cpp/holding_space/holding_space_model.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/guid.h" |
| #include "base/scoped_observation.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "chrome/browser/chromeos/file_manager/app_id.h" |
| #include "chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h" |
| #include "chrome/browser/chromeos/file_manager/fileapi_util.h" |
| #include "chrome/browser/chromeos/file_manager/path_util.h" |
| #include "chrome/browser/chromeos/file_manager/volume_manager.h" |
| #include "chrome/browser/chromeos/file_manager/volume_manager_factory.h" |
| #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/nearby_sharing/common/nearby_share_features.h" |
| #include "chrome/browser/prefs/browser_prefs.h" |
| #include "chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h" |
| #include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h" |
| #include "chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.h" |
| #include "chrome/browser/ui/ash/holding_space/holding_space_util.h" |
| #include "chrome/browser/ui/ash/holding_space/scoped_test_mount_point.h" |
| #include "chrome/test/base/browser_with_test_window_test.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "chromeos/disks/disk_mount_manager.h" |
| #include "components/account_id/account_id.h" |
| #include "components/download/public/common/mock_download_item.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/sync_preferences/pref_service_mock_factory.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "components/user_manager/scoped_user_manager.h" |
| #include "content/public/browser/download_item_utils.h" |
| #include "content/public/test/mock_download_manager.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "storage/browser/test/async_file_test_helper.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| namespace ash { |
| |
| using holding_space::ScopedTestMountPoint; |
| |
| namespace { |
| |
| // Creates an empty holding space image. |
| std::unique_ptr<HoldingSpaceImage> CreateTestHoldingSpaceImage( |
| HoldingSpaceItem::Type type, |
| const base::FilePath& file_path) { |
| return std::make_unique<HoldingSpaceImage>( |
| HoldingSpaceImage::GetMaxSizeForType(type), file_path, |
| /*async_bitmap_resolver=*/base::DoNothing()); |
| } |
| |
| std::vector<HoldingSpaceItem::Type> GetHoldingSpaceItemTypes() { |
| std::vector<HoldingSpaceItem::Type> types; |
| for (int i = 0; i <= static_cast<int>(HoldingSpaceItem::Type::kMaxValue); ++i) |
| types.push_back(static_cast<HoldingSpaceItem::Type>(i)); |
| return types; |
| } |
| |
| std::unique_ptr<KeyedService> BuildVolumeManager( |
| content::BrowserContext* context) { |
| return std::make_unique<file_manager::VolumeManager>( |
| Profile::FromBrowserContext(context), |
| nullptr /* drive_integration_service */, |
| nullptr /* power_manager_client */, |
| chromeos::disks::DiskMountManager::GetInstance(), |
| nullptr /* file_system_provider_service */, |
| file_manager::VolumeManager::GetMtpStorageInfoCallback()); |
| } |
| |
| // Utility class which can wait until a `HoldingSpaceModel` for a given profile |
| // is attached to the `HoldingSpaceController`. |
| class HoldingSpaceModelAttachedWaiter : public HoldingSpaceControllerObserver { |
| public: |
| explicit HoldingSpaceModelAttachedWaiter(Profile* profile) |
| : profile_(profile) { |
| holding_space_controller_observer_.Add(HoldingSpaceController::Get()); |
| } |
| |
| void Wait() { |
| if (IsModelAttached()) |
| return; |
| |
| wait_loop_ = std::make_unique<base::RunLoop>(); |
| wait_loop_->Run(); |
| wait_loop_.reset(); |
| } |
| |
| private: |
| // HoldingSpaceControllerObserver: |
| void OnHoldingSpaceModelAttached(HoldingSpaceModel* model) override { |
| if (wait_loop_ && IsModelAttached()) |
| wait_loop_->Quit(); |
| } |
| |
| void OnHoldingSpaceModelDetached(HoldingSpaceModel* model) override {} |
| |
| bool IsModelAttached() const { |
| HoldingSpaceKeyedService* const holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile_); |
| return HoldingSpaceController::Get()->model() == |
| holding_space_service->model_for_testing(); |
| } |
| |
| Profile* const profile_; |
| ScopedObserver<HoldingSpaceController, HoldingSpaceControllerObserver> |
| holding_space_controller_observer_{this}; |
| std::unique_ptr<base::RunLoop> wait_loop_; |
| }; |
| |
| class ItemUpdatedWaiter : public HoldingSpaceModelObserver { |
| public: |
| explicit ItemUpdatedWaiter(HoldingSpaceModel* model) { |
| model_observer_.Observe(model); |
| } |
| |
| ItemUpdatedWaiter(const ItemUpdatedWaiter&) = delete; |
| ItemUpdatedWaiter& operator=(const ItemUpdatedWaiter&) = delete; |
| ~ItemUpdatedWaiter() override = default; |
| |
| void Wait(const HoldingSpaceItem* item) { |
| ASSERT_FALSE(wait_item_); |
| ASSERT_FALSE(wait_loop_); |
| |
| wait_item_ = item; |
| |
| wait_loop_ = std::make_unique<base::RunLoop>(); |
| wait_loop_->Run(); |
| wait_loop_.reset(); |
| |
| wait_item_ = nullptr; |
| } |
| |
| private: |
| // HoldingSpaceModelObserver: |
| void OnHoldingSpaceItemUpdated(const HoldingSpaceItem* item) override { |
| if (item == wait_item_) |
| wait_loop_->Quit(); |
| } |
| |
| const HoldingSpaceItem* wait_item_ = nullptr; |
| std::unique_ptr<base::RunLoop> wait_loop_; |
| |
| base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver> |
| model_observer_{this}; |
| }; |
| |
| class ItemsFinalizedWaiter : public HoldingSpaceModelObserver { |
| public: |
| // Predicate that determines whether the waiter should wait for an item to be |
| // finalized. |
| using ItemFilter = |
| base::RepeatingCallback<bool(const HoldingSpaceItem* item)>; |
| |
| explicit ItemsFinalizedWaiter(HoldingSpaceModel* model) : model_(model) {} |
| ItemsFinalizedWaiter(const ItemsFinalizedWaiter&) = delete; |
| ItemsFinalizedWaiter& operator=(const ItemsFinalizedWaiter&) = delete; |
| ~ItemsFinalizedWaiter() override = default; |
| |
| // NOTE: The filter defaults to all items. |
| void Wait(const ItemFilter& filter = ItemFilter()) { |
| ASSERT_FALSE(wait_loop_); |
| |
| filter_ = filter; |
| if (FilteredItemsFinalized()) |
| return; |
| |
| base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver> |
| model_observer{this}; |
| model_observer.Observe(model_); |
| |
| wait_loop_ = std::make_unique<base::RunLoop>(); |
| wait_loop_->Run(); |
| wait_loop_.reset(); |
| filter_ = ItemFilter(); |
| } |
| |
| void OnHoldingSpaceItemsRemoved( |
| const std::vector<const HoldingSpaceItem*>& items) override { |
| if (FilteredItemsFinalized()) |
| wait_loop_->Quit(); |
| } |
| |
| void OnHoldingSpaceItemFinalized(const HoldingSpaceItem* item) override { |
| if (FilteredItemsFinalized()) |
| wait_loop_->Quit(); |
| } |
| |
| private: |
| bool FilteredItemsFinalized() const { |
| for (auto& item : model_->items()) { |
| if (filter_ && !filter_.Run(item.get())) |
| continue; |
| if (!item->IsFinalized()) |
| return false; |
| } |
| return true; |
| } |
| |
| HoldingSpaceModel* const model_; |
| ItemFilter filter_; |
| std::unique_ptr<base::RunLoop> wait_loop_; |
| }; |
| |
| class ItemImageUpdateWaiter { |
| public: |
| explicit ItemImageUpdateWaiter(const HoldingSpaceItem* item) { |
| image_subscription_ = |
| item->image().AddImageSkiaChangedCallback(base::BindRepeating( |
| &ItemImageUpdateWaiter::OnHoldingSpaceItemImageChanged, |
| base::Unretained(this))); |
| } |
| ItemImageUpdateWaiter(const ItemImageUpdateWaiter&) = delete; |
| ItemImageUpdateWaiter& operator=(const ItemImageUpdateWaiter&) = delete; |
| ~ItemImageUpdateWaiter() = default; |
| |
| void Wait() { run_loop_.Run(); } |
| |
| private: |
| void OnHoldingSpaceItemImageChanged() { run_loop_.Quit(); } |
| |
| base::RunLoop run_loop_; |
| base::CallbackListSubscription image_subscription_; |
| }; |
| |
| // A mock `content::DownloadManager` which can notify observers of events. |
| class MockDownloadManager : public content::MockDownloadManager { |
| public: |
| // content::MockDownloadManager: |
| void AddObserver(Observer* observer) override { |
| observers_.AddObserver(observer); |
| } |
| |
| void RemoveObserver(Observer* observer) override { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void NotifyDownloadCreated(download::DownloadItem* item) { |
| for (auto& observer : observers_) |
| observer.OnDownloadCreated(this, item); |
| } |
| |
| private: |
| base::ObserverList<content::DownloadManager::Observer>::Unchecked observers_; |
| }; |
| |
| } // namespace |
| |
| class HoldingSpaceKeyedServiceTest : public BrowserWithTestWindowTest { |
| public: |
| HoldingSpaceKeyedServiceTest() |
| : fake_user_manager_(new chromeos::FakeChromeUserManager), |
| user_manager_enabler_(base::WrapUnique(fake_user_manager_)), |
| download_manager_( |
| std::make_unique<testing::NiceMock<MockDownloadManager>>()) { |
| scoped_feature_list_.InitAndEnableFeature(features::kTemporaryHoldingSpace); |
| HoldingSpaceImage::SetUseZeroInvalidationDelayForTesting(true); |
| } |
| |
| HoldingSpaceKeyedServiceTest(const HoldingSpaceKeyedServiceTest& other) = |
| delete; |
| HoldingSpaceKeyedServiceTest& operator=( |
| const HoldingSpaceKeyedServiceTest& other) = delete; |
| ~HoldingSpaceKeyedServiceTest() override { |
| HoldingSpaceImage::SetUseZeroInvalidationDelayForTesting(false); |
| } |
| |
| TestingProfile* CreateProfile() override { |
| const std::string kPrimaryProfileName = "primary_profile"; |
| const AccountId account_id(AccountId::FromUserEmail(kPrimaryProfileName)); |
| |
| fake_user_manager_->AddUser(account_id); |
| fake_user_manager_->LoginUser(account_id); |
| |
| GetSessionControllerClient()->AddUserSession(kPrimaryProfileName); |
| GetSessionControllerClient()->SwitchActiveUser(account_id); |
| |
| return profile_manager()->CreateTestingProfile( |
| kPrimaryProfileName, |
| /*testing_factories=*/{ |
| {file_manager::VolumeManagerFactory::GetInstance(), |
| base::BindRepeating(&BuildVolumeManager)}}); |
| } |
| |
| TestingProfile* CreateGuestProfile() { |
| user_manager::User* guest_user = fake_user_manager_->AddGuestUser(); |
| fake_user_manager_->LoginUser(fake_user_manager_->GetGuestAccountId()); |
| |
| chromeos::ProfileHelper::Get()->SetProfileToUserMappingForTesting( |
| guest_user); |
| |
| return profile_manager()->CreateTestingProfile( |
| guest_user->GetAccountId().GetUserEmail(), |
| /*testing_factories=*/{ |
| {file_manager::VolumeManagerFactory::GetInstance(), |
| base::BindRepeating(&BuildVolumeManager)}}); |
| } |
| |
| TestingProfile* CreateSecondaryProfile( |
| std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs = nullptr) { |
| const std::string kSecondaryProfileName = "secondary_profile"; |
| const AccountId account_id(AccountId::FromUserEmail(kSecondaryProfileName)); |
| fake_user_manager_->AddUser(account_id); |
| fake_user_manager_->LoginUser(account_id); |
| return profile_manager()->CreateTestingProfile( |
| kSecondaryProfileName, std::move(prefs), |
| base::ASCIIToUTF16("Test profile"), 1 /*avatar_id*/, |
| std::string() /*supervised_user_id*/, |
| /*testing_factories=*/ |
| {{file_manager::VolumeManagerFactory::GetInstance(), |
| base::BindRepeating(&BuildVolumeManager)}}); |
| } |
| |
| using PopulatePrefStoreCallback = base::OnceCallback<void(TestingPrefStore*)>; |
| TestingProfile* CreateSecondaryProfile(PopulatePrefStoreCallback callback) { |
| // Create and initialize pref registry. |
| auto registry = base::MakeRefCounted<user_prefs::PrefRegistrySyncable>(); |
| RegisterUserProfilePrefs(registry.get()); |
| |
| // Create and initialize pref store. |
| auto pref_store = base::MakeRefCounted<TestingPrefStore>(); |
| std::move(callback).Run(pref_store.get()); |
| |
| // Create and initialize pref factory. |
| sync_preferences::PrefServiceMockFactory prefs_factory; |
| prefs_factory.set_user_prefs(pref_store); |
| |
| // Create and return profile. |
| return CreateSecondaryProfile(prefs_factory.CreateSyncable(registry)); |
| } |
| |
| void ActivateSecondaryProfile() { |
| const std::string kSecondaryProfileName = "secondary_profile"; |
| const AccountId account_id(AccountId::FromUserEmail(kSecondaryProfileName)); |
| GetSessionControllerClient()->AddUserSession(kSecondaryProfileName); |
| GetSessionControllerClient()->SwitchActiveUser(account_id); |
| } |
| |
| TestSessionControllerClient* GetSessionControllerClient() { |
| return ash_test_helper()->test_session_controller_client(); |
| } |
| |
| // Resolves an absolute file path in the file manager's file system context, |
| // and returns the file's file system URL. |
| GURL GetFileSystemUrl(Profile* profile, |
| const base::FilePath& absolute_file_path) { |
| GURL file_system_url; |
| EXPECT_TRUE(file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl( |
| profile, absolute_file_path, file_manager::kFileManagerAppId, |
| &file_system_url)); |
| return file_system_url; |
| } |
| |
| // Resolves a file system URL in the file manager's file system context, and |
| // returns the file's virtual path relative to the mount point root. |
| // Returns an empty file if the URL cannot be resolved to a file. For example, |
| // if it's not well formed, or the file manager app cannot access it. |
| base::FilePath GetVirtualPathFromUrl( |
| const GURL& url, |
| const std::string& expected_mount_point) { |
| storage::FileSystemContext* fs_context = |
| file_manager::util::GetFileSystemContextForExtensionId( |
| GetProfile(), file_manager::kFileManagerAppId); |
| storage::FileSystemURL fs_url = fs_context->CrackURL(url); |
| |
| base::RunLoop run_loop; |
| base::FilePath result; |
| base::FilePath* result_ptr = &result; |
| fs_context->ResolveURL( |
| fs_url, |
| base::BindLambdaForTesting( |
| [&run_loop, &expected_mount_point, &result_ptr]( |
| base::File::Error result, const storage::FileSystemInfo& info, |
| const base::FilePath& file_path, |
| storage::FileSystemContext::ResolvedEntryType type) { |
| EXPECT_EQ(base::File::Error::FILE_OK, result); |
| EXPECT_EQ(storage::FileSystemContext::RESOLVED_ENTRY_FILE, type); |
| if (expected_mount_point == info.name) { |
| *result_ptr = file_path; |
| } else { |
| ADD_FAILURE() << "Mount point name '" << info.name |
| << "' does not match expected '" |
| << expected_mount_point << "'"; |
| } |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| return result; |
| } |
| |
| std::unique_ptr<download::MockDownloadItem> CreateMockDownloadItem( |
| base::FilePath full_file_path) { |
| auto item = |
| std::make_unique<testing::NiceMock<download::MockDownloadItem>>(); |
| ON_CALL(*item, GetId()).WillByDefault(testing::Return(1)); |
| ON_CALL(*item, GetGuid()) |
| .WillByDefault(testing::ReturnRefOfCopy( |
| std::string("14CA04AF-ECEC-4B13-8829-817477EFAB83"))); |
| ON_CALL(*item, GetFullPath()) |
| .WillByDefault(testing::ReturnRefOfCopy(full_file_path)); |
| ON_CALL(*item, GetURL()) |
| .WillByDefault(testing::ReturnRefOfCopy(GURL("foo/bar"))); |
| ON_CALL(*item, GetMimeType()).WillByDefault(testing::Return(std::string())); |
| content::DownloadItemUtils::AttachInfo(item.get(), GetProfile(), nullptr); |
| return item; |
| } |
| |
| MockDownloadManager* download_manager() { return download_manager_.get(); } |
| |
| private: |
| // BrowserWithTestWindowTest: |
| void SetUp() override { |
| // Needed by `file_manager::VolumeManager`. |
| chromeos::disks::DiskMountManager::InitializeForTesting( |
| new file_manager::FakeDiskMountManager); |
| SetUpDownloadManager(); |
| BrowserWithTestWindowTest::SetUp(); |
| holding_space_util::SetNowForTesting(base::nullopt); |
| } |
| |
| void TearDown() override { |
| BrowserWithTestWindowTest::TearDown(); |
| chromeos::disks::DiskMountManager::Shutdown(); |
| } |
| |
| void SetUpDownloadManager() { |
| // The `content::DownloadManager` needs to be set prior to initialization |
| // of the `HoldingSpaceDownloadsDelegate`. This must happen before the |
| // `HoldingSpaceKeyedService` is created for the profile under test. |
| MockDownloadManager* mock_download_manager = download_manager(); |
| HoldingSpaceDownloadsDelegate::SetDownloadManagerForTesting( |
| mock_download_manager); |
| |
| // Spoof initialization of the `mock_download_manager`. |
| ON_CALL(*mock_download_manager, IsManagerInitialized) |
| .WillByDefault(testing::Return(true)); |
| } |
| |
| chromeos::FakeChromeUserManager* fake_user_manager_; |
| user_manager::ScopedUserManager user_manager_enabler_; |
| std::unique_ptr<MockDownloadManager> download_manager_; |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests adding a screenshot item. Verifies that adding a screenshot creates a |
| // holding space item with a file system URL that can be accessed by the file |
| // manager app. |
| TEST_F(HoldingSpaceKeyedServiceTest, AddScreenshotItem) { |
| // Create a test downloads mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| // Wait for the holding space model. |
| HoldingSpaceModelAttachedWaiter(GetProfile()).Wait(); |
| |
| // Verify that the holding space model gets set even if the holding space |
| // keyed service is not explicitly created. |
| HoldingSpaceModel* const initial_model = |
| HoldingSpaceController::Get()->model(); |
| EXPECT_TRUE(initial_model); |
| |
| HoldingSpaceKeyedService* const holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| const base::FilePath item_1_virtual_path("Screenshot 1.png"); |
| // Create a fake screenshot file on the local file system - later parts of the |
| // test will try to resolve the file's file system URL, which fails if the |
| // file does not exist. |
| const base::FilePath item_1_full_path = |
| downloads_mount->CreateFile(item_1_virtual_path, "red"); |
| ASSERT_FALSE(item_1_full_path.empty()); |
| |
| holding_space_service->AddScreenshot(item_1_full_path); |
| |
| const base::FilePath item_2_virtual_path = |
| base::FilePath("Alt/Screenshot 2.png"); |
| // Create a fake screenshot file on the local file system - later parts of the |
| // test will try to resolve the file's file system URL, which fails if the |
| // file does not exist. |
| const base::FilePath item_2_full_path = |
| downloads_mount->CreateFile(item_2_virtual_path, "blue"); |
| ASSERT_FALSE(item_2_full_path.empty()); |
| holding_space_service->AddScreenshot(item_2_full_path); |
| |
| EXPECT_EQ(initial_model, HoldingSpaceController::Get()->model()); |
| EXPECT_EQ(HoldingSpaceController::Get()->model(), |
| holding_space_service->model_for_testing()); |
| |
| HoldingSpaceModel* const model = HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(2u, model->items().size()); |
| |
| const HoldingSpaceItem* item_1 = model->items()[0].get(); |
| EXPECT_EQ(item_1_full_path, item_1->file_path()); |
| EXPECT_TRUE(gfx::BitmapsAreEqual( |
| *holding_space_util::ResolveImage( |
| holding_space_service->thumbnail_loader_for_testing(), |
| HoldingSpaceItem::Type::kScreenshot, item_1_full_path) |
| ->GetImageSkia() |
| .bitmap(), |
| *item_1->image().GetImageSkia().bitmap())); |
| // Verify the item file system URL resolves to the correct file in the file |
| // manager's context. |
| EXPECT_EQ(item_1_virtual_path, |
| GetVirtualPathFromUrl(item_1->file_system_url(), |
| downloads_mount->name())); |
| EXPECT_EQ(base::ASCIIToUTF16("Screenshot 1.png"), item_1->text()); |
| |
| const HoldingSpaceItem* item_2 = model->items()[1].get(); |
| EXPECT_EQ(item_2_full_path, item_2->file_path()); |
| EXPECT_TRUE(gfx::BitmapsAreEqual( |
| *holding_space_util::ResolveImage( |
| holding_space_service->thumbnail_loader_for_testing(), |
| HoldingSpaceItem::Type::kScreenshot, item_2_full_path) |
| ->GetImageSkia() |
| .bitmap(), |
| *item_2->image().GetImageSkia().bitmap())); |
| // Verify the item file system URL resolves to the correct file in the file |
| // manager's context. |
| EXPECT_EQ(item_2_virtual_path, |
| GetVirtualPathFromUrl(item_2->file_system_url(), |
| downloads_mount->name())); |
| EXPECT_EQ(base::ASCIIToUTF16("Screenshot 2.png"), item_2->text()); |
| } |
| |
| TEST_F(HoldingSpaceKeyedServiceTest, GuestUserProfile) { |
| // Service instances should be created for guest users. |
| TestingProfile* const guest_profile = CreateGuestProfile(); |
| ASSERT_TRUE(guest_profile); |
| ASSERT_FALSE(guest_profile->IsOffTheRecord()); |
| HoldingSpaceKeyedService* const guest_profile_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(guest_profile); |
| ASSERT_TRUE(guest_profile_service); |
| |
| // Construct an incognito profile from `guest_profile`. |
| TestingProfile::Builder incognito_guest_profile_builder; |
| incognito_guest_profile_builder.SetGuestSession(); |
| incognito_guest_profile_builder.SetProfileName( |
| guest_profile->GetProfileUserName()); |
| Profile* const incognito_guest_profile = |
| incognito_guest_profile_builder.BuildIncognito(guest_profile); |
| ASSERT_TRUE(incognito_guest_profile); |
| ASSERT_TRUE(incognito_guest_profile->IsOffTheRecord()); |
| |
| // Service instances should be created for guest users w/ OTR profiles but |
| // should redirect to use the original (e.g. non-incognito) profile. |
| HoldingSpaceKeyedService* const incognito_guest_profile_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| incognito_guest_profile); |
| ASSERT_EQ(incognito_guest_profile_service, guest_profile_service); |
| } |
| |
| TEST_F(HoldingSpaceKeyedServiceTest, OffTheRecordProfile) { |
| // Service instances should be created for on the record profiles. |
| HoldingSpaceKeyedService* const primary_profile_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| ASSERT_TRUE(primary_profile_service); |
| |
| // Construct an incognito profile from the primary profile. |
| TestingProfile::Builder incognito_primary_profile_builder; |
| incognito_primary_profile_builder.SetProfileName( |
| GetProfile()->GetProfileUserName()); |
| Profile* const incognito_primary_profile = |
| incognito_primary_profile_builder.BuildIncognito(GetProfile()); |
| ASSERT_TRUE(incognito_primary_profile); |
| ASSERT_TRUE(incognito_primary_profile->IsOffTheRecord()); |
| |
| // Service instances should *not* typically be created for OTR profiles. The |
| // once exception is for guest users who redirect to use original profile. |
| HoldingSpaceKeyedService* const incognito_primary_profile_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| incognito_primary_profile); |
| ASSERT_FALSE(incognito_primary_profile_service); |
| } |
| |
| TEST_F(HoldingSpaceKeyedServiceTest, SecondaryUserProfile) { |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| |
| TestingProfile* const second_profile = CreateSecondaryProfile(); |
| HoldingSpaceKeyedService* const secondary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| second_profile); |
| |
| // Just creating a secondary profile shouldn't change the active client/model. |
| EXPECT_EQ(HoldingSpaceController::Get()->client(), |
| primary_holding_space_service->client_for_testing()); |
| EXPECT_EQ(HoldingSpaceController::Get()->model(), |
| primary_holding_space_service->model_for_testing()); |
| |
| // Switching the active user should change the active client/model (multi-user |
| // support). |
| ActivateSecondaryProfile(); |
| EXPECT_EQ(HoldingSpaceController::Get()->client(), |
| secondary_holding_space_service->client_for_testing()); |
| EXPECT_EQ(HoldingSpaceController::Get()->model(), |
| secondary_holding_space_service->model_for_testing()); |
| } |
| |
| // Verifies that updates to the holding space model are persisted. |
| TEST_F(HoldingSpaceKeyedServiceTest, UpdatePersistentStorage) { |
| // Create a file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| HoldingSpaceModel* const primary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| |
| EXPECT_EQ(primary_holding_space_model, |
| primary_holding_space_service->model_for_testing()); |
| |
| base::ListValue persisted_holding_space_items; |
| |
| // Verify persistent storage is updated when adding each type of item. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| const base::FilePath file_path = downloads_mount->CreateArbitraryFile(); |
| const GURL file_system_url = GetFileSystemUrl(GetProfile(), file_path); |
| |
| auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem( |
| type, file_path, file_system_url, |
| base::BindOnce( |
| &holding_space_util::ResolveImage, |
| primary_holding_space_service->thumbnail_loader_for_testing())); |
| |
| persisted_holding_space_items.Append(holding_space_item->Serialize()); |
| primary_holding_space_model->AddItem(std::move(holding_space_item)); |
| |
| EXPECT_EQ(*GetProfile()->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items); |
| } |
| |
| // Verify persistent storage is updated when removing each type of item. |
| while (!primary_holding_space_model->items().empty()) { |
| const auto* holding_space_item = |
| primary_holding_space_model->items()[0].get(); |
| |
| persisted_holding_space_items.Remove(0, /*out_value=*/nullptr); |
| primary_holding_space_model->RemoveItem(holding_space_item->id()); |
| |
| EXPECT_EQ(*GetProfile()->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items); |
| } |
| } |
| |
| // Verifies that when a file backing a holding space item is moved, the holding |
| // space item is updated in place and persistence storage is updated. |
| TEST_F(HoldingSpaceKeyedServiceTest, UpdatePersistentStorageAfterMove) { |
| // Create a file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| // Cache the holding space model for the primary profile. |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| HoldingSpaceModel* const primary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(primary_holding_space_model, |
| primary_holding_space_service->model_for_testing()); |
| |
| // Cache the file system context. |
| storage::FileSystemContext* context = |
| file_manager::util::GetFileSystemContextForExtensionId( |
| GetProfile(), file_manager::kFileManagerAppId); |
| ASSERT_TRUE(context); |
| |
| base::ListValue persisted_holding_space_items; |
| |
| // Verify persistent storage is updated when adding each type of item. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| // Note that each item is being added to a unique parent directory so that |
| // moving the parent directory later will not affect other items. |
| const base::FilePath file_path = downloads_mount->CreateFile( |
| base::FilePath(base::NumberToString(static_cast<int>(type))) |
| .Append("foo.txt"), |
| /*content=*/std::string()); |
| const GURL file_system_url = GetFileSystemUrl(GetProfile(), file_path); |
| |
| // Create the holding space item. |
| auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem( |
| type, file_path, file_system_url, |
| base::BindOnce( |
| &holding_space_util::ResolveImage, |
| primary_holding_space_service->thumbnail_loader_for_testing())); |
| |
| // Add the holding space item to the model and verify persistence. |
| persisted_holding_space_items.Append(holding_space_item->Serialize()); |
| primary_holding_space_model->AddItem(std::move(holding_space_item)); |
| EXPECT_EQ(*GetProfile()->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items); |
| } |
| |
| // Verify persistent storage is updated when moving each type of item and |
| // that the holding space items themselves are updated in place. |
| for (size_t i = 0; i < primary_holding_space_model->items().size(); ++i) { |
| const auto* holding_space_item = |
| primary_holding_space_model->items()[i].get(); |
| |
| // Rename the file backing the holding space item. |
| base::FilePath file_path = holding_space_item->file_path(); |
| base::FilePath new_file_path = file_path.InsertBeforeExtension(" (Moved)"); |
| GURL file_path_url = GetFileSystemUrl(GetProfile(), file_path); |
| GURL new_file_path_url = GetFileSystemUrl(GetProfile(), new_file_path); |
| ASSERT_EQ(storage::AsyncFileTestHelper::Move( |
| context, context->CrackURL(file_path_url), |
| context->CrackURL(new_file_path_url)), |
| base::File::FILE_OK); |
| |
| // File changes must be posted to the UI thread, wait for the update to |
| // reach the holding space model. |
| ItemUpdatedWaiter(primary_holding_space_model).Wait(holding_space_item); |
| |
| // Verify that the holding space item has been updated in place. |
| ASSERT_EQ(holding_space_item->file_path(), new_file_path); |
| ASSERT_EQ(holding_space_item->file_system_url(), new_file_path_url); |
| ASSERT_EQ(holding_space_item->text(), |
| new_file_path.BaseName().LossyDisplayName()); |
| |
| // Verify that persistence has been updated. |
| persisted_holding_space_items.GetList()[i] = |
| holding_space_item->Serialize(); |
| ASSERT_EQ(*GetProfile()->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items); |
| |
| // Cache the base name of the file backing the holding space item as it will |
| // not change due to rename of the holding space item's parent directory. |
| base::FilePath base_name = holding_space_item->file_path().BaseName(); |
| |
| // Rename the file backing the holding space item's parent directory. |
| file_path = new_file_path.DirName(); |
| new_file_path = file_path.InsertBeforeExtension(" (Moved)"); |
| file_path_url = GetFileSystemUrl(GetProfile(), file_path); |
| new_file_path_url = GetFileSystemUrl(GetProfile(), new_file_path); |
| ASSERT_EQ(storage::AsyncFileTestHelper::Move( |
| context, context->CrackURL(file_path_url), |
| context->CrackURL(new_file_path_url)), |
| base::File::FILE_OK); |
| |
| // File changes must be posted to the UI thread, wait for the update to |
| // reach the holding space model. |
| ItemUpdatedWaiter(primary_holding_space_model).Wait(holding_space_item); |
| |
| // The file backing the holding space item is expected to have re-parented. |
| new_file_path = new_file_path.Append(base_name); |
| new_file_path_url = GetFileSystemUrl(GetProfile(), new_file_path); |
| |
| // Verify that the holding space item has been updated in place. |
| ASSERT_EQ(holding_space_item->file_path(), new_file_path); |
| ASSERT_EQ(holding_space_item->file_system_url(), new_file_path_url); |
| ASSERT_EQ(holding_space_item->text(), |
| new_file_path.BaseName().LossyDisplayName()); |
| |
| // Verify that persistence has been updated. |
| persisted_holding_space_items.GetList()[i] = |
| holding_space_item->Serialize(); |
| ASSERT_EQ(*GetProfile()->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items); |
| } |
| } |
| |
| // Tests that holding space item's image representation gets updated when the |
| // backing file is changed using move operation. Furthermore, verifies that |
| // conflicts caused by moving a holding space item file to another path present |
| // in the holding space get resolved. |
| TEST_F(HoldingSpaceKeyedServiceTest, UpdateItemsOverwrittenByMove) { |
| // Create a file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| // Cache the holding space model for the primary profile. |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| HoldingSpaceModel* const primary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(primary_holding_space_model, |
| primary_holding_space_service->model_for_testing()); |
| |
| // Cache the file system context. |
| storage::FileSystemContext* context = |
| file_manager::util::GetFileSystemContextForExtensionId( |
| GetProfile(), file_manager::kFileManagerAppId); |
| ASSERT_TRUE(context); |
| |
| struct ItemInfo { |
| std::string item_id; |
| base::FilePath path; |
| GURL file_system_url; |
| }; |
| struct TestCase { |
| ItemInfo src; |
| ItemInfo dst; |
| }; |
| std::map<HoldingSpaceItem::Type, TestCase> test_config; |
| |
| base::ListValue persisted_holding_space_items; |
| |
| // Configure holding space state for the test. For each item adds two holding |
| // space items to the model - "src" and "dst" (during the test, the src item's |
| // file will be moved to the dst item's path). |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| auto add_item = [&](const std::string& file_name, ItemInfo* info) { |
| info->path = downloads_mount->CreateFile( |
| base::FilePath(base::NumberToString(static_cast<int>(type))) |
| .Append(file_name), |
| /*content=*/std::string()); |
| info->file_system_url = GetFileSystemUrl(GetProfile(), info->path); |
| |
| // Create the holding space item. |
| auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem( |
| type, info->path, info->file_system_url, |
| base::BindOnce( |
| &holding_space_util::ResolveImage, |
| primary_holding_space_service->thumbnail_loader_for_testing())); |
| info->item_id = holding_space_item->id(); |
| |
| // Add the holding space item to the model and verify persistence. |
| persisted_holding_space_items.Append(holding_space_item->Serialize()); |
| primary_holding_space_model->AddItem(std::move(holding_space_item)); |
| }; |
| |
| TestCase& test_case = test_config[type]; |
| add_item("src.txt", &test_case.src); |
| add_item("dst.txt", &test_case.dst); |
| |
| ASSERT_NE(test_case.src.item_id, test_case.dst.item_id); |
| } |
| |
| EXPECT_EQ(*GetProfile()->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items); |
| |
| base::ListValue final_persisted_holding_space_items; |
| // Runs the test logic. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| const TestCase& test_case = test_config[type]; |
| |
| const HoldingSpaceItem* src_item = |
| primary_holding_space_model->GetItem(test_case.src.item_id); |
| ASSERT_TRUE(src_item); |
| |
| // Move a file that was not in the holding space to the src path. Verify the |
| // holding space item associated with this path remains in the holding space |
| // in this case, and that its image representation gets updated. |
| const base::FilePath path_not_in_holding_space = |
| downloads_mount->CreateFile( |
| base::FilePath(base::NumberToString(static_cast<int>(type))) |
| .Append("not_in_holding_space.txt"), |
| /*content=*/std::string()); |
| |
| ItemImageUpdateWaiter image_update_waiter(src_item); |
| ASSERT_EQ(storage::AsyncFileTestHelper::Move( |
| context, |
| context->CrackURL(GetFileSystemUrl( |
| GetProfile(), path_not_in_holding_space)), |
| context->CrackURL(src_item->file_system_url())), |
| base::File::FILE_OK); |
| |
| image_update_waiter.Wait(); |
| |
| ASSERT_EQ(src_item, |
| primary_holding_space_model->GetItem(test_case.src.item_id)); |
| EXPECT_TRUE(primary_holding_space_model->GetItem(test_case.dst.item_id)); |
| |
| ASSERT_EQ(src_item->file_path(), test_case.src.path); |
| ASSERT_EQ(src_item->file_system_url(), test_case.src.file_system_url); |
| |
| // Move the file at the source item path to the destination item path. |
| // Verify that, given that both paths are represented in the holding space, |
| // the item initially associated with the destination path is removed from |
| // the holding space (to avoid two items with the same backing file). |
| ASSERT_EQ(storage::AsyncFileTestHelper::Move( |
| context, context->CrackURL(test_case.src.file_system_url), |
| context->CrackURL(test_case.dst.file_system_url)), |
| base::File::FILE_OK); |
| |
| // File changes must be posted to the UI thread, wait for the update to |
| // reach the holding space model. |
| ItemUpdatedWaiter(primary_holding_space_model).Wait(src_item); |
| |
| const HoldingSpaceItem* item = |
| primary_holding_space_model->GetItem(test_case.src.item_id); |
| ASSERT_EQ(src_item, |
| primary_holding_space_model->GetItem(test_case.src.item_id)); |
| EXPECT_FALSE(primary_holding_space_model->GetItem(test_case.dst.item_id)); |
| |
| // Verify that the holding space item has been updated in place. |
| ASSERT_EQ(src_item->file_path(), test_case.dst.path); |
| ASSERT_EQ(src_item->file_system_url(), test_case.dst.file_system_url); |
| |
| final_persisted_holding_space_items.Append(item->Serialize()); |
| } |
| |
| EXPECT_EQ(*GetProfile()->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| final_persisted_holding_space_items); |
| } |
| |
| // Verifies that the holding space model is restored from persistence. Note that |
| // when restoring from persistence, existence of backing files is verified and |
| // any stale holding space items are removed. |
| TEST_F(HoldingSpaceKeyedServiceTest, RestorePersistentStorage) { |
| // Create file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| |
| HoldingSpaceModel::ItemList restored_holding_space_items; |
| base::ListValue persisted_holding_space_items_after_restoration; |
| |
| // Create a secondary profile w/ a pre-populated pref store. |
| TestingProfile* const secondary_profile = CreateSecondaryProfile( |
| base::BindLambdaForTesting([&](TestingPrefStore* pref_store) { |
| auto persisted_holding_space_items_before_restoration = |
| std::make_unique<base::ListValue>(); |
| |
| // Persist some holding space items of each type. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| const base::FilePath file = downloads_mount->CreateArbitraryFile(); |
| const GURL file_system_url = GetFileSystemUrl(GetProfile(), file); |
| |
| auto fresh_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, file, file_system_url, |
| base::BindOnce(&holding_space_util::ResolveImage, |
| primary_holding_space_service |
| ->thumbnail_loader_for_testing())); |
| |
| persisted_holding_space_items_before_restoration->Append( |
| fresh_holding_space_item->Serialize()); |
| |
| // We expect the `fresh_holding_space_item` to still be in persistence |
| // after model restoration since its backing file exists. |
| persisted_holding_space_items_after_restoration.Append( |
| fresh_holding_space_item->Serialize()); |
| |
| // We expect the `fresh_holding_space_item` to be restored from |
| // persistence since its backing file exists. |
| restored_holding_space_items.push_back( |
| std::move(fresh_holding_space_item)); |
| |
| base::FilePath file_path = downloads_mount->GetRootPath().AppendASCII( |
| base::UnguessableToken::Create().ToString()); |
| auto stale_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, file_path, GURL("filesystem:fake_file_system_url"), |
| base::BindOnce(&CreateTestHoldingSpaceImage)); |
| |
| // NOTE: While the `stale_holding_space_item` is persisted here, we do |
| // *not* expect it to be restored or to be persisted after model |
| // restoration since its backing file does *not* exist. |
| persisted_holding_space_items_before_restoration->Append( |
| stale_holding_space_item->Serialize()); |
| } |
| |
| pref_store->SetValueSilently( |
| HoldingSpacePersistenceDelegate::kPersistencePath, |
| std::move(persisted_holding_space_items_before_restoration), |
| PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| })); |
| |
| ActivateSecondaryProfile(); |
| HoldingSpaceModelAttachedWaiter(secondary_profile).Wait(); |
| |
| HoldingSpaceKeyedService* const secondary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| secondary_profile); |
| HoldingSpaceModel* const secondary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(secondary_holding_space_model, |
| secondary_holding_space_service->model_for_testing()); |
| |
| ItemsFinalizedWaiter(secondary_holding_space_model).Wait(); |
| ASSERT_EQ(secondary_holding_space_model->items().size(), |
| restored_holding_space_items.size()); |
| |
| // Verify in-memory holding space items. |
| for (size_t i = 0; i < secondary_holding_space_model->items().size(); ++i) { |
| const auto& item = secondary_holding_space_model->items()[i]; |
| const auto& restored_item = restored_holding_space_items[i]; |
| EXPECT_EQ(*item, *restored_item) |
| << "Expected equality of values at index " << i << ":" |
| << "\n\tActual: " << item->id() |
| << "\n\rRestored: " << restored_item->id(); |
| } |
| |
| // Verify persisted holding space items. |
| EXPECT_EQ(*secondary_profile->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items_after_restoration); |
| } |
| |
| // Verifies that items from volumes that are not immediately mounted during |
| // startup get restored into the holding space. |
| TEST_F(HoldingSpaceKeyedServiceTest, |
| RestorePersistentStorageForDelayedVolumeMount) { |
| // Create file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| auto delayed_mount = std::make_unique<ScopedTestMountPoint>( |
| "drivefs-delayed_mount", storage::kFileSystemTypeDriveFs, |
| file_manager::VOLUME_TYPE_GOOGLE_DRIVE); |
| base::FilePath delayed_mount_file_name = base::FilePath("delayed file"); |
| |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| |
| std::vector<std::string> finalized_items_before_delayed_mount; |
| HoldingSpaceModel::ItemList restored_holding_space_items; |
| base::ListValue persisted_holding_space_items_after_restoration; |
| base::ListValue persisted_holding_space_items_after_delayed_mount; |
| |
| // Create a secondary profile w/ a pre-populated pref store. |
| TestingProfile* const secondary_profile = CreateSecondaryProfile( |
| base::BindLambdaForTesting([&](TestingPrefStore* pref_store) { |
| auto persisted_holding_space_items_before_restoration = |
| std::make_unique<base::ListValue>(); |
| |
| // Persist some holding space items of each type. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| const base::FilePath delayed_mount_file = |
| delayed_mount->GetRootPath().Append(delayed_mount_file_name); |
| auto delayed_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, delayed_mount_file, GURL("filesystem:fake"), |
| base::BindOnce(&CreateTestHoldingSpaceImage)); |
| // The item should be restored after delayed volume mount, and remain |
| // in persistent storage. |
| persisted_holding_space_items_before_restoration->Append( |
| delayed_holding_space_item->Serialize()); |
| persisted_holding_space_items_after_restoration.Append( |
| delayed_holding_space_item->Serialize()); |
| persisted_holding_space_items_after_delayed_mount.Append( |
| delayed_holding_space_item->Serialize()); |
| restored_holding_space_items.push_back( |
| std::move(delayed_holding_space_item)); |
| |
| const base::FilePath non_existent_path = |
| delayed_mount->GetRootPath().Append("non-existent"); |
| auto non_existant_delayed_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, non_existent_path, GURL("filesystem:fake"), |
| base::BindOnce(&CreateTestHoldingSpaceImage)); |
| // The item should be removed from the model and persistent storage |
| // after delayed volume mount (when it can be confirmed the backing |
| // file does not exist) - the item should remain in persistent storage |
| // until the associated volume is mounted. |
| persisted_holding_space_items_before_restoration->Append( |
| non_existant_delayed_holding_space_item->Serialize()); |
| persisted_holding_space_items_after_restoration.Append( |
| non_existant_delayed_holding_space_item->Serialize()); |
| |
| const base::FilePath file = downloads_mount->CreateArbitraryFile(); |
| const GURL file_system_url = GetFileSystemUrl(GetProfile(), file); |
| auto fresh_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, file, file_system_url, |
| base::BindOnce(&holding_space_util::ResolveImage, |
| primary_holding_space_service |
| ->thumbnail_loader_for_testing())); |
| |
| // The item should be immediately added to the model, and remain in |
| // the persistent storage. |
| persisted_holding_space_items_before_restoration->Append( |
| fresh_holding_space_item->Serialize()); |
| finalized_items_before_delayed_mount.push_back( |
| fresh_holding_space_item->id()); |
| persisted_holding_space_items_after_restoration.Append( |
| fresh_holding_space_item->Serialize()); |
| persisted_holding_space_items_after_delayed_mount.Append( |
| fresh_holding_space_item->Serialize()); |
| restored_holding_space_items.push_back( |
| std::move(fresh_holding_space_item)); |
| } |
| |
| pref_store->SetValueSilently( |
| HoldingSpacePersistenceDelegate::kPersistencePath, |
| std::move(persisted_holding_space_items_before_restoration), |
| PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| })); |
| |
| ActivateSecondaryProfile(); |
| HoldingSpaceModelAttachedWaiter(secondary_profile).Wait(); |
| |
| HoldingSpaceKeyedService* const secondary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| secondary_profile); |
| HoldingSpaceModel* const secondary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| |
| EXPECT_EQ(secondary_holding_space_model, |
| secondary_holding_space_service->model_for_testing()); |
| |
| ItemsFinalizedWaiter(secondary_holding_space_model) |
| .Wait( |
| /*filter=*/base::BindLambdaForTesting( |
| [&downloads_mount](const HoldingSpaceItem* item) -> bool { |
| return downloads_mount->GetRootPath().IsParent( |
| item->file_path()); |
| })); |
| |
| std::vector<std::string> finalized_items; |
| for (const auto& item : secondary_holding_space_model->items()) { |
| if (item->IsFinalized()) |
| finalized_items.push_back(item->id()); |
| } |
| EXPECT_EQ(finalized_items_before_delayed_mount, finalized_items); |
| |
| // Verify persisted holding space items. |
| EXPECT_EQ(*secondary_profile->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items_after_restoration); |
| |
| delayed_mount->CreateFile(delayed_mount_file_name, "fake"); |
| delayed_mount->Mount(secondary_profile); |
| |
| ItemsFinalizedWaiter(secondary_holding_space_model).Wait(); |
| |
| EXPECT_EQ(secondary_holding_space_model->items().size(), |
| restored_holding_space_items.size()); |
| |
| // Verify in-memory holding space items. |
| for (size_t i = 0; i < secondary_holding_space_model->items().size(); ++i) { |
| const auto& item = secondary_holding_space_model->items()[i]; |
| const auto& restored_item = restored_holding_space_items[i]; |
| SCOPED_TRACE(testing::Message() << "Item at index " << i); |
| |
| EXPECT_TRUE(item->IsFinalized()); |
| |
| EXPECT_EQ(item->id(), restored_item->id()); |
| EXPECT_EQ(item->type(), restored_item->type()); |
| EXPECT_EQ(item->text(), restored_item->text()); |
| EXPECT_EQ(item->file_path(), item->file_path()); |
| // NOTE: `restored_item` was created with a fake file system URL (as it |
| // could not be properly resolved at the time of item creation). |
| EXPECT_EQ(item->file_system_url(), |
| GetFileSystemUrl(secondary_profile, restored_item->file_path())); |
| } |
| |
| // Verify persisted holding space items. |
| EXPECT_EQ(*secondary_profile->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items_after_delayed_mount); |
| } |
| |
| // Verifies that items from volumes that are not immediately mounted during |
| // startup get restored into the holding space - same as |
| // RestorePersistentStorageForDelayedVolumeMount, but the volume gets mounted |
| // while item restoration is in progress. |
| TEST_F(HoldingSpaceKeyedServiceTest, |
| RestorePersistentStorageForDelayedVolumeMountDuringRestoration) { |
| // Create file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| auto delayed_mount = std::make_unique<ScopedTestMountPoint>( |
| "drivefs-delayed_mount", storage::kFileSystemTypeDriveFs, |
| file_manager::VOLUME_TYPE_GOOGLE_DRIVE); |
| base::FilePath delayed_mount_file_name = base::FilePath("delayed file"); |
| |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| |
| HoldingSpaceModel::ItemList restored_holding_space_items; |
| base::ListValue persisted_holding_space_items_after_delayed_mount; |
| |
| // Create a secondary profile w/ a pre-populated pref store. |
| TestingProfile* const secondary_profile = CreateSecondaryProfile( |
| base::BindLambdaForTesting([&](TestingPrefStore* pref_store) { |
| auto persisted_holding_space_items_before_restoration = |
| std::make_unique<base::ListValue>(); |
| |
| // Persist some holding space items of each type. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| const base::FilePath delayed_mount_file = |
| delayed_mount->GetRootPath().Append(delayed_mount_file_name); |
| auto delayed_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, delayed_mount_file, GURL("filesystem:fake"), |
| base::BindOnce(&CreateTestHoldingSpaceImage)); |
| // The item should be restored after delayed volume mount, and remain |
| // in persistent storage. |
| persisted_holding_space_items_before_restoration->Append( |
| delayed_holding_space_item->Serialize()); |
| persisted_holding_space_items_after_delayed_mount.Append( |
| delayed_holding_space_item->Serialize()); |
| restored_holding_space_items.push_back( |
| std::move(delayed_holding_space_item)); |
| |
| base::FilePath non_existent_path = |
| delayed_mount->GetRootPath().Append("non-existent"); |
| auto non_existant_delayed_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, non_existent_path, GURL("filesystem:fake"), |
| base::BindOnce(&CreateTestHoldingSpaceImage)); |
| // The item should be removed from the model and persistent storage |
| // after delayed volume mount (when it can be confirmed the backing |
| // file does not exist) - the item should remain in persistent storage |
| // until the associated volume is mounted. |
| persisted_holding_space_items_before_restoration->Append( |
| non_existant_delayed_holding_space_item->Serialize()); |
| |
| const base::FilePath file = downloads_mount->CreateArbitraryFile(); |
| const GURL file_system_url = GetFileSystemUrl(GetProfile(), file); |
| auto fresh_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, file, file_system_url, |
| base::BindOnce(&holding_space_util::ResolveImage, |
| primary_holding_space_service |
| ->thumbnail_loader_for_testing())); |
| |
| // The item should be immediately added to the model, and remain in |
| // the persistent storage. |
| persisted_holding_space_items_before_restoration->Append( |
| fresh_holding_space_item->Serialize()); |
| persisted_holding_space_items_after_delayed_mount.Append( |
| fresh_holding_space_item->Serialize()); |
| restored_holding_space_items.push_back( |
| std::move(fresh_holding_space_item)); |
| } |
| |
| pref_store->SetValueSilently( |
| HoldingSpacePersistenceDelegate::kPersistencePath, |
| std::move(persisted_holding_space_items_before_restoration), |
| PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| })); |
| |
| ActivateSecondaryProfile(); |
| |
| delayed_mount->CreateFile(delayed_mount_file_name, "fake"); |
| delayed_mount->Mount(secondary_profile); |
| |
| HoldingSpaceModelAttachedWaiter(secondary_profile).Wait(); |
| |
| HoldingSpaceKeyedService* const secondary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| secondary_profile); |
| HoldingSpaceModel* const secondary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| |
| EXPECT_EQ(secondary_holding_space_model, |
| secondary_holding_space_service->model_for_testing()); |
| |
| ItemsFinalizedWaiter(secondary_holding_space_model).Wait(); |
| ASSERT_EQ(secondary_holding_space_model->items().size(), |
| restored_holding_space_items.size()); |
| |
| // Verify in-memory holding space items. |
| for (size_t i = 0; i < secondary_holding_space_model->items().size(); ++i) { |
| const auto& item = secondary_holding_space_model->items()[i]; |
| const auto& restored_item = restored_holding_space_items[i]; |
| SCOPED_TRACE(testing::Message() << "Item at index " << i); |
| |
| EXPECT_TRUE(item->IsFinalized()); |
| |
| EXPECT_EQ(item->id(), restored_item->id()); |
| EXPECT_EQ(item->type(), restored_item->type()); |
| EXPECT_EQ(item->text(), restored_item->text()); |
| EXPECT_EQ(item->file_path(), item->file_path()); |
| // NOTE: `restored_item` was created with a fake file system URL (as it |
| // could not be properly resolved at the time of item creation). |
| EXPECT_EQ(item->file_system_url(), |
| GetFileSystemUrl(secondary_profile, restored_item->file_path())); |
| } |
| |
| // Verify persisted holding space items. |
| EXPECT_EQ(*secondary_profile->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items_after_delayed_mount); |
| } |
| |
| // Verifies that mounting volumes that contain no holding space items does not |
| // interfere with holding space restoration. |
| TEST_F(HoldingSpaceKeyedServiceTest, |
| RestorePersistentStorageWithUnrelatedVolumeMounts) { |
| // Create file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| auto delayed_mount_1 = std::make_unique<ScopedTestMountPoint>( |
| "drivefs-delayed_mount_1", storage::kFileSystemTypeDriveFs, |
| file_manager::VOLUME_TYPE_GOOGLE_DRIVE); |
| |
| auto delayed_mount_2 = std::make_unique<ScopedTestMountPoint>( |
| "drivefs-delayed_mount_2", storage::kFileSystemTypeDriveFs, |
| file_manager::VOLUME_TYPE_GOOGLE_DRIVE); |
| |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| |
| std::vector<std::string> finalized_items_before_delayed_mount; |
| HoldingSpaceModel::ItemList restored_holding_space_items; |
| base::ListValue persisted_holding_space_items_after_restoration; |
| base::ListValue persisted_holding_space_items_after_delayed_mount; |
| |
| // Create a secondary profile w/ a pre-populated pref store. |
| TestingProfile* const secondary_profile = CreateSecondaryProfile( |
| base::BindLambdaForTesting([&](TestingPrefStore* pref_store) { |
| auto persisted_holding_space_items_before_restoration = |
| std::make_unique<base::ListValue>(); |
| |
| // Persist some holding space items of each type. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| const base::FilePath file = downloads_mount->CreateArbitraryFile(); |
| const GURL file_system_url = GetFileSystemUrl(GetProfile(), file); |
| auto fresh_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, file, file_system_url, |
| base::BindOnce(&holding_space_util::ResolveImage, |
| primary_holding_space_service |
| ->thumbnail_loader_for_testing())); |
| |
| // The item should be immediately added to the model, and remain in |
| // the persistent storage. |
| persisted_holding_space_items_before_restoration->Append( |
| fresh_holding_space_item->Serialize()); |
| finalized_items_before_delayed_mount.push_back( |
| fresh_holding_space_item->id()); |
| persisted_holding_space_items_after_restoration.Append( |
| fresh_holding_space_item->Serialize()); |
| persisted_holding_space_items_after_delayed_mount.Append( |
| fresh_holding_space_item->Serialize()); |
| restored_holding_space_items.push_back( |
| std::move(fresh_holding_space_item)); |
| } |
| |
| pref_store->SetValueSilently( |
| HoldingSpacePersistenceDelegate::kPersistencePath, |
| std::move(persisted_holding_space_items_before_restoration), |
| PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| })); |
| |
| ActivateSecondaryProfile(); |
| delayed_mount_1->Mount(secondary_profile); |
| HoldingSpaceModelAttachedWaiter(secondary_profile).Wait(); |
| |
| HoldingSpaceKeyedService* const secondary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| secondary_profile); |
| HoldingSpaceModel* const secondary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| |
| EXPECT_EQ(secondary_holding_space_model, |
| secondary_holding_space_service->model_for_testing()); |
| |
| ItemsFinalizedWaiter(secondary_holding_space_model).Wait(); |
| |
| std::vector<std::string> finalized_items; |
| for (const auto& item : secondary_holding_space_model->items()) { |
| if (item->IsFinalized()) |
| finalized_items.push_back(item->id()); |
| } |
| EXPECT_EQ(finalized_items_before_delayed_mount, finalized_items); |
| |
| // Verify persisted holding space items. |
| EXPECT_EQ(*secondary_profile->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items_after_restoration); |
| |
| delayed_mount_2->Mount(secondary_profile); |
| ItemsFinalizedWaiter(secondary_holding_space_model).Wait(); |
| |
| EXPECT_EQ(secondary_holding_space_model->items().size(), |
| restored_holding_space_items.size()); |
| |
| // Verify in-memory holding space items. |
| for (size_t i = 0; i < secondary_holding_space_model->items().size(); ++i) { |
| const auto& item = secondary_holding_space_model->items()[i]; |
| const auto& restored_item = restored_holding_space_items[i]; |
| SCOPED_TRACE(testing::Message() << "Item at index " << i); |
| |
| EXPECT_TRUE(item->IsFinalized()); |
| |
| EXPECT_EQ(item->id(), restored_item->id()); |
| EXPECT_EQ(item->type(), restored_item->type()); |
| EXPECT_EQ(item->text(), restored_item->text()); |
| EXPECT_EQ(item->file_path(), item->file_path()); |
| // NOTE: `restored_item` was created with a fake file system URL (as it |
| // could not be properly resolved at the time of item creation). |
| EXPECT_EQ(item->file_system_url(), |
| GetFileSystemUrl(secondary_profile, restored_item->file_path())); |
| } |
| |
| // Verify persisted holding space items. |
| EXPECT_EQ(*secondary_profile->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items_after_delayed_mount); |
| } |
| |
| // Tests that items from an unmounted volume get removed from the holding space. |
| TEST_F(HoldingSpaceKeyedServiceTest, RemoveItemsFromUnmountedVolumes) { |
| auto test_mount_1 = std::make_unique<ScopedTestMountPoint>( |
| "test_mount_1", storage::kFileSystemTypeNativeLocal, |
| file_manager::VOLUME_TYPE_TESTING); |
| test_mount_1->Mount(GetProfile()); |
| HoldingSpaceModelAttachedWaiter(GetProfile()).Wait(); |
| |
| auto test_mount_2 = std::make_unique<ScopedTestMountPoint>( |
| "test_mount_2", storage::kFileSystemTypeNativeLocal, |
| file_manager::VOLUME_TYPE_TESTING); |
| test_mount_2->Mount(GetProfile()); |
| HoldingSpaceModelAttachedWaiter(GetProfile()).Wait(); |
| HoldingSpaceKeyedService* const holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| const HoldingSpaceModel* holding_space_model = |
| holding_space_service->model_for_testing(); |
| |
| const base::FilePath file_path_1 = test_mount_1->CreateArbitraryFile(); |
| holding_space_service->AddScreenshot(file_path_1); |
| |
| const base::FilePath file_path_2 = test_mount_2->CreateArbitraryFile(); |
| holding_space_service->AddDownload(file_path_2); |
| |
| const base::FilePath file_path_3 = test_mount_1->CreateArbitraryFile(); |
| holding_space_service->AddDownload(file_path_3); |
| |
| EXPECT_EQ(3u, GetProfile() |
| ->GetPrefs() |
| ->GetList(HoldingSpacePersistenceDelegate::kPersistencePath) |
| ->GetList() |
| .size()); |
| EXPECT_EQ(3u, holding_space_model->items().size()); |
| |
| test_mount_1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, GetProfile() |
| ->GetPrefs() |
| ->GetList(HoldingSpacePersistenceDelegate::kPersistencePath) |
| ->GetList() |
| .size()); |
| ASSERT_EQ(1u, holding_space_model->items().size()); |
| EXPECT_EQ(file_path_2, holding_space_model->items()[0]->file_path()); |
| } |
| |
| // Verifies that screenshots restored from persistence are not older than |
| // kMaxFileAge. |
| TEST_F(HoldingSpaceKeyedServiceTest, RemoveOlderFilesFromPersistance) { |
| // Create file system mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| HoldingSpaceKeyedService* const primary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| |
| HoldingSpaceModel::ItemList restored_holding_space_items; |
| base::ListValue persisted_holding_space_items_after_restoration; |
| |
| // Create a secondary profile w/ a pre-populated pref store. |
| TestingProfile* const secondary_profile = CreateSecondaryProfile( |
| base::BindLambdaForTesting([&](TestingPrefStore* pref_store) { |
| auto persisted_holding_space_items_before_restoration = |
| std::make_unique<base::ListValue>(); |
| |
| // Persist some holding space items of each type. |
| for (const HoldingSpaceItem::Type type : GetHoldingSpaceItemTypes()) { |
| const base::FilePath file = downloads_mount->CreateArbitraryFile(); |
| const GURL file_system_url = GetFileSystemUrl(GetProfile(), file); |
| |
| auto fresh_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, file, file_system_url, |
| base::BindOnce(&holding_space_util::ResolveImage, |
| primary_holding_space_service |
| ->thumbnail_loader_for_testing())); |
| |
| persisted_holding_space_items_before_restoration->Append( |
| fresh_holding_space_item->Serialize()); |
| |
| // Only pinned files are exempt from age checks. In this test, we |
| // expect all holding space items of other types to be removed from |
| // persistence during restoration due to being older than kMaxFileAge. |
| if (type == HoldingSpaceItem::Type::kPinnedFile) { |
| persisted_holding_space_items_after_restoration.Append( |
| fresh_holding_space_item->Serialize()); |
| restored_holding_space_items.push_back( |
| std::move(fresh_holding_space_item)); |
| } |
| |
| const base::FilePath stale_item_file = |
| downloads_mount->GetRootPath().AppendASCII( |
| base::UnguessableToken::Create().ToString()); |
| auto stale_holding_space_item = |
| HoldingSpaceItem::CreateFileBackedItem( |
| type, stale_item_file, |
| GetFileSystemUrl(GetProfile(), stale_item_file), |
| base::BindOnce(&CreateTestHoldingSpaceImage)); |
| |
| // NOTE: While the `stale_holding_space_item` is persisted here, we do |
| // *not* expect it to be restored or to be persisted after model |
| // restoration since its backing file does *not* exist. |
| persisted_holding_space_items_before_restoration->Append( |
| stale_holding_space_item->Serialize()); |
| } |
| |
| pref_store->SetValueSilently( |
| HoldingSpacePersistenceDelegate::kPersistencePath, |
| std::move(persisted_holding_space_items_before_restoration), |
| PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| })); |
| |
| holding_space_util::SetNowForTesting(base::Time::Now() + kMaxFileAge); |
| |
| ActivateSecondaryProfile(); |
| HoldingSpaceModelAttachedWaiter(secondary_profile).Wait(); |
| |
| HoldingSpaceKeyedService* const secondary_holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService( |
| secondary_profile); |
| HoldingSpaceModel* const secondary_holding_space_model = |
| HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(secondary_holding_space_model, |
| secondary_holding_space_service->model_for_testing()); |
| |
| ItemsFinalizedWaiter(secondary_holding_space_model).Wait(); |
| |
| ASSERT_EQ(secondary_holding_space_model->items().size(), |
| restored_holding_space_items.size()); |
| |
| // Verify in-memory holding space items. |
| for (size_t i = 0; i < secondary_holding_space_model->items().size(); ++i) { |
| const auto& item = secondary_holding_space_model->items()[i]; |
| const auto& restored_item = restored_holding_space_items[i]; |
| EXPECT_EQ(*item, *restored_item) |
| << "Expected equality of values at index " << i << ":" |
| << "\n\tActual: " << item->id() |
| << "\n\rRestored: " << restored_item->id(); |
| } |
| |
| // Verify persisted holding space items. |
| EXPECT_EQ(*secondary_profile->GetPrefs()->GetList( |
| HoldingSpacePersistenceDelegate::kPersistencePath), |
| persisted_holding_space_items_after_restoration); |
| } |
| |
| TEST_F(HoldingSpaceKeyedServiceTest, AddDownloadItem) { |
| TestingProfile* profile = GetProfile(); |
| HoldingSpaceModelAttachedWaiter(profile).Wait(); |
| |
| // Create a test downloads mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(profile); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| // Create a fake download file on the local file system - later parts of the |
| // test will try to resolve the file's file system URL, which fails if the |
| // file does not exist. |
| const base::FilePath download_item_virtual_path("Download 1.png"); |
| const base::FilePath download_item_full_path = |
| downloads_mount->CreateFile(download_item_virtual_path, "download 1"); |
| |
| MockDownloadManager* mock_download_manager = download_manager(); |
| std::unique_ptr<download::MockDownloadItem> item( |
| CreateMockDownloadItem(download_item_full_path)); |
| |
| download::MockDownloadItem* mock_download_item = item.get(); |
| EXPECT_CALL(*mock_download_manager, MockCreateDownloadItem(testing::_)) |
| .WillRepeatedly(testing::DoAll( |
| testing::InvokeWithoutArgs([mock_download_manager, |
| mock_download_item]() { |
| mock_download_manager->NotifyDownloadCreated(mock_download_item); |
| }), |
| testing::Return(item.get()))); |
| |
| std::vector<GURL> url_chain; |
| url_chain.push_back(item->GetURL()); |
| mock_download_manager->CreateDownloadItem( |
| base::GenerateGUID(), item->GetId(), item->GetFullPath(), |
| item->GetFullPath(), url_chain, GURL(), GURL(), GURL(), GURL(), |
| url::Origin(), item->GetMimeType(), item->GetMimeType(), |
| base::Time::Now(), base::Time::Now(), "", "", 10, 10, "", |
| download::DownloadItem::IN_PROGRESS, |
| download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, |
| download::DOWNLOAD_INTERRUPT_REASON_NONE, false, base::Time::Now(), false, |
| std::vector<download::DownloadItem::ReceivedSlice>()); |
| |
| HoldingSpaceModel* const model = HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(0u, model->items().size()); |
| |
| EXPECT_CALL(*item, GetState()) |
| .WillRepeatedly(testing::Return(download::DownloadItem::IN_PROGRESS)); |
| item->NotifyObserversDownloadUpdated(); |
| |
| ASSERT_EQ(0u, model->items().size()); |
| |
| EXPECT_CALL(*item, GetState()) |
| .WillRepeatedly(testing::Return(download::DownloadItem::COMPLETE)); |
| item->NotifyObserversDownloadUpdated(); |
| |
| ASSERT_EQ(1u, model->items().size()); |
| |
| const HoldingSpaceItem* download_item = model->items()[0].get(); |
| EXPECT_EQ(download_item_full_path, download_item->file_path()); |
| EXPECT_EQ(download_item_virtual_path, |
| GetVirtualPathFromUrl(download_item->file_system_url(), |
| downloads_mount->name())); |
| } |
| |
| class HoldingSpaceKeyedServiceNearbySharingTest |
| : public HoldingSpaceKeyedServiceTest { |
| public: |
| HoldingSpaceKeyedServiceNearbySharingTest() { |
| scoped_feature_list_.InitWithFeatures( |
| {::features::kNearbySharing, ash::features::kTemporaryHoldingSpace}, |
| {}); |
| } |
| |
| ~HoldingSpaceKeyedServiceNearbySharingTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(HoldingSpaceKeyedServiceNearbySharingTest, AddNearbyShareItem) { |
| // Create a test downloads mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| // Wait for the holding space model. |
| HoldingSpaceModelAttachedWaiter(GetProfile()).Wait(); |
| |
| // Verify that the holding space model gets set even if the holding space |
| // keyed service is not explicitly created. |
| HoldingSpaceModel* const initial_model = |
| HoldingSpaceController::Get()->model(); |
| EXPECT_TRUE(initial_model); |
| |
| HoldingSpaceKeyedService* const holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| const base::FilePath item_1_virtual_path("File 1.png"); |
| // Create a fake nearby shared file on the local file system - later parts of |
| // the test will try to resolve the file's file system URL, which fails if the |
| // file does not exist. |
| const base::FilePath item_1_full_path = |
| downloads_mount->CreateFile(item_1_virtual_path, "red"); |
| ASSERT_FALSE(item_1_full_path.empty()); |
| |
| holding_space_service->AddNearbyShare(item_1_full_path); |
| |
| const base::FilePath item_2_virtual_path = base::FilePath("Alt/File 2.png"); |
| // Create a fake nearby shared file on the local file system - later parts of |
| // the test will try to resolve the file's file system URL, which fails if the |
| // file does not exist. |
| const base::FilePath item_2_full_path = |
| downloads_mount->CreateFile(item_2_virtual_path, "blue"); |
| ASSERT_FALSE(item_2_full_path.empty()); |
| holding_space_service->AddNearbyShare(item_2_full_path); |
| |
| EXPECT_EQ(initial_model, HoldingSpaceController::Get()->model()); |
| EXPECT_EQ(HoldingSpaceController::Get()->model(), |
| holding_space_service->model_for_testing()); |
| |
| HoldingSpaceModel* const model = HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(2u, model->items().size()); |
| |
| const HoldingSpaceItem* item_1 = model->items()[0].get(); |
| EXPECT_EQ(item_1_full_path, item_1->file_path()); |
| EXPECT_TRUE(gfx::BitmapsAreEqual( |
| *holding_space_util::ResolveImage( |
| holding_space_service->thumbnail_loader_for_testing(), |
| HoldingSpaceItem::Type::kNearbyShare, item_1_full_path) |
| ->GetImageSkia() |
| .bitmap(), |
| *item_1->image().GetImageSkia().bitmap())); |
| // Verify the item file system URL resolves to the correct file in the file |
| // manager's context. |
| EXPECT_EQ(item_1_virtual_path, |
| GetVirtualPathFromUrl(item_1->file_system_url(), |
| downloads_mount->name())); |
| EXPECT_EQ(base::ASCIIToUTF16("File 1.png"), item_1->text()); |
| |
| const HoldingSpaceItem* item_2 = model->items()[1].get(); |
| EXPECT_EQ(item_2_full_path, item_2->file_path()); |
| EXPECT_TRUE(gfx::BitmapsAreEqual( |
| *holding_space_util::ResolveImage( |
| holding_space_service->thumbnail_loader_for_testing(), |
| HoldingSpaceItem::Type::kNearbyShare, item_2_full_path) |
| ->GetImageSkia() |
| .bitmap(), |
| *item_2->image().GetImageSkia().bitmap())); |
| // Verify the item file system URL resolves to the correct file in the file |
| // manager's context. |
| EXPECT_EQ(item_2_virtual_path, |
| GetVirtualPathFromUrl(item_2->file_system_url(), |
| downloads_mount->name())); |
| EXPECT_EQ(base::ASCIIToUTF16("File 2.png"), item_2->text()); |
| } |
| |
| TEST_F(HoldingSpaceKeyedServiceTest, AddScreenRecordingItem) { |
| // Create a test downloads mount point. |
| std::unique_ptr<ScopedTestMountPoint> downloads_mount = |
| ScopedTestMountPoint::CreateAndMountDownloads(GetProfile()); |
| ASSERT_TRUE(downloads_mount->IsValid()); |
| |
| // Wait for the holding space model. |
| HoldingSpaceModelAttachedWaiter(GetProfile()).Wait(); |
| |
| // Verify that the holding space model gets set even if the holding space |
| // keyed service is not explicitly created. |
| HoldingSpaceModel* const initial_model = |
| HoldingSpaceController::Get()->model(); |
| EXPECT_TRUE(initial_model); |
| |
| HoldingSpaceKeyedService* const holding_space_service = |
| HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile()); |
| const base::FilePath item_1_virtual_path("Screen Recording 1.mpg"); |
| // Create some fake screen recording file on the local file system - later |
| // parts of the test will try to resolve the file's file system URL, which |
| // fails if the file does not exist. |
| const base::FilePath item_1_full_path = |
| downloads_mount->CreateFile(item_1_virtual_path, "recording 1"); |
| ASSERT_FALSE(item_1_full_path.empty()); |
| |
| holding_space_service->AddScreenRecording(item_1_full_path); |
| |
| const base::FilePath item_2_virtual_path = |
| base::FilePath("Alt/Screen Recording 2.mpg"); |
| const base::FilePath item_2_full_path = |
| downloads_mount->CreateFile(item_2_virtual_path, "recording 2"); |
| ASSERT_FALSE(item_2_full_path.empty()); |
| holding_space_service->AddScreenRecording(item_2_full_path); |
| |
| EXPECT_EQ(initial_model, HoldingSpaceController::Get()->model()); |
| EXPECT_EQ(HoldingSpaceController::Get()->model(), |
| holding_space_service->model_for_testing()); |
| |
| HoldingSpaceModel* const model = HoldingSpaceController::Get()->model(); |
| ASSERT_EQ(2u, model->items().size()); |
| |
| const HoldingSpaceItem* item_1 = model->items()[0].get(); |
| EXPECT_EQ(item_1_full_path, item_1->file_path()); |
| EXPECT_TRUE(gfx::BitmapsAreEqual( |
| *holding_space_util::ResolveImage( |
| holding_space_service->thumbnail_loader_for_testing(), |
| HoldingSpaceItem::Type::kScreenRecording, item_1_full_path) |
| ->GetImageSkia() |
| .bitmap(), |
| *item_1->image().GetImageSkia().bitmap())); |
| // Verify the item file system URL resolves to the correct file in the file |
| // manager's context. |
| EXPECT_EQ(item_1_virtual_path, |
| GetVirtualPathFromUrl(item_1->file_system_url(), |
| downloads_mount->name())); |
| EXPECT_EQ(base::ASCIIToUTF16("Screen Recording 1.mpg"), item_1->text()); |
| |
| const HoldingSpaceItem* item_2 = model->items()[1].get(); |
| EXPECT_EQ(item_2_full_path, item_2->file_path()); |
| EXPECT_TRUE(gfx::BitmapsAreEqual( |
| *holding_space_util::ResolveImage( |
| holding_space_service->thumbnail_loader_for_testing(), |
| HoldingSpaceItem::Type::kScreenRecording, item_2_full_path) |
| ->GetImageSkia() |
| .bitmap(), |
| *item_2->image().GetImageSkia().bitmap())); |
| // Verify the item file system URL resolves to the correct file in the file |
| // manager's context. |
| EXPECT_EQ(item_2_virtual_path, |
| GetVirtualPathFromUrl(item_2->file_system_url(), |
| downloads_mount->name())); |
| EXPECT_EQ(base::ASCIIToUTF16("Screen Recording 2.mpg"), item_2->text()); |
| |
| // Attempt to add an item with an empty file. Verify nothing gets added to the |
| // model. |
| const base::FilePath item_3_virtual_path = base::FilePath(""); |
| const base::FilePath item_3_full_path = |
| downloads_mount->CreateFile(item_3_virtual_path, ""); |
| ASSERT_TRUE(item_3_full_path.empty()); |
| holding_space_service->AddScreenRecording(item_3_full_path); |
| |
| ASSERT_EQ(2u, model->items().size()); |
| } |
| |
| } // namespace ash |