chromium/chrome/browser/ash/file_system_provider/cloud_file_system_unittest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/file_system_provider/cloud_file_system.h"

#include "base/base64.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/file_system_provider/abort_callback.h"
#include "chrome/browser/ash/file_system_provider/content_cache/cache_manager.h"
#include "chrome/browser/ash/file_system_provider/content_cache/content_cache.h"
#include "chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h"
#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
#include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h"
#include "chrome/browser/ash/file_system_provider/mount_path_util.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "net/base/io_buffer.h"
#include "testing/gmock/include/gmock/gmock-actions.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::file_system_provider {
namespace {

using base::test::IsNotNullCallback;
using base::test::RunClosure;
using base::test::RunOnceCallback;
using base::test::TestFuture;
using testing::_;
using testing::DoAll;
using testing::Field;
using testing::IsEmpty;
using testing::IsFalse;
using testing::Return;
using testing::ReturnRef;

using OpenFileFuture =
    TestFuture<int, base::File::Error, std::unique_ptr<EntryMetadata>>;
using ReadFileFuture = TestFuture<int, bool, base::File::Error>;
using GetMetadataFuture =
    TestFuture<std::unique_ptr<EntryMetadata>, base::File::Error>;
using FileErrorFuture = TestFuture<base::File::Error>;

const char kExtensionId[] = "mbflcebpggnecokmikipoihdbecnjfoj";
const char kFileSystemId[] = "cloud-fs-id";
const char kDisplayName[] = "Cloud FS";

// An action for a mock function which is equivalent to `RunOnceCallback<I>()`,
// `run_loop.Quit()` and `Return(AbortCallback())`. That is, it invokes the
// Run() method on the I-th (0-based) argument of the mock function with
// argument `result`, quits the `run_loop` and then returns an
// `AbortCallback()`. This is used when mocking `ProvidedFileSystemInterface`
// functions that have a `StatusCallback`. This waits for the function to be
// called, runs the callback and returns an AbortCallback().
template <size_t I>
auto RunStatusCallbackAndQuitRunLoopAndReturnAbortCallback(
    base::RunLoop& run_loop,
    base::File::Error result) {
  return [&run_loop, result](auto&&... args) -> AbortCallback {
    auto callback =
        std::move(base::gmock_callback_support_internal::get<I>(args...));
    std::move(callback).Run(result);
    run_loop.Quit();
    return AbortCallback();
  };
}

class MockCacheManager : public CacheManager {
 public:
  MOCK_METHOD(void,
              InitializeForProvider,
              (const ProvidedFileSystemInfo& file_system_info,
               FileErrorOrContentCacheCallback callback),
              (override));
  MOCK_METHOD(void,
              UninitializeForProvider,
              (const ProvidedFileSystemInfo& file_system_info),
              (override));
  MOCK_METHOD(bool,
              IsProviderInitialized,
              (const ProvidedFileSystemInfo& file_system_info),
              (override));
  MOCK_METHOD(void, AddObserver, (Observer * observer), (override));
  MOCK_METHOD(void, RemoveObserver, (Observer * observer), (override));
};

class MockContentCache : public ContentCache {
 public:
  MOCK_METHOD(void, SetMaxCacheItems, (size_t max_cache_items), (override));
  MOCK_METHOD(void,
              ReadBytes,
              (const OpenedCloudFile& file,
               scoped_refptr<net::IOBuffer> buffer,
               int64_t offset,
               int length,
               ProvidedFileSystemInterface::ReadChunkReceivedCallback callback),
              (override));
  MOCK_METHOD(void,
              WriteBytes,
              (const OpenedCloudFile& file,
               scoped_refptr<net::IOBuffer> buffer,
               int64_t offset,
               int length,
               FileErrorCallback callback),
              (override));
  MOCK_METHOD(void, CloseFile, (const OpenedCloudFile& file), (override));
  MOCK_METHOD(void, LoadFromDisk, (base::OnceClosure callback), (override));
  MOCK_METHOD(std::vector<base::FilePath>, GetCachedFilePaths, (), (override));
  MOCK_METHOD(void,
              Notify,
              (ProvidedFileSystemObserver::Changes & changes),
              (override));
  MOCK_METHOD(void,
              ObservedVersionTag,
              (const base::FilePath& entry_path,
               const std::string& version_tag),
              (override));
  MOCK_METHOD(void, Evict, (const base::FilePath& file_path), (override));
  MOCK_METHOD(void, AddObserver, (Observer * observer), (override));
  MOCK_METHOD(void, RemoveObserver, (Observer * observer), (override));

  base::WeakPtr<MockContentCache> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  base::WeakPtrFactory<MockContentCache> weak_ptr_factory_{this};
};

class MockContentCacheObserver : public ContentCache::Observer {
 public:
  MOCK_METHOD(void,
              OnItemEvicted,
              (const base::FilePath& fsp_path),
              (override));
  MOCK_METHOD(void,
              OnItemRemovedFromDisk,
              (const base::FilePath& fsp_path, int64_t bytes_removed),
              (override));
};

class MockProvidedFileSystem : public ProvidedFileSystemInterface {
 public:
  MOCK_METHOD(AbortCallback,
              RequestUnmount,
              (storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              GetMetadata,
              (const base::FilePath& entry_path,
               ProvidedFileSystemInterface::MetadataFieldMask fields,
               ProvidedFileSystemInterface::GetMetadataCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              GetActions,
              (const std::vector<base::FilePath>& entry_paths,
               GetActionsCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              ExecuteAction,
              (const std::vector<base::FilePath>& entry_paths,
               const std::string& action_id,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              ReadDirectory,
              (const base::FilePath& directory_path,
               storage::AsyncFileUtil::ReadDirectoryCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              OpenFile,
              (const base::FilePath& file_path,
               OpenFileMode mode,
               OpenFileCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              CloseFile,
              (int file_handle,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              ReadFile,
              (int file_handle,
               net::IOBuffer* buffer,
               int64_t offset,
               int length,
               ReadChunkReceivedCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              CreateDirectory,
              (const base::FilePath& directory_path,
               bool recursive,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              DeleteEntry,
              (const base::FilePath& entry_path,
               bool recursive,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              CreateFile,
              (const base::FilePath& file_path,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              CopyEntry,
              (const base::FilePath& source_path,
               const base::FilePath& target_path,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              MoveEntry,
              (const base::FilePath& source_path,
               const base::FilePath& target_path,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              Truncate,
              (const base::FilePath& file_path,
               int64_t length,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              WriteFile,
              (int file_handle,
               net::IOBuffer* buffer,
               int64_t offset,
               int length,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(AbortCallback,
              FlushFile,
              (int file_handle,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(
      AbortCallback,
      AddWatcher,
      (const GURL& origin,
       const base::FilePath& entry_path,
       bool recursive,
       bool persistent,
       storage::AsyncFileUtil::StatusCallback callback,
       storage::WatcherManager::NotificationCallback notification_callback),
      (override));
  MOCK_METHOD(void,
              RemoveWatcher,
              (const GURL& origin,
               const base::FilePath& entry_path,
               bool recursive,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(const ProvidedFileSystemInfo&,
              GetFileSystemInfo,
              (),
              (const, override));
  MOCK_METHOD(OperationRequestManager*, GetRequestManager, (), (override));
  MOCK_METHOD(Watchers*, GetWatchers, (), (override));
  MOCK_METHOD(const OpenedFiles&, GetOpenedFiles, (), (const, override));
  MOCK_METHOD(void,
              AddObserver,
              (ProvidedFileSystemObserver * observer),
              (override));
  MOCK_METHOD(void,
              RemoveObserver,
              (ProvidedFileSystemObserver * observer),
              (override));
  MOCK_METHOD(void,
              Notify,
              (const base::FilePath& entry_path,
               bool recursive,
               storage::WatcherManager::ChangeType change_type,
               std::unique_ptr<ProvidedFileSystemObserver::Changes> changes,
               const std::string& tag,
               storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(void,
              Configure,
              (storage::AsyncFileUtil::StatusCallback callback),
              (override));
  MOCK_METHOD(base::WeakPtr<ProvidedFileSystemInterface>,
              GetWeakPtr,
              (),
              (override));
  MOCK_METHOD(std::unique_ptr<ScopedUserInteraction>,
              StartUserInteraction,
              (),
              (override));

  base::WeakPtr<MockProvidedFileSystem> GetMockWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  base::WeakPtrFactory<MockProvidedFileSystem> weak_ptr_factory_{this};
};

// Holder for the constructed mock content cache and the cloud file system.
struct MockContentCacheAndCloudFileSystemAndFakeFsp {
  base::WeakPtr<MockContentCache> mock_content_cache;
  base::WeakPtr<FakeProvidedFileSystem> fake_fsp;
  std::unique_ptr<CloudFileSystem> cloud_file_system;
};

struct MockContentCacheObserverAndCloudFileSystemAndFakeFsp {
  std::unique_ptr<MockContentCacheObserver> mock_content_cache_observer;
  base::WeakPtr<FakeProvidedFileSystem> fake_fsp;
  std::unique_ptr<CloudFileSystem> cloud_file_system;
};

class FileSystemProviderCloudFileSystemTest : public testing::Test,
                                              public CacheManager::Observer {
 protected:
  FileSystemProviderCloudFileSystemTest() = default;
  ~FileSystemProviderCloudFileSystemTest() override = default;

  void SetUp() override { profile_ = std::make_unique<TestingProfile>(); }

  const ProvidedFileSystemInfo GetFileSystemInfo(bool with_mock_cache_manager) {
    MountOptions mount_options;
    mount_options.file_system_id = kFileSystemId;
    mount_options.display_name = kDisplayName;
    mount_options.supports_notify_tag = true;
    mount_options.writable = true;
    const base::FilePath mount_path = util::GetMountPath(
        profile_.get(), ProviderId::CreateFromExtensionId(kExtensionId),
        kFileSystemId);
    return ProvidedFileSystemInfo(
        kExtensionId, mount_options, mount_path,
        /*configurable=*/false,
        /*watchable=*/true, extensions::SOURCE_NETWORK, IconSet(),
        with_mock_cache_manager ? CacheType::LRU : CacheType::NONE);
  }

  // Creates a CloudFileSystem which wraps a FakeProvidedFileSystem.
  std::unique_ptr<CloudFileSystem> CreateCloudFileSystem(
      std::unique_ptr<ProvidedFileSystemInterface> provided_file_system,
      bool with_mock_cache_manager) {
    // Start the CloudFileSystem initialisation.
    std::unique_ptr<CloudFileSystem> cloud_file_system =
        std::make_unique<CloudFileSystem>(
            std::move(provided_file_system),
            with_mock_cache_manager ? &mock_cache_manager_ : nullptr);
    return cloud_file_system;
  }

  base::WeakPtr<MockContentCache> CreateMockContentCache() {
    std::unique_ptr<MockContentCache> mock_content_cache =
        std::make_unique<MockContentCache>();
    base::WeakPtr<MockContentCache> cache_weak_ptr =
        mock_content_cache->GetWeakPtr();
    EXPECT_CALL(mock_cache_manager_,
                InitializeForProvider(_, IsNotNullCallback()))
        .WillOnce(RunOnceCallback<1>(std::move(mock_content_cache)));
    return cache_weak_ptr;
  }

  std::unique_ptr<MockContentCacheObserver>
  CreateContentCacheAndMockObserver() {
    EXPECT_TRUE(temp_cache_dir_.CreateUniqueTempDir());

    // Initialize a `ContextDatabase` in memory on a blocking task runner.
    std::unique_ptr<ContextDatabase> context_db =
        std::make_unique<ContextDatabase>(base::FilePath());
    scoped_refptr<base::SequencedTaskRunner> db_task_runner =
        base::ThreadPool::CreateSequencedTaskRunner(
            {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
             base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
    BoundContextDatabase db(db_task_runner, std::move(context_db));
    TestFuture<bool> future;
    db.AsyncCall(&ContextDatabase::Initialize).Then(future.GetCallback());
    EXPECT_TRUE(future.Get());

    auto content_cache_observer = std::make_unique<MockContentCacheObserver>();
    std::unique_ptr<ContentCache> content_cache =
        ContentCacheImpl::Create(temp_cache_dir_.GetPath(), std::move(db));
    content_cache->AddObserver(content_cache_observer.get());

    EXPECT_CALL(mock_cache_manager_,
                InitializeForProvider(_, IsNotNullCallback()))
        .WillOnce(RunOnceCallback<1>(std::move(content_cache)));
    return content_cache_observer;
  }

  MockContentCacheObserverAndCloudFileSystemAndFakeFsp
  CreateContentCacheAndObserverAndCloudFileSystemWithFakeFsp() {
    std::unique_ptr<FakeProvidedFileSystem> provided_file_system =
        std::make_unique<FakeProvidedFileSystem>(
            GetFileSystemInfo(/*with_mock_cache_manager=*/true));
    return MockContentCacheObserverAndCloudFileSystemAndFakeFsp{
        .mock_content_cache_observer = CreateContentCacheAndMockObserver(),
        .fake_fsp = provided_file_system->GetFakeWeakPtr(),
        .cloud_file_system = CreateCloudFileSystem(
            std::move(provided_file_system), /*with_mock_cache_manager=*/true)};
  }

  MockContentCacheAndCloudFileSystemAndFakeFsp
  CreateMockContentCacheAndCloudFileSystemWithFakeFsp() {
    std::unique_ptr<FakeProvidedFileSystem> provided_file_system =
        std::make_unique<FakeProvidedFileSystem>(
            GetFileSystemInfo(/*with_mock_cache_manager=*/true));
    return MockContentCacheAndCloudFileSystemAndFakeFsp{
        .mock_content_cache = CreateMockContentCache(),
        .fake_fsp = provided_file_system->GetFakeWeakPtr(),
        .cloud_file_system = CreateCloudFileSystem(
            std::move(provided_file_system), /*with_mock_cache_manager=*/true)};
  }

  void CloseFileSuccessfully(CloudFileSystem& cloud_file_system,
                             int file_handle) {
    FileErrorFuture close_file_future;
    cloud_file_system.CloseFile(file_handle, close_file_future.GetCallback());
    EXPECT_EQ(close_file_future.Get(), base::File::FILE_OK);
  }

  void ReadFileSuccessfully(CloudFileSystem& cloud_file_system,
                            int file_handle,
                            scoped_refptr<net::IOBuffer> buffer,
                            int64_t offset = 0,
                            int length = 1) {
    ReadFileFuture read_file_future;
    cloud_file_system.ReadFile(file_handle, buffer.get(), offset, length,
                               read_file_future.GetRepeatingCallback());
    EXPECT_EQ(read_file_future.Get<int>(), 1);
    EXPECT_EQ(read_file_future.Get<base::File::Error>(), base::File::FILE_OK);
  }

  void WriteFileSuccessfully(CloudFileSystem& cloud_file_system,
                             int file_handle,
                             scoped_refptr<net::IOBuffer> buffer,
                             int64_t offset = 0,
                             int length = 1) {
    FileErrorFuture write_file_future;
    cloud_file_system.WriteFile(file_handle, buffer.get(), offset, length,
                                write_file_future.GetRepeatingCallback());
    EXPECT_EQ(write_file_future.Get(), base::File::FILE_OK);
  }

  void DeleteFileSuccessfully(CloudFileSystem& cloud_file_system,
                              const base::FilePath& entry_path) {
    FileErrorFuture delete_file_future;
    cloud_file_system.DeleteEntry(entry_path, /*recursive=*/false,
                                  delete_file_future.GetRepeatingCallback());
    EXPECT_EQ(delete_file_future.Get(), base::File::FILE_OK);
  }

  int GetFileHandleFromSuccessfulOpenFile(
      CloudFileSystem& cloud_file_system,
      const base::FilePath& file_path,
      OpenFileMode mode = OPEN_FILE_MODE_READ) {
    OpenFileFuture open_file_future;
    cloud_file_system.OpenFile(file_path, mode, open_file_future.GetCallback());
    EXPECT_EQ(open_file_future.Get<base::File::Error>(), base::File::FILE_OK);
    return open_file_future.Get<int>();
  }

  void DeleteEntryOnFakeFileSystem(
      base::WeakPtr<FakeProvidedFileSystem> fake_fsp,
      const base::FilePath& entry_path) {
    FileErrorFuture delete_entry_future;
    fake_fsp->DeleteEntry(entry_path, /*recursive=*/true,
                          delete_entry_future.GetCallback());
    EXPECT_EQ(delete_entry_future.Get(), base::File::FILE_OK);
  }

  void UpdateEntryWithVersionTagOnFakeFileSystem(
      base::WeakPtr<FakeProvidedFileSystem> fake_fsp,
      const base::FilePath& entry_path,
      const std::string& version_tag) {
    fake_fsp->GetEntry(entry_path)->metadata->cloud_file_info->version_tag =
        version_tag;
  }

  MockCacheManager mock_cache_manager_;
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfile> profile_;
  base::ScopedTempDir temp_cache_dir_;
};

TEST_F(FileSystemProviderCloudFileSystemTest, ContiguousReadsWriteToCache) {
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Open the `kFakeFilePath` file to stage it in the `FakeProvidedFileSystem`.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));

  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  // Set the first read bytes to return `base::File::FILE_ERROR_NOT_FOUND`, this
  // indicates that the data is not cached in the content cache.
  EXPECT_CALL(*mock_content_cache, ReadBytes(_, buffer, /*offset=*/0,
                                             /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(/*bytes_read=*/-1, /*has_more=*/false,
                                   base::File::FILE_ERROR_NOT_FOUND));

  // Set the first write bytes to return successfully, this indicates the post
  // FSP stream to disk succeeded.
  EXPECT_CALL(*mock_content_cache,
              WriteBytes(_, buffer, /*offset=*/0,
                         /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(base::File::FILE_OK));

  // Read the first chunk.
  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/0,
                       /*length=*/1);

  // Expect watcher added for file.
  EXPECT_THAT(
      cloud_file_system->GetWatchers(),
      Pointee(ElementsAre(Pair(
          _, AllOf(Field(&Watcher::entry_path, base::FilePath(kFakeFilePath)),
                   Field(&Watcher::recursive, IsFalse()))))));

  // Read the next chunk.
  EXPECT_CALL(*mock_content_cache, ReadBytes(_, buffer, /*offset=*/1,
                                             /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(/*bytes_read=*/-1, /*has_more=*/false,
                                   base::File::FILE_ERROR_NOT_FOUND));
  EXPECT_CALL(*mock_content_cache,
              WriteBytes(_, buffer, /*offset=*/1,
                         /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(base::File::FILE_OK));
  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/1,
                       /*length=*/1);

  // Expect no new watcher added for the file.
  EXPECT_THAT(
      cloud_file_system->GetWatchers(),
      Pointee(ElementsAre(Pair(
          _, AllOf(Field(&Watcher::entry_path, base::FilePath(kFakeFilePath)),
                   Field(&Watcher::recursive, IsFalse()))))));

  CloseFileSuccessfully(*cloud_file_system, file_handle);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       UpToDateItemsInCacheShouldReturnWithoutCallingTheFsp) {
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Open the `kFakeFilePath` file to stage it in the `FakeProvidedFileSystem`.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));

  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  // Set the first read bytes to return `base::File::FILE_OK`, this indicates
  // that the data is fresh and available in the cache.
  EXPECT_CALL(*mock_content_cache, ReadBytes(_, buffer, /*offset=*/0,
                                             /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(/*bytes_read=*/1, /*has_more=*/false,
                                   base::File::FILE_OK));

  // Expect that `WriteBytes` should not be called.
  EXPECT_CALL(*mock_content_cache, WriteBytes(_, _, _, _, _)).Times(0);

  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer);
  CloseFileSuccessfully(*cloud_file_system, file_handle);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       WhenReadingUpToDateItemsFromCacheFailsShouldDeferToFsp) {
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Open the `kFakeFilePath` file to stage it in the `FakeProvidedFileSystem`.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));

  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  // Make the inner callback to return `base::File::FILE_ERROR_FAILED` to
  // indicate that the actual underlying read of the cached file failed.
  EXPECT_CALL(*mock_content_cache, ReadBytes(_, buffer, /*offset=*/0,
                                             /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(/*bytes_read=*/-1, /*has_more=*/false,
                                   base::File::FILE_ERROR_FAILED));

  // Ensure that the read still completes successfully, this indicates it
  // deferred the actual read to the underlying FSP instead of the failed cloud
  // file system.
  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer);
  CloseFileSuccessfully(*cloud_file_system, file_handle);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       ContentCacheFailsWritingBytesShouldStillReturnSuccessfully) {
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Open the `kFakeFilePath` file to stage it in the `FakeProvidedFileSystem`.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));

  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  // Set the first read bytes to return `base::File::FILE_ERROR_NOT_FOUND`, this
  // indicates that the data is not cached in the content cache.
  EXPECT_CALL(*mock_content_cache, ReadBytes(_, buffer, /*offset=*/0,
                                             /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(/*bytes_read=*/-1, /*has_more=*/false,
                                   base::File::FILE_ERROR_NOT_FOUND));

  // Set the first write bytes to return `base::File::FILE_ERROR_FAILED`, this
  // simulates a failure whilst streaming to disk. Given the FSP succeeded, we
  // should succeed back to the caller and follow up requests will defer
  // straight to the FSP.
  EXPECT_CALL(*mock_content_cache,
              WriteBytes(_, buffer, /*offset=*/0,
                         /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(base::File::FILE_ERROR_FAILED));

  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer);
  // Expect no watcher is added.
  EXPECT_THAT(cloud_file_system->GetWatchers(), Pointee(IsEmpty()));

  CloseFileSuccessfully(*cloud_file_system, file_handle);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       FilesOpenForWriteShouldAlwaysGoToTheFspNotContentCache) {
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Open the `kFakeFilePath` file to stage it in the `FakeProvidedFileSystem`.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath), OPEN_FILE_MODE_WRITE);

  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  // Neither the `ReadBytes` nor the `WriteBytes` should be called.
  EXPECT_CALL(*mock_content_cache, ReadBytes(_, _, _, _, _)).Times(0);
  EXPECT_CALL(*mock_content_cache, WriteBytes(_, _, _, _, _)).Times(0);

  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer);
  // Expect no watcher is added.
  EXPECT_THAT(cloud_file_system->GetWatchers(), Pointee(IsEmpty()));

  CloseFileSuccessfully(*cloud_file_system, file_handle);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       IfFspReadFailsOnFirstCallContentCacheShouldNotWriteBytes) {
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Open the `kFakeFilePath` file to stage it in the `FakeProvidedFileSystem`.
  const base::FilePath fake_file_path(kFakeFilePath);
  int file_handle =
      GetFileHandleFromSuccessfulOpenFile(*cloud_file_system, fake_file_path);

  // Remove the entry from the underlying FSP, this should result in a
  // base::File::FILE_ERROR_INVALID_OPERATION on the `ReadFile` request.
  DeleteEntryOnFakeFileSystem(fake_fsp, fake_file_path);

  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  // Set the first read bytes to return `base::File::FILE_ERROR_NOT_FOUND`, this
  // simulates a cache miss.
  EXPECT_CALL(*mock_content_cache, ReadBytes(_, _, /*offset=*/0,
                                             /*length=*/1, IsNotNullCallback()))
      .WillOnce(RunOnceCallback<4>(/*bytes_read=*/-1, /*has_more=*/false,
                                   base::File::FILE_ERROR_NOT_FOUND));

  // Assert that `WriteBytes` never gets called as the underlying FSP
  // should respond with a `base::File::FILE_ERROR_INVALID_OPERATION` due to the
  // file not existing between the `OpenFile` and the `ReadFile`.
  EXPECT_CALL(*mock_content_cache, WriteBytes(_, _, _, _, _)).Times(0);

  ReadFileFuture read_file_future;
  cloud_file_system->ReadFile(file_handle, buffer.get(), /*offset=*/0,
                              /*length=*/1,
                              read_file_future.GetRepeatingCallback());
  EXPECT_EQ(read_file_future.Get<int>(), 0);
  EXPECT_EQ(read_file_future.Get<base::File::Error>(),
            base::File::FILE_ERROR_INVALID_OPERATION);
  // Expect no watcher is added.
  EXPECT_THAT(cloud_file_system->GetWatchers(), Pointee(IsEmpty()));

  CloseFileSuccessfully(*cloud_file_system, file_handle);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       WatchersAddedForEachFileAlreadyInTheCacheOnStartUp) {
  base::WeakPtr<MockContentCache> mock_content_cache = CreateMockContentCache();
  std::unique_ptr<MockProvidedFileSystem> provided_file_system =
      std::make_unique<MockProvidedFileSystem>();
  base::WeakPtr<MockProvidedFileSystem> mock_fsp =
      provided_file_system->GetMockWeakPtr();

  // Mock the fake file system info.
  auto file_system_info = GetFileSystemInfo(/*with_mock_cache_manager=*/true);
  EXPECT_CALL(*mock_fsp, GetFileSystemInfo())
      .WillRepeatedly(ReturnRef(file_system_info));

  // Set the content cache to already have files.
  std::vector<base::FilePath> cached_files(
      {base::FilePath("/a.txt"), base::FilePath("/b.txt")});
  EXPECT_CALL(*mock_content_cache, GetCachedFilePaths())
      .WillOnce(Return(cached_files));

  // Expect that AddWatcher is called for each cached file.
  base::RunLoop run_loop1;
  EXPECT_CALL(*mock_fsp,
              AddWatcher(GURL("chrome://content-cache/"),
                         base::FilePath("/a.txt"), /*recursive=*/false,
                         /*persistent=*/false, _, _))
      .WillOnce(RunStatusCallbackAndQuitRunLoopAndReturnAbortCallback<4>(
          run_loop1, base::File::Error::FILE_OK));
  base::RunLoop run_loop2;
  EXPECT_CALL(*mock_fsp,
              AddWatcher(GURL("chrome://content-cache/"),
                         base::FilePath("/b.txt"), /*recursive=*/false,
                         /*persistent=*/false, _, _))
      .WillOnce(RunStatusCallbackAndQuitRunLoopAndReturnAbortCallback<4>(
          run_loop2, base::File::Error::FILE_OK));

  // Initialise the CloudFileSystem.
  std::unique_ptr<CloudFileSystem> cloud_file_system = CreateCloudFileSystem(
      std::move(provided_file_system), /*with_mock_cache_manager=*/true);
  run_loop1.Run();
  run_loop2.Run();
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       RetryToAddWatcherOnSecurityErrorOnStartUp) {
  base::WeakPtr<MockContentCache> mock_content_cache = CreateMockContentCache();
  std::unique_ptr<MockProvidedFileSystem> provided_file_system =
      std::make_unique<MockProvidedFileSystem>();
  base::WeakPtr<MockProvidedFileSystem> mock_fsp =
      provided_file_system->GetMockWeakPtr();

  // Mock the fake file system info.
  auto file_system_info = GetFileSystemInfo(/*with_mock_cache_manager=*/true);
  EXPECT_CALL(*mock_fsp, GetFileSystemInfo())
      .WillRepeatedly(ReturnRef(file_system_info));

  // Set the content cache to already have a file.
  std::vector<base::FilePath> cached_files({base::FilePath("/a.txt")});
  EXPECT_CALL(*mock_content_cache, GetCachedFilePaths())
      .WillOnce(Return(cached_files));

  // Expect that AddWatcher is called for the cached file. Fail the first
  // AddWatcher call with `FILE_ERROR_SECURITY` and pass the follow up call.
  base::RunLoop run_loop1;
  base::RunLoop run_loop2;
  EXPECT_CALL(*mock_fsp,
              AddWatcher(GURL("chrome://content-cache/"),
                         base::FilePath("/a.txt"), /*recursive=*/false,
                         /*persistent=*/false, IsNotNullCallback(), _))
      .WillOnce(RunStatusCallbackAndQuitRunLoopAndReturnAbortCallback<4>(
          run_loop1, base::File::Error::FILE_ERROR_SECURITY))
      .WillOnce(RunStatusCallbackAndQuitRunLoopAndReturnAbortCallback<4>(
          run_loop2, base::File::Error::FILE_OK));

  // Initialise the CloudFileSystem.
  std::unique_ptr<CloudFileSystem> cloud_file_system = CreateCloudFileSystem(
      std::move(provided_file_system), /*with_mock_cache_manager=*/true);
  run_loop1.Run();
  run_loop2.Run();
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       NoRetryToAddWatcherOnNonSecurityErrorOnStartUp) {
  base::WeakPtr<MockContentCache> mock_content_cache = CreateMockContentCache();
  std::unique_ptr<MockProvidedFileSystem> provided_file_system =
      std::make_unique<MockProvidedFileSystem>();
  base::WeakPtr<MockProvidedFileSystem> mock_fsp =
      provided_file_system->GetMockWeakPtr();

  // Mock the fake file system info.
  auto file_system_info = GetFileSystemInfo(/*with_mock_cache_manager=*/true);
  EXPECT_CALL(*mock_fsp, GetFileSystemInfo())
      .WillRepeatedly(ReturnRef(file_system_info));

  // Set the content cache to already have a file.
  std::vector<base::FilePath> cached_files({base::FilePath("/a.txt")});
  EXPECT_CALL(*mock_content_cache, GetCachedFilePaths())
      .WillOnce(Return(cached_files));

  // Expect that AddWatcher is called for the cached file. Fail the AddWatcher
  // call with `FILE_ERROR_FAILED` and expect that no follow up call is made.
  base::RunLoop run_loop;
  EXPECT_CALL(*mock_fsp,
              AddWatcher(GURL("chrome://content-cache/"),
                         base::FilePath("/a.txt"), /*recursive=*/false,
                         /*persistent=*/false, IsNotNullCallback(), _))
      .WillOnce(RunStatusCallbackAndQuitRunLoopAndReturnAbortCallback<4>(
          run_loop, base::File::Error::FILE_ERROR_FAILED));

  // Initialise the CloudFileSystem.
  std::unique_ptr<CloudFileSystem> cloud_file_system = CreateCloudFileSystem(
      std::move(provided_file_system), /*with_mock_cache_manager=*/true);
  run_loop.Run();
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       WatcherAddedWhenFileAddedAndRemovedWhenFileEvicted) {
  // Underlying FakeProvidedFileSystem is (always) initialised with fake file
  // with kFakeFilePath.
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache_observer, fake_fsp, cloud_file_system] =
      CreateContentCacheAndObserverAndCloudFileSystemWithFakeFsp();

  // Add file to the cache.
  int file_handle =
      GetFileHandleFromSuccessfulOpenFile(*cloud_file_system, fake_file_path);
  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);
  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/0,
                       /*length=*/1);
  CloseFileSuccessfully(*cloud_file_system, file_handle);

  // There should be a watcher added for the file.
  EXPECT_THAT(cloud_file_system->GetWatchers(),
              Pointee(UnorderedElementsAre(
                  Pair(_, AllOf(Field(&Watcher::entry_path, fake_file_path),
                                Field(&Watcher::recursive, IsFalse()))))));

  // Remove the entry from the underlying FSP, this should result in a
  // base::File::FILE_ERROR_NOT_FOUND on the `GetMetadata` request.
  DeleteEntryOnFakeFileSystem(fake_fsp, fake_file_path);

  // The file will be evicted after the unsuccessful GetMetadata request.
  EXPECT_CALL(*mock_content_cache_observer, OnItemEvicted(fake_file_path));
  GetMetadataFuture get_metadata_future;
  cloud_file_system->GetMetadata(fake_file_path,
                                 /*fields*/ {},
                                 get_metadata_future.GetRepeatingCallback());
  EXPECT_EQ(get_metadata_future.Get<base::File::Error>(),
            base::File::FILE_ERROR_NOT_FOUND);

  // Expect watcher is removed.
  EXPECT_THAT(cloud_file_system->GetWatchers(), Pointee(IsEmpty()));
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       NotFoundFromGetMetadataEvictsCachedFile) {
  // Underlying FakeProvidedFileSystem is (always) initialised with fake file
  // with kFakeFilePath.
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // The file won't be evicted after the successful GetMetadata request.
  EXPECT_CALL(*mock_content_cache, Evict(fake_file_path)).Times(0);
  GetMetadataFuture get_metadata_future1;
  cloud_file_system->GetMetadata(fake_file_path,
                                 /*fields*/ {},
                                 get_metadata_future1.GetRepeatingCallback());
  EXPECT_EQ(get_metadata_future1.Get<base::File::Error>(), base::File::FILE_OK);

  // Remove the entry from the underlying FSP, this should result in a
  // base::File::FILE_ERROR_NOT_FOUND on the `GetMetadata` request.
  DeleteEntryOnFakeFileSystem(fake_fsp, fake_file_path);

  // The file will be evicted after the unsuccessful GetMetadata request.
  EXPECT_CALL(*mock_content_cache, Evict(fake_file_path)).Times(1);
  GetMetadataFuture get_metadata_future2;
  cloud_file_system->GetMetadata(fake_file_path,
                                 /*fields*/ {},
                                 get_metadata_future2.GetRepeatingCallback());
  EXPECT_EQ(get_metadata_future2.Get<base::File::Error>(),
            base::File::FILE_ERROR_NOT_FOUND);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       NotFoundFromOpenFileEvictsCachedFile) {
  // Underlying FakeProvidedFileSystem is (always) initialised with fake file
  // with kFakeFilePath.
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // The file won't be evicted after the successful `OpenFile` request.
  EXPECT_CALL(*mock_content_cache, Evict(fake_file_path)).Times(0);
  OpenFileFuture open_file_future1;
  cloud_file_system->OpenFile(fake_file_path, OPEN_FILE_MODE_READ,
                              open_file_future1.GetRepeatingCallback());
  EXPECT_EQ(open_file_future1.Get<base::File::Error>(), base::File::FILE_OK);

  // Remove the entry from the underlying FSP, this should result in a
  // base::File::FILE_ERROR_NOT_FOUND on the `OpenFile` request.
  DeleteEntryOnFakeFileSystem(fake_fsp, fake_file_path);

  // The file will be evicted after the unsuccessful `OpenFile` request.
  EXPECT_CALL(*mock_content_cache, Evict(fake_file_path)).Times(1);
  OpenFileFuture open_file_future2;
  cloud_file_system->OpenFile(fake_file_path, OPEN_FILE_MODE_READ,
                              open_file_future2.GetRepeatingCallback());
  EXPECT_EQ(open_file_future2.Get<base::File::Error>(),
            base::File::FILE_ERROR_NOT_FOUND);
}

TEST_F(FileSystemProviderCloudFileSystemTest, OkFromWriteFileEvictsCachedFile) {
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Open the `kFakeFilePath` file to stage it in the `FakeProvidedFileSystem`.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));

  // The file will be evicted after the successful `WriteFile` request.
  EXPECT_CALL(*mock_content_cache, Evict(fake_file_path)).Times(1);
  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);
  WriteFileSuccessfully(*cloud_file_system, file_handle, buffer);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       OkFromDeleteEntryEvictsCachedFile) {
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // The file will be evicted after the successful `WriteFile` request.
  EXPECT_CALL(*mock_content_cache, Evict(fake_file_path)).Times(1);
  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);
  DeleteFileSuccessfully(*cloud_file_system, fake_file_path);
}

TEST_F(FileSystemProviderCloudFileSystemTest, CurrentReaderCanReadEvictedFile) {
  // Underlying FakeProvidedFileSystem is (always) initialised with fake file
  // with kFakeFilePath.
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache_observer, fake_fsp, cloud_file_system] =
      CreateContentCacheAndObserverAndCloudFileSystemWithFakeFsp();

  // Read 2 bytes of the `kFakeFilePath` file and insert them into the cache.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));
  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/0,
                       /*length=*/1);
  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/1,
                       /*length=*/1);
  CloseFileSuccessfully(*cloud_file_system, file_handle);

  // Open the file again.
  file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));

  // Read the first byte from the cache.
  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/0,
                       /*length=*/1);

  // Remove the entry from the underlying FSP to ensure that the ReadFile is
  // indeed reading from the cache.
  DeleteEntryOnFakeFileSystem(fake_fsp, fake_file_path);

  // Send a delete notification. Expect that the item gets evicted.
  auto changes = std::make_unique<ProvidedFileSystemObserver::Changes>();
  changes->emplace_back(
      fake_file_path, storage::WatcherManager::ChangeType::DELETED,
      std::make_unique<ash::file_system_provider::CloudFileInfo>("versionA"));
  EXPECT_CALL(*mock_content_cache_observer, OnItemEvicted(fake_file_path));
  cloud_file_system->Notify(fake_file_path, /*recursive=*/true,
                            storage::WatcherManager::ChangeType::DELETED,
                            std::move(changes), /*tag=*/"", base::DoNothing());

  // Read the second byte from the cache.
  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/1,
                       /*length=*/1);

  // Fail to read the third byte. Expect that the request is forwarded to the
  // FSP which responds with a `base::File::FILE_ERROR_INVALID_OPERATION` due to
  // the file not existing.
  ReadFileFuture read_file_future;
  cloud_file_system->ReadFile(file_handle, buffer.get(), /*offset=*/2,
                              /*length=*/1,
                              read_file_future.GetRepeatingCallback());
  EXPECT_EQ(read_file_future.Get<int>(), 0);
  EXPECT_EQ(read_file_future.Get<base::File::Error>(),
            base::File::FILE_ERROR_INVALID_OPERATION);

  // Close the file, expect that it now gets removed.
  base::RunLoop run_loop;
  EXPECT_CALL(*mock_content_cache_observer,
              OnItemRemovedFromDisk(fake_file_path, /*bytes_removed=*/2))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  CloseFileSuccessfully(*cloud_file_system, file_handle);
  run_loop.Run();
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       GetMetadataCallsObservedVersionTag) {
  // Underlying FakeProvidedFileSystem is (always) initialised with fake file
  // with kFakeFilePath.
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Expect the successful GetMetadata request for a CloudFileInfo triggers a
  // `ObservedVersionTag()` call.
  EXPECT_CALL(*mock_content_cache,
              ObservedVersionTag(fake_file_path, kFakeFileVersionTag))
      .Times(1);
  GetMetadataFuture get_metadata_future;
  cloud_file_system->GetMetadata(
      fake_file_path,
      /*fields*/ ProvidedFileSystemInterface::METADATA_FIELD_CLOUD_FILE_INFO,
      get_metadata_future.GetRepeatingCallback());
  EXPECT_EQ(get_metadata_future.Get<base::File::Error>(), base::File::FILE_OK);
}

TEST_F(FileSystemProviderCloudFileSystemTest, OpenFileCallsObservedVersionTag) {
  // Underlying FakeProvidedFileSystem is (always) initialised with fake file
  // with kFakeFilePath.
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache, fake_fsp, cloud_file_system] =
      CreateMockContentCacheAndCloudFileSystemWithFakeFsp();

  // Expect the successful OpenFile request triggers a `ObservedVersionTag()`
  // call.
  EXPECT_CALL(*mock_content_cache,
              ObservedVersionTag(fake_file_path, kFakeFileVersionTag))
      .Times(1);
  OpenFileFuture open_file_future1;
  cloud_file_system->OpenFile(fake_file_path, OPEN_FILE_MODE_READ,
                              open_file_future1.GetRepeatingCallback());
  EXPECT_EQ(open_file_future1.Get<base::File::Error>(), base::File::FILE_OK);
}

TEST_F(FileSystemProviderCloudFileSystemTest,
       OpenFileForNewVersionEvictsStaleCachedFile) {
  // Underlying FakeProvidedFileSystem is (always) initialised with fake file
  // with kFakeFilePath.
  const base::FilePath fake_file_path(kFakeFilePath);
  auto [mock_content_cache_observer, fake_fsp, cloud_file_system] =
      CreateContentCacheAndObserverAndCloudFileSystemWithFakeFsp();

  // Read 1 byte of the `kFakeFilePath` file and insert it into the cache.
  int file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));
  scoped_refptr<net::IOBuffer> buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(1);

  ReadFileSuccessfully(*cloud_file_system, file_handle, buffer, /*offset=*/0,
                       /*length=*/1);
  CloseFileSuccessfully(*cloud_file_system, file_handle);

  // Update the version tag.
  UpdateEntryWithVersionTagOnFakeFileSystem(fake_fsp, fake_file_path,
                                            "new version");

  // Expect that opening the file causes the stale cache version to be evicted.
  EXPECT_CALL(*mock_content_cache_observer, OnItemEvicted(fake_file_path));
  file_handle = GetFileHandleFromSuccessfulOpenFile(
      *cloud_file_system, base::FilePath(kFakeFilePath));

  // Close the file, expect that it now gets removed.
  base::RunLoop run_loop;
  EXPECT_CALL(*mock_content_cache_observer,
              OnItemRemovedFromDisk(fake_file_path, /*bytes_removed=*/1))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  CloseFileSuccessfully(*cloud_file_system, file_handle);
  run_loop.Run();
}

}  // namespace
}  // namespace ash::file_system_provider