chromium/chromeos/ash/components/drivefs/drivefs_pinning_manager_unittest.cc

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

#include "chromeos/ash/components/drivefs/drivefs_pinning_manager.h"

#include <iomanip>
#include <list>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/spaced/fake_spaced_client.h"
#include "chromeos/ash/components/dbus/spaced/spaced_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-test-utils.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/drive/file_errors.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace drivefs::pinning {
namespace {

using ash::FakeSpacedClient;
using ash::FakeUserDataAuthClient;
using ash::SpacedClient;
using ash::UserDataAuthClient;
using base::BindOnce;
using base::OnceCallback;
using base::RunLoop;
using base::Seconds;
using base::SequencedTaskRunner;
using base::test::RunClosure;
using base::test::RunOnceCallback;
using base::test::TaskEnvironment;
using drive::FileError;
using mojom::DocsOfflineEnableStatus;
using mojom::FileChange;
using mojom::FileMetadata;
using mojom::FileMetadataPtr;
using mojom::ItemEvent;
using mojom::ItemEventPtr;
using mojom::ProgressEvent;
using mojom::QueryItem;
using mojom::QueryItemPtr;
using mojom::QueryParameters;
using mojom::SearchQuery;
using mojom::SyncingStatus;
using mojom::SyncingStatusPtr;
using std::string;
using std::vector;
using testing::_;
using testing::AnyNumber;
using testing::DoAll;
using testing::Field;
using testing::Invoke;
using testing::IsEmpty;
using testing::Return;
using testing::SizeIs;
using testing::UnorderedElementsAre;

using Id = PinningManager::Id;
using Path = base::FilePath;
using CompletionCallback = base::MockOnceCallback<void(Stage)>;

const FileError kFileOk = FileError::FILE_ERROR_OK;

// Shorthand way to represent drive files with the information that is relevant
// for the pinning manager.
struct DriveItem {
  static int64_t counter;
  int64_t stable_id = ++counter;
  int64_t size = 0;
  Path path;
  FileMetadata::Type type = FileMetadata::Type::kFile;
  bool pinned = false;
  bool available_offline = false;
};

int64_t DriveItem::counter = 0;

FileMetadataPtr MakeMetadata(const DriveItem& item) {
  FileMetadataPtr md = FileMetadata::New();
  md->stable_id = item.stable_id;
  md->type = item.type;
  md->size = item.size;
  md->pinned = item.pinned;
  md->available_offline = item.available_offline;
  md->capabilities = mojom::Capabilities::New();
  return md;
}

// An action that takes a `vector<DriveItem>` and is used to update the items
// that are returned via the `GetNextPage` callback. These shorthand items are
// converted to mojo types that represent the actual types returned. NOTE:
// `arg0` in the below represents the pointer passed via parameters to the
// `MOCK_METHOD` of `OnGetNextPage`.
ACTION_P(PopulateSearchItems, items) {
  vector<QueryItemPtr> result;
  result.reserve(items.size());
  for (const DriveItem& item : items) {
    QueryItemPtr p = QueryItem::New();
    // Paths must be parented at "/root" to be considered for space
    // calculations.
    p->path = item.path.empty() ? Path("/root/file.txt") : item.path;
    p->metadata = MakeMetadata(item);
    result.push_back(std::move(p));
  }
  *arg0 = std::move(result);
}

// An action that populates no search results. This is required as the final
// `GetNextPage` query will return 0 items and this ensures the `MOCK_METHOD`
// returns the appropriate type (instead of `std::nullopt`).
ACTION(PopulateNoSearchItems) {
  *arg0 = vector<QueryItemPtr>();
}

class MockDriveFs : public mojom::DriveFsInterceptorForTesting,
                    public SearchQuery {
 public:
  MockDriveFs() = default;

  MockDriveFs(const MockDriveFs&) = delete;
  MockDriveFs& operator=(const MockDriveFs&) = delete;

  mojom::DriveFs* GetForwardingInterface() override {
    NOTREACHED() << "No calls should make it to the forwarding interface";
  }

  MOCK_METHOD(void, OnStartSearchQuery, (const QueryParameters&));

  void StartSearchQuery(mojo::PendingReceiver<SearchQuery> query,
                        const mojom::QueryParametersPtr query_params) override {
    EXPECT_TRUE(query_params);
    queries_.emplace_back(this, std::move(query));
    OnStartSearchQuery(*query_params);
  }

  MOCK_METHOD(FileError,
              OnGetNextPage,
              (std::optional<vector<QueryItemPtr>> * items));

  void GetNextPage(GetNextPageCallback callback) override {
    std::optional<vector<QueryItemPtr>> items;
    const FileError error = OnGetNextPage(&items);
    SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, BindOnce(std::move(callback), error, std::move(items)));
  }

  MOCK_METHOD(void,
              SetPinned,
              (const Path&, bool, OnceCallback<void(FileError)>),
              (override));

  MOCK_METHOD(void,
              SetPinnedByStableId,
              (int64_t, bool, OnceCallback<void(FileError)>),
              (override));

  MOCK_METHOD(void,
              GetMetadata,
              (const Path&, OnceCallback<void(FileError, FileMetadataPtr)>),
              (override));

  MOCK_METHOD(void,
              GetMetadataByStableId,
              (int64_t, OnceCallback<void(FileError, FileMetadataPtr)>),
              (override));

  MOCK_METHOD(void,
              SetDocsOfflineEnabled,
              (bool, OnceCallback<void(FileError, DocsOfflineEnableStatus)>),
              (override));

 private:
  std::list<mojo::Receiver<SearchQuery>> queries_;
};

class MockSpaceGetter {
 public:
  MOCK_METHOD(void, GetFreeSpace, (const Path&, PinningManager::SpaceResult));
};

class MockObserver : public PinningManager::Observer {
 public:
  MOCK_METHOD(void, OnProgress, (const Progress&), (override));
};

constexpr int kMaxQueueSize = 200;

}  // namespace

class DriveFsPinningManagerTest : public testing::Test {
 protected:
  ~DriveFsPinningManagerTest() override {
    logging::SetMinLogLevel(original_log_level_);
  }

  DriveFsPinningManagerTest() {
    logging::SetMinLogLevel(-3);
    CHECK(temp_dir_.CreateUniqueTempDir());
    profile_path_ = temp_dir_.GetPath().Append("Profile");
    gcache_dir_ = profile_path_.Append("GCache");
    mount_path_ = temp_dir_.GetPath();
  }

  void SetUp() override {
    UserDataAuthClient::InitializeFake();
    SpacedClient::InitializeFake();
    chromeos::PowerManagerClient::InitializeFake();
  }

  void TearDown() override {
    UserDataAuthClient::Shutdown();
    SpacedClient::Shutdown();
    chromeos::PowerManagerClient::Shutdown();
  }

  PinningManager::SpaceGetter GetSpaceGetter() {
    return base::BindRepeating(&MockSpaceGetter::GetFreeSpace,
                               base::Unretained(&space_getter_));
  }

  const int original_log_level_ = logging::GetMinLogLevel();
  TaskEnvironment task_environment_{TaskEnvironment::TimeSource::MOCK_TIME};
  base::ScopedTempDir temp_dir_;
  base::FilePath profile_path_;
  base::FilePath mount_path_;
  Path gcache_dir_;
  MockSpaceGetter space_getter_;
  testing::StrictMock<MockDriveFs> drivefs_;
};

// Tests ToString(Stage).
TEST_F(DriveFsPinningManagerTest, Stage) {
  std::unordered_set<std::string> labels;
  for (const Stage stage : {
           Stage::kStopped,
           Stage::kPausedOffline,
           Stage::kPausedBatterySaver,
           Stage::kGettingFreeSpace,
           Stage::kListingFiles,
           Stage::kSyncing,
           Stage::kSuccess,
           Stage::kCannotGetFreeSpace,
           Stage::kCannotListFiles,
           Stage::kNotEnoughSpace,
           Stage(-1),
           Stage(-2),
       }) {
    const std::string label = ToString(stage);
    EXPECT_NE(label, "");
    EXPECT_TRUE(labels.insert(label).second)
        << "Not unique: " << std::quoted(label);
  }
}

// Tests Progress::IsError().
TEST_F(DriveFsPinningManagerTest, IsError) {
  for (const Stage stage : {
           Stage::kCannotGetFreeSpace,
           Stage::kCannotListFiles,
           Stage::kNotEnoughSpace,
       }) {
    Progress progress;
    progress.stage = stage;
    EXPECT_TRUE(std::as_const(progress).IsError()) << " for " << stage;
  }

  for (const Stage stage : {
           Stage::kStopped,
           Stage::kPausedOffline,
           Stage::kPausedBatterySaver,
           Stage::kGettingFreeSpace,
           Stage::kListingFiles,
           Stage::kSyncing,
           Stage::kSuccess,
       }) {
    Progress progress;
    progress.stage = stage;
    EXPECT_FALSE(std::as_const(progress).IsError()) << " for " << stage;
  }
}

// Tests PinningManager::CanPin().
TEST_F(DriveFsPinningManagerTest, CanPin) {
  using Type = FileMetadata::Type;
  using CanPinStatus = FileMetadata::CanPinStatus;

  Path path("/root/poi");
  FileMetadata md;
  md.stable_id = 57;
  md.size = 1456754;
  md.can_pin = CanPinStatus::kOk;
  md.pinned = false;
  md.available_offline = false;

  // Non-empty file can be pinned.
  md.type = Type::kFile;
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // Hosted doc can't be pinned.
  md.size = 0;
  md.type = Type::kHosted;
  EXPECT_FALSE(PinningManager::CanPin(md, path));

  // Directory cannot be pinned.
  md.type = Type::kDirectory;
  EXPECT_FALSE(PinningManager::CanPin(md, path));

  // Back to pinnable case.
  md.type = Type::kFile;
  md.size = 1;
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // Zero-sized file can be pinned.
  md.size = 0;
  EXPECT_TRUE(PinningManager::CanPin(md, path));
  md.size = 1456754;
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // Unpinnable file cannot be pinned.
  md.can_pin = CanPinStatus::kDisabled;
  EXPECT_FALSE(PinningManager::CanPin(md, path));
  md.can_pin = CanPinStatus::kOk;
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // Already pinned and cached file does not need to be pinned.
  md.pinned = true;
  md.available_offline = true;
  EXPECT_FALSE(PinningManager::CanPin(md, path));

  // Already pinned file that is not cached yet should be monitored as if it was
  // just pinned.
  md.pinned = true;
  md.available_offline = false;
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // Unpinned file should be pinned even if it is already cached.
  md.pinned = false;
  md.available_offline = true;
  EXPECT_TRUE(PinningManager::CanPin(md, path));
  md.available_offline = false;
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // Trashed item shouldn't be pinned.
  md.trashed = true;
  EXPECT_FALSE(PinningManager::CanPin(md, path));
  md.trashed = false;
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // Shortcut cannot be pinned.
  md.shortcut_details = mojom::ShortcutDetails::New();
  md.shortcut_details->target_stable_id = 987;
  md.shortcut_details->target_lookup_status =
      mojom::ShortcutDetails::LookupStatus::kOk;
  EXPECT_FALSE(PinningManager::CanPin(md, path));
  md.shortcut_details.reset();
  EXPECT_TRUE(PinningManager::CanPin(md, path));

  // File that is not under /root/... can be pinned.
  path = Path("/shared/poi");
  EXPECT_TRUE(PinningManager::CanPin(md, path));
}

// Tests PinningManager::Add().
TEST_F(DriveFsPinningManagerTest, Add) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 0);
    EXPECT_EQ(progress.bytes_to_pin, 0);
    EXPECT_EQ(progress.required_space, 0);
    EXPECT_EQ(progress.skipped_items, 0);
  }

  const Id id1 = Id(101);
  const Path path1 = Path("/root/Path 1");
  const int64_t size1 = 698248964;

  const Id id2 = Id(102);
  const Path path2 = Path("/root/Path 2");
  const int64_t size2 = 78964533;

  const Id id3 = Id(103);
  const Path path3 = Path("/root/Path 3");
  const int64_t size3 = 896545;

  const Id id4 = Id(104);
  const Path path4 = Path("/root/Path 4");
  const int64_t size4 = 8645;

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  // Add an item.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id1);
    md.type = FileMetadata::Type::kFile;
    md.size = size1;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = false;
    EXPECT_TRUE(manager.Add(md, path1));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Try to add a conflicting item with the same ID, but different path and
  // size.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id1);
    md.type = FileMetadata::Type::kFile;
    md.size = size2;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = false;
    EXPECT_FALSE(manager.Add(md, path2));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size1);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_FALSE(file.pinned);
    EXPECT_TRUE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 0);
    EXPECT_EQ(progress.bytes_to_pin, size1);
    EXPECT_EQ(progress.required_space, 698249216);
    EXPECT_EQ(progress.syncing_files, 0);
    EXPECT_EQ(progress.files_to_pin, 1);
    EXPECT_EQ(progress.skipped_items, 0);
  }

  // Add a second item, but which is already pinned this time.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id2);
    md.type = FileMetadata::Type::kFile;
    md.size = size2;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = true;
    md.available_offline = false;
    EXPECT_TRUE(manager.Add(md, path2));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(2));

  {
    const auto it = manager.files_to_track_.find(id2);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id2);
    EXPECT_EQ(file.path, path2);
    EXPECT_EQ(file.total, size2);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_TRUE(file.in_progress);
    EXPECT_TRUE(file.pinned);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 0);
    EXPECT_EQ(progress.bytes_to_pin, size1 + size2);
    EXPECT_EQ(progress.required_space, 777216000);
    EXPECT_EQ(progress.syncing_files, 1);
    EXPECT_EQ(progress.files_to_pin, 2);
    EXPECT_EQ(progress.skipped_items, 0);
  }

  // Add a third item, but which is not pinned yet, although already available
  // offline.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id3);
    md.type = FileMetadata::Type::kFile;
    md.size = size3;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = true;
    EXPECT_TRUE(manager.Add(md, path3));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1, id3));
  EXPECT_THAT(manager.files_to_track_, SizeIs(3));

  {
    const auto it = manager.files_to_track_.find(id3);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id3);
    EXPECT_EQ(file.path, path3);
    EXPECT_EQ(file.total, size3);
    EXPECT_EQ(file.transferred, size3);
    EXPECT_TRUE(file.in_progress);
    EXPECT_FALSE(file.pinned);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, size3);
    EXPECT_EQ(progress.bytes_to_pin, size1 + size2 + size3);
    EXPECT_EQ(progress.required_space, 777216000);
    EXPECT_EQ(progress.syncing_files, 1);
    EXPECT_EQ(progress.files_to_pin, 3);
    EXPECT_EQ(progress.skipped_items, 0);
  }

  // Try to add a forth item, but which is both pinned and already available
  // offline. This should be skipped.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id4);
    md.type = FileMetadata::Type::kFile;
    md.size = size4;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = true;
    md.available_offline = true;
    EXPECT_FALSE(manager.Add(md, path4));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1, id3));
  EXPECT_THAT(manager.files_to_track_, SizeIs(3));

  {
    const auto it = manager.files_to_track_.find(id3);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id3);
    EXPECT_EQ(file.path, path3);
    EXPECT_EQ(file.total, size3);
    EXPECT_EQ(file.transferred, size3);
    EXPECT_TRUE(file.in_progress);
    EXPECT_FALSE(file.pinned);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, size3);
    EXPECT_EQ(progress.bytes_to_pin, size1 + size2 + size3);
    EXPECT_EQ(progress.required_space, 777216000);
    EXPECT_EQ(progress.syncing_files, 1);
    EXPECT_EQ(progress.files_to_pin, 3);
    EXPECT_EQ(progress.skipped_items, 1);
  }

  // Pretend that all the files have already been synchronised.
  EXPECT_FALSE(manager.progress_.emptied_queue);
  manager.progress_.pinned_files = 3;
  manager.progress_.pinned_bytes = size1 + size2 + size3;
  manager.progress_.files_to_pin = 0;
  manager.progress_.required_space = 0;
  manager.progress_.syncing_files = 0;
  manager.progress_.emptied_queue = true;
  manager.files_to_pin_.clear();
  manager.files_to_track_.clear();

  // Add an item.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id1);
    md.type = FileMetadata::Type::kFile;
    md.size = size1;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = false;
    EXPECT_TRUE(manager.Add(md, path1));
  }

  // The pinned files and bytes should have been reset.
  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 0);
    EXPECT_EQ(progress.bytes_to_pin, size1);
    EXPECT_EQ(progress.required_space, 698249216);
    EXPECT_EQ(progress.syncing_files, 0);
    EXPECT_EQ(progress.files_to_pin, 1);
  }
}

// Tests PinningManager::Update().
TEST_F(DriveFsPinningManagerTest, Update) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.pinned_bytes = 5000;
  manager.progress_.bytes_to_pin = 10000;
  manager.progress_.required_space = 20480;

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 5000);
    EXPECT_EQ(progress.bytes_to_pin, 10000);
    EXPECT_EQ(progress.required_space, 20480);
  }

  const Id id1 = Id(549);
  const Path path1 = Path("Path 1");
  const int64_t size1 = 2000;

  const Id id2 = Id(17);
  const Path path2 = Path("Path 2");
  const int64_t size2 = 5000;

  // Put in place a file to track.
  {
    const auto [it, ok] = manager.files_to_track_.try_emplace(
        id1, PinningManager::File{.path = path1, .total = size1});
    ASSERT_TRUE(ok);
    manager.progress_.syncing_files++;
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Try to update an unknown file.
  EXPECT_FALSE(manager.Update(id2, path2, size2, size2));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size1);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_FALSE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 5000);
    EXPECT_EQ(progress.bytes_to_pin, 10000);
    EXPECT_EQ(progress.required_space, 20480);
  }

  // These updates should not modify anything.
  EXPECT_FALSE(manager.Update(id1, path1, -1, -1));
  EXPECT_FALSE(manager.Update(id1, path1, 0, -1));
  EXPECT_FALSE(manager.Update(id1, path1, -1, size1));
  EXPECT_FALSE(manager.Update(id1, path1, 0, size1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size1);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_FALSE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 5000);
    EXPECT_EQ(progress.bytes_to_pin, 10000);
    EXPECT_EQ(progress.required_space, 20480);
  }

  // Update total size.
  EXPECT_TRUE(manager.Update(id1, path1, -1, size2));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size2);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_TRUE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 5000);
    EXPECT_EQ(progress.bytes_to_pin, 13000);
    EXPECT_EQ(progress.required_space, 24576);
  }

  // Update transferred bytes.
  EXPECT_TRUE(manager.Update(id1, path1, size1, -1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size2);
    EXPECT_EQ(file.transferred, size1);
    EXPECT_TRUE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 7000);
    EXPECT_EQ(progress.bytes_to_pin, 13000);
    EXPECT_EQ(progress.required_space, 20480);
  }

  // Update path.
  EXPECT_TRUE(manager.Update(id1, path2, -1, -1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path2);
    EXPECT_EQ(file.total, size2);
    EXPECT_EQ(file.transferred, size1);
    EXPECT_TRUE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 7000);
    EXPECT_EQ(progress.bytes_to_pin, 13000);
    EXPECT_EQ(progress.required_space, 20480);
  }

  // Progress goes backwards.
  EXPECT_TRUE(manager.Update(id1, path2, 1000, -1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path2);
    EXPECT_EQ(file.total, size2);
    EXPECT_EQ(file.transferred, 1000);
    EXPECT_TRUE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 6000);
    EXPECT_EQ(progress.bytes_to_pin, 13000);
    EXPECT_EQ(progress.required_space, 20480);
  }
}

// Tests PinningManager::Remove().
TEST_F(DriveFsPinningManagerTest, Remove) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.pinned_bytes = 5000;
  manager.progress_.bytes_to_pin = 10000;
  manager.progress_.required_space = 20480;

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 5000);
    EXPECT_EQ(progress.bytes_to_pin, 10000);
    EXPECT_EQ(progress.required_space, 20480);
  }

  const Id id1 = Id(549);
  const Path path1 = Path("Path 1");

  const Id id2 = Id(17);
  const Path path2 = Path("Path 2");

  // Put in place a file to track.
  {
    const auto [it, ok] = manager.files_to_track_.try_emplace(
        id1, PinningManager::File{.path = path1,
                                  .transferred = 1200,
                                  .total = 3000,
                                  .pinned = true,
                                  .in_progress = true});
    ASSERT_TRUE(ok);
    manager.progress_.syncing_files++;
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.syncing_files, 1);

  // Try to remove an unknown file.
  EXPECT_FALSE(manager.Remove(id2, path2));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.syncing_files, 1);

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, 3000);
    EXPECT_EQ(file.transferred, 1200);
    EXPECT_TRUE(file.in_progress);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 5000);
    EXPECT_EQ(progress.bytes_to_pin, 10000);
    EXPECT_EQ(progress.required_space, 20480);
    EXPECT_EQ(progress.syncing_files, 1);
  }

  // Remove file with default final size.
  EXPECT_TRUE(manager.Remove(id1, path2));
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 6800);
    EXPECT_EQ(progress.bytes_to_pin, 10000);
    EXPECT_EQ(progress.required_space, 20480);
    EXPECT_EQ(progress.syncing_files, 0);
  }

  // Put in place a file to track.
  {
    ASSERT_TRUE(manager.files_to_track_
                    .try_emplace(id1, PinningManager::File{.path = path1,
                                                           .transferred = 1200,
                                                           .total = 3000,
                                                           .pinned = false,
                                                           .in_progress = true})
                    .second);
    ASSERT_TRUE(manager.files_to_pin_.insert(id1).second);
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Remove file while setting size to zero.
  EXPECT_TRUE(manager.Remove(id1, path2, 0));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 5600);
    EXPECT_EQ(progress.bytes_to_pin, 7000);
    EXPECT_EQ(progress.required_space, 20480);
    EXPECT_EQ(progress.syncing_files, 0);
  }

  // Put in place a file to track.
  {
    const auto [it, ok] = manager.files_to_track_.try_emplace(
        id1, PinningManager::File{.path = path1,
                                  .transferred = 5000,
                                  .total = 6000,
                                  .pinned = true,
                                  .in_progress = true});
    ASSERT_TRUE(ok);
    manager.progress_.syncing_files++;
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Remove file while setting size to a different value that the expected one.
  EXPECT_TRUE(manager.Remove(id1, path1, 10000));
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 10600);
    EXPECT_EQ(progress.bytes_to_pin, 11000);
    EXPECT_EQ(progress.required_space, 20480);
    EXPECT_EQ(progress.syncing_files, 0);
  }
}

// Tests PinningManager::OnFileCreated().
TEST_F(DriveFsPinningManagerTest, OnFileCreated) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);

  const DriveItem item{.size = 2487};
  FileChange event;
  event.type = FileChange::Type::kCreate;
  event.stable_id = item.stable_id;
  event.path = Path("/root/Path 1");

  // Should not have any effect since the Pin manager is in kStopped stage.
  EXPECT_CALL(drivefs_, GetMetadataByStableId(_, _)).Times(0);
  manager.OnFileCreated(std::as_const(event));

  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 0);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  // Switch to kListingFiles stage.
  manager.progress_.stage = Stage::kListingFiles;
  EXPECT_CALL(drivefs_, GetMetadataByStableId(item.stable_id, _))
      .Times(1)
      .WillOnce(RunOnceCallback<1>(kFileOk, MakeMetadata(item)));
  manager.OnFileCreated(std::as_const(event));

  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(Id(item.stable_id)));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Calling OnFileCreated again with an already tracked ID should not have any
  // effect.
  EXPECT_CALL(drivefs_, GetMetadataByStableId(_, _)).Times(0);
  event.path = Path("/root/Path 2");
  manager.OnFileCreated(std::as_const(event));

  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(Id(item.stable_id)));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Events with path outside /root should be ignored.
  event.path = Path("/.files-by-id/54853");
  event.stable_id++;
  manager.OnFileCreated(std::as_const(event));

  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(Id(item.stable_id)));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Spurious events with no stable_id should be ignored (b/268419828).
  event.stable_id = 0;
  manager.OnFileCreated(std::as_const(event));

  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(Id(item.stable_id)));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  manager.progress_.stage = Stage::kStopped;
}

// Tests PinningManager::OnFileDeleted().
TEST_F(DriveFsPinningManagerTest, OnFileDeleted) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;

  const DriveItem item{.size = 2487};
  const Path path("/root/Path 1");

  FileChange event;
  event.type = FileChange::Type::kDelete;
  event.stable_id = item.stable_id;
  event.path = path;

  // Add a tracked file.
  ASSERT_TRUE(manager.Add(*MakeMetadata(item), path));
  EXPECT_THAT(manager.files_to_pin_, SizeIs(1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);

  // Notify that the file has been deleted.
  manager.OnFileDeleted(std::as_const(event));

  // The file should have been removed from the tracked files.
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 1);

  // Receiving a delete notification for an untracked file shouldn't do
  // anything.
  manager.OnFileDeleted(std::as_const(event));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 1);

  manager.Stop();
}

// Tests PinningManager::OnFilesChanged().
TEST_F(DriveFsPinningManagerTest, OnFilesChanged) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;

  int64_t id = 101;
  const Path path("/root/Path 1");

  std::vector<FileChange> events;
  {
    FileChange& event = events.emplace_back();
    event.type = FileChange::Type(-1);
    event.stable_id = id;
    event.path = path;
  }
  {
    FileChange& event = events.emplace_back();
    event.type = FileChange::Type::kCreate;
    event.stable_id = id;
    event.path = path;
  }
  {
    FileChange& event = events.emplace_back();
    event.type = FileChange::Type::kModify;
    event.stable_id = id;
    event.path = path;
  }
  {
    FileChange& event = events.emplace_back();
    event.type = FileChange::Type::kDelete;
    event.stable_id = id;
    event.path = path;
  }

  EXPECT_CALL(drivefs_, GetMetadataByStableId(id, _));
  manager.OnFilesChanged(std::as_const(events));

  manager.Stop();
}

// Tests PinningManager::OnFilePinned().
TEST_F(DriveFsPinningManagerTest, OnFilePinned) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;

  const Id id = Id(61);
  const Path path("/root/Path 1");
  const int64_t size = 2000;

  // Cannot pin an unknown file.
  manager.OnFilePinned(id, path, FileError::FILE_ERROR_NOT_FOUND);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.syncing_files, 0);
  EXPECT_EQ(manager.progress_.failed_files, 0);

  // Add a file to track.
  {
    const auto [it, ok] = manager.files_to_track_.try_emplace(
        id, PinningManager::File{.path = path, .total = size, .pinned = true});
    ASSERT_TRUE(ok);
    manager.progress_.syncing_files++;
    manager.progress_.bytes_to_pin += size;
    manager.progress_.required_space += 4096;
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Cannot pin a known file.
  manager.OnFilePinned(id, path, FileError::FILE_ERROR_NOT_FOUND);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 0);
  EXPECT_EQ(manager.progress_.syncing_files, 0);
  EXPECT_EQ(manager.progress_.failed_files, 1);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  manager.progress_.failed_files = 0;

  // Pinned an unknown file.
  manager.OnFilePinned(id, path, FileError::FILE_ERROR_OK);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 0);
  EXPECT_EQ(manager.progress_.syncing_files, 0);
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  // Add a file to track.
  const auto [it, ok] = manager.files_to_track_.try_emplace(
      id, PinningManager::File{.path = path, .total = size, .pinned = true});
  ASSERT_TRUE(ok);
  manager.progress_.syncing_files++;
  manager.progress_.bytes_to_pin += size;
  manager.progress_.required_space += 4096;

  // Pinned a known file.
  manager.OnFilePinned(id, path, FileError::FILE_ERROR_OK);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2000);
  EXPECT_EQ(manager.progress_.syncing_files, 1);
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Pinned a known file that hasn't been asked to be pinned.
  it->second.pinned = false;
  manager.files_to_pin_.insert(id);
  manager.OnFilePinned(id, path, FileError::FILE_ERROR_OK);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2000);
  EXPECT_EQ(manager.progress_.syncing_files, 1);
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());

  manager.Stop();
}

// Tests PinningManager::OnMetadataForCreatedFile().
TEST_F(DriveFsPinningManagerTest, OnMetadataForCreatedFile) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  manager.progress_.stage = Stage::kListingFiles;

  const Id id = Id(101);
  const Path path("/root/Path 1");
  const DriveItem item{.stable_id = static_cast<int64_t>(id), .size = 2487};

  // Cannot get metadata for an untracked file.
  manager.OnMetadataForCreatedFile(id, path, drive::FILE_ERROR_ACCESS_DENIED,
                                   nullptr);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  // Add a tracked file.
  ASSERT_TRUE(manager.Add(*MakeMetadata(item), path));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  // Cannot get metadata for a tracked file.
  manager.OnMetadataForCreatedFile(
      id, path, FileError::FILE_ERROR_ACCESS_DENIED, nullptr);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 1);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  // Get metadata for an untracked file.
  manager.progress_.failed_files = 0;
  manager.OnMetadataForCreatedFile(id, path, kFileOk, MakeMetadata(item));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  manager.progress_.stage = Stage::kStopped;
}

// Tests PinningManager::OnFileModified().
TEST_F(DriveFsPinningManagerTest, OnFileModified) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);

  const DriveItem item{.size = 2487};
  const Id id = Id(item.stable_id);
  const Path path1("/root/Path 1");
  FileChange event;
  event.type = FileChange::Type::kModify;
  event.stable_id = item.stable_id;
  event.path = path1;

  // Should not have any effect since this file is not tracked.
  EXPECT_CALL(drivefs_, GetMetadataByStableId(_, _)).Times(0);
  manager.OnFileModified(std::as_const(event));

  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 0);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  // Add a tracked file.
  const Path path2("/root/Path 2");
  ASSERT_TRUE(manager.Add(*MakeMetadata(item), path2));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  {
    const auto it = manager.files_to_track_.find(id);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [got_id, file] = *it;
    EXPECT_EQ(got_id, id);
    EXPECT_EQ(file.path, path2);
  }

  // Should modify the path.
  EXPECT_CALL(drivefs_, GetMetadataByStableId(event.stable_id, _))
      .Times(1)
      .WillOnce(RunOnceCallback<1>(kFileOk, MakeMetadata(item)));
  manager.OnFileModified(std::as_const(event));

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  {
    const auto it = manager.files_to_track_.find(id);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [got_id, file] = *it;
    EXPECT_EQ(got_id, id);
    EXPECT_EQ(file.path, path1);
  }
}

// Tests PinningManager::OnMetadataForModifiedFile().
TEST_F(DriveFsPinningManagerTest, OnMetadataForModifiedFile) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  manager.progress_.stage = Stage::kListingFiles;

  const Id id = Id(101);
  const Path path("/root/Path 1");
  DriveItem item{.stable_id = static_cast<int64_t>(id), .size = 2487};

  // Cannot get metadata for an untracked file.
  manager.OnMetadataForModifiedFile(id, path, drive::FILE_ERROR_ACCESS_DENIED,
                                    nullptr);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  // Add a tracked and unpinned file.
  ASSERT_TRUE(manager.Add(*MakeMetadata(item), path));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  // Cannot get metadata for a tracked file.
  manager.OnMetadataForModifiedFile(
      id, path, FileError::FILE_ERROR_ACCESS_DENIED, nullptr);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 1);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  // Get metadata for an untracked file.
  manager.progress_.failed_files = 0;
  manager.OnMetadataForModifiedFile(id, path, kFileOk, MakeMetadata(item));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  // Add a tracked file.
  ASSERT_TRUE(manager.Add(*MakeMetadata(item), path));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  {
    const auto it = manager.files_to_track_.find(id);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [got_id, file] = *it;
    EXPECT_EQ(got_id, id);
    EXPECT_EQ(file.path, path);
  }

  // Metadata indicates that the file is still not pinned.
  manager.OnMetadataForModifiedFile(id, path, kFileOk, MakeMetadata(item));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  {
    const auto it = manager.files_to_track_.find(id);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [got_id, file] = *it;
    EXPECT_EQ(got_id, id);
    EXPECT_EQ(file.path, path);
    EXPECT_FALSE(file.pinned);
  }

  // Metadata indicates that the file is pinned but not available offline.
  item.pinned = true;
  manager.OnMetadataForModifiedFile(id, path, kFileOk, MakeMetadata(item));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 2487);
  EXPECT_EQ(manager.progress_.required_space, 4096);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  // Metadata indicates that the file is pinned and available offline.
  item.available_offline = true;
  item.size = 87489;
  manager.OnMetadataForModifiedFile(id, path, kFileOk, MakeMetadata(item));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 1);
  EXPECT_EQ(manager.progress_.pinned_bytes, 87489);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 87489);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  // Reset counters.
  manager.progress_.pinned_files = 0;
  manager.progress_.pinned_bytes = 0;
  manager.progress_.bytes_to_pin = 0;

  // Add a tracked and pinned file.
  item.pinned = true;
  item.available_offline = false;
  ASSERT_TRUE(manager.Add(*MakeMetadata(item), path));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  // Metadata indicates that the file has been unexpectedly unpinned.
  item.pinned = false;
  manager.OnMetadataForModifiedFile(id, path, kFileOk, MakeMetadata(item));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_EQ(manager.progress_.failed_files, 1);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.bytes_to_pin, 0);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  manager.progress_.stage = Stage::kStopped;
}

// Tests PinningManager::OnItemProgress().
TEST_F(DriveFsPinningManagerTest, OnItemProgress) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.bytes_to_pin = 30000;
  manager.progress_.required_space = 32768;
  manager.progress_.stage = Stage::kSyncing;

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 0);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 32768);
  }

  const Id id1 = Id(549);
  const Path path1 = mount_path_.Append("Path 1");

  const Id id2 = Id(17);
  const Path path2 = mount_path_.Append("Path 2");

  // Put in place a couple of files to track.
  {
    const auto [it, ok] = manager.files_to_track_.try_emplace(
        id1, PinningManager::File{
                 .path = Path("/Path 1"), .total = 10000, .pinned = true});
    ASSERT_TRUE(ok);
    manager.progress_.syncing_files++;
  }
  {
    const auto [it, ok] = manager.files_to_track_.try_emplace(
        id2, PinningManager::File{
                 .path = Path("/Path 2"), .total = 20000, .pinned = true});
    ASSERT_TRUE(ok);
    manager.progress_.syncing_files++;
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(2));

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.syncing_files, 2);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 0);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 32768);
  }

  // Mark file 1 as queued.
  {
    ProgressEvent event;
    event.stable_id = static_cast<int64_t>(id1);
    event.file_path = path1;
    event.progress = 0;
    manager.OnItemProgress(event);
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(2));

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.syncing_files, 2);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 0);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 32768);
  }

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, Path("/Path 1"));
    EXPECT_EQ(file.total, 10000);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_TRUE(file.pinned);
    EXPECT_FALSE(file.in_progress);
  }

  // Mark file 1 as in progress.
  {
    ProgressEvent event;
    event.stable_id = static_cast<int64_t>(id1);
    event.file_path = path1;
    event.progress = 20;
    manager.OnItemProgress(event);
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(2));

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.syncing_files, 2);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 2000);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 28672);
  }

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, Path("/Path 1"));
    EXPECT_EQ(file.total, 10000);
    EXPECT_EQ(file.transferred, 2000);
    EXPECT_TRUE(file.pinned);
    EXPECT_TRUE(file.in_progress);
  }

  // Mark file 2 as in progress.
  {
    ProgressEvent event;
    event.stable_id = static_cast<int64_t>(id2);
    event.file_path = path2;
    event.progress = 50;
    manager.OnItemProgress(event);
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(2));

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.syncing_files, 2);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 0);
    EXPECT_EQ(progress.pinned_bytes, 12000);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 16384);
  }

  {
    const auto it = manager.files_to_track_.find(id2);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id2);
    EXPECT_EQ(file.path, Path("/Path 2"));
    EXPECT_EQ(file.total, 20000);
    EXPECT_EQ(file.transferred, 10000);
    EXPECT_TRUE(file.pinned);
    EXPECT_TRUE(file.in_progress);
  }

  // Mark file 1 as completed.
  {
    ProgressEvent event;
    event.stable_id = static_cast<int64_t>(id1);
    event.file_path = path1;
    event.progress = 100;
    manager.OnItemProgress(event);
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.syncing_files, 1);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 1);
    EXPECT_EQ(progress.pinned_bytes, 20000);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 8192);
  }

  {
    const auto it = manager.files_to_track_.find(id1);
    EXPECT_EQ(it, manager.files_to_track_.end());
  }

  // Paths not parented at the `mount_path_` should be ignored.
  {
    ProgressEvent event;
    event.stable_id = 329;
    event.file_path = profile_path_.Append("Path 3");
    event.progress = 0;
    manager.OnItemProgress(event);
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.syncing_files, 1);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 1);
    EXPECT_EQ(progress.pinned_bytes, 20000);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 8192);
  }

  // Untracked ids should not increment the pinned file count.
  {
    ProgressEvent event;
    event.stable_id = 458;
    event.file_path = mount_path_.Append("Path 2");
    event.progress = 100;
    manager.OnItemProgress(event);
  }

  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(progress.syncing_files, 1);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 1);
    EXPECT_EQ(progress.pinned_bytes, 20000);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 8192);
  }

  manager.Stop();

  // Events received when the PinningManager is stopped are ignored.
  {
    ProgressEvent event;
    event.stable_id = static_cast<int64_t>(id2);
    event.file_path = path2;
    event.progress = 80;
    manager.OnItemProgress(event);
  }

  {
    const Progress progress = manager.GetProgress();
    EXPECT_EQ(manager.progress_.stage, Stage::kStopped);
    EXPECT_EQ(progress.syncing_files, 0);
    EXPECT_EQ(progress.failed_files, 0);
    EXPECT_EQ(progress.pinned_files, 1);
    EXPECT_EQ(progress.pinned_bytes, 20000);
    EXPECT_EQ(progress.bytes_to_pin, 30000);
    EXPECT_EQ(progress.required_space, 8192);
  }
}

// Tests what happens when PinningManager cannot get free space during initial
// setup.
TEST_F(DriveFsPinningManagerTest, CannotGetFreeSpace1) {
  CompletionCallback completion_callback;
  RunLoop run_loop;

  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(0);
  EXPECT_CALL(drivefs_, OnGetNextPage(_)).Times(0);
  EXPECT_CALL(completion_callback, Run(Stage::kCannotGetFreeSpace))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _))
      .WillOnce(RunOnceCallback<1>(-1));

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());
  manager.SetCompletionCallback(completion_callback.Get());
  manager.Start();
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kCannotGetFreeSpace);
  EXPECT_EQ(progress.free_space, 0);
  EXPECT_EQ(progress.required_space, 0);
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
}

// Tests what happens when PinningManager cannot get free space during the
// periodic check.
TEST_F(DriveFsPinningManagerTest, CannotGetFreeSpace2) {
  CompletionCallback completion_callback;
  RunLoop run_loop;

  EXPECT_CALL(completion_callback, Run(Stage::kCannotGetFreeSpace))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _))
      .WillOnce(RunOnceCallback<1>(-1));

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());
  manager.SetCompletionCallback(completion_callback.Get());
  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;
  manager.CheckFreeSpace();
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kCannotGetFreeSpace);
  EXPECT_EQ(progress.free_space, 0);
  EXPECT_EQ(progress.required_space, 0);
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
}

TEST_F(DriveFsPinningManagerTest, CannotListFiles) {
  CompletionCallback completion_callback;
  RunLoop run_loop;

  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(1);
  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(Return(FileError::FILE_ERROR_FAILED));
  EXPECT_CALL(completion_callback, Run(Stage::kCannotListFiles))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _))
      .WillOnce(RunOnceCallback<1>(1 << 30));  // 1 GB.

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());
  manager.SetCompletionCallback(completion_callback.Get());
  manager.Start();
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kCannotListFiles);
  EXPECT_EQ(progress.free_space, 1 << 30);
  EXPECT_EQ(progress.required_space, 0);
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
}

// Tests what happens when PinningManager cannot get enough free space during
// the initial setup.
TEST_F(DriveFsPinningManagerTest, NotEnoughSpace) {
  CompletionCallback completion_callback;
  RunLoop run_loop;

  // Mock Drive search to return 3 unpinned files that total just above 512 MB.
  // The available space of 2.5 GB is not enough if you take in account the 2 GB
  // margin.
  const vector<DriveItem> items = {
      {.size = int64_t(300) << 20}, {.size = int64_t(212) << 20}, {.size = 1}};

  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(1);
  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(DoAll(PopulateSearchItems(items), Return(kFileOk)))
      .WillOnce(DoAll(PopulateNoSearchItems(), Return(kFileOk)));
  EXPECT_CALL(completion_callback, Run(Stage::kNotEnoughSpace))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _))
      .WillOnce(RunOnceCallback<1>(int64_t(2560) << 20));  // 2.5 GB.

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());
  manager.SetCompletionCallback(completion_callback.Get());
  manager.Start();
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kNotEnoughSpace);
  EXPECT_EQ(progress.free_space, int64_t(2560) << 20);
  EXPECT_EQ(progress.required_space, (int64_t(512) << 20) + (int64_t(4) << 10));
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
}

// Tests what happens when PinningManager cannot get enough free space during
// the periodic check.
TEST_F(DriveFsPinningManagerTest, NotEnoughSpace2) {
  CompletionCallback completion_callback;
  RunLoop run_loop;

  EXPECT_CALL(completion_callback, Run(Stage::kNotEnoughSpace))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _))
      .WillOnce(RunOnceCallback<1>(200 << 20));  // 200 MB

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());
  manager.SetCompletionCallback(completion_callback.Get());
  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;
  manager.CheckFreeSpace();
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kNotEnoughSpace);
  EXPECT_EQ(progress.free_space, 200 << 20);
  EXPECT_EQ(progress.required_space, 0);
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
}

// Tests what happens when PinningManager cannot get enough free space that has
// been emitted by the `LowDiskSpace` message sent via cryptohome UserDataAuth
// service.
TEST_F(DriveFsPinningManagerTest, NotEnoughSpace3) {
  CompletionCallback completion_callback;
  RunLoop run_loop;

  EXPECT_CALL(completion_callback, Run(Stage::kNotEnoughSpace))
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetCompletionCallback(completion_callback.Get());
  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;
  FakeUserDataAuthClient::Get()->NotifyLowDiskSpace(200 << 20);
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kNotEnoughSpace);
  EXPECT_EQ(progress.free_space, 200 << 20);
  EXPECT_EQ(progress.required_space, 0);
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
}

TEST_F(DriveFsPinningManagerTest, OnSpaceUpdate) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;

  SpacedClient::Observer::SpaceEvent event;
  const SpacedClient::Observer::SpaceEvent& cevent = event;

  event.set_free_space_bytes(int64_t(2) << 30);
  manager.OnSpaceUpdate(cevent);
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);
  EXPECT_EQ(manager.progress_.free_space, int64_t(2) << 30);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);

  EXPECT_FALSE(manager.spaced_client_.IsObserving());
  FakeSpacedClient::Get()->set_connected(true);

  // Transition to kNotEnoughSpace.
  event.set_free_space_bytes(int64_t(1) << 30);
  manager.OnSpaceUpdate(cevent);
  EXPECT_EQ(manager.progress_.stage, Stage::kNotEnoughSpace);
  EXPECT_EQ(manager.progress_.free_space, int64_t(1) << 30);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_TRUE(manager.spaced_client_.IsObserving());

  // Still in kNotEnoughSpace.
  event.clear_free_space_bytes();
  manager.OnSpaceUpdate(cevent);
  EXPECT_EQ(manager.progress_.stage, Stage::kNotEnoughSpace);
  EXPECT_EQ(manager.progress_.free_space, 0);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_TRUE(manager.spaced_client_.IsObserving());

  // Go back to enough space.
  event.set_free_space_bytes(int64_t(2) << 30);
  manager.OnSpaceUpdate(cevent);
  EXPECT_EQ(manager.progress_.stage, Stage::kSuccess);
  EXPECT_EQ(manager.progress_.free_space, int64_t(2) << 30);
  EXPECT_EQ(manager.progress_.required_space, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_EQ(manager.progress_.pinned_files, 0);
  EXPECT_FALSE(manager.spaced_client_.IsObserving());

  manager.progress_.stage = Stage::kStopped;
}

TEST_F(DriveFsPinningManagerTest, StartMonitoringSpace) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;
  EXPECT_FALSE(manager.spaced_client_.IsObserving());

  // If SpacedClient is not connected, then StartMonitoringSpace should fail.
  FakeSpacedClient::Get()->set_connected(false);
  EXPECT_FALSE(manager.StartMonitoringSpace());
  EXPECT_FALSE(manager.spaced_client_.IsObserving());

  // If SpacedClient is connected, then StartMonitoringSpace should succeed.
  FakeSpacedClient::Get()->set_connected(true);
  EXPECT_TRUE(manager.StartMonitoringSpace());
  EXPECT_TRUE(manager.spaced_client_.IsObserving());

  // StartMonitoringSpace called when it is already monitoring.
  EXPECT_TRUE(manager.StartMonitoringSpace());
  EXPECT_TRUE(manager.spaced_client_.IsObserving());

  // Stop monitoring.
  manager.StopMonitoringSpace();
  EXPECT_FALSE(manager.spaced_client_.IsObserving());

  // Stop monitoring when it is already stopped.
  manager.StopMonitoringSpace();
  EXPECT_FALSE(manager.spaced_client_.IsObserving());

  manager.progress_.stage = Stage::kStopped;
}

TEST_F(DriveFsPinningManagerTest, CalculateRequiredSpace) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_TRUE(manager.progress_.should_pin);

  // Pin manager not in the right stage to start calculating required space.
  manager.progress_.stage = Stage::kGettingFreeSpace;
  EXPECT_FALSE(manager.CalculateRequiredSpace());
  EXPECT_TRUE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.progress_.stage = Stage::kListingFiles;
  EXPECT_FALSE(manager.CalculateRequiredSpace());
  EXPECT_TRUE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kListingFiles);

  manager.progress_.stage = Stage::kSyncing;
  EXPECT_FALSE(manager.CalculateRequiredSpace());
  EXPECT_TRUE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);

  manager.progress_.stage = Stage::kPausedOffline;
  EXPECT_FALSE(manager.CalculateRequiredSpace());
  EXPECT_TRUE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);

  manager.progress_.stage = Stage::kPausedBatterySaver;
  EXPECT_FALSE(manager.CalculateRequiredSpace());
  EXPECT_TRUE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);

  // Pin manager already calculating required space.
  manager.progress_.should_pin = false;
  manager.progress_.stage = Stage::kGettingFreeSpace;
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.progress_.stage = Stage::kListingFiles;
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kListingFiles);

  manager.progress_.stage = Stage::kSyncing;
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);

  // Pin manager is stopped. Start calculating required space.
  manager.progress_.should_pin = true;
  manager.progress_.stage = Stage::kStopped;
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.progress_.should_pin = true;
  manager.progress_.stage = Stage::kSuccess;
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.progress_.should_pin = true;
  manager.progress_.stage = Stage::kCannotListFiles;
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.progress_.should_pin = true;
  manager.progress_.stage = Stage::kCannotGetFreeSpace;
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.progress_.should_pin = true;
  manager.progress_.stage = Stage::kCannotEnableDocsOffline;
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  EXPECT_FALSE(manager.progress_.should_pin);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.progress_.stage = Stage::kStopped;
}

TEST_F(DriveFsPinningManagerTest, JustCheckRequiredSpace) {
  base::HistogramTester histogram_tester;
  CompletionCallback completion_callback;
  RunLoop run_loop;

  // Mock Drive search to return 2 unpinned files that total to 512 MB. The
  // available space of 2.5GB is just enough if you take in account the 2 GB
  // margin.
  const vector<DriveItem> items = {{.size = 300 << 20}, {.size = 212 << 20}};

  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(1);
  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(DoAll(PopulateSearchItems(items), Return(kFileOk)))
      .WillOnce(DoAll(PopulateNoSearchItems(), Return(kFileOk)));
  EXPECT_CALL(completion_callback, Run(Stage::kSuccess))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _))
      .WillOnce(RunOnceCallback<1>(int64_t(2560) << 20));  // 2.5 GB.

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());
  manager.SetCompletionCallback(completion_callback.Get());
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kSuccess);
  EXPECT_EQ(progress.free_space, int64_t(2560) << 20);
  EXPECT_EQ(progress.required_space, 512 << 20);
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
  histogram_tester.ExpectUniqueTimeSample(
      "FileBrowser.GoogleDrive.BulkPinning.TimeSpentListing",
      progress.time_spent_listing_items, 1);
}

TEST_F(DriveFsPinningManagerTest, WhenMoreResultsReturnedNextPageIsAttempted) {
  CompletionCallback completion_callback;
  RunLoop run_loop;

  const vector<DriveItem> items_1 = {{.size = 100 << 20}, {.size = 100 << 20}};
  const vector<DriveItem> items_2 = {{.size = 100 << 20}, {.size = 100 << 20}};

  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(1);
  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(DoAll(PopulateSearchItems(items_1), Return(kFileOk)))
      .WillOnce(DoAll(PopulateNoSearchItems(),
                      Return(FileError::FILE_ERROR_OK_WITH_MORE_RESULTS)))
      .WillOnce(DoAll(PopulateSearchItems(items_2), Return(kFileOk)))
      .WillOnce(DoAll(PopulateNoSearchItems(), Return(kFileOk)));
  EXPECT_CALL(completion_callback, Run(Stage::kSuccess))
      .WillOnce(RunClosure(run_loop.QuitClosure()));
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _))
      .WillOnce(RunOnceCallback<1>(int64_t(2560) << 20));  // 2.5 GB.

  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());
  manager.SetCompletionCallback(completion_callback.Get());
  EXPECT_TRUE(manager.CalculateRequiredSpace());
  run_loop.Run();

  const Progress progress = manager.GetProgress();
  EXPECT_EQ(progress.stage, Stage::kSuccess);
  EXPECT_EQ(progress.free_space, int64_t(2560) << 20);
  EXPECT_EQ(progress.required_space, 400 << 20);
  EXPECT_EQ(progress.pinned_bytes, 0);
  EXPECT_EQ(progress.pinned_files, 0);
}

// Tests PinningManager::SetOnline() and BatterySaverModeStateChanged().
TEST_F(DriveFsPinningManagerTest, SetOnlineAndBatteryOk) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.SetSpaceGetter(GetSpaceGetter());

  auto set_online = [&](bool online) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
    manager.SetOnline(online);
    EXPECT_EQ(manager.is_online_, online);
  };

  auto set_battery_ok = [&](bool ok) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
    power_manager::BatterySaverModeState state;
    state.set_enabled(!ok);
    manager.BatterySaverModeStateChanged(state);
    EXPECT_EQ(manager.is_battery_ok_, ok);
  };

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);
  EXPECT_TRUE(manager.is_online_);
  EXPECT_TRUE(manager.is_battery_ok_);

  // Online or battery change before starting should remain in Stopped state.
  set_online(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);
  set_online(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);
  set_battery_ok(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);
  set_battery_ok(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);

  // Start when not online should go to PausedOffline.
  set_online(false);
  set_battery_ok(true);
  manager.Start();
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);
  manager.Stop();
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);

  // Start when battery saver should go to PausedBatterySaver.
  set_online(true);
  set_battery_ok(false);
  manager.Start();
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);
  manager.Stop();
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);

  // Start should go to state GettingFreeSpace.
  set_online(true);
  set_battery_ok(true);
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  manager.Start();
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  // Once started, going offline should toggle pause.
  set_online(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);
  set_online(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  set_online(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  // Once started, battery saver should toggle pause.
  set_battery_ok(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);
  set_battery_ok(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  set_battery_ok(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  // When both offline and battery saver are set, we should remain in the
  // current Paused state. Resolving one of them should leave us in correct
  // Paused state.
  set_online(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);
  set_battery_ok(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);
  set_battery_ok(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);
  set_battery_ok(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);
  set_online(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  set_battery_ok(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);
  set_battery_ok(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);
  set_online(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);
  set_online(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);
  set_online(false);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedBatterySaver);
  set_battery_ok(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kPausedOffline);
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  set_online(true);
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  // Stop should go to state Stopped.
  manager.Stop();
  EXPECT_EQ(manager.progress_.stage, Stage::kStopped);
}

// Tests PinningManager::HandleQueryItem().
TEST_F(DriveFsPinningManagerTest, HandleQueryItem) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kListingFiles;
  manager.progress_.max_active_queries = 2;
  manager.progress_.active_queries = 2;
  manager.progress_.total_queries = 2;

  const auto reset = [&manager, saved_progress = manager.progress_]() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
    manager.progress_ = saved_progress;
    manager.listed_items_.clear();
    manager.files_to_pin_.clear();
    manager.files_to_track_.clear();
  };

  const Id dir_id = Id(101);
  const Path dir_path("/root/Folder");
  const Path absolute_dir_path(mount_path_.Append("root/Folder"));
  QueryItem item;
  item.path = Path("/root/Folder/Item");
  item.metadata = FileMetadata::New();
  FileMetadata& md = *item.metadata;
  md.stable_id = 102;
  md.size = 0;
  md.pinned = false;
  md.available_offline = false;
  md.capabilities = mojom::Capabilities::New();

  // Unexpected item type.
  md.type = FileMetadata::Type(-1);

  EXPECT_EQ(manager.progress_.skipped_items, 0);
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));
  reset();

  // Broken shortcuts.
  using LookupStatus = mojom::ShortcutDetails::LookupStatus;
  md.shortcut_details = mojom::ShortcutDetails::New();
  md.type = FileMetadata::Type::kFile;
  md.shortcut_details->target_stable_id = 201;
  md.shortcut_details->target_lookup_status = LookupStatus::kUnknown;
  md.stable_id++;

  EXPECT_EQ(manager.progress_.listed_shortcuts, 0);
  EXPECT_EQ(manager.progress_.broken_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);

  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 1);
  EXPECT_EQ(manager.progress_.broken_shortcuts, 1);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(0));
  reset();

  md.shortcut_details->target_lookup_status = LookupStatus::kPermissionDenied;
  md.shortcut_details->target_stable_id++;
  md.stable_id++;
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 1);
  EXPECT_EQ(manager.progress_.broken_shortcuts, 1);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(0));
  reset();

  md.shortcut_details->target_lookup_status = LookupStatus::kNotFound;
  md.shortcut_details->target_stable_id++;
  md.stable_id++;
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 1);
  EXPECT_EQ(manager.progress_.broken_shortcuts, 1);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(0));
  reset();

  // Shortcut to trashed item.
  md.shortcut_details->target_lookup_status = LookupStatus::kOk;
  md.shortcut_details->target_stable_id++;
  md.trashed = true;
  md.stable_id++;
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 1);
  EXPECT_EQ(manager.progress_.broken_shortcuts, 1);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(0));
  reset();
  md.trashed = false;

  // Valid shortcut to file.
  const Id target_id = Id(++md.shortcut_details->target_stable_id);
  const Id stable_id = Id(++md.stable_id);
  ASSERT_NE(target_id, stable_id);
  md.shortcut_details->target_lookup_status = LookupStatus::kOk;
  md.type = FileMetadata::Type::kFile;
  md.size = 10000;
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_FALSE(md.shortcut_details);
  EXPECT_EQ(Id(md.stable_id), target_id);
  EXPECT_EQ(manager.progress_.skipped_items, 0);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 1);
  EXPECT_EQ(manager.progress_.broken_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 1);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));
  EXPECT_THAT(manager.listed_items_,
              UnorderedElementsAre<PinningManager::ListedItems::value_type>(
                  {target_id, dir_id}));
  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(target_id));
  reset();

  // Shortcut to hosted doc, that gets skipped.
  md.shortcut_details = mojom::ShortcutDetails::New();
  md.shortcut_details->target_lookup_status = LookupStatus::kOk;
  md.shortcut_details->target_stable_id = static_cast<int64_t>(target_id);
  md.stable_id = static_cast<int64_t>(stable_id);
  md.type = FileMetadata::Type::kHosted;
  md.size = 0;
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_FALSE(md.shortcut_details);
  EXPECT_EQ(Id(md.stable_id), target_id);
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 1);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 1);
  EXPECT_EQ(manager.progress_.pinned_bytes, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));
  EXPECT_THAT(manager.listed_items_,
              UnorderedElementsAre<PinningManager::ListedItems::value_type>(
                  {target_id, dir_id}));
  EXPECT_THAT(manager.files_to_pin_, SizeIs(0));
  reset();

  // Valid shortcut to directory to directory outside My drive.
  md.shortcut_details = mojom::ShortcutDetails::New();
  md.shortcut_details->target_lookup_status = LookupStatus::kOk;
  md.shortcut_details->target_stable_id = static_cast<int64_t>(target_id);
  md.shortcut_details->target_path =
      temp_dir_.GetPath().Append(".shortcuts-by-id/target_dir");
  md.stable_id = static_cast<int64_t>(stable_id);
  md.type = FileMetadata::Type::kDirectory;
  item.path = Path("/root/Folder");
  md.size = 0;
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 1);
  EXPECT_EQ(manager.progress_.broken_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(0));
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_TRUE(md.shortcut_details);
  EXPECT_NE(Id(md.stable_id), target_id);
  reset();

  // Unexpected path
  md.shortcut_details.reset();
  md.type = FileMetadata::Type::kFile;
  md.stable_id = static_cast<int64_t>(stable_id);
  item.path = Path("/root/Unexpected");

  EXPECT_EQ(manager.progress_.files_to_pin, 0);
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 0);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 1);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_EQ(manager.progress_.files_to_pin, 1);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));
  reset();

  // File
  md.stable_id++;
  md.type = FileMetadata::Type::kFile;
  item.path = Path("/root/Folder/File");

  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 0);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 1);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_EQ(manager.progress_.files_to_pin, 1);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));
  reset();

  // Hosted doc
  md.stable_id++;
  md.type = FileMetadata::Type::kHosted;
  item.path = Path("/root/Folder/Doc");

  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 0);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 1);
  EXPECT_EQ(manager.progress_.files_to_pin, 0);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));
  reset();

  // Directory
  md.stable_id++;
  md.type = FileMetadata::Type::kDirectory;
  item.path = Path("/root/Folder/Dir");

  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(1);
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 0);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 1);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_EQ(manager.progress_.files_to_pin, 0);
  EXPECT_EQ(manager.progress_.max_active_queries, 3);
  EXPECT_EQ(manager.progress_.active_queries, 3);
  EXPECT_EQ(manager.progress_.total_queries, 3);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));

  // Already visited directory
  manager.HandleQueryItem(dir_id, dir_path, std::as_const(item));
  EXPECT_EQ(manager.progress_.skipped_items, 1);
  EXPECT_EQ(manager.progress_.listed_shortcuts, 0);
  EXPECT_EQ(manager.progress_.listed_dirs, 1);
  EXPECT_EQ(manager.progress_.listed_files, 0);
  EXPECT_EQ(manager.progress_.listed_docs, 0);
  EXPECT_EQ(manager.progress_.files_to_pin, 0);
  EXPECT_EQ(manager.progress_.max_active_queries, 3);
  EXPECT_EQ(manager.progress_.active_queries, 3);
  EXPECT_EQ(manager.progress_.total_queries, 3);
  EXPECT_THAT(manager.listed_items_, SizeIs(1));

  manager.Stop();
}

// Tests PinningManager::OnNextPage() when a query is dropped before the
// response is received.
TEST_F(DriveFsPinningManagerTest, DropQuery) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kListingFiles;
  manager.progress_.max_active_queries = 2;
  manager.progress_.active_queries = 2;

  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(
          Invoke([&manager](std::optional<vector<QueryItemPtr>>* const items) {
            manager.Stop();
            *items = {};
            return FileError::FILE_ERROR_OK;
          }));

  PinningManager::Query query;
  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(1);
  drivefs_.StartSearchQuery(query.BindNewPipeAndPassReceiver(),
                            QueryParameters::New());

  manager.GetNextPage(Id(101), Path("/root/My Folder"), std::move(query));
  task_environment_.RunUntilIdle();
}

// Tests PinningManager::OnSearchResult() when a query finishes and there are
// still other active queries.
TEST_F(DriveFsPinningManagerTest, OnSearchResult) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kListingFiles;
  manager.progress_.max_active_queries = 2;
  manager.progress_.active_queries = 2;

  manager.OnSearchResult(Id(101), Path("/root/My Folder"),
                         PinningManager::Query(), FileError::FILE_ERROR_OK, {});

  EXPECT_EQ(manager.progress_.stage, Stage::kListingFiles);
  EXPECT_EQ(manager.progress_.max_active_queries, 2);
  EXPECT_EQ(manager.progress_.active_queries, 1);

  manager.OnSearchResult(Id(100), Path("/root"), PinningManager::Query(),
                         FileError::FILE_ERROR_OK, {});

  EXPECT_EQ(manager.progress_.stage, Stage::kNotEnoughSpace);
  EXPECT_EQ(manager.progress_.max_active_queries, 2);
  EXPECT_EQ(manager.progress_.active_queries, 0);

  manager.Stop();
}

// Tests PinningManager::OnSearchResult() with transient errors.
TEST_F(DriveFsPinningManagerTest, OnTransientError) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kListingFiles;

  EXPECT_CALL(drivefs_, OnStartSearchQuery(_)).Times(1);
  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(Return(FileError::FILE_ERROR_NO_CONNECTION));
  manager.ListItems(Id::kNone, Path("/root"));
  EXPECT_EQ(manager.progress_.stage, Stage::kListingFiles);

  task_environment_.FastForwardBy(Seconds(4));
  EXPECT_EQ(manager.progress_.stage, Stage::kListingFiles);
  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(Return(FileError::FILE_ERROR_SERVICE_UNAVAILABLE));
  task_environment_.FastForwardBy(Seconds(1));
  EXPECT_EQ(manager.progress_.stage, Stage::kListingFiles);

  task_environment_.FastForwardBy(Seconds(4));
  EXPECT_EQ(manager.progress_.stage, Stage::kListingFiles);
  EXPECT_CALL(drivefs_, OnGetNextPage(_))
      .WillOnce(Return(FileError::FILE_ERROR_NO_MEMORY));
  task_environment_.FastForwardBy(Seconds(1));

  EXPECT_EQ(manager.progress_.stage, Stage::kCannotListFiles);
}

// Tests PinningManager::OnError().
TEST_F(DriveFsPinningManagerTest, OnError) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;

  using mojom::DriveError;
  DriveError error;
  error.stable_id = 214;
  error.path = Path("My Path");

  using Type = DriveError::Type;

  // These error types should just be logged.
  for (const Type type : {
           Type::kCantUploadStorageFull,
           Type::kCantUploadStorageFullOrganization,
           Type::kCantUploadSharedDriveStorageFull,
           Type(-1),

       }) {
    error.type = type;
    manager.OnError(std::as_const(error));
    EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);
  }

  // Error of type kPinningFailedDiskFull should stop the pin manager.
  error.type = Type::kPinningFailedDiskFull;
  manager.OnError(std::as_const(error));
  EXPECT_EQ(manager.progress_.stage, Stage::kNotEnoughSpace);

  // Stopping the pin manager should retain the existing error stage.
  manager.Stop();
  EXPECT_EQ(manager.progress_.stage, Stage::kNotEnoughSpace);

  // Error of type kPinningFailedDiskFull should not have any effect if the pin
  // manager is already stopped.
  manager.OnError(std::as_const(error));
  EXPECT_EQ(manager.progress_.stage, Stage::kNotEnoughSpace);
}

// Tests that calling PinningManager::Start() when the PinningManager is already
// in progress does not have any effect.
TEST_F(DriveFsPinningManagerTest, StartWhenInProgress) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kGettingFreeSpace;
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.Start();
  EXPECT_EQ(manager.progress_.stage, Stage::kGettingFreeSpace);

  manager.Stop();
}

// Tests PinningManager::StartPinning().
TEST_F(DriveFsPinningManagerTest, StartPinning) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kListingFiles;
  DCHECK_EQ(manager.progress_.free_space, 0);

  manager.StartPinning();
  EXPECT_EQ(manager.progress_.stage, Stage::kNotEnoughSpace);

  manager.progress_.stage = Stage::kListingFiles;
  manager.progress_.free_space = int64_t(4) << 30;  // 4 GB
  manager.progress_.should_pin = false;

  EXPECT_CALL(drivefs_, SetDocsOfflineEnabled(true, _))
      .Times(1)
      .WillOnce(RunOnceCallback<1>(drive::FILE_ERROR_OK,
                                   DocsOfflineEnableStatus::kSuccess));
  manager.StartPinning();
  EXPECT_EQ(manager.progress_.stage, Stage::kSuccess);

  manager.progress_.stage = Stage::kListingFiles;
  manager.progress_.should_pin = true;

  const Id id1 = Id(101);
  const Path path1 = Path("/root/Path 1");
  const int64_t size1 = 6248964;

  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  // Add an item.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id1);
    md.type = FileMetadata::Type::kFile;
    md.size = size1;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = false;
    EXPECT_TRUE(manager.Add(md, path1));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  manager.SetSpaceGetter(GetSpaceGetter());
  EXPECT_CALL(space_getter_, GetFreeSpace(gcache_dir_, _)).Times(1);
  EXPECT_CALL(drivefs_, SetPinnedByStableId(static_cast<int64_t>(id1), true, _))
      .Times(1);
  manager.StartPinning();
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);

  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size1);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_TRUE(file.pinned);
    EXPECT_TRUE(file.in_progress);
  }

  task_environment_.FastForwardBy(Seconds(19));
  EXPECT_CALL(drivefs_, GetMetadataByStableId(static_cast<int64_t>(id1), _))
      .Times(1);
  task_environment_.FastForwardBy(Seconds(1));

  manager.Stop();
}

// Tests PinningManager::PinSomeFiles().
TEST_F(DriveFsPinningManagerTest, PinSomeFiles) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kListingFiles;
  DCHECK_EQ(manager.progress_.free_space, 0);

  manager.progress_.stage = Stage::kSyncing;
  manager.progress_.free_space = 1 << 30;  // 1 GB

  const Id id1 = Id(101);
  const Path path1 = Path("/root/Path 1");
  const int64_t size1 = 6248964;

  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  // Add an item.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id1);
    md.type = FileMetadata::Type::kFile;
    md.size = size1;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = false;
    EXPECT_TRUE(manager.Add(md, path1));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id1));
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.syncing_files, 0);

  EXPECT_CALL(drivefs_, SetPinnedByStableId(static_cast<int64_t>(id1), true, _))
      .Times(1);
  manager.PinSomeFiles();
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);

  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, SizeIs(1));
  EXPECT_EQ(manager.progress_.syncing_files, 1);

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size1);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_TRUE(file.pinned);
    EXPECT_TRUE(file.in_progress);
  }

  // Add manager.queue_size_ + 20 items to pin.
  for (int i = 0; i < manager.queue_size_ + 20; ++i) {
    FileMetadata md;
    md.stable_id = 200 + i;
    md.type = FileMetadata::Type::kFile;
    md.size = 1000 + 10 * i;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = false;
    EXPECT_TRUE(
        manager.Add(md, Path(base::StringPrintf("/root/Path %02d", i))));
  }

  EXPECT_THAT(manager.files_to_pin_, SizeIs(manager.queue_size_ + 20));
  EXPECT_THAT(manager.files_to_track_, SizeIs(manager.queue_size_ + 21));
  EXPECT_EQ(manager.progress_.syncing_files, 1);

  EXPECT_CALL(drivefs_, SetPinnedByStableId(_, true, _))
      .Times(manager.queue_size_ - 1);
  manager.PinSomeFiles();
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);
  EXPECT_THAT(manager.files_to_pin_, SizeIs(21));
  EXPECT_THAT(manager.files_to_track_, SizeIs(manager.queue_size_ + 21));
  EXPECT_EQ(manager.progress_.syncing_files, manager.queue_size_);

  // Remove 30 files from the set of files to track.
  {
    std::vector<Id> pinned_ids;
    pinned_ids.reserve(manager.files_to_track_.size());
    for (const auto& [id, file] : manager.files_to_track_) {
      if (file.pinned) {
        pinned_ids.push_back(id);
      }
    }

    EXPECT_THAT(pinned_ids, SizeIs(manager.queue_size_));
    pinned_ids.resize(30);
    for (const Id id : pinned_ids) {
      manager.Remove(id, Path(), 0);
    }
  }

  EXPECT_THAT(manager.files_to_pin_, SizeIs(21));
  EXPECT_THAT(manager.files_to_track_, SizeIs(manager.queue_size_ - 9));
  EXPECT_EQ(manager.progress_.syncing_files, manager.queue_size_ - 30);

  EXPECT_CALL(drivefs_, SetPinnedByStableId(_, true, _)).Times(21);
  manager.PinSomeFiles();
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, SizeIs(manager.queue_size_ - 9));
  EXPECT_EQ(manager.progress_.syncing_files, manager.queue_size_ - 9);

  manager.files_to_track_.clear();
  manager.progress_.syncing_files = 0;
  EXPECT_FALSE(manager.progress_.emptied_queue);
  manager.PinSomeFiles();
  EXPECT_EQ(manager.progress_.stage, Stage::kSyncing);
  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());
  EXPECT_TRUE(manager.progress_.emptied_queue);

  manager.progress_.emptied_queue = false;
  task_environment_.FastForwardBy(Seconds(4));
  manager.PinSomeFiles();
  EXPECT_TRUE(manager.progress_.emptied_queue);

  manager.progress_.emptied_queue = false;
  task_environment_.FastForwardBy(Seconds(400));
  manager.PinSomeFiles();
  EXPECT_TRUE(manager.progress_.emptied_queue);

  manager.progress_.emptied_queue = false;
  task_environment_.FastForwardBy(Seconds(4000));
  manager.PinSomeFiles();
  EXPECT_TRUE(manager.progress_.emptied_queue);

  manager.Stop();
}

// Tests PinningManager::CheckStalledFiles().
TEST_F(DriveFsPinningManagerTest, CheckStalledFiles) {
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  manager.progress_.stage = Stage::kSyncing;
  manager.progress_.free_space = 1 << 30;  // 1 GB

  const Id id1 = Id(101);
  const Path path1 = Path("/root/Path 1");
  const int64_t size1 = 6248964;

  const Id id2 = Id(102);
  const Path path2 = Path("/root/Path 2");
  const int64_t size2 = 257835;

  EXPECT_THAT(manager.files_to_pin_, IsEmpty());
  EXPECT_THAT(manager.files_to_track_, IsEmpty());

  // Add an item.
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id1);
    md.type = FileMetadata::Type::kFile;
    md.size = size1;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = true;
    md.available_offline = false;
    EXPECT_TRUE(manager.Add(md, path1));
  }
  {
    FileMetadata md;
    md.stable_id = static_cast<int64_t>(id2);
    md.type = FileMetadata::Type::kFile;
    md.size = size2;
    md.can_pin = FileMetadata::CanPinStatus::kOk;
    md.pinned = false;
    md.available_offline = false;
    EXPECT_TRUE(manager.Add(md, path2));
  }

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id2));
  EXPECT_THAT(manager.files_to_track_, SizeIs(2));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_EQ(file.path, path1);
    EXPECT_EQ(file.total, size1);
    EXPECT_EQ(file.transferred, 0);
    EXPECT_TRUE(file.pinned);
    EXPECT_TRUE(file.in_progress);
  }

  manager.CheckStalledFiles();

  EXPECT_THAT(manager.files_to_pin_, UnorderedElementsAre(id2));
  EXPECT_THAT(manager.files_to_track_, SizeIs(2));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_FALSE(file.in_progress);
  }

  task_environment_.FastForwardBy(Seconds(9));
  EXPECT_CALL(drivefs_, GetMetadataByStableId(static_cast<int64_t>(id1), _))
      .Times(1);
  task_environment_.FastForwardBy(Seconds(1));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_FALSE(file.in_progress);
    file.in_progress = true;
  }

  task_environment_.FastForwardBy(Seconds(19));

  {
    const auto it = manager.files_to_track_.find(id1);
    ASSERT_NE(it, manager.files_to_track_.end());
    const auto& [id, file] = *it;
    EXPECT_EQ(id, id1);
    EXPECT_FALSE(file.in_progress);
  }

  EXPECT_CALL(drivefs_, GetMetadataByStableId(static_cast<int64_t>(id1), _))
      .Times(10);
  task_environment_.FastForwardBy(Seconds(100));

  manager.Stop();
}

// Tests PinningManager::NotifyProgress.
TEST_F(DriveFsPinningManagerTest, NotifyProgress) {
  MockObserver observer;
  PinningManager::Observer observer2;
  PinningManager manager(profile_path_, mount_path_, &drivefs_, kMaxQueueSize);
  manager.AddObserver(&observer);
  manager.AddObserver(&observer2);

  DCHECK_CALLED_ON_VALID_SEQUENCE(manager.sequence_checker_);
  EXPECT_CALL(observer, OnProgress(testing::Ref(manager.progress_))).Times(1);
  manager.NotifyProgress();
  manager.RemoveObserver(&observer);
}

TEST_F(DriveFsPinningManagerTest, IsUntrackedPath) {}

}  // namespace drivefs::pinning