chromium/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc

// Copyright 2020 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/ui/ash/holding_space/holding_space_keyed_service.h"

#include <map>
#include <string>
#include <vector>

#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/holding_space/holding_space_client.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_file.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 "ash/public/cpp/holding_space/holding_space_progress.h"
#include "ash/public/cpp/holding_space/holding_space_util.h"
#include "ash/public/cpp/image_util.h"
#include "base/containers/fixed_flat_set.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/time/time_override.h"
#include "chrome/browser/ash/arc/fileapi/arc_file_system_bridge.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/trash_common_util.h"
#include "chrome/browser/ash/file_manager/trash_io_task.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/file_manager/volume_manager_factory.h"
#include "chrome/browser/ash/file_suggest/file_suggest_keyed_service_factory.h"
#include "chrome/browser/ash/file_suggest/file_suggest_test_util.h"
#include "chrome/browser/ash/file_suggest/file_suggest_util.h"
#include "chrome/browser/ash/file_suggest/mock_file_suggest_keyed_service.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
#include "chrome/browser/file_system_access/file_system_access_permission_context_factory.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_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_test_util.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/browser/ui/webui/print_preview/pdf_printer_handler.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/disks/fake_disk_mount_manager.h"
#include "chromeos/ui/base/file_icon_util.h"
#include "components/account_id/account_id.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/user_names.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/test/fake_download_item.h"
#include "content/public/test/mock_download_manager.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 "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/skia/include/core/SkBitmap.h"
#include "ui/chromeos/styles/cros_styles.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/skia_util.h"

namespace ash {
namespace {

// Aliases ---------------------------------------------------------------------

using ::ash::holding_space::ScopedTestMountPoint;
using ::ash::holding_space_metrics::FilePickerBindingContext;
using ::base::Bucket;
using ::base::BucketsAre;
using ::base::BucketsAreArray;
using ::testing::AllOf;
using ::testing::Conditional;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::IsFalse;
using ::testing::IsTrue;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::ResultOf;
using ::testing::Value;

// Constants -------------------------------------------------------------------

constexpr char kTotalCountV2HistogramPrefix[] =
    "HoldingSpace.Item.TotalCountV2";

// Helpers ---------------------------------------------------------------------

// Returns whether the bitmaps backing the specified `gfx::ImageSkia` are equal.
bool BitmapsAreEqual(const gfx::ImageSkia& a, const gfx::ImageSkia& b) {
  return gfx::BitmapsAreEqual(*a.bitmap(), *b.bitmap());
}

// Creates an empty holding space image.
std::unique_ptr<HoldingSpaceImage> CreateTestHoldingSpaceImage(
    HoldingSpaceItem::Type type,
    const base::FilePath& file_path) {
  return std::make_unique<HoldingSpaceImage>(
      holding_space_util::GetMaxImageSizeForType(type), file_path,
      /*async_bitmap_resolver=*/base::DoNothing());
}

std::unique_ptr<KeyedService> BuildArcFileSystemBridge(
    content::BrowserContext* context) {
  EXPECT_TRUE(arc::ArcServiceManager::Get());
  EXPECT_TRUE(arc::ArcServiceManager::Get()->arc_bridge_service());
  return std::make_unique<arc::ArcFileSystemBridge>(
      context, arc::ArcServiceManager::Get()->arc_bridge_service());
}

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 */,
      disks::DiskMountManager::GetInstance(),
      nullptr /* file_system_provider_service */,
      file_manager::VolumeManager::GetMtpStorageInfoCallback());
}

HoldingSpaceItem* AddUninitializedItem(HoldingSpaceModel* model,
                                       HoldingSpaceItem::Type type,
                                       const base::FilePath& path) {
  // Create a holding space item and use it to create a serialized item
  // dictionary.
  auto item = HoldingSpaceItem::CreateFileBackedItem(
      type,
      HoldingSpaceFile(path, HoldingSpaceFile::FileSystemType::kTest,
                       GURL("filesystem:ignored")),
      base::BindOnce(&CreateTestHoldingSpaceImage));
  const auto serialized_holding_space_item = item->Serialize();
  auto deserialized_item = HoldingSpaceItem::Deserialize(
      serialized_holding_space_item,
      /*image_resolver=*/base::BindOnce(&CreateTestHoldingSpaceImage));

  auto* deserialized_item_ptr = deserialized_item.get();
  model->AddItem(std::move(deserialized_item));
  return deserialized_item_ptr;
}

// Returns the expected TotalCountV2 histogram samples for the specified
// `model`. The names of histograms returned are:
// * "HoldingSpace.Item.TotalCountV2.All"
// * "HoldingSpace.Item.TotalCountV2.All.FileSystemType.{fs_type}"
// * "HoldingSpace.Item.TotalCountV2.{type}"
// * "HoldingSpace.Item.TotalCountV2.{type}.FileSystemType.{fs_type}"
std::map<std::string, std::vector<Bucket>>
GetExpectedTotalCountV2HistogramSamples(const HoldingSpaceModel* model) {
  // Aliases.
  using FileSystemType = HoldingSpaceFile::FileSystemType;
  using Type = HoldingSpaceItem::Type;

  std::map<std::string, std::vector<Bucket>> result;

  // Fill "HoldingSpace.Item.TotalCountV2.All".
  result.emplace(base::StrCat({kTotalCountV2HistogramPrefix, ".All"}),
                 std::vector<Bucket>(
                     {Bucket(/*sample=*/model->items().size(), /*count=*/1u)}));

  // File system types are allowlisted based on need to limit the number of
  // recorded histograms arising from combinations with holding space item type.
  constexpr auto kAllowlistedFsTypes = base::MakeFixedFlatSet<FileSystemType>(
      {FileSystemType::kDriveFs, FileSystemType::kLocal});

  // Fill "HoldingSpace.Item.TotalCountV2.All.FileSystemType.{fs_type}".
  for (const FileSystemType fs_type : kAllowlistedFsTypes) {
    result.emplace(
        base::StrCat({kTotalCountV2HistogramPrefix, ".All.FileSystemType.",
                      holding_space_util::ToString(fs_type)}),
        std::vector<Bucket>({Bucket(/*sample=*/base::ranges::count(
                                        model->items(), fs_type,
                                        [&](const auto& item) {
                                          return item->file().file_system_type;
                                        }),
                                    /*count=*/1u)}));
  }

  // Fill "HoldingSpace.Item.TotalCountV2.{type}".
  for (const Type type : holding_space_util::GetAllItemTypes()) {
    result.emplace(base::StrCat({kTotalCountV2HistogramPrefix, ".",
                                 holding_space_util::ToString(type)}),
                   std::vector<Bucket>({Bucket(
                       /*sample=*/base::ranges::count(model->items(), type,
                                                      &HoldingSpaceItem::type),
                       /*count=*/1u)}));

    // Fill "HoldingSpace.Item.TotalCountV2.{type}.FileSystemType.{fs_type}".
    for (const FileSystemType fs_type : kAllowlistedFsTypes) {
      result.emplace(
          base::StrCat({kTotalCountV2HistogramPrefix, ".",
                        holding_space_util::ToString(type), ".FileSystemType.",
                        holding_space_util::ToString(fs_type)}),
          std::vector<Bucket>({Bucket(
              /*sample=*/base::ranges::count_if(
                  model->items(),
                  [&](const auto& item) {
                    return item->type() == type &&
                           item->file().file_system_type == fs_type;
                  }),
              /*count=*/1u)}));
    }
  }

  return result;
}

// Returns a new map of histogram samples having merged `a` and `b`.
std::map<std::string, std::vector<Bucket>> MergeHistogramSamples(
    const std::map<std::string, std::vector<Bucket>>& a,
    const std::map<std::string, std::vector<Bucket>>& b) {
  std::map<std::string, std::vector<Bucket>> result = a;
  for (const auto& [name, buckets] : b) {
    auto name_it = result.find(name);

    // Case: Name did *not* exist in other map. Add all buckets.
    if (name_it == result.end()) {
      result.emplace(name, buckets);
      continue;
    }

    std::vector<Bucket>& result_buckets = name_it->second;

    // Case: Name *did* exist in other map.
    for (const auto& bucket : buckets) {
      auto bucket_it =
          base::ranges::find(result_buckets, bucket.min, &Bucket::min);

      // Case: Bucket did *not* exist in other map. Add bucket.
      if (bucket_it == result_buckets.end()) {
        result_buckets.emplace_back(bucket);
        continue;
      }

      // Case: Bucket *did* exist in other map. Update bucket.
      bucket_it->count += bucket.count;
    }
  }
  return result;
}

bool ShouldRestoreFromPersistence(HoldingSpaceItem::Type type) {
  if (HoldingSpaceItem::IsSuggestionType(type) &&
      !features::IsHoldingSpaceSuggestionsEnabled()) {
    return false;
  }
  return true;
}

// Waiters ---------------------------------------------------------------------

// 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_observation_.Observe(
        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();
    }
  }

  bool IsModelAttached() const {
    HoldingSpaceKeyedService* const holding_space_service =
        HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile_);
    return HoldingSpaceController::Get()->model() ==
           holding_space_service->model_for_testing();
  }

  const raw_ptr<Profile> profile_;
  base::ScopedObservation<HoldingSpaceController,
                          HoldingSpaceControllerObserver>
      holding_space_controller_observation_{this};
  std::unique_ptr<base::RunLoop> wait_loop_;
};

class ItemUpdatedWaiter : public HoldingSpaceModelObserver {
 public:
  ItemUpdatedWaiter(HoldingSpaceModel* model, const HoldingSpaceItem* item)
      : wait_item_(item) {
    model_observer_.Observe(model);
  }

  ItemUpdatedWaiter(const ItemUpdatedWaiter&) = delete;
  ItemUpdatedWaiter& operator=(const ItemUpdatedWaiter&) = delete;
  ~ItemUpdatedWaiter() override = default;

  void Wait() {
    ASSERT_TRUE(wait_item_);
    ASSERT_FALSE(wait_loop_);
    if (wait_item_updated_) {
      // The item has already been updated, no waiting necessary.
      wait_item_updated_ = false;
      return;
    }

    wait_loop_ = std::make_unique<base::RunLoop>();
    wait_loop_->Run();
    wait_loop_.reset();
  }

 private:
  // HoldingSpaceModelObserver:
  void OnHoldingSpaceItemUpdated(
      const HoldingSpaceItem* item,
      const HoldingSpaceItemUpdatedFields& updated_fields) override {
    if (!wait_loop_) {
      // `wait_loop_` is nullptr, if wait has not yet been called.
      if (item == wait_item_) {
        wait_item_updated_ = true;
      }
      return;
    }
    if (item == wait_item_) {
      wait_loop_->Quit();
    }
  }

  raw_ptr<const HoldingSpaceItem> wait_item_ = nullptr;
  std::unique_ptr<base::RunLoop> wait_loop_;
  bool wait_item_updated_ = false;

  base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
      model_observer_{this};
};

class ItemRemovedWaiter : public HoldingSpaceModelObserver {
 public:
  ItemRemovedWaiter(HoldingSpaceModel* model, const HoldingSpaceItem* item)
      : wait_item_(item) {
    model_observer_.Observe(model);
  }

  ItemRemovedWaiter(const ItemRemovedWaiter&) = delete;
  ItemRemovedWaiter& operator=(const ItemRemovedWaiter&) = delete;
  ~ItemRemovedWaiter() override = default;

  void Wait() {
    ASSERT_TRUE(wait_item_);
    ASSERT_FALSE(wait_loop_);
    if (wait_item_removed_) {
      // The item has already been removed, no waiting necessary.
      wait_item_removed_ = false;
      return;
    }

    wait_loop_ = std::make_unique<base::RunLoop>();
    wait_loop_->Run();
    wait_loop_.reset();
  }

 private:
  // HoldingSpaceModelObserver:
  void OnHoldingSpaceItemsRemoved(
      const std::vector<const HoldingSpaceItem*>& items) override {
    if (items.size() != 1 || items[0] != wait_item_) {
      return;
    }

    if (wait_loop_) {
      wait_loop_->Quit();
    } else {
      wait_item_removed_ = true;
    }
  }

  raw_ptr<const HoldingSpaceItem, DanglingUntriaged> wait_item_ = nullptr;
  std::unique_ptr<base::RunLoop> wait_loop_;
  bool wait_item_removed_ = false;

  base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
      model_observer_{this};
};

class ItemsInitializedWaiter : public HoldingSpaceModelObserver {
 public:
  // Predicate that determines whether the waiter should wait for an item to be
  // initialized.
  using ItemFilter =
      base::RepeatingCallback<bool(const HoldingSpaceItem* item)>;

  explicit ItemsInitializedWaiter(HoldingSpaceModel* model) : model_(model) {}
  ItemsInitializedWaiter(const ItemsInitializedWaiter&) = delete;
  ItemsInitializedWaiter& operator=(const ItemsInitializedWaiter&) = delete;
  ~ItemsInitializedWaiter() override = default;

  // NOTE: The filter defaults to all items.
  void Wait(const ItemFilter& filter = ItemFilter()) {
    ASSERT_FALSE(wait_loop_);

    filter_ = filter;
    if (FilteredItemsInitialized()) {
      return;
    }

    base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
        model_observer{this};
    model_observer.Observe(model_.get());

    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 (FilteredItemsInitialized()) {
      wait_loop_->Quit();
    }
  }

  void OnHoldingSpaceItemInitialized(const HoldingSpaceItem* item) override {
    if (FilteredItemsInitialized()) {
      wait_loop_->Quit();
    }
  }

 private:
  bool FilteredItemsInitialized() const {
    for (auto& item : model_->items()) {
      if (filter_ && !filter_.Run(item.get())) {
        continue;
      }
      if (!item->IsInitialized()) {
        return false;
      }
    }
    return true;
  }

  const raw_ptr<HoldingSpaceModel> 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_;
};

// Mocks -----------------------------------------------------------------------

// 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 Shutdown() override {
    for (auto& observer : observers_) {
      observer.ManagerGoingDown(this);
    }
  }

  void NotifyDownloadCreated(download::DownloadItem* item) {
    for (auto& observer : observers_) {
      observer.OnDownloadCreated(this, item);
    }
  }

 private:
  base::ObserverList<content::DownloadManager::Observer>::Unchecked observers_;
};

}  // namespace

// HoldingSpaceKeyedServiceTest ------------------------------------------------

class HoldingSpaceKeyedServiceTest : public BrowserWithTestWindowTest {
 public:
  HoldingSpaceKeyedServiceTest()
      : BrowserWithTestWindowTest(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
    HoldingSpaceImage::SetUseZeroInvalidationDelayForTesting(true);
  }

  HoldingSpaceKeyedServiceTest(const HoldingSpaceKeyedServiceTest& other) =
      delete;
  HoldingSpaceKeyedServiceTest& operator=(
      const HoldingSpaceKeyedServiceTest& other) = delete;
  ~HoldingSpaceKeyedServiceTest() override {
    HoldingSpaceImage::SetUseZeroInvalidationDelayForTesting(false);
  }

  // BrowserWithTestWindowTest:
  void SetUp() override {
    ash::ProfileHelper::SetProfileToUserForTestingEnabled(true);

    // The test's task environment starts with a mock time close to the Unix
    // epoch, but the files that back holding space items are created with
    // accurate timestamps. Advance the clock so that the test's mock time and
    // the time used for file operations are in sync for file age calculations.
    task_environment()->AdvanceClock(base::subtle::TimeNowIgnoringOverride() -
                                     base::Time::Now());
    // Needed by `file_manager::VolumeManager`.
    disks::DiskMountManager::InitializeForTesting(
        new disks::FakeDiskMountManager);

    // Needed for `app_list::MockFileSuggestKeyedService`.
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());

    BrowserWithTestWindowTest::SetUp();

    WaitUntilFileSuggestServiceReady(
        FileSuggestKeyedServiceFactory::GetInstance()->GetService(
            GetProfile()));
  }

  void TearDown() override {
    BrowserWithTestWindowTest::TearDown();
    disks::DiskMountManager::Shutdown();

    ash::ProfileHelper::SetProfileToUserForTestingEnabled(false);
  }

  TestingProfile::TestingFactories GetTestingFactories() override {
    return {
        TestingProfile::TestingFactory{
            arc::ArcFileSystemBridge::GetFactory(),
            base::BindRepeating(&BuildArcFileSystemBridge)},
        TestingProfile::TestingFactory{
            file_manager::VolumeManagerFactory::GetInstance(),
            base::BindRepeating(&BuildVolumeManager)},
        TestingProfile::TestingFactory{
            FileSuggestKeyedServiceFactory::GetInstance(),
            base::BindRepeating(
                &MockFileSuggestKeyedService::BuildMockFileSuggestKeyedService,
                temp_dir_.GetPath())}};
  }

  TestingProfile* CreateProfile(const std::string& profile_name) override {
    auto* profile = BrowserWithTestWindowTest::CreateProfile(profile_name);
    SetUpDownloadManager(profile);
    return profile;
  }

  TestingProfile* CreateSecondaryProfile(
      std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs = nullptr) {
    constexpr char kSecondaryProfileName[] = "secondary_profile";
    LogIn(kSecondaryProfileName);
    auto* profile = profile_manager()->CreateTestingProfile(
        kSecondaryProfileName, std::move(prefs), /*user_name=*/std::u16string(),
        /*avatar_id=*/0, GetTestingFactories());
    OnUserProfileCreated(kSecondaryProfileName, profile);
    return profile;
  }

  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::util::GetFileManagerURL(),
        &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::GetFileManagerFileSystemContext(GetProfile());
    storage::FileSystemURL fs_url =
        fs_context->CrackURLInFirstPartyContext(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;
  }

  // Creates and returns a fake download item for `profile` with the specified
  // `state`, `file_path`, `target_file_path`, `received_bytes`, and
  // `total_bytes`.
  std::unique_ptr<content::FakeDownloadItem> CreateFakeDownloadItem(
      Profile* profile,
      download::DownloadItem::DownloadState state,
      const base::FilePath& file_path,
      const base::FilePath& target_file_path,
      int64_t received_bytes,
      int64_t total_bytes) {
    auto fake_download_item = std::make_unique<content::FakeDownloadItem>();
    fake_download_item->SetDummyFilePath(file_path);
    fake_download_item->SetReceivedBytes(received_bytes);
    fake_download_item->SetState(state);
    fake_download_item->SetTargetFilePath(target_file_path);
    fake_download_item->SetTotalBytes(total_bytes);

    // Notify observers of the created download.
    download_managers_[profile]->NotifyDownloadCreated(
        fake_download_item.get());
    return fake_download_item;
  }

 protected:
  // Creates a `MockDownloadManager` for `profile` to use.
  void SetUpDownloadManager(Profile* profile) {
    auto manager = std::make_unique<testing::NiceMock<MockDownloadManager>>();
    ON_CALL(*manager, IsManagerInitialized)
        .WillByDefault(testing::Return(true));
    download_managers_[profile] = manager.get();
    profile->SetDownloadManagerForTesting(std::move(manager));
  }

 private:
  std::map<Profile*, testing::NiceMock<MockDownloadManager>*>
      download_managers_;
  arc::ArcServiceManager arc_service_manager_;
  base::ScopedTempDir temp_dir_;
};

class HoldingSpaceKeyedServiceWithExperimentalFeatureTest
    : public HoldingSpaceKeyedServiceTest,
      public testing::WithParamInterface<
          /*enable_suggestions=*/bool> {
 public:
  HoldingSpaceKeyedServiceWithExperimentalFeatureTest() {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;
    (GetParam() ? enabled_features : disabled_features)
        .push_back(features::kHoldingSpaceSuggestions);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
                         /*enabled_suggestions=*/testing::Bool());

class HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest
    : public HoldingSpaceKeyedServiceWithExperimentalFeatureTest {
 public:
  HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest() {
    // To let ProfileHelper::GetUserByProfile() directly return
    // the created guest user, without faking directory paths.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        ash::switches::kIgnoreUserProfileMappingForTests);
  }

  void TearDown() override {
    profile_.reset();
    HoldingSpaceKeyedServiceWithExperimentalFeatureTest::TearDown();
  }

  std::string GetDefaultProfileName() override {
    return user_manager::kGuestUserName;
  }

  void LogIn(const std::string& email) override {
    CHECK_EQ(email, user_manager::kGuestUserName);
    auto account_id = user_manager::GuestAccountId();

    user_manager()->AddGuestUser(account_id);
    user_manager()->UserLoggedIn(
        account_id,
        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
        /*browser_restart=*/false,
        /*is_child=*/false);
  }

  TestingProfile* CreateProfile(const std::string& profile_name) override {
    CHECK_EQ(profile_name, user_manager::kGuestUserName);
    CHECK(!profile_);

    // Construct a guest session profile.
    // Profile is created outside of TestingProfileManager management
    // to inject more factories.
    TestingProfile::Builder guest_profile_builder;
    guest_profile_builder.SetGuestSession();
    guest_profile_builder.SetProfileName(profile_name);
    guest_profile_builder.AddTestingFactories(
        {TestingProfile::TestingFactory{
             arc::ArcFileSystemBridge::GetFactory(),
             base::BindRepeating(&BuildArcFileSystemBridge)},
         TestingProfile::TestingFactory{
             file_manager::VolumeManagerFactory::GetInstance(),
             base::BindRepeating(&BuildVolumeManager)}});
    profile_ = guest_profile_builder.Build();
    OnUserProfileCreated(profile_name, profile_.get());
    return profile_.get();
  }

  std::unique_ptr<Browser> CreateBrowser(
      Profile* profile,
      Browser::Type browser_type,
      bool hosted_app,
      BrowserWindow* browser_window) override {
    // Do not create browser.
    return nullptr;
  }

 private:
  std::unique_ptr<TestingProfile> profile_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest,
    /*enabled_suggestions=*/testing::Bool());

TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest,
       GuestUserProfile) {
  auto* guest_profile = profile();

  // Service instances should be created for guest sessions but note that the
  // service factory will redirect to use the primary OTR profile.
  ASSERT_TRUE(guest_profile);
  ASSERT_FALSE(guest_profile->IsOffTheRecord());
  HoldingSpaceKeyedService* const guest_profile_service =
      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(guest_profile);
  ASSERT_TRUE(guest_profile_service);

  // Since the service factory redirects to use the primary OTR profile in the
  // case of guest sessions, retrieving the service instance for the primary OTR
  // profile should yield the same result as retrieving the service instance for
  // a non-OTR guest session profile.
  ASSERT_TRUE(guest_profile->GetPrimaryOTRProfile(/*create_if_needed=*/true));
  HoldingSpaceKeyedService* const primary_otr_guest_profile_service =
      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
          guest_profile->GetPrimaryOTRProfile(/*create_if_needed=*/true));
  ASSERT_EQ(guest_profile_service, primary_otr_guest_profile_service);

  // Construct a second OTR profile from `guest_profile`.
  TestingProfile::Builder secondary_otr_guest_profile_builder;
  secondary_otr_guest_profile_builder.SetGuestSession();
  secondary_otr_guest_profile_builder.SetProfileName(
      guest_profile->GetProfileUserName());
  TestingProfile* const secondary_otr_guest_profile =
      secondary_otr_guest_profile_builder.BuildOffTheRecord(
          guest_profile, Profile::OTRProfileID::CreateUniqueForTesting());
  ASSERT_TRUE(secondary_otr_guest_profile);
  ASSERT_TRUE(secondary_otr_guest_profile->IsOffTheRecord());

  // Service instances should be created for non-primary OTR guest session
  // profiles but as stated earlier the service factory will redirect to use the
  // primary OTR profile. This means that the secondary OTR profile service
  // instance should be equal to that explicitly created for the primary OTR
  // profile.
  HoldingSpaceKeyedService* const secondary_otr_guest_profile_service =
      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
          secondary_otr_guest_profile);
  ASSERT_TRUE(secondary_otr_guest_profile_service);
  ASSERT_EQ(primary_otr_guest_profile_service,
            secondary_otr_guest_profile_service);
}

TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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());
  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());
  EXPECT_EQ(HoldingSpaceController::Get()->model(),
            secondary_holding_space_service->model_for_testing());
}

TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       RecordsUserPreferencesAtStartUp) {
  // Initially expect no user preferences recorded.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectTotalCount(
      "HoldingSpace.UserPreferences.PreviewsEnabled", /*count=*/0u);
  histogram_tester.ExpectTotalCount(
      "HoldingSpace.UserPreferences.SuggestionsExpanded", /*count=*/0u);

  constexpr bool kPreviewsEnabled = false;
  constexpr bool kSuggestionsExpanded = false;

  // Create a profile with explicitly set user preferences.
  TestingProfile* const secondary_profile = CreateSecondaryProfile(
      base::BindLambdaForTesting([&](TestingPrefStore* pref_store) {
        pref_store->SetValueSilently(
            "ash.holding_space.previews_enabled", base::Value(kPreviewsEnabled),
            PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS);
        pref_store->SetValueSilently(
            "ash.holding_space.suggestions_expanded",
            base::Value(kSuggestionsExpanded),
            PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS);
      }));

  // Ensure service creation for the created profile.
  HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(secondary_profile);

  // Expect user preferences recorded.
  histogram_tester.ExpectTotalCount(
      "HoldingSpace.UserPreferences.PreviewsEnabled", /*count=*/1u);
  histogram_tester.ExpectBucketCount(
      "HoldingSpace.UserPreferences.PreviewsEnabled",
      /*sample=*/kPreviewsEnabled, /*expected_count=*/1u);
  histogram_tester.ExpectTotalCount(
      "HoldingSpace.UserPreferences.SuggestionsExpanded", /*count=*/1u);
  histogram_tester.ExpectBucketCount(
      "HoldingSpace.UserPreferences.SuggestionsExpanded",
      /*sample=*/kSuggestionsExpanded,
      /*expected_count=*/1u);
}

// Verifies that updates to the holding space model are persisted.
TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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::Value::List persisted_holding_space_items;

  // Verify persistent storage is updated when adding each type of item.
  for (const auto type : holding_space_util::GetAllItemTypes()) {
    const base::FilePath file_path = downloads_mount->CreateArbitraryFile();
    const GURL file_system_url = GetFileSystemUrl(GetProfile(), file_path);
    const HoldingSpaceFile::FileSystemType file_system_type =
        holding_space_util::ResolveFileSystemType(GetProfile(),
                                                  file_system_url);

    auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
        type, HoldingSpaceFile(file_path, file_system_type, 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.erase(persisted_holding_space_items.begin());
    primary_holding_space_model->RemoveItem(holding_space_item->id());

    EXPECT_EQ(GetProfile()->GetPrefs()->GetList(
                  HoldingSpacePersistenceDelegate::kPersistencePath),
              persisted_holding_space_items);
  }
}

// Verifies that only finalized holding space items are persisted and that,
// once finalized, previously in progress holding space items are persisted at
// the appropriate index.
TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       PersistenceOfInProgressItems) {
  // 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.
  HoldingSpaceKeyedService* const holding_space_service =
      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(GetProfile());
  HoldingSpaceModel* const holding_space_model =
      HoldingSpaceController::Get()->model();
  EXPECT_EQ(holding_space_model, holding_space_service->model_for_testing());

  // Initially, both the model and persistent storage should be empty.
  EXPECT_EQ(holding_space_model->items().size(), 0u);
  EXPECT_EQ(GetProfile()
                ->GetPrefs()
                ->GetList(HoldingSpacePersistenceDelegate::kPersistencePath)
                .size(),
            0u);

  // Add a finalized item to holding space. Because the item is finalized, it
  // should immediately be added to persistent storage.
  base::FilePath file_path = downloads_mount->CreateArbitraryFile();
  GURL file_system_url = GetFileSystemUrl(GetProfile(), file_path);
  HoldingSpaceFile::FileSystemType file_system_type =
      holding_space_util::ResolveFileSystemType(GetProfile(), file_system_url);
  auto finalized_holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
      HoldingSpaceItem::Type::kDownload,
      HoldingSpaceFile(file_path, file_system_type, file_system_url),
      base::BindOnce(&holding_space_util::ResolveImage,
                     holding_space_service->thumbnail_loader_for_testing()));
  auto* finalized_holding_space_item_ptr = finalized_holding_space_item.get();
  holding_space_model->AddItem(std::move(finalized_holding_space_item));

  base::Value::List persisted_holding_space_items;
  persisted_holding_space_items.Append(
      finalized_holding_space_item_ptr->Serialize());

  EXPECT_EQ(GetProfile()->GetPrefs()->GetList(
                HoldingSpacePersistenceDelegate::kPersistencePath),
            persisted_holding_space_items);

  // Add an in-progress item to holding space. Because the item is in progress,
  // it should *not* be added to persistent storage.
  file_path = downloads_mount->CreateArbitraryFile();
  file_system_url = GetFileSystemUrl(GetProfile(), file_path);
  file_system_type =
      holding_space_util::ResolveFileSystemType(GetProfile(), file_system_url);
  auto in_progress_holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
      HoldingSpaceItem::Type::kDownload,
      HoldingSpaceFile(file_path, file_system_type,
                       GetFileSystemUrl(GetProfile(), file_path)),
      HoldingSpaceProgress(/*current_bytes=*/50, /*total_bytes=*/100),
      base::BindOnce(&holding_space_util::ResolveImage,
                     holding_space_service->thumbnail_loader_for_testing()));
  auto* in_progress_holding_space_item_ptr =
      in_progress_holding_space_item.get();
  holding_space_model->AddItem(std::move(in_progress_holding_space_item));

  EXPECT_EQ(GetProfile()->GetPrefs()->GetList(
                HoldingSpacePersistenceDelegate::kPersistencePath),
            persisted_holding_space_items);

  // Add another finalized item to holding space. Because the item is finalized,
  // it should immediately be added to persistent storage.
  file_path = downloads_mount->CreateArbitraryFile();
  file_system_url = GetFileSystemUrl(GetProfile(), file_path);
  file_system_type =
      holding_space_util::ResolveFileSystemType(GetProfile(), file_system_url);
  finalized_holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
      HoldingSpaceItem::Type::kDownload,
      HoldingSpaceFile(file_path, file_system_type, file_system_url),
      base::BindOnce(&holding_space_util::ResolveImage,
                     holding_space_service->thumbnail_loader_for_testing()));
  finalized_holding_space_item_ptr = finalized_holding_space_item.get();
  holding_space_model->AddItem(std::move(finalized_holding_space_item));

  persisted_holding_space_items.Append(
      finalized_holding_space_item_ptr->Serialize());

  EXPECT_EQ(GetProfile()->GetPrefs()->GetList(
                HoldingSpacePersistenceDelegate::kPersistencePath),
            persisted_holding_space_items);

  // Update the file path for a finalized item. Because the item is finalized,
  // it should be updated immediately in persistent storage.
  file_path = downloads_mount->CreateArbitraryFile();
  file_system_url = GetFileSystemUrl(GetProfile(), file_path);
  file_system_type =
      holding_space_util::ResolveFileSystemType(GetProfile(), file_system_url);
  holding_space_model->UpdateItem(finalized_holding_space_item_ptr->id())
      ->SetBackingFile(
          HoldingSpaceFile(file_path, file_system_type, file_system_url));

  ASSERT_EQ(persisted_holding_space_items.size(), 2u);
  persisted_holding_space_items[1u] =
      base::Value(finalized_holding_space_item_ptr->Serialize());

  EXPECT_EQ(GetProfile()->GetPrefs()->GetList(
                HoldingSpacePersistenceDelegate::kPersistencePath),
            persisted_holding_space_items);

  // Update the file path for the in-progress item. Because the item is still in
  // progress, it should not be added/updated to/in persistent storage.
  file_path = downloads_mount->CreateArbitraryFile();
  file_system_url = GetFileSystemUrl(GetProfile(), file_path);
  file_system_type =
      holding_space_util::ResolveFileSystemType(GetProfile(), file_system_url);
  holding_space_model->UpdateItem(in_progress_holding_space_item_ptr->id())
      ->SetBackingFile(
          HoldingSpaceFile(file_path, file_system_type, file_system_url));

  EXPECT_EQ(GetProfile()->GetPrefs()->GetList(
                HoldingSpacePersistenceDelegate::kPersistencePath),
            persisted_holding_space_items);

  // Update the progress for the in-progress item. Because the item is still in
  // progress it should not be added/updated to/in persistent storage.
  holding_space_model->UpdateItem(in_progress_holding_space_item_ptr->id())
      ->SetProgress(
          HoldingSpaceProgress(/*current_bytes=*/75, /*total_bytes=*/100));

  EXPECT_EQ(GetProfile()->GetPrefs()->GetList(
                HoldingSpacePersistenceDelegate::kPersistencePath),
            persisted_holding_space_items);

  // Mark the in-progress item as finalized. Because the item is finalized, it
  // should be added to persistent storage at the appropriate index.
  holding_space_model->UpdateItem(in_progress_holding_space_item_ptr->id())
      ->SetProgress(
          HoldingSpaceProgress(/*current_bytes=*/100, /*total_bytes=*/100));

  ASSERT_EQ(persisted_holding_space_items.size(), 2u);
  persisted_holding_space_items.Insert(
      persisted_holding_space_items.begin() + 1u,
      base::Value(in_progress_holding_space_item_ptr->Serialize()));

  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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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::GetFileManagerFileSystemContext(GetProfile());
  ASSERT_TRUE(context);

  base::Value::List persisted_holding_space_items;

  // Verify persistent storage is updated when adding each type of item.
  for (const auto type : holding_space_util::GetAllItemTypes()) {
    // 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);
    const HoldingSpaceFile::FileSystemType file_system_type =
        holding_space_util::ResolveFileSystemType(GetProfile(),
                                                  file_system_url);

    // Create the holding space item.
    auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
        type, HoldingSpaceFile(file_path, file_system_type, 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().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);
    {
      ItemUpdatedWaiter waiter(primary_holding_space_model, holding_space_item);
      ASSERT_EQ(
          storage::AsyncFileTestHelper::Move(
              context, context->CrackURLInFirstPartyContext(file_path_url),
              context->CrackURLInFirstPartyContext(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.
      waiter.Wait();
    }

    // Verify that the holding space item has been updated in place.
    ASSERT_EQ(holding_space_item->file().file_path, new_file_path);
    ASSERT_EQ(holding_space_item->file().file_system_url, new_file_path_url);
    ASSERT_EQ(holding_space_item->GetText(),
              new_file_path.BaseName().LossyDisplayName());

    // Verify that persistence has been updated.
    persisted_holding_space_items[i] =
        base::Value(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().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);
    {
      ItemUpdatedWaiter waiter(primary_holding_space_model, holding_space_item);
      ASSERT_EQ(
          storage::AsyncFileTestHelper::Move(
              context, context->CrackURLInFirstPartyContext(file_path_url),
              context->CrackURLInFirstPartyContext(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.
      waiter.Wait();
    }

    // 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().file_path, new_file_path);
    ASSERT_EQ(holding_space_item->file().file_system_url, new_file_path_url);
    ASSERT_EQ(holding_space_item->GetText(),
              new_file_path.BaseName().LossyDisplayName());

    // Verify that persistence has been updated.
    persisted_holding_space_items[i] =
        base::Value(holding_space_item->Serialize());
    ASSERT_EQ(GetProfile()->GetPrefs()->GetList(
                  HoldingSpacePersistenceDelegate::kPersistencePath),
              persisted_holding_space_items);
  }
}

// Verifies that files that are trashed via the `TrashIOTask` are removed from
// the holding space model.
TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       TrashedFilesAreRemovedFromTheModel) {
  // Create a file system mount point.
  std::unique_ptr<ScopedTestMountPoint> downloads_mount =
      ScopedTestMountPoint::CreateAndMountDownloads(GetProfile());
  ASSERT_TRUE(downloads_mount->IsValid());

  // Ensure that required trash folders exist for the `downloads_mount`.
  const base::FilePath trash_path = downloads_mount->GetRootPath().Append(
      file_manager::trash::kTrashFolderName);
  ASSERT_TRUE(base::CreateDirectory(
      trash_path.Append(file_manager::trash::kFilesFolderName)));
  ASSERT_TRUE(base::CreateDirectory(
      trash_path.Append(file_manager::trash::kInfoFolderName)));

  // 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());

  // Add each item to the holding space model.
  for (const auto type : holding_space_util::GetAllItemTypes()) {
    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);
    const HoldingSpaceFile::FileSystemType file_system_type =
        holding_space_util::ResolveFileSystemType(GetProfile(),
                                                  file_system_url);

    // Create the holding space item.
    auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
        type, HoldingSpaceFile(file_path, file_system_type, 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.
    primary_holding_space_model->AddItem(std::move(holding_space_item));
  }

  // Use the File Manager's context for testing. Note that we specifically do
  // not use a test context since we want a production context which uses file
  // system operations that notify the `FileChangeService` on completion.
  storage::FileSystemContext* file_system_context =
      file_manager::util::GetFileManagerFileSystemContext(GetProfile());
  const blink::StorageKey kTestStorageKey =
      blink::StorageKey::CreateFromStringForTesting("chrome-extension://abc");

  // Keep sending the items in the model to the trash as each "trash" operation
  // should remove the item from the model.
  while (!primary_holding_space_model->items().empty()) {
    const auto* holding_space_item =
        primary_holding_space_model->items()[0].get();
    base::FilePath file_path = holding_space_item->file().file_path;

    ItemRemovedWaiter waiter(primary_holding_space_model, holding_space_item);

    base::test::TestFuture<file_manager::io_task::ProgressStatus> status;
    file_manager::io_task::TrashIOTask task(
        {file_system_context->CrackURLInFirstPartyContext(
            GetFileSystemUrl(GetProfile(), file_path))},
        GetProfile(), file_system_context, /*base_path=*/base::FilePath());
    task.Execute(base::DoNothing(), status.GetCallback());
    EXPECT_EQ(status.Get().state, file_manager::io_task::State::kSuccess);

    waiter.Wait();
  }

  // After trashing all the items (they now reside in .Trash/files/foo.txt) they
  // should not be visible in the holding space model.
  ASSERT_EQ(primary_holding_space_model->items().size(), 0u);
}

// 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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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::GetFileManagerFileSystemContext(GetProfile());
  ASSERT_TRUE(context);

  struct ItemInfo {
    std::string item_id;
    base::FilePath path;
    GURL file_system_url;
    HoldingSpaceFile::FileSystemType file_system_type;
  };
  struct TestCase {
    ItemInfo src;
    ItemInfo dst;
  };
  std::map<HoldingSpaceItem::Type, TestCase> test_config;

  base::Value::List 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 auto type : holding_space_util::GetAllItemTypes()) {
    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);
      info->file_system_type = holding_space_util::ResolveFileSystemType(
          GetProfile(), info->file_system_url);

      // Create the holding space item.
      auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
          type,
          HoldingSpaceFile(info->path, info->file_system_type,
                           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::Value::List final_persisted_holding_space_items;
  // Runs the test logic.
  for (const auto type : holding_space_util::GetAllItemTypes()) {
    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->CrackURLInFirstPartyContext(GetFileSystemUrl(
                      GetProfile(), path_not_in_holding_space)),
                  context->CrackURLInFirstPartyContext(
                      src_item->file().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().file_path, test_case.src.path);
    ASSERT_EQ(src_item->file().file_system_url, test_case.src.file_system_url);
    ASSERT_EQ(src_item->file().file_system_type,
              test_case.src.file_system_type);

    {
      ItemUpdatedWaiter waiter(primary_holding_space_model, src_item);
      // 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->CrackURLInFirstPartyContext(
                        test_case.src.file_system_url),
                    context->CrackURLInFirstPartyContext(
                        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.
      waiter.Wait();
    }

    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().file_path, test_case.dst.path);
    ASSERT_EQ(src_item->file().file_system_url, test_case.dst.file_system_url);
    ASSERT_EQ(src_item->file().file_system_type,
              test_case.dst.file_system_type);

    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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       RestorePersistentStorage) {
  // Verify expected histograms.
  base::HistogramTester histogram_tester;
  EXPECT_THAT(
      histogram_tester.GetTotalCountsForPrefix(kTotalCountV2HistogramPrefix),
      IsEmpty());

  // 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());

  // Verify `expected_histograms` after "waiting" for metrics debounce.
  task_environment()->FastForwardBy(base::Seconds(30));
  auto expected_histograms = GetExpectedTotalCountV2HistogramSamples(
      primary_holding_space_service->model_for_testing());
  for (const auto& [name, expected_buckets] : expected_histograms) {
    EXPECT_THAT(histogram_tester.GetAllSamples(name),
                BucketsAreArray(expected_buckets));
  }

  HoldingSpaceModel::ItemList restored_holding_space_items;
  base::Value::List 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) {
        base::Value::List persisted_holding_space_items_before_restoration;

        // Persist some holding space items of each type.
        for (const auto type : holding_space_util::GetAllItemTypes()) {
          const base::FilePath file = downloads_mount->CreateArbitraryFile();
          const GURL file_system_url = GetFileSystemUrl(GetProfile(), file);
          const HoldingSpaceFile::FileSystemType file_system_type =
              holding_space_util::ResolveFileSystemType(GetProfile(),
                                                        file_system_url);

          auto fresh_holding_space_item =
              HoldingSpaceItem::CreateFileBackedItem(
                  type,
                  HoldingSpaceFile(file, file_system_type, 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());

          if (ShouldRestoreFromPersistence(type)) {
            // 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,
                  HoldingSpaceFile(file_path,
                                   HoldingSpaceFile::FileSystemType::kTest,
                                   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,
            base::Value(
                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());

  ItemsInitializedWaiter(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);

  // Verify expected histograms after "waiting" for metrics debounce.
  // NOTE: Histograms are profile-agnostic and cumulative so we need to merge
  // `expected_histograms` from the primary profile with those of the secondary.
  task_environment()->FastForwardBy(base::Seconds(30));
  expected_histograms = MergeHistogramSamples(
      expected_histograms,
      GetExpectedTotalCountV2HistogramSamples(secondary_holding_space_model));
  for (const auto& [name, expected_buckets] : expected_histograms) {
    EXPECT_THAT(histogram_tester.GetAllSamples(name),
                BucketsAreArray(expected_buckets));
  }
}

// Verifies that items from volumes that are not immediately mounted during
// startup get restored into the holding space.
TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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> initialized_items_before_delayed_mount;
  HoldingSpaceModel::ItemList restored_holding_space_items;
  base::Value::List persisted_holding_space_items_after_restoration;
  base::Value::List 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) {
        base::Value::List persisted_holding_space_items_before_restoration;

        // Persist some holding space items of each type.
        for (const auto type : holding_space_util::GetAllItemTypes()) {
          const base::FilePath delayed_mount_file =
              delayed_mount->GetRootPath().Append(delayed_mount_file_name);
          auto delayed_holding_space_item =
              HoldingSpaceItem::CreateFileBackedItem(
                  type,
                  HoldingSpaceFile(delayed_mount_file,
                                   HoldingSpaceFile::FileSystemType::kTest,
                                   GURL("filesystem:fake")),
                  base::BindOnce(&CreateTestHoldingSpaceImage));
          persisted_holding_space_items_before_restoration.Append(
              delayed_holding_space_item->Serialize());

          const bool should_restore = ShouldRestoreFromPersistence(type);

          // If an item should be restored, it should be restored after delayed
          // volume mount, and remain in persistent storage.
          if (should_restore) {
            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,
                  HoldingSpaceFile(non_existent_path,
                                   HoldingSpaceFile::FileSystemType::kTest,
                                   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());

          if (should_restore) {
            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);
          const HoldingSpaceFile::FileSystemType file_system_type =
              holding_space_util::ResolveFileSystemType(GetProfile(),
                                                        file_system_url);
          auto fresh_holding_space_item =
              HoldingSpaceItem::CreateFileBackedItem(
                  type,
                  HoldingSpaceFile(file, file_system_type, 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());

          // The item should be immediately added to the model, and remain in
          // the persistent storage if it should be restored.
          if (should_restore) {
            initialized_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,
            base::Value(
                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());

  ItemsInitializedWaiter(secondary_holding_space_model)
      .Wait(
          /*filter=*/base::BindLambdaForTesting(
              [&downloads_mount](const HoldingSpaceItem* item) -> bool {
                return downloads_mount->GetRootPath().IsParent(
                    item->file().file_path);
              }));

  std::vector<std::string> initialized_items;
  for (const auto& item : secondary_holding_space_model->items()) {
    if (item->IsInitialized()) {
      initialized_items.push_back(item->id());
    }
  }
  EXPECT_EQ(initialized_items_before_delayed_mount, initialized_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);

  ItemsInitializedWaiter(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->IsInitialized());

    EXPECT_EQ(item->id(), restored_item->id());
    EXPECT_EQ(item->type(), restored_item->type());
    EXPECT_EQ(item->GetText(), restored_item->GetText());
    EXPECT_EQ(item->file().file_path, restored_item->file().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().file_system_url,
        GetFileSystemUrl(secondary_profile, restored_item->file().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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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::Value::List 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) {
        base::Value::List persisted_holding_space_items_before_restoration;

        // Persist some holding space items of each type.
        for (const auto type : holding_space_util::GetAllItemTypes()) {
          const base::FilePath delayed_mount_file =
              delayed_mount->GetRootPath().Append(delayed_mount_file_name);
          auto delayed_holding_space_item =
              HoldingSpaceItem::CreateFileBackedItem(
                  type,
                  HoldingSpaceFile(delayed_mount_file,
                                   HoldingSpaceFile::FileSystemType::kTest,
                                   GURL("filesystem:fake")),
                  base::BindOnce(&CreateTestHoldingSpaceImage));
          persisted_holding_space_items_before_restoration.Append(
              delayed_holding_space_item->Serialize());

          const bool should_restore = ShouldRestoreFromPersistence(type);

          // The item is restored after delayed volume mount, and remain
          // in persistent storage if it should be restored.
          if (should_restore) {
            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,
                  HoldingSpaceFile(non_existent_path,
                                   HoldingSpaceFile::FileSystemType::kTest,
                                   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);
          const HoldingSpaceFile::FileSystemType file_system_type =
              holding_space_util::ResolveFileSystemType(GetProfile(),
                                                        file_system_url);
          auto fresh_holding_space_item =
              HoldingSpaceItem::CreateFileBackedItem(
                  type,
                  HoldingSpaceFile(file, file_system_type, 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());

          // The item should be immediately added to the model, and remain in
          // the persistent storage if it should be restored.
          if (should_restore) {
            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,
            base::Value(
                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());

  ItemsInitializedWaiter(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->IsInitialized());

    EXPECT_EQ(item->id(), restored_item->id());
    EXPECT_EQ(item->type(), restored_item->type());
    EXPECT_EQ(item->GetText(), restored_item->GetText());
    EXPECT_EQ(item->file().file_path, restored_item->file().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().file_system_url,
        GetFileSystemUrl(secondary_profile, restored_item->file().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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       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> initialized_items_before_delayed_mount;
  HoldingSpaceModel::ItemList restored_holding_space_items;
  base::Value::List persisted_holding_space_items_after_restoration;
  base::Value::List 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) {
        base::Value::List persisted_holding_space_items_before_restoration;

        // Persist some holding space items of each type.
        for (const auto type : holding_space_util::GetAllItemTypes()) {
          const base::FilePath file = downloads_mount->CreateArbitraryFile();
          const GURL file_system_url = GetFileSystemUrl(GetProfile(), file);
          const HoldingSpaceFile::FileSystemType file_system_type =
              holding_space_util::ResolveFileSystemType(GetProfile(),
                                                        file_system_url);

          auto fresh_holding_space_item =
              HoldingSpaceItem::CreateFileBackedItem(
                  type,
                  HoldingSpaceFile(file, file_system_type, 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());

          // The item should be immediately added to the model, and remain in
          // the persistent storage if it should be restored.
          if (ShouldRestoreFromPersistence(type)) {
            initialized_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,
            base::Value(
                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());

  ItemsInitializedWaiter(secondary_holding_space_model).Wait();

  std::vector<std::string> initialized_items;
  for (const auto& item : secondary_holding_space_model->items()) {
    if (item->IsInitialized()) {
      initialized_items.push_back(item->id());
    }
  }
  EXPECT_EQ(initialized_items_before_delayed_mount, initialized_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);
  ItemsInitializedWaiter(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->IsInitialized());

    EXPECT_EQ(item->id(), restored_item->id());
    EXPECT_EQ(item->type(), restored_item->type());
    EXPECT_EQ(item->GetText(), restored_item->GetText());
    EXPECT_EQ(item->file().file_path, restored_item->file().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().file_system_url,
        GetFileSystemUrl(secondary_profile, restored_item->file().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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       RemoveItemsFromUnmountedVolumes) {
  auto test_mount_1 = std::make_unique<ScopedTestMountPoint>(
      "test_mount_1", storage::kFileSystemTypeLocal,
      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::kFileSystemTypeLocal,
      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->AddItemOfType(HoldingSpaceItem::Type::kScreenshot,
                                       file_path_1);

  const base::FilePath file_path_2 = test_mount_2->CreateArbitraryFile();
  holding_space_service->AddItemOfType(HoldingSpaceItem::Type::kDownload,
                                       file_path_2);

  const base::FilePath file_path_3 = test_mount_1->CreateArbitraryFile();
  holding_space_service->AddItemOfType(HoldingSpaceItem::Type::kDownload,
                                       file_path_3);

  EXPECT_EQ(3u, GetProfile()
                    ->GetPrefs()
                    ->GetList(HoldingSpacePersistenceDelegate::kPersistencePath)
                    .size());
  EXPECT_EQ(3u, holding_space_model->items().size());

  test_mount_1.reset();
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1u, GetProfile()
                    ->GetPrefs()
                    ->GetList(HoldingSpacePersistenceDelegate::kPersistencePath)
                    .size());
  ASSERT_EQ(1u, holding_space_model->items().size());
  EXPECT_EQ(file_path_2, holding_space_model->items()[0]->file().file_path);
}

// Verifies that files restored from persistence are not older than
// `kMaxFileAge`.
// TODO(crbug.com/1427927): Flaky on Linux.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_RemoveOlderFilesFromPersistence \
  DISABLED_RemoveOlderFilesFromPersistence
#else
#define MAYBE_RemoveOlderFilesFromPersistence RemoveOlderFilesFromPersistence
#endif
TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       MAYBE_RemoveOlderFilesFromPersistence) {
  // 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::Value::List persisted_holding_space_items_after_restoration;
  base::Time last_creation_time = base::Time::Now();

  // Create a secondary profile w/ a pre-populated pref store.
  TestingProfile* const secondary_profile = CreateSecondaryProfile(
      base::BindLambdaForTesting([&](TestingPrefStore* pref_store) {
        base::Value::List persisted_holding_space_items_before_restoration;

        // Persist some holding space items of each type.
        for (const auto type : holding_space_util::GetAllItemTypes()) {
          const base::FilePath file = downloads_mount->CreateArbitraryFile();
          const GURL file_system_url = GetFileSystemUrl(GetProfile(), file);
          const HoldingSpaceFile::FileSystemType file_system_type =
              holding_space_util::ResolveFileSystemType(GetProfile(),
                                                        file_system_url);

          auto fresh_holding_space_item =
              HoldingSpaceItem::CreateFileBackedItem(
                  type,
                  HoldingSpaceFile(file, file_system_type, 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());

          bool should_restore = ShouldRestoreFromPersistence(type);

          if (should_restore) {
            // We expect all holding space items of other types to be removed
            // from persistence during restoration due to being older than
            // `kMaxFileAge`.
            should_restore = type == HoldingSpaceItem::Type::kPinnedFile;
          }

          if (should_restore) {
            persisted_holding_space_items_after_restoration.Append(
                fresh_holding_space_item->Serialize());
            restored_holding_space_items.push_back(
                std::move(fresh_holding_space_item));
          }

          base::File::Info file_info;
          ASSERT_TRUE(base::GetFileInfo(file, &file_info));
          last_creation_time = file_info.creation_time;
        }

        pref_store->SetValueSilently(
            HoldingSpacePersistenceDelegate::kPersistencePath,
            base::Value(
                std::move(persisted_holding_space_items_before_restoration)),
            PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS);
      }));

  // Fast-forward to a point where the created files are too old to be restored
  // from persistence.
  task_environment()->FastForwardBy(last_creation_time - 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());

  ItemsInitializedWaiter(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_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       AddArcDownloadItem) {
  // Wait for the holding space model to attach.
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space `model` is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_EQ(0u, model->items().size());

  // 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.
  const base::FilePath file_path = downloads_mount->CreateFile(
      /*relative_path=*/base::FilePath("Download.png"),
      /*content=*/"foo");

  // Simulate an `OnMediaStoreUriAdded()` event from ARC.
  auto* arc_file_system_bridge =
      arc::ArcFileSystemBridge::GetForBrowserContext(profile);
  ASSERT_TRUE(arc_file_system_bridge);
  arc_file_system_bridge->OnMediaStoreUriAdded(
      GURL("uri"), arc::mojom::MediaStoreMetadata::NewDownload(
                       arc::mojom::MediaStoreDownloadMetadata::New(
                           /*display_name=*/file_path.BaseName().value(),
                           /*owner_package_name=*/"com.bar.foo",
                           /*relative_path=*/base::FilePath("Download/"))));

  // Verify that an item of type `kArcDownload` was added to holding space.
  ASSERT_EQ(1u, model->items().size());
  const HoldingSpaceItem* arc_download_item = model->items()[0].get();
  EXPECT_EQ(arc_download_item->type(), HoldingSpaceItem::Type::kArcDownload);
  EXPECT_EQ(arc_download_item->file().file_path,
            file_manager::util::GetDownloadsFolderForProfile(profile).Append(
                base::FilePath("Download.png")));
}

TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       AddInProgressDownloadItem) {
  // Wait for the holding space model to attach.
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space model is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_TRUE(model);
  EXPECT_EQ(model->items().size(), 0u);

  // Create a downloads mount point.
  std::unique_ptr<ScopedTestMountPoint> downloads_mount =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(downloads_mount->IsValid());

  // Cache current state, file paths, received bytes, and total bytes.
  auto current_state = download::DownloadItem::IN_PROGRESS;
  base::FilePath current_path;
  base::FilePath current_target_path;
  int64_t current_received_bytes = 0;
  int64_t current_total_bytes = 100;
  bool current_is_dangerous = false;
  download::DownloadDangerType current_danger_type =
      download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS;

  // Create a fake download item and cache a function to update it.
  std::unique_ptr<content::FakeDownloadItem> fake_download_item =
      CreateFakeDownloadItem(profile, current_state, current_path,
                             current_target_path, current_received_bytes,
                             current_total_bytes);
  auto UpdateFakeDownloadItem = [&]() {
    fake_download_item->SetDummyFilePath(current_path);
    fake_download_item->SetReceivedBytes(current_received_bytes);
    fake_download_item->SetState(current_state);
    fake_download_item->SetTargetFilePath(current_target_path);
    fake_download_item->SetTotalBytes(current_total_bytes);
    fake_download_item->SetIsDangerous(current_is_dangerous);
    fake_download_item->SetDangerType(current_danger_type);
    fake_download_item->NotifyDownloadUpdated();
  };

  // Verify that no holding space item has been created since the download does
  // not yet have file path set.
  EXPECT_EQ(model->items().size(), 0u);

  // Update the file paths for the download.
  current_path = downloads_mount->CreateFile(base::FilePath("foo.crdownload"));
  current_target_path = downloads_mount->CreateFile(base::FilePath("foo.png"));
  UpdateFakeDownloadItem();

  // Verify that a holding space item has been created.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_EQ(model->items()[0]->progress().GetValue(), 0.f);

  constexpr gfx::Size kImageSize(20, 20);
  constexpr bool kDarkBackground = false;

  {
    // Once the `ThumbnailLoader` has finished processing the initial request,
    // the image should represent the file type of the *target* file for the
    // underlying download, not its current backing file.
    base::RunLoop run_loop;
    auto image_skia_changed_subscription =
        model->items()[0]->image().AddImageSkiaChangedCallback(
            base::BindLambdaForTesting([&]() {
              gfx::ImageSkia actual_image =
                  model->items()[0]->image().GetImageSkia(kImageSize,
                                                          kDarkBackground);
              gfx::ImageSkia expected_image = chromeos::GetIconForPath(
                  current_target_path, kDarkBackground);
              EXPECT_TRUE(BitmapsAreEqual(actual_image, expected_image));
              run_loop.Quit();
            }));

    // But initially the holding space image should be an empty bitmap. Note
    // that requesting the image is what spawns the initial request.
    gfx::ImageSkia actual_image =
        model->items()[0]->image().GetImageSkia(kImageSize, kDarkBackground);
    gfx::ImageSkia expected_image = image_util::CreateEmptyImage(kImageSize);
    EXPECT_TRUE(BitmapsAreEqual(actual_image, expected_image));

    // Wait for the `ThumbnailLoader` to finish processing the initial request.
    run_loop.Run();
  }

  // Update the total bytes for the download.
  current_total_bytes = -1;
  UpdateFakeDownloadItem();

  // Verify that the holding space item has indeterminate progress.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_TRUE(model->items()[0]->progress().IsIndeterminate());

  // Update the received bytes and total bytes for the download.
  current_received_bytes = 50;
  current_total_bytes = 100;
  UpdateFakeDownloadItem();

  // Verify that the holding space item has expected progress.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_EQ(model->items()[0]->progress().GetValue(), 0.5f);

  // Remove the holding space item from the model.
  model->RemoveIf(
      base::BindRepeating([](const HoldingSpaceItem* item) { return true; }));
  EXPECT_EQ(model->items().size(), 0u);

  // Complete the download.
  current_state = download::DownloadItem::COMPLETE;
  current_path = current_target_path;
  current_received_bytes = current_total_bytes;
  UpdateFakeDownloadItem();

  // Verify that no holding space item has been created since the holding space
  // associated with the completed download was previously removed.
  EXPECT_EQ(model->items().size(), 0u);

  // Create a new download.
  current_state = download::DownloadItem::IN_PROGRESS;
  current_path = base::FilePath();
  current_target_path = base::FilePath();
  current_received_bytes = 0;
  fake_download_item = CreateFakeDownloadItem(
      profile, current_state, current_path, current_target_path,
      current_received_bytes, current_total_bytes);

  // Verify that no holding space item has been created since the download does
  // not yet have file path set.
  EXPECT_EQ(model->items().size(), 0u);

  // Update the file paths and received bytes for the download.
  current_path = downloads_mount->CreateFile(base::FilePath("bar.crdownload"));
  current_target_path = downloads_mount->CreateFile(base::FilePath("bar.zip"));
  current_received_bytes = 50;
  UpdateFakeDownloadItem();

  // Verify that a holding space item has been created.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_EQ(model->items()[0]->progress().GetValue(), 0.5f);

  // Not dangerous in-progress items should only have Cancel and Pause
  // in-progress commands.
  EXPECT_EQ(model->items()[0]->in_progress_commands().size(), 2u);
  EXPECT_TRUE(holding_space_util::SupportsInProgressCommand(
      model->items()[0].get(), HoldingSpaceCommandId::kCancelItem));
  EXPECT_TRUE(holding_space_util::SupportsInProgressCommand(
      model->items()[0].get(), HoldingSpaceCommandId::kPauseItem));

  {
    // Once the `ThumbnailLoader` has finished processing the request, the image
    // should represent the file type of the *target* file for the underlying
    // download, not its current backing file.
    base::RunLoop run_loop;
    auto image_skia_changed_subscription =
        model->items()[0]->image().AddImageSkiaChangedCallback(
            base::BindLambdaForTesting([&]() {
              gfx::ImageSkia actual_image =
                  model->items()[0]->image().GetImageSkia(kImageSize,
                                                          kDarkBackground);
              gfx::ImageSkia expected_image = chromeos::GetIconForPath(
                  current_target_path, kDarkBackground);
              EXPECT_TRUE(BitmapsAreEqual(actual_image, expected_image));
              run_loop.Quit();
            }));

    // But initially the holding space image should be an empty bitmap. Note
    // that requesting the image is what spawns the initial request.
    gfx::ImageSkia actual_image =
        model->items()[0]->image().GetImageSkia(kImageSize, kDarkBackground);
    gfx::ImageSkia expected_image = image_util::CreateEmptyImage(kImageSize);
    EXPECT_TRUE(BitmapsAreEqual(actual_image, expected_image));

    // Wait for the `ThumbnailLoader` to finish processing the initial request.
    run_loop.Run();
  }

  // Mark the download as dangerous and maybe malicious.
  current_is_dangerous = true;
  current_danger_type = download::DownloadDangerType::
      DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT;
  UpdateFakeDownloadItem();

  // Dangerous in-progress items should only have Cancel in-progress commands.
  EXPECT_EQ(model->items()[0]->in_progress_commands().size(), 1u);
  EXPECT_TRUE(holding_space_util::SupportsInProgressCommand(
      model->items()[0].get(), HoldingSpaceCommandId::kCancelItem));

  {
    // Because the download has been marked as dangerous and maybe malicious,
    // the image should represent that the underlying download is in error.
    base::RunLoop run_loop;
    auto image_skia_changed_subscription =
        model->items()[0]->image().AddImageSkiaChangedCallback(
            base::BindLambdaForTesting([&]() {
              gfx::ImageSkia actual_image =
                  model->items()[0]->image().GetImageSkia(kImageSize,
                                                          kDarkBackground);
              gfx::ImageSkia expected_image =
                  gfx::ImageSkiaOperations::CreateSuperimposedImage(
                      image_util::CreateEmptyImage(kImageSize),
                      gfx::CreateVectorIcon(
                          vector_icons::kErrorOutlineIcon,
                          kHoldingSpaceIconSize,
                          cros_styles::ResolveColor(
                              cros_styles::ColorName::kIconColorAlert,
                              kDarkBackground)));
              EXPECT_TRUE(BitmapsAreEqual(actual_image, expected_image));
              run_loop.Quit();
            }));

    // Force a thumbnail request and wait for the `ThumbnailLoader` to finish
    // processing the request.
    model->items()[0]->image().GetImageSkia(kImageSize, kDarkBackground);
    run_loop.Run();
  }

  // Mark the download as *not* being malicious.
  current_danger_type =
      download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
  UpdateFakeDownloadItem();

  // Dangerous in-progress items should only have Cancel in-progress commands.
  EXPECT_EQ(model->items()[0]->in_progress_commands().size(), 1u);
  EXPECT_TRUE(holding_space_util::SupportsInProgressCommand(
      model->items()[0].get(), HoldingSpaceCommandId::kCancelItem));

  {
    // Because the download has been marked as dangerous but *not* malicious,
    // the image should represent that the underlying download is in warning.
    base::RunLoop run_loop;
    auto image_skia_changed_subscription =
        model->items()[0]->image().AddImageSkiaChangedCallback(
            base::BindLambdaForTesting([&]() {
              gfx::ImageSkia actual_image =
                  model->items()[0]->image().GetImageSkia(kImageSize,
                                                          kDarkBackground);
              gfx::ImageSkia expected_image =
                  gfx::ImageSkiaOperations::CreateSuperimposedImage(
                      image_util::CreateEmptyImage(kImageSize),
                      gfx::CreateVectorIcon(
                          vector_icons::kErrorOutlineIcon,
                          kHoldingSpaceIconSize,
                          cros_styles::ResolveColor(
                              cros_styles::ColorName::kIconColorWarning,
                              kDarkBackground)));
              EXPECT_TRUE(BitmapsAreEqual(actual_image, expected_image));
              run_loop.Quit();
            }));

    // Force a thumbnail request and wait for the `ThumbnailLoader` to finish
    // processing the request.
    model->items()[0]->image().GetImageSkia(kImageSize, kDarkBackground);
    run_loop.Run();
  }

  // Complete the download.
  current_state = download::DownloadItem::COMPLETE;
  current_path = current_target_path;
  current_received_bytes = current_total_bytes;
  UpdateFakeDownloadItem();

  // Verify that the holding space item has been updated.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_TRUE(model->items()[0]->progress().IsComplete());

  // The image should be representative of the file type of the *target* file
  // for the underlying download which by this point is actually the same file
  // path as the backing file path.
  gfx::ImageSkia actual_image =
      model->items()[0]->image().GetImageSkia(kImageSize, kDarkBackground);
  gfx::ImageSkia expected_image =
      chromeos::GetIconForPath(current_target_path, kDarkBackground);
  EXPECT_TRUE(BitmapsAreEqual(actual_image, expected_image));
}

TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest, RemoveAll) {
  // Wait for the holding space model to attach.
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space `model` is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_EQ(0u, model->items().size());

  // Create a test mount point.
  std::unique_ptr<ScopedTestMountPoint> mount_point =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(mount_point->IsValid());

  auto* service =
      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile);

  // Create files on the file system.
  const base::FilePath download_path = mount_point->CreateFile(
      /*relative_path=*/base::FilePath("bar"), /*content=*/"bar");
  const base::FilePath pinned_file_path = mount_point->CreateFile(
      /*relative_path=*/base::FilePath("foo"), /*content=*/"foo");

  // Add them both to holding space, one in pinned files the other in downloads.
  service->AddItemOfType(HoldingSpaceItem::Type::kDownload, download_path);
  service->AddPinnedFiles(
      {file_manager::util::GetFileManagerFileSystemContext(profile)
           ->CrackURLInFirstPartyContext(
               holding_space_util::ResolveFileSystemUrl(profile,
                                                        pinned_file_path))},
      holding_space_metrics::EventSource::kTest);

  ASSERT_EQ(2u, model->items().size());
  service->RemoveAll();
  EXPECT_EQ(0u, model->items().size());
}

TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       CreateInterruptedDownloadItem) {
  // Wait for the holding space model to attach.
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space model is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_TRUE(model);
  EXPECT_EQ(model->items().size(), 0u);

  // Create a downloads mount point.
  std::unique_ptr<ScopedTestMountPoint> downloads_mount =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(downloads_mount->IsValid());

  // Cache current state, file paths, received bytes, and total bytes.
  auto current_state = download::DownloadItem::INTERRUPTED;
  base::FilePath current_path;
  base::FilePath current_target_path;
  int64_t current_received_bytes = 0;
  int64_t current_total_bytes = 100;
  bool current_is_dangerous = false;

  // Create a fake download item and cache a function to update it.
  std::unique_ptr<content::FakeDownloadItem> fake_download_item =
      CreateFakeDownloadItem(profile, current_state, current_path,
                             current_target_path, current_received_bytes,
                             current_total_bytes);
  auto UpdateFakeDownloadItem = [&]() {
    fake_download_item->SetDummyFilePath(current_path);
    fake_download_item->SetReceivedBytes(current_received_bytes);
    fake_download_item->SetState(current_state);
    fake_download_item->SetTargetFilePath(current_target_path);
    fake_download_item->SetTotalBytes(current_total_bytes);
    fake_download_item->SetIsDangerous(current_is_dangerous);
    fake_download_item->NotifyDownloadUpdated();
  };

  // Verify that no holding space item has been created since the download does
  // not yet have file path set.
  EXPECT_EQ(model->items().size(), 0u);

  // Update the file paths for the download.
  current_path = downloads_mount->CreateFile(base::FilePath("foo.crdownload"));
  current_target_path = downloads_mount->CreateFile(base::FilePath("foo.png"));
  UpdateFakeDownloadItem();

  // Verify that no holding space item has been created since the download is
  // not in progress yet.
  EXPECT_EQ(model->items().size(), 0u);

  current_state = download::DownloadItem::IN_PROGRESS;
  UpdateFakeDownloadItem();

  // Verify that a holding space item is created.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_EQ(model->items()[0]->progress().GetValue(), 0.f);

  // Complete the download.
  current_state = download::DownloadItem::COMPLETE;
  current_path = current_target_path;
  current_received_bytes = current_total_bytes;
  UpdateFakeDownloadItem();

  // Verify that completing a download results in exactly one holding space item
  // existing for it, regardless of whether the in-progress downloads feature is
  // enabled.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_TRUE(model->items()[0]->progress().IsComplete());
}

TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
       InterruptAndResumeDownload) {
  // Wait for the holding space model to attach.
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space model is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_TRUE(model);
  EXPECT_EQ(model->items().size(), 0u);

  // Create a downloads mount point.
  std::unique_ptr<ScopedTestMountPoint> downloads_mount =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(downloads_mount->IsValid());

  // Cache current state, file paths, received bytes, and total bytes.
  auto current_state = download::DownloadItem::IN_PROGRESS;
  base::FilePath current_path;
  base::FilePath current_target_path;
  int64_t current_received_bytes = 0;
  int64_t current_total_bytes = 100;
  bool current_is_dangerous = false;

  // Create a fake download item and cache a function to update it.
  std::unique_ptr<content::FakeDownloadItem> fake_download_item =
      CreateFakeDownloadItem(profile, current_state, current_path,
                             current_target_path, current_received_bytes,
                             current_total_bytes);
  auto UpdateFakeDownloadItem = [&]() {
    fake_download_item->SetDummyFilePath(current_path);
    fake_download_item->SetReceivedBytes(current_received_bytes);
    fake_download_item->SetState(current_state);
    fake_download_item->SetTargetFilePath(current_target_path);
    fake_download_item->SetTotalBytes(current_total_bytes);
    fake_download_item->SetIsDangerous(current_is_dangerous);
    fake_download_item->NotifyDownloadUpdated();
  };

  // Verify that no holding space item has been created since the download does
  // not yet have file path set.
  EXPECT_EQ(model->items().size(), 0u);

  // Update the file paths for the download.
  current_path = downloads_mount->CreateFile(base::FilePath("foo.crdownload"));
  current_target_path = downloads_mount->CreateFile(base::FilePath("foo.png"));
  UpdateFakeDownloadItem();

  // Verify that a holding space item is created.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_EQ(model->items()[0]->progress().GetValue(), 0.f);

  // Make some progress and interrupt the download.
  current_received_bytes = 50;
  current_state = download::DownloadItem::INTERRUPTED;
  UpdateFakeDownloadItem();

  // Verify that interrupting an in-progress download destroys its holding
  // space item (if the in-progress downloads feature is enabled).
  ASSERT_EQ(model->items().size(), 0u);

  // Resume the download.
  current_state = download::DownloadItem::IN_PROGRESS;
  UpdateFakeDownloadItem();

  // Verify that resuming an interrupted download creates a new holding space
  // item.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_EQ(model->items()[0]->progress().GetValue(), 0.5f);

  // Complete the download.
  current_state = download::DownloadItem::COMPLETE;
  current_path = current_target_path;
  current_received_bytes = current_total_bytes;
  UpdateFakeDownloadItem();

  // Verify that completing a download results in exactly one holding space item
  // existing for it, regardless of whether the in-progress downloads feature is
  // enabled.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(model->items()[0]->file().file_path, current_path);
  EXPECT_TRUE(model->items()[0]->progress().IsComplete());
}

// Base class for tests which verify adding and removing items from holding
// space works as intended, parameterized by holding space item type.
class HoldingSpaceKeyedServiceAddAndRemoveItemTest
    : public HoldingSpaceKeyedServiceTest,
      public ::testing::WithParamInterface<HoldingSpaceItem::Type> {
 public:
  // Returns the holding space service associated with the specified `profile`.
  HoldingSpaceKeyedService* GetService(Profile* profile) {
    return HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile);
  }

  // Returns the type of holding space item under test.
  HoldingSpaceItem::Type GetType() const { return GetParam(); }

  // Adds an item of `type` to the holding space belonging to `profile`, backed
  // by the file at the specified absolute `file_path`. Returns the `id` of the
  // added holding space item.
  const std::string& AddItem(Profile* profile,
                             HoldingSpaceItem::Type type,
                             const base::FilePath& file_path) {
    auto* const holding_space_service = GetService(profile);
    EXPECT_TRUE(holding_space_service);
    const auto* holding_space_model =
        holding_space_service->model_for_testing();
    EXPECT_TRUE(holding_space_model);

    switch (type) {
      case HoldingSpaceItem::Type::kArcDownload:
      case HoldingSpaceItem::Type::kDownload:
      case HoldingSpaceItem::Type::kLacrosDownload:
        EXPECT_EQ(
            holding_space_model->ContainsItem(type, file_path),
            holding_space_service->AddItemOfType(type, file_path).empty());
        break;
      case HoldingSpaceItem::Type::kDiagnosticsLog:
      case HoldingSpaceItem::Type::kNearbyShare:
        holding_space_service->AddItemOfType(type, file_path);
        break;
      case HoldingSpaceItem::Type::kDriveSuggestion:
      case HoldingSpaceItem::Type::kLocalSuggestion:
        holding_space_service->SetSuggestions(
            /*suggestions=*/{{type, file_path}});
        break;
      case HoldingSpaceItem::Type::kPinnedFile:
        holding_space_service->AddPinnedFiles(
            {file_manager::util::GetFileManagerFileSystemContext(profile)
                 ->CrackURLInFirstPartyContext(
                     holding_space_util::ResolveFileSystemUrl(profile,
                                                              file_path))},
            holding_space_metrics::EventSource::kTest);
        break;
      case HoldingSpaceItem::Type::kPhoneHubCameraRoll:
        EXPECT_EQ(
            holding_space_model->ContainsItem(type, file_path),
            holding_space_service
                ->AddItemOfType(HoldingSpaceItem::Type::kPhoneHubCameraRoll,
                                file_path, HoldingSpaceProgress())
                .empty());
        break;
      case HoldingSpaceItem::Type::kPrintedPdf:
        holding_space_service->AddPrintedPdf(file_path,
                                             /*from_incognito_profile=*/false);
        break;
      case HoldingSpaceItem::Type::kPhotoshopWeb:
      case HoldingSpaceItem::Type::kScan:
      case HoldingSpaceItem::Type::kScreenRecording:
      case HoldingSpaceItem::Type::kScreenRecordingGif:
      case HoldingSpaceItem::Type::kScreenshot:
        holding_space_service->AddItemOfType(type, file_path);
        break;
    }

    const auto* item = holding_space_model->GetItem(type, file_path);
    EXPECT_TRUE(item);
    return item->id();
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    HoldingSpaceKeyedServiceAddAndRemoveItemTest,
    testing::ValuesIn(holding_space_util::GetAllItemTypes()));

TEST_P(HoldingSpaceKeyedServiceAddAndRemoveItemTest, AddAndRemoveItem) {
  // Wait for the holding space model to attach.
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space `model` is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_EQ(0u, model->items().size());

  // Verify expected histograms.
  base::HistogramTester histogram_tester;
  EXPECT_THAT(
      histogram_tester.GetTotalCountsForPrefix(kTotalCountV2HistogramPrefix),
      IsEmpty());

  // Create a test mount point.
  std::unique_ptr<ScopedTestMountPoint> mount_point =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(mount_point->IsValid());

  // Create a file on the file system.
  const base::FilePath file_path = mount_point->CreateFile(
      /*relative_path=*/base::FilePath("foo"), /*content=*/"foo");

  // Add a holding space item of the type under test.
  const std::string id = AddItem(profile, GetType(), file_path);

  // Verify a holding space item has been added to the model.
  ASSERT_EQ(model->items().size(), 1u);

  HoldingSpaceKeyedService* const service = GetService(profile);
  ASSERT_TRUE(service);
  EXPECT_TRUE(service->ContainsItem(id));

  // Verify holding space `item` metadata.
  HoldingSpaceItem* const item = model->items()[0].get();
  EXPECT_EQ(item->id(), id);
  EXPECT_EQ(item->type(), GetType());
  EXPECT_EQ(item->GetText(), file_path.BaseName().LossyDisplayName());
  EXPECT_EQ(item->file().file_path, file_path);
  EXPECT_EQ(item->file().file_system_url,
            holding_space_util::ResolveFileSystemUrl(profile, file_path));

  // Verify holding space `item` image.
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      *holding_space_util::ResolveImage(
           GetService(profile)->thumbnail_loader_for_testing(), GetType(),
           file_path)
           ->GetImageSkia()
           .bitmap(),
      *item->image().GetImageSkia().bitmap()));

  // Verify `expected_histograms` after "waiting" for metrics debounce.
  task_environment()->FastForwardBy(base::Seconds(30));
  auto expected_histograms = GetExpectedTotalCountV2HistogramSamples(model);
  for (const auto& [name, expected_buckets] : expected_histograms) {
    EXPECT_THAT(histogram_tester.GetAllSamples(name),
                BucketsAreArray(expected_buckets));
  }

  // Attempt to add a holding space item of the same type and `file_path`.
  const std::string& id2 = AddItem(profile, GetType(), file_path);

  ASSERT_EQ(model->items().size(), 1u);

  // Attempts to add already represented items should be ignored.
  EXPECT_EQ(model->items()[0].get(), item);
  EXPECT_EQ(id, id2);
  EXPECT_TRUE(service->ContainsItem(id));
  EXPECT_TRUE(service->ContainsItem(id2));

  // Remove the holding space item.
  service->RemoveItem(id);
  EXPECT_TRUE(model->items().empty());
  EXPECT_FALSE(service->ContainsItem(id));
  EXPECT_FALSE(service->ContainsItem(id2));

  // Verify `expected_histograms` after "waiting" for metrics debounce.
  // NOTE: Histograms are cumulative so we need to merge `expected_histograms`
  // from the previous state with those of the current.
  task_environment()->FastForwardBy(base::Seconds(30));
  expected_histograms = MergeHistogramSamples(
      expected_histograms, GetExpectedTotalCountV2HistogramSamples(model));
  for (const auto& [name, expected_buckets] : expected_histograms) {
    EXPECT_THAT(histogram_tester.GetAllSamples(name),
                BucketsAreArray(expected_buckets));
  }
}

TEST_P(HoldingSpaceKeyedServiceAddAndRemoveItemTest, AddAndRemoveItemOfType) {
  // Wait for the holding space model to attach.
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space `model` is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_EQ(0u, model->items().size());

  // Create a test mount point.
  std::unique_ptr<ScopedTestMountPoint> mount_point =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(mount_point->IsValid());

  // Create a file on the file system.
  const base::FilePath file_path = mount_point->CreateFile(
      /*relative_path=*/base::FilePath("foo"), /*content=*/"foo");

  // Add a holding space item of the type under test.
  const auto& id = GetService(profile)->AddItemOfType(GetType(), file_path);

  // Verify a holding space item has been added to the model.
  ASSERT_EQ(model->items().size(), 1u);

  // Verify holding space `item` metadata.
  HoldingSpaceItem* const item = model->items()[0].get();
  EXPECT_EQ(item->id(), id);
  EXPECT_EQ(item->type(), GetType());
  EXPECT_EQ(item->GetText(), file_path.BaseName().LossyDisplayName());
  EXPECT_EQ(item->file().file_path, file_path);
  EXPECT_EQ(item->file().file_system_url,
            holding_space_util::ResolveFileSystemUrl(profile, file_path));

  // Verify holding space `item` image.
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      *holding_space_util::ResolveImage(
           GetService(profile)->thumbnail_loader_for_testing(), GetType(),
           file_path)
           ->GetImageSkia()
           .bitmap(),
      *item->image().GetImageSkia().bitmap()));

  // Attempt to add a holding space item of the same type and `file_path`.
  EXPECT_TRUE(GetService(profile)->AddItemOfType(GetType(), file_path).empty());

  // Attempts to add already represented items should be ignored.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0].get(), item);

  // Remove the holding space item.
  GetService(profile)->RemoveItem(id);
  EXPECT_TRUE(model->items().empty());
}

class HoldingSpaceKeyedServiceNearbySharingTest
    : public HoldingSpaceKeyedServiceTest {
 public:
  HoldingSpaceKeyedServiceNearbySharingTest() {
    scoped_feature_list_.InitAndEnableFeature(::features::kNearbySharing);
  }

 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->AddItemOfType(HoldingSpaceItem::Type::kNearbyShare,
                                       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->AddItemOfType(HoldingSpaceItem::Type::kNearbyShare,
                                       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().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().file_system_url,
                                  downloads_mount->name()));
  EXPECT_EQ(u"File 1.png", item_1->GetText());

  const HoldingSpaceItem* item_2 = model->items()[1].get();
  EXPECT_EQ(item_2_full_path, item_2->file().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().file_system_url,
                                  downloads_mount->name()));
  EXPECT_EQ(u"File 2.png", item_2->GetText());
}

// Base class for tests of Photoshop Web integration. Parameterized by the
// binding context to use for the file picker during testing.
class HoldingSpaceKeyedServicePhotoshopWebIntegrationTest
    : public HoldingSpaceKeyedServiceTest,
      public ::testing::WithParamInterface<
          /*file_picker_binding_context=*/GURL> {
 public:
  // The binding context to use for the file picker given test parameterization.
  const GURL& GetFilePickerBindingContext() const { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    HoldingSpaceKeyedServicePhotoshopWebIntegrationTest,
    /*file_picker_binding_context=*/
    ::testing::Values(GURL(),
                      GURL("https://google.com/"),
                      GURL("https://photoshop.adobe.com/")));

// Verifies that a Photoshop Web item will be added to the user's Holding Space
// under expected circumstances.
TEST_P(HoldingSpaceKeyedServicePhotoshopWebIntegrationTest,
       AddPhotoshopWebItem) {
  // Cache `profile`.
  TestingProfile* const profile = GetProfile();

  // Wait for `model` attachment and verify initial state.
  HoldingSpaceModelAttachedWaiter(profile).Wait();
  const HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_TRUE(model);
  ASSERT_EQ(model->items().size(), 0u);

  // Create `mount_point`.
  std::unique_ptr<ScopedTestMountPoint> mount_point =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(mount_point->IsValid());

  // Create file and resolve metadata.
  const base::FilePath file_path =
      mount_point->CreateFile(/*relative_path=*/base::FilePath("foo"));
  const GURL file_system_url =
      holding_space_util::ResolveFileSystemUrl(profile, file_path);
  const HoldingSpaceFile::FileSystemType file_system_type =
      holding_space_util::ResolveFileSystemType(profile, file_system_url);

  // Verify initial histogram state.
  base::HistogramTester histogram_tester;
  EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix(
                  "HoldingSpace.FileCreatedFromShowSaveFilePicker."),
              IsEmpty());

  // Propagate file creation event from a file picker with the binding context
  // specified by test parameterization.
  FileSystemAccessPermissionContextFactory::GetForProfile(profile)
      ->OnFileCreatedFromShowSaveFilePicker(
          GetFilePickerBindingContext(),
          file_manager::util::GetFileManagerFileSystemContext(profile)
              ->CrackURLInFirstPartyContext(file_system_url));

  // A Photoshop Web item should be added to the user's Holding Space iff the
  // binding context for the file picker is from the domain associated with
  // Photoshop Web.
  const bool is_file_picker_binding_context_photoshop_web =
      GetFilePickerBindingContext().DomainIs("photoshop.adobe.com");

  // Verify model state.
  EXPECT_THAT(
      model->items(),
      Conditional(
          is_file_picker_binding_context_photoshop_web,
          ElementsAre(Pointee(AllOf(
              Property(&HoldingSpaceItem::type,
                       HoldingSpaceItem::Type::kPhotoshopWeb),
              Property(&HoldingSpaceItem::file,
                       AllOf(Field(&HoldingSpaceFile::file_path, file_path),
                             Field(&HoldingSpaceFile::file_system_type,
                                   file_system_type),
                             Field(&HoldingSpaceFile::file_system_url,
                                   file_system_url)))))),
          IsEmpty()));

  // Verify histogram state.
  EXPECT_THAT(histogram_tester.GetAllSamples(
                  "HoldingSpace.FileCreatedFromShowSaveFilePicker.Extension"),
              BucketsAre(Bucket(
                  holding_space_metrics::FilePathToExtension(file_path), 1u)));
  EXPECT_THAT(
      histogram_tester.GetAllSamples(
          "HoldingSpace.FileCreatedFromShowSaveFilePicker."
          "FilePickerBindingContext"),
      Conditional(
          is_file_picker_binding_context_photoshop_web,
          BucketsAre(Bucket(FilePickerBindingContext::kPhotoshopWeb, 1u)),
          BucketsAre(Bucket(FilePickerBindingContext::kUnknown, 1u))));
}

// Base class for tests of print-to-PDF integration. Parameterized by whether
// tests should use an incognito browser.
class HoldingSpaceKeyedServicePrintToPdfIntegrationTest
    : public HoldingSpaceKeyedServiceTest,
      public testing::WithParamInterface<bool /* from_incognito_profile */> {
 public:
  // Starts a job to print an empty PDF to the specified `file_path`.
  // NOTE: This method will not return until the print job completes.
  void StartPrintToPdfAndWaitForSave(const std::u16string& job_title,
                                     const base::FilePath& file_path) {
    base::RunLoop run_loop;
    pdf_printer_handler_->SetPdfSavedClosureForTesting(run_loop.QuitClosure());
    pdf_printer_handler_->SetPrintToPdfPathForTesting(file_path);

    pdf_printer_handler_->StartPrint(
        job_title,
        /*settings=*/base::Value::Dict(),
        base::MakeRefCounted<base::RefCountedString>(std::string()),
        /*callback=*/base::DoNothing());

    run_loop.Run();
  }

  // Returns true if the test should use an incognito browser, false otherwise.
  bool UseIncognitoBrowser() const { return GetParam(); }

 private:
  // HoldingSpaceKeyedServiceTest:
  void SetUp() override {
    HoldingSpaceKeyedServiceTest::SetUp();

    // Create the PDF printer handler.
    Browser* browser = GetBrowserForPdfPrinterHandler();
    pdf_printer_handler_ = std::make_unique<printing::PdfPrinterHandler>(
        browser->profile(), browser->tab_strip_model()->GetActiveWebContents(),
        /*sticky_settings=*/nullptr);
  }

  void TearDown() override {
    incognito_browser_.reset();
    HoldingSpaceKeyedServiceTest::TearDown();
  }

  Browser* GetBrowserForPdfPrinterHandler() {
    if (!UseIncognitoBrowser()) {
      return browser();
    }
    if (!incognito_browser_) {
      incognito_browser_ =
          CreateBrowserWithTestWindowForParams(Browser::CreateParams(
              profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
              /*user_gesture=*/true));
    }
    return incognito_browser_.get();
  }

  std::unique_ptr<printing::PdfPrinterHandler> pdf_printer_handler_;
  std::unique_ptr<Browser> incognito_browser_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         HoldingSpaceKeyedServicePrintToPdfIntegrationTest,
                         /*from_incognito_profile=*/::testing::Bool());

// Verifies that print-to-PDF adds an associated item to holding space.
TEST_P(HoldingSpaceKeyedServicePrintToPdfIntegrationTest, AddPrintedPdfItem) {
  // Create a file system mount point.
  std::unique_ptr<ScopedTestMountPoint> mount_point =
      ScopedTestMountPoint::CreateAndMountDownloads(GetProfile());
  ASSERT_TRUE(mount_point->IsValid());

  // Cache a pointer to the holding space model.
  const HoldingSpaceModel* model =
      HoldingSpaceKeyedServiceFactory::GetInstance()
          ->GetService(GetProfile())
          ->model_for_testing();

  // Verify that the holding space is initially empty.
  EXPECT_EQ(model->items().size(), 0u);

  // Start a job to print an empty PDF to `file_path`.
  base::FilePath file_path = mount_point->GetRootPath().Append("foo.pdf");
  StartPrintToPdfAndWaitForSave(u"job_title", file_path);

  // Verify that holding space is populated with the expected item.
  ASSERT_EQ(model->items().size(), 1u);
  EXPECT_EQ(model->items()[0]->type(), HoldingSpaceItem::Type::kPrintedPdf);
  EXPECT_EQ(model->items()[0]->file().file_path, file_path);
}

// Base class for tests of incognito profile integration.
class HoldingSpaceKeyedServiceIncognitoDownloadsTest
    : public HoldingSpaceKeyedServiceTest {
 public:
  // HoldingSpaceKeyedServiceTest:
  TestingProfile* CreateProfile(const std::string& profile_name) override {
    TestingProfile* profile =
        HoldingSpaceKeyedServiceTest::CreateProfile(profile_name);

    // Construct an incognito profile from the primary profile.
    TestingProfile::Builder incognito_profile_builder;
    incognito_profile_builder.SetProfileName(profile->GetProfileUserName());
    incognito_profile_ = incognito_profile_builder.BuildIncognito(profile);
    EXPECT_TRUE(incognito_profile_);
    EXPECT_TRUE(incognito_profile_->IsIncognitoProfile());
    SetUpDownloadManager(incognito_profile_);
    EXPECT_NE(incognito_profile_->GetDownloadManager(),
              profile->GetDownloadManager());

    return profile;
  }

  // Returns the incognito profile spawned from the test's main profile.
  TestingProfile* incognito_profile() { return incognito_profile_; }

 private:
  raw_ptr<TestingProfile, DanglingUntriaged> incognito_profile_ = nullptr;
};

TEST_F(HoldingSpaceKeyedServiceIncognitoDownloadsTest, 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());

  // Cache current state, file path, received bytes, and total bytes.
  auto current_state = download::DownloadItem::IN_PROGRESS;
  base::FilePath current_path;
  int64_t current_received_bytes = 0;
  int64_t current_total_bytes = 100;

  // Create a fake in-progress download item for the incognito profile and cache
  // a function to update it.
  std::unique_ptr<content::FakeDownloadItem> fake_download_item =
      CreateFakeDownloadItem(incognito_profile(), current_state, current_path,
                             /*target_file_path=*/base::FilePath(),
                             current_received_bytes, current_total_bytes);
  auto UpdateFakeDownloadItem = [&]() {
    fake_download_item->SetDummyFilePath(current_path);
    fake_download_item->SetReceivedBytes(current_received_bytes);
    fake_download_item->SetState(current_state);
    fake_download_item->SetTotalBytes(current_total_bytes);
    fake_download_item->NotifyDownloadUpdated();
  };

  // Verify holding space is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_EQ(0u, model->items().size());

  // Update the file path for the download.
  current_path = downloads_mount->CreateFile(base::FilePath("tmp/temp_path"));
  UpdateFakeDownloadItem();

  // Verify that a holding space item is created.
  ASSERT_EQ(1u, model->items().size());
  HoldingSpaceItem* download_item = model->items()[0].get();
  EXPECT_EQ(download_item->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(download_item->file().file_path, current_path);
  EXPECT_EQ(download_item->progress().GetValue(), 0.f);

  // Complete the download.
  current_state = download::DownloadItem::COMPLETE;
  current_path = downloads_mount->CreateFile(base::FilePath("tmp/final_path"));
  current_received_bytes = current_total_bytes;
  UpdateFakeDownloadItem();

  // Verify that a completed holding space item exists.
  ASSERT_EQ(1u, model->items().size());
  download_item = model->items()[0].get();
  EXPECT_EQ(download_item->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(download_item->file().file_path, current_path);
  EXPECT_TRUE(download_item->progress().IsComplete());
}

TEST_F(HoldingSpaceKeyedServiceIncognitoDownloadsTest,
       AddInProgressDownloadItem) {
  TestingProfile* profile = GetProfile();
  HoldingSpaceModelAttachedWaiter(profile).Wait();

  // Verify the holding space model is empty.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  ASSERT_TRUE(model);
  EXPECT_EQ(model->items().size(), 0u);

  // Create a test downloads mount point.
  std::unique_ptr<ScopedTestMountPoint> downloads_mount =
      ScopedTestMountPoint::CreateAndMountDownloads(profile);
  ASSERT_TRUE(downloads_mount->IsValid());

  // Cache current state, file paths, received bytes, and total bytes.
  auto current_state = download::DownloadItem::IN_PROGRESS;
  base::FilePath current_path;
  base::FilePath current_target_path;
  int64_t current_received_bytes = 0;
  int64_t current_total_bytes = 100;
  bool current_is_dangerous = false;

  // Create a fake download item and cache a function to update it.
  std::unique_ptr<content::FakeDownloadItem> fake_download_item =
      CreateFakeDownloadItem(incognito_profile(), current_state, current_path,
                             current_target_path, current_received_bytes,
                             current_total_bytes);
  auto UpdateFakeDownloadItem = [&]() {
    fake_download_item->SetDummyFilePath(current_path);
    fake_download_item->SetReceivedBytes(current_received_bytes);
    fake_download_item->SetState(current_state);
    fake_download_item->SetTargetFilePath(current_target_path);
    fake_download_item->SetTotalBytes(current_total_bytes);
    fake_download_item->SetIsDangerous(current_is_dangerous);
    fake_download_item->NotifyDownloadUpdated();
  };

  // Verify that no holding space item has been created since the download does
  // not yet have file path set.
  EXPECT_EQ(model->items().size(), 0u);

  // Update the file paths for the download.
  current_path = downloads_mount->CreateFile(base::FilePath("foo.crdownload"));
  current_target_path = downloads_mount->CreateFile(base::FilePath("foo.png"));
  UpdateFakeDownloadItem();

  // Verify that a holding space item is created.
  ASSERT_EQ(1u, model->items().size());
  HoldingSpaceItem* download_item = model->items()[0].get();
  EXPECT_EQ(download_item->type(), HoldingSpaceItem::Type::kDownload);
  EXPECT_EQ(download_item->file().file_path, current_path);
  EXPECT_FALSE(download_item->progress().IsComplete());

  // Verify that destroying a profile with an in-progress download destroys
  // the holding space item.
  profile->DestroyOffTheRecordProfile(incognito_profile());
  ASSERT_EQ(0u, model->items().size());
}

class HoldingSpaceSuggestionsDelegateTest
    : public HoldingSpaceKeyedServiceTest,
      public testing::WithParamInterface<bool> {
 public:
  HoldingSpaceSuggestionsDelegateTest() {
    scoped_feature_list_.InitWithFeatureState(
        features::kHoldingSpaceSuggestions, GetParam());
  }

  void SetUp() override {
    HoldingSpaceKeyedServiceTest::SetUp();

    // Create mount points to host test files.
    TestingProfile* profile = GetProfile();
    drive_mount_point_ = std::make_unique<ScopedTestMountPoint>(
        "drive_test_mount", storage::kFileSystemTypeDriveFs,
        file_manager::VOLUME_TYPE_TESTING);
    drive_mount_point_->Mount(profile);
    local_mount_point_ = std::make_unique<ScopedTestMountPoint>(
        "local_test_mount", storage::kFileSystemTypeLocal,
        file_manager::VOLUME_TYPE_TESTING);
    local_mount_point_->Mount(profile);

    HoldingSpaceModelAttachedWaiter(profile).Wait();
  }

  void TearDown() override {
    drive_mount_point_.reset();
    local_mount_point_.reset();
    HoldingSpaceKeyedServiceTest::TearDown();
  }

  MockFileSuggestKeyedService* GetFileSuggestKeyedService() {
    return static_cast<MockFileSuggestKeyedService*>(
        FileSuggestKeyedServiceFactory::GetInstance()->GetService(
            GetProfile()));
  }

  ScopedTestMountPoint* drive_mount_point() { return drive_mount_point_.get(); }
  ScopedTestMountPoint* local_mount_point() { return local_mount_point_.get(); }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<ScopedTestMountPoint> drive_mount_point_;
  std::unique_ptr<ScopedTestMountPoint> local_mount_point_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         HoldingSpaceSuggestionsDelegateTest,
                         /*enable_suggestion_feature=*/testing::Bool());

// Verifies that suggestion refresh through the holding space client is WAI.
TEST_P(HoldingSpaceSuggestionsDelegateTest, SuggestionRefresh) {
  using Type = HoldingSpaceItem::Type;

  // Populate drive and local file suggestions.
  const base::FilePath file_path_1 = drive_mount_point()->CreateArbitraryFile();
  const base::FilePath file_path_2 = local_mount_point()->CreateArbitraryFile();
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kDriveFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kDriveFile, file_path_1,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kLocalFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kLocalFile, file_path_2,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  // Verify initial suggestions.  Note that suggestions are reversed in the
  // holding space model to account for the fact that items are presented in
  // reverse-chronological order.
  const bool suggestion_feature_enabled =
      features::IsHoldingSpaceSuggestionsEnabled();
  HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
  EXPECT_THAT(GetSuggestionsInModel(*model),
              ::testing::Conditional(
                  suggestion_feature_enabled,
                  ::testing::ElementsAre(
                      std::make_pair(Type::kLocalSuggestion, file_path_2),
                      std::make_pair(Type::kDriveSuggestion, file_path_1)),
                  ::testing::IsEmpty()));

  // Create additional files to back refreshed suggestions.
  const base::FilePath file_path_3 = drive_mount_point()->CreateArbitraryFile();
  const base::FilePath file_path_4 = local_mount_point()->CreateArbitraryFile();

  // Refresh suggestions through the holding space client. Verify that
  // `FileSuggestKeyedService::GetSuggestFileData()` is called if and only if
  // the suggestions feature is enabled.
  EXPECT_CALL(*GetFileSuggestKeyedService(),
              GetSuggestFileData(FileSuggestionType::kDriveFile, ::testing::_))
      .Times(suggestion_feature_enabled ? 1u : 0u)
      .WillOnce(base::test::RunOnceCallback<1u>(
          std::make_optional(std::vector<FileSuggestData>{
              {FileSuggestionType::kDriveFile, file_path_3,
               /*title=*/std::nullopt,
               /*new_prediction_reason=*/std::nullopt,
               /*modified_time=*/std::nullopt,
               /*viewed_time=*/std::nullopt,
               /*shared_time=*/std::nullopt,
               /*new_score=*/std::nullopt,
               /*drive_file_id=*/std::nullopt,
               /*icon_url=*/std::nullopt}})));
  EXPECT_CALL(*GetFileSuggestKeyedService(),
              GetSuggestFileData(FileSuggestionType::kLocalFile, ::testing::_))
      .Times(suggestion_feature_enabled ? 1u : 0u)
      .WillOnce(base::test::RunOnceCallback<1u>(
          std::make_optional(std::vector<FileSuggestData>{
              {FileSuggestionType::kLocalFile, file_path_4,
               /*title=*/std::nullopt,
               /*new_prediction_reason=*/std::nullopt,
               /*modified_time=*/std::nullopt,
               /*viewed_time=*/std::nullopt,
               /*shared_time=*/std::nullopt,
               /*new_score=*/std::nullopt,
               /*drive_file_id=*/std::nullopt,
               /*icon_url=*/std::nullopt}})));
  HoldingSpaceController::Get()->client()->RefreshSuggestions();

  // Verify that all suggestions have been updated in the model if and only if
  // the suggestions feature is enabled.
  EXPECT_THAT(GetSuggestionsInModel(*model),
              ::testing::Conditional(
                  suggestion_feature_enabled,
                  ::testing::ElementsAre(
                      std::make_pair(Type::kLocalSuggestion, file_path_4),
                      std::make_pair(Type::kDriveSuggestion, file_path_3)),
                  ::testing::IsEmpty()));
}

// Verifies that suggestion removal through the holding space client is WAI.
TEST_P(HoldingSpaceSuggestionsDelegateTest, SuggestionRemoval) {
  using Type = HoldingSpaceItem::Type;

  // Populate drive and local file suggestions.
  const base::FilePath file_path_1 = drive_mount_point()->CreateArbitraryFile();
  const base::FilePath file_path_2 = local_mount_point()->CreateArbitraryFile();
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kDriveFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kDriveFile, file_path_1,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kLocalFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kLocalFile, file_path_2,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  // Verify initial suggestions.  Note that suggestions are reversed in the
  // holding space model to account for the fact that items are presented in
  // reverse-chronological order.
  const bool suggestion_feature_enabled =
      features::IsHoldingSpaceSuggestionsEnabled();
  HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
  EXPECT_THAT(GetSuggestionsInModel(*model),
              ::testing::Conditional(
                  suggestion_feature_enabled,
                  ::testing::ElementsAre(
                      std::make_pair(Type::kLocalSuggestion, file_path_2),
                      std::make_pair(Type::kDriveSuggestion, file_path_1)),
                  ::testing::IsEmpty()));

  // Remove all suggestions through the holding space client. Verify that
  // `FileSuggestKeyedService::RemoveSuggestionsAndNotify()` is called if and
  // only if the suggestions feature is enabled.
  EXPECT_CALL(*GetFileSuggestKeyedService(),
              RemoveSuggestionsAndNotify(
                  std::vector<base::FilePath>({file_path_1, file_path_2})))
      .Times(suggestion_feature_enabled ? 1u : 0u);
  HoldingSpaceController::Get()->client()->RemoveSuggestions(
      {file_path_1, file_path_2});
  task_environment()->FastForwardBy(base::Seconds(1));

  // Verify that all suggestions have been removed from the `model`.
  EXPECT_THAT(GetSuggestionsInModel(*model), IsEmpty());
}

TEST_P(HoldingSpaceSuggestionsDelegateTest, VerifySuggestionsInModel) {
  const base::FilePath file_path_1 = drive_mount_point()->CreateArbitraryFile();

  // Update Drive file suggestions. Fast-forward to ensure the suggestion fetch
  // completes.
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kDriveFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kDriveFile, file_path_1,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  const bool suggestion_feature_enabled =
      features::IsHoldingSpaceSuggestionsEnabled();

  // Populate the expected suggestions array if the holding space suggestion
  // feature is enabled. There should be no suggestions in the model when the
  // feature is disabled.
  std::vector<std::pair<HoldingSpaceItem::Type, base::FilePath>> expected;
  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kDriveSuggestion, file_path_1}};
  }

  // Check the model after Drive file suggestions update.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  const base::FilePath file_path_2 = local_mount_point()->CreateArbitraryFile();

  // Update local file suggestions and check the model.
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kLocalFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kLocalFile, file_path_2,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  task_environment()->RunUntilIdle();

  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kLocalSuggestion, file_path_2},
                {HoldingSpaceItem::Type::kDriveSuggestion, file_path_1}};
  }
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  const base::FilePath file_path_3 = drive_mount_point()->CreateArbitraryFile();

  // Update Drive file suggestions again and check the model.
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kDriveFile,
      /*suggestions=*/
      std::vector<FileSuggestData>{{FileSuggestionType::kDriveFile, file_path_1,
                                    /*title=*/std::nullopt,
                                    /*new_prediction_reason=*/std::nullopt,
                                    /*modified_time=*/std::nullopt,
                                    /*viewed_time=*/std::nullopt,
                                    /*shared_time=*/std::nullopt,
                                    /*new_score=*/std::nullopt,
                                    /*drive_file_id=*/std::nullopt,
                                    /*icon_url=*/std::nullopt},
                                   {FileSuggestionType::kDriveFile, file_path_3,
                                    /*title=*/std::nullopt,
                                    /*new_prediction_reason=*/std::nullopt,
                                    /*modified_time=*/std::nullopt,
                                    /*viewed_time=*/std::nullopt,
                                    /*shared_time=*/std::nullopt,
                                    /*new_score=*/std::nullopt,
                                    /*drive_file_id=*/std::nullopt,
                                    /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kLocalSuggestion, file_path_2},
                {HoldingSpaceItem::Type::kDriveSuggestion, file_path_3},
                {HoldingSpaceItem::Type::kDriveSuggestion, file_path_1}};
  }
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Update Drive file suggestions with an empty array.
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kDriveFile,
      /*suggestions=*/std::vector<FileSuggestData>{});
  task_environment()->FastForwardBy(base::Seconds(1));

  // Drive file suggestions should be removed from the model if suggestions are
  // enabled.
  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kLocalSuggestion, file_path_2}};
  }
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Update local file suggestions with an empty array.
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kLocalFile,
      /*suggestions=*/std::vector<FileSuggestData>{});
  task_environment()->FastForwardBy(base::Seconds(1));

  // There should be no suggestions in the model.
  expected.clear();
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);
}

TEST_P(HoldingSpaceSuggestionsDelegateTest, DownloadsFolderNotSuggested) {
  auto downloads_mount =
      local_mount_point()->CreateAndMountDownloads(GetProfile());
  auto downloads_path =
      file_manager::util::GetDownloadsFolderForProfile(GetProfile());
  auto other_folder_path = downloads_path.Append("contained_folder");
  ASSERT_TRUE(base::CreateDirectory(other_folder_path));
  const base::FilePath file_path = local_mount_point()->CreateArbitraryFile();

  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kLocalFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kLocalFile, downloads_path,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt},
          {FileSuggestionType::kLocalFile, other_folder_path,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt},
          {FileSuggestionType::kLocalFile, file_path,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  std::vector<std::pair<HoldingSpaceItem::Type, base::FilePath>> expected;
  if (features::IsHoldingSpaceSuggestionsEnabled()) {
    expected = {{HoldingSpaceItem::Type::kLocalSuggestion, file_path},
                {HoldingSpaceItem::Type::kLocalSuggestion, other_folder_path}};
  }

  EXPECT_EQ(GetSuggestionsInModel(*HoldingSpaceController::Get()->model()),
            expected);
}

TEST_P(HoldingSpaceSuggestionsDelegateTest, PinAndUnpinSuggestions) {
  const base::FilePath file_path_1 = drive_mount_point()->CreateArbitraryFile();
  const GURL file_system_url_1 = GetFileSystemUrl(GetProfile(), file_path_1);
  const HoldingSpaceFile::FileSystemType file_system_type_1 =
      holding_space_util::ResolveFileSystemType(GetProfile(),
                                                file_system_url_1);

  // Update Drive file suggestions. Fast-forward to ensure the suggestion fetch
  // completes.
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kDriveFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kDriveFile, file_path_1,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  const bool suggestion_feature_enabled =
      features::IsHoldingSpaceSuggestionsEnabled();

  // Populate the expected suggestions array if the holding space suggestion
  // feature is enabled. There should be no suggestions in the model when the
  // feature is disabled.
  std::vector<std::pair<HoldingSpaceItem::Type, base::FilePath>> expected;
  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kDriveSuggestion, file_path_1}};
  }

  // Check the model after Drive file suggestions update.
  HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  const base::FilePath file_path_2 = local_mount_point()->CreateArbitraryFile();

  // Update local file suggestions and check the model.
  GetFileSuggestKeyedService()->SetSuggestionsForType(
      FileSuggestionType::kLocalFile,
      /*suggestions=*/std::vector<FileSuggestData>{
          {FileSuggestionType::kLocalFile, file_path_2,
           /*title=*/std::nullopt,
           /*new_prediction_reason=*/std::nullopt,
           /*modified_time=*/std::nullopt,
           /*viewed_time=*/std::nullopt,
           /*shared_time=*/std::nullopt,
           /*new_score=*/std::nullopt,
           /*drive_file_id=*/std::nullopt,
           /*icon_url=*/std::nullopt}});
  task_environment()->RunUntilIdle();

  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kLocalSuggestion, file_path_2},
                {HoldingSpaceItem::Type::kDriveSuggestion, file_path_1}};
  }
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Pin the suggested Drive file and verify that the suggestion is removed
  // from the model if suggestions are enabled.
  auto pinned_item = HoldingSpaceItem::CreateFileBackedItem(
      HoldingSpaceItem::Type::kPinnedFile,
      HoldingSpaceFile(file_path_1, file_system_type_1, file_system_url_1),
      base::BindOnce(&CreateTestHoldingSpaceImage));
  const auto& pinned_item_id = pinned_item->id();
  model->AddItem(std::move(pinned_item));
  task_environment()->RunUntilIdle();

  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kLocalSuggestion, file_path_2}};
  }
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Unpin the suggested Drive file and verify that the suggestion is re-added
  // to the model if suggestions are enabled.
  model->RemoveItem(pinned_item_id);
  task_environment()->RunUntilIdle();

  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kLocalSuggestion, file_path_2},
                {HoldingSpaceItem::Type::kDriveSuggestion, file_path_1}};
  }
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Add an uninitialized pinned item for the suggested local file to the model
  // and verify that there is no change to the model's suggestions.
  auto* uninitialized_pinned_item_ptr = AddUninitializedItem(
      model, HoldingSpaceItem::Type::kPinnedFile, file_path_2);

  // The `expected` suggestions should not have changed.
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Remove the suggested local file's uninitialized pinned item and verify
  // that there is no change to the model's suggestions.
  model->RemoveItem(uninitialized_pinned_item_ptr->id());

  // The `expected` suggestions should not have changed.
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Add an uninitialized pinned item for the suggested local file to the model
  // and verify that there is no change to the model's suggestions.
  auto* partially_initialized_pinned_item_ptr = AddUninitializedItem(
      model, HoldingSpaceItem::Type::kPinnedFile, file_path_2);

  // The `expected` suggestions should not have changed.
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);

  // Initialize the pinned item for the suggested local file and verify that
  // the suggestion is removed from the model if suggestions are enabled.
  model->InitializeOrRemoveItem(
      partially_initialized_pinned_item_ptr->id(),
      HoldingSpaceFile(file_path_2, HoldingSpaceFile::FileSystemType::kTest,
                       GetFileSystemUrl(GetProfile(), file_path_2)));
  task_environment()->RunUntilIdle();

  if (suggestion_feature_enabled) {
    expected = {{HoldingSpaceItem::Type::kDriveSuggestion, file_path_1}};
  }
  EXPECT_EQ(GetSuggestionsInModel(*model), expected);
}

// Verifies the file suggestion update on a profile with restored suggestions.
TEST_P(HoldingSpaceSuggestionsDelegateTest, RestoreSuggestions) {
  const base::FilePath drive_file = drive_mount_point()->CreateArbitraryFile();
  const GURL drive_file_system_url = GetFileSystemUrl(GetProfile(), drive_file);
  const HoldingSpaceFile::FileSystemType drive_file_system_type =
      holding_space_util::ResolveFileSystemType(GetProfile(),
                                                drive_file_system_url);

  std::unique_ptr<HoldingSpaceItem> drive_file_suggestion =
      HoldingSpaceItem::CreateFileBackedItem(
          HoldingSpaceItem::Type::kDriveSuggestion,
          HoldingSpaceFile(drive_file, drive_file_system_type,
                           drive_file_system_url),
          base::BindOnce(&CreateTestHoldingSpaceImage));

  // Create a secondary profile with a persisted drive file suggestion.
  TestingProfile* const secondary_profile = CreateSecondaryProfile(
      base::BindLambdaForTesting([&](TestingPrefStore* pref_store) {
        base::Value::List persisted_items;
        persisted_items.Append(drive_file_suggestion->Serialize());
        pref_store->SetValueSilently(
            HoldingSpacePersistenceDelegate::kPersistencePath,
            base::Value(std::move(persisted_items)),
            PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS);
      }));

  // Activate `secondary_profile`. Wait until the model updates.
  ActivateSecondaryProfile();
  HoldingSpaceModelAttachedWaiter(secondary_profile).Wait();
  HoldingSpaceModel* const secondary_holding_space_model =
      HoldingSpaceController::Get()->model();
  ItemsInitializedWaiter(secondary_holding_space_model).Wait();
  const bool suggestion_feature_enabled =
      features::IsHoldingSpaceSuggestionsEnabled();
  EXPECT_EQ(secondary_holding_space_model->items().size(),
            suggestion_feature_enabled ? 1u : 0u);

  // Update local file suggestions on the secondary profile. Fast-forward to
  // ensure the suggestion fetch completes.
  const base::FilePath local_file = local_mount_point()->CreateArbitraryFile();
  static_cast<MockFileSuggestKeyedService*>(
      FileSuggestKeyedServiceFactory::GetInstance()->GetService(
          secondary_profile))
      ->SetSuggestionsForType(FileSuggestionType::kLocalFile,
                              /*suggestions=*/std::vector<FileSuggestData>{
                                  {FileSuggestionType::kLocalFile, local_file,
                                   /*title=*/std::nullopt,
                                   /*new_prediction_reason=*/std::nullopt,
                                   /*modified_time=*/std::nullopt,
                                   /*viewed_time=*/std::nullopt,
                                   /*shared_time=*/std::nullopt,
                                   /*new_score=*/std::nullopt,
                                   /*drive_file_id=*/std::nullopt,
                                   /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  const auto& model_items = secondary_holding_space_model->items();
  if (suggestion_feature_enabled) {
    // The drive and local file suggestions should coexist in the model.
    ASSERT_EQ(model_items.size(), 2u);
    EXPECT_EQ(model_items[0]->file().file_path, local_file);
    EXPECT_EQ(model_items[1]->file().file_path, drive_file);
  } else {
    EXPECT_TRUE(model_items.empty());
  }
}

// Verifies by updating file suggestions in the holding space model which
// contains the suggested files from an unmounted file system.
TEST_P(HoldingSpaceSuggestionsDelegateTest, UpdateSuggestionsWithDelayedMount) {
  auto delayed_mount = std::make_unique<ScopedTestMountPoint>(
      "drivefs-delayed_mount",
      /*file_system_type=*/storage::kFileSystemTypeDriveFs,
      /*volume_type=*/file_manager::VOLUME_TYPE_GOOGLE_DRIVE);
  const base::FilePath delayed_mount_file_path =
      delayed_mount->GetRootPath().Append("delayed file");
  auto delayed_holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
      HoldingSpaceItem::Type::kDriveSuggestion,
      HoldingSpaceFile(delayed_mount_file_path,
                       HoldingSpaceFile::FileSystemType::kTest,
                       GURL("filesystem:fake")),
      base::BindOnce(&CreateTestHoldingSpaceImage));

  // Create a secondary profile with a persisted delayed file suggestion.
  TestingProfile* const secondary_profile = CreateSecondaryProfile(
      base::BindLambdaForTesting([&](TestingPrefStore* pref_store) {
        base::Value::List persisted_items;
        persisted_items.Append(delayed_holding_space_item->Serialize());
        pref_store->SetValueSilently(
            HoldingSpacePersistenceDelegate::kPersistencePath,
            base::Value(std::move(persisted_items)),
            PersistentPrefStore::DEFAULT_PREF_WRITE_FLAGS);
      }));

  // Activate `secondary_profile`. Wait until the model updates.
  ActivateSecondaryProfile();
  HoldingSpaceModelAttachedWaiter(secondary_profile).Wait();
  HoldingSpaceModel* const secondary_holding_space_model =
      HoldingSpaceController::Get()->model();
  const bool suggestion_feature_enabled =
      features::IsHoldingSpaceSuggestionsEnabled();
  EXPECT_EQ(secondary_holding_space_model->items().size(),
            suggestion_feature_enabled ? 1u : 0u);

  // Update with a local file suggestion.
  const base::FilePath local_file = local_mount_point()->CreateArbitraryFile();
  static_cast<MockFileSuggestKeyedService*>(
      FileSuggestKeyedServiceFactory::GetInstance()->GetService(
          secondary_profile))
      ->SetSuggestionsForType(FileSuggestionType::kLocalFile,
                              /*suggestions=*/std::vector<FileSuggestData>{
                                  {FileSuggestionType::kLocalFile, local_file,
                                   /*title=*/std::nullopt,
                                   /*new_prediction_reason=*/std::nullopt,
                                   /*modified_time=*/std::nullopt,
                                   /*viewed_time=*/std::nullopt,
                                   /*shared_time=*/std::nullopt,
                                   /*new_score=*/std::nullopt,
                                   /*drive_file_id=*/std::nullopt,
                                   /*icon_url=*/std::nullopt}});
  task_environment()->FastForwardBy(base::Seconds(1));

  const auto& model_items = secondary_holding_space_model->items();
  if (suggestion_feature_enabled) {
    ASSERT_EQ(model_items.size(), 2u);
    EXPECT_EQ(model_items[0]->file().file_path, local_file);
    EXPECT_EQ(model_items[0]->type(), HoldingSpaceItem::Type::kLocalSuggestion);
    EXPECT_TRUE(model_items[0]->IsInitialized());

    EXPECT_EQ(model_items[1]->file().file_path, delayed_mount_file_path);
    EXPECT_EQ(model_items[1]->type(), HoldingSpaceItem::Type::kDriveSuggestion);
    EXPECT_FALSE(model_items[1]->IsInitialized());
  } else {
    EXPECT_TRUE(model_items.empty());
  }
}

}  // namespace ash