chromium/chrome/browser/ash/drive/drive_integration_service_browsertest.cc

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

#include "chrome/browser/ash/drive/drive_integration_service.h"

#include <memory>
#include <optional>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/browser/ash/drive/drive_integration_service_browser_test_base.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chromeos/ash/components/drivefs/drivefs_search_query.h"
#include "chromeos/ash/components/drivefs/fake_drivefs.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "chromeos/ash/components/standalone_browser/feature_refs.h"
#include "chromeos/components/drivefs/mojom/drivefs_native_messaging.mojom.h"
#include "chromeos/crosapi/mojom/drive_integration_service.mojom.h"
#include "components/drive/drive_pref_names.h"
#include "components/drive/file_errors.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/pref_test_utils.h"
#include "content/public/test/browser_test.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace drive {

using ::base::test::RunClosure;
using ::base::test::RunOnceCallback;
using ::base::test::TestFuture;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::FieldsAre;
using ::testing::Optional;
using ::testing::Pointee;
using ::testing::Property;

using DriveIntegrationServiceBrowserTest =
    DriveIntegrationServiceBrowserTestBase;

// Verify DriveIntegrationService is created during login.
IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest, CreatedDuringLogin) {
  EXPECT_TRUE(
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile()));
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
                       ClearCacheAndRemountFileSystem) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());
  base::FilePath cache_path = drive_service->GetDriveFsHost()->GetDataPath();
  base::FilePath log_folder_path = drive_service->GetDriveFsLogPath().DirName();
  base::FilePath cache_file;
  base::FilePath log_file;

  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    ASSERT_TRUE(base::CreateDirectory(cache_path));
    ASSERT_TRUE(base::CreateDirectory(log_folder_path));
    ASSERT_TRUE(base::CreateTemporaryFileInDir(cache_path, &cache_file));
    ASSERT_TRUE(base::CreateTemporaryFileInDir(log_folder_path, &log_file));
  }

  base::RunLoop run_loop;
  auto quit_closure = run_loop.QuitClosure();
  drive_service->ClearCacheAndRemountFileSystem(
      base::BindLambdaForTesting([=](bool success) {
        base::ScopedAllowBlockingForTesting allow_blocking;
        EXPECT_TRUE(success);
        EXPECT_FALSE(base::PathExists(cache_file));
        EXPECT_TRUE(base::PathExists(log_file));
        quit_closure.Run();
      }));

  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
                       DisableDrivePolicyTest) {
  // First make sure the pref is set to its default value which should permit
  // drive.
  browser()->profile()->GetPrefs()->SetBoolean(prefs::kDisableDrive, false);

  drive::DriveIntegrationService* integration_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(
          browser()->profile());

  EXPECT_TRUE(integration_service);
  EXPECT_TRUE(integration_service->is_enabled());

  // ...next try to disable drive.
  browser()->profile()->GetPrefs()->SetBoolean(prefs::kDisableDrive, true);

  EXPECT_EQ(integration_service,
            drive::DriveIntegrationServiceFactory::FindForProfile(
                browser()->profile()));
  EXPECT_FALSE(integration_service->is_enabled());
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
                       SearchDriveByFileNameTest) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  drive::DriveIntegrationService* drive_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(
          browser()->profile());

  base::FilePath mount_path = drive_service->GetMountPointPath();
  ASSERT_TRUE(base::WriteFile(mount_path.Append("bar"), ""));
  ASSERT_TRUE(base::WriteFile(mount_path.Append("baz"), ""));
  auto base_time = base::Time::Now() - base::Seconds(10);
  auto earlier_time = base_time - base::Seconds(10);
  ASSERT_TRUE(base::TouchFile(mount_path.Append("bar"), base_time, base_time));
  ASSERT_TRUE(
      base::TouchFile(mount_path.Append("baz"), earlier_time, earlier_time));

  base::RunLoop run_loop;
  auto quit_closure = run_loop.QuitClosure();
  drive_service->SearchDriveByFileName(
      "ba", 10, drivefs::mojom::QueryParameters::SortField::kLastViewedByMe,
      drivefs::mojom::QueryParameters::SortDirection::kAscending,
      drivefs::mojom::QueryParameters::QuerySource::kLocalOnly,
      base::BindLambdaForTesting(
          [=](FileError error,
              std::optional<std::vector<drivefs::mojom::QueryItemPtr>> items) {
            EXPECT_THAT(
                items,
                Optional(ElementsAre(
                    Pointee(Field(
                        "path", &drivefs::mojom::QueryItem::path,
                        Property(
                            "BaseName", &base::FilePath::BaseName,
                            Property("value", &base::FilePath::value, "baz")))),
                    Pointee(
                        Field("path", &drivefs::mojom::QueryItem::path,
                              Property("BaseName", &base::FilePath::BaseName,
                                       Property("value", &base::FilePath::value,
                                                "bar")))))));
            quit_closure.Run();
          }));
  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
                       CreateSearchQueryByFileNameTest) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  drive::DriveIntegrationService* drive_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(
          browser()->profile());

  base::FilePath mount_path = drive_service->GetMountPointPath();
  ASSERT_TRUE(base::WriteFile(mount_path.Append("bar"), ""));
  ASSERT_TRUE(base::WriteFile(mount_path.Append("baz"), ""));
  base::Time base_time = base::Time::Now() - base::Seconds(10);
  base::Time earlier_time = base_time - base::Seconds(10);
  ASSERT_TRUE(base::TouchFile(mount_path.Append("bar"), base_time, base_time));
  ASSERT_TRUE(
      base::TouchFile(mount_path.Append("baz"), earlier_time, earlier_time));

  std::unique_ptr<drivefs::DriveFsSearchQuery> query =
      drive_service->CreateSearchQueryByFileName(
          "ba", 10, drivefs::mojom::QueryParameters::SortField::kLastViewedByMe,
          drivefs::mojom::QueryParameters::SortDirection::kAscending,
          drivefs::mojom::QueryParameters::QuerySource::kLocalOnly);
  ASSERT_TRUE(query);

  base::test::TestFuture<
      FileError, std::optional<std::vector<drivefs::mojom::QueryItemPtr>>>
      future;
  query->GetNextPage(future.GetCallback());

  EXPECT_THAT(
      future.Take(),
      FieldsAre(
          FILE_ERROR_OK,
          Optional(ElementsAre(
              Pointee(Field(
                  "path", &drivefs::mojom::QueryItem::path,
                  Property("BaseName", &base::FilePath::BaseName,
                           Property("value", &base::FilePath::value, "baz")))),
              Pointee(Field("path", &drivefs::mojom::QueryItem::path,
                            Property("BaseName", &base::FilePath::BaseName,
                                     Property("value", &base::FilePath::value,
                                              "bar"))))))));
  // TODO: b/277018122 - Fix repeated `GetNextPage` calls in
  // `drivefs::FakeDriveFs` and call `GetNextPage` again here with a smaller
  // page size.
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest, GetThumbnailTest) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  drive::DriveIntegrationService* drive_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(
          browser()->profile());

  base::FilePath mount_path = drive_service->GetMountPointPath();
  ASSERT_TRUE(base::WriteFile(mount_path.Append("bar"), ""));

  base::RunLoop run_loop;
  auto quit_closure = run_loop.QuitClosure();
  drive_service->GetThumbnail(
      base::FilePath("/bar"), true,
      base::BindLambdaForTesting(
          [=](const std::optional<std::vector<uint8_t>>& image) {
            ASSERT_FALSE(image.has_value());
            quit_closure.Run();
          }));
  run_loop.Run();
}

class DriveIntegrationServiceWithGaiaDisabledBrowserTest
    : public DriveIntegrationServiceBrowserTest {
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(ash::switches::kDisableGaiaServices);
  }
};

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceWithGaiaDisabledBrowserTest,
                       DriveDisabled) {
  // First make sure the pref is set to its default value which would normally
  // permit drive.
  browser()->profile()->GetPrefs()->SetBoolean(prefs::kDisableDrive, false);

  drive::DriveIntegrationService* integration_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(
          browser()->profile());

  ASSERT_TRUE(integration_service);
  EXPECT_FALSE(integration_service->is_enabled());
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest, GetMetadata) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  base::FilePath mount_path = drive_service->GetMountPointPath();
  base::FilePath file_path;
  base::CreateTemporaryFileInDir(mount_path, &file_path);

  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->GetMetadata(
        base::FilePath("/foo/bar"),
        base::BindLambdaForTesting(
            [=](FileError error, drivefs::mojom::FileMetadataPtr metadata_ptr) {
              EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
              quit_closure.Run();
            }));
    run_loop.Run();
  }

  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->GetMetadata(
        file_path,
        base::BindLambdaForTesting(
            [=](FileError error, drivefs::mojom::FileMetadataPtr metadata_ptr) {
              EXPECT_EQ(FILE_ERROR_OK, error);
              quit_closure.Run();
            }));
    run_loop.Run();
  }
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
                       LocateFilesByItemIds) {
  Profile* profile = browser()->profile();
  InitTestFileMountRoot(profile);
  AddDriveFileWithRelativePath(profile, /*drive_file_id=*/"abc123",
                               /*directory_path=*/base::FilePath(""),
                               /*new_file_relative_path=*/nullptr,
                               /*new_file_absolute_path=*/nullptr);
  base::FilePath relative_file_path;
  AddDriveFileWithRelativePath(profile, /*drive_file_id=*/"qwertyqwerty",
                               /*directory_path=*/base::FilePath("aa"),
                               &relative_file_path,
                               /*new_file_absolute_path=*/nullptr);
  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    DriveIntegrationServiceFactory::FindForProfile(profile)
        ->LocateFilesByItemIds(
            {"qwertyqwerty", "foobar"},
            base::BindLambdaForTesting(
                [=](std::optional<
                    std::vector<drivefs::mojom::FilePathOrErrorPtr>> result) {
                  ASSERT_EQ(2u, result->size());
                  EXPECT_EQ(relative_file_path, base::FilePath("/").Append(
                                                    result->at(0)->get_path()));
                  EXPECT_EQ(FILE_ERROR_NOT_FOUND, result->at(1)->get_error());
                  quit_closure.Run();
                }));
    run_loop.Run();
  }
}

class DriveIntegrationServiceWithPrefDisabledBrowserTest
    : public DriveIntegrationServiceBrowserTest {
  drive::DriveIntegrationService* CreateDriveIntegrationService(
      Profile* profile) override {
    profile->GetPrefs()->SetBoolean(prefs::kDisableDrive, true);
    return DriveIntegrationServiceBrowserTest::CreateDriveIntegrationService(
        profile);
  }
};

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceWithPrefDisabledBrowserTest,
                       RenableAndDisableDrive) {
  auto* profile = browser()->profile();
  auto* drive_service = DriveIntegrationServiceFactory::FindForProfile(profile);
  EXPECT_FALSE(drive_service->is_enabled());

  profile->GetPrefs()->SetBoolean(prefs::kDisableDrive, false);
  EXPECT_TRUE(drive_service->is_enabled());

  profile->GetPrefs()->SetBoolean(prefs::kDisableDrive, true);
  EXPECT_FALSE(drive_service->is_enabled());
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
                       EnableMirrorSync_FeatureDisabled) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->ToggleMirroring(
        /*enabled=*/true,
        base::BindLambdaForTesting(
            [quit_closure](drivefs::mojom::MirrorSyncStatus status) {
              EXPECT_EQ(drivefs::mojom::MirrorSyncStatus::kFeatureNotEnabled,
                        status);
              quit_closure.Run();
            }));
    run_loop.Run();
  }

  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->ToggleMirroring(
        /*enabled=*/false,
        base::BindLambdaForTesting(
            [quit_closure](drivefs::mojom::MirrorSyncStatus status) {
              EXPECT_EQ(drivefs::mojom::MirrorSyncStatus::kFeatureNotEnabled,
                        status);
              quit_closure.Run();
            }));
    run_loop.Run();
  }
}

class DriveMirrorSyncStatusObserver : public DriveIntegrationService::Observer {
 public:
  explicit DriveMirrorSyncStatusObserver(bool expected_status)
      : expected_status_(expected_status) {
    quit_closure_ = run_loop_.QuitClosure();
  }

  void WaitForStatusChange() { run_loop_.Run(); }

  void OnMirroringEnabled() override {
    quit_closure_.Run();
    EXPECT_TRUE(expected_status_);
  }

  void OnMirroringDisabled() override {
    quit_closure_.Run();
    EXPECT_FALSE(expected_status_);
  }

 private:
  base::RunLoop run_loop_;
  base::RepeatingClosure quit_closure_;
  bool expected_status_ = false;
};

class DriveIntegrationBrowserTestWithMirrorSyncEnabled
    : public DriveIntegrationServiceBrowserTest {
 public:
  DriveIntegrationBrowserTestWithMirrorSyncEnabled() {
    scoped_feature_list_.InitWithFeatures({ash::features::kDriveFsMirroring},
                                          {});
  }

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

  ~DriveIntegrationBrowserTestWithMirrorSyncEnabled() override {}

  void SetUpOnMainThread() override { MockGetSyncingPaths(); }

  void ToggleMirrorSync(bool status, bool expect_fail = false) {
    DriveMirrorSyncStatusObserver observer(expect_fail ? !status : status);
    Profile* const profile = browser()->profile();
    observer.Observe(DriveIntegrationServiceFactory::FindForProfile(profile));
    PrefService* const prefs = profile->GetPrefs();
    prefs->SetBoolean(prefs::kDriveFsEnableMirrorSync, status);
    observer.WaitForStatusChange();
    EXPECT_EQ(prefs->GetBoolean(prefs::kDriveFsEnableMirrorSync),
              expect_fail ? !status : status);
  }

  void MockGetSyncingPaths() {
    drivefs::FakeDriveFs* fake_drivefs =
        GetFakeDriveFsForProfile(browser()->profile());
    ON_CALL(*fake_drivefs, GetSyncingPaths(_))
        .WillByDefault(testing::Invoke(
            fake_drivefs, &drivefs::FakeDriveFs::GetSyncingPathsForTesting));
  }

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

class DriveIntegrationBrowserTestWithBulkPinningEnabled
    : public DriveIntegrationServiceBrowserTest {
 public:
  DriveIntegrationBrowserTestWithBulkPinningEnabled() {
    scoped_feature_list_.InitWithFeatures(
        {ash::features::kDriveFsBulkPinning,
         ash::features::kFeatureManagementDriveFsBulkPinning},
        {});
  }

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

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       EnableMirrorSync) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  // Ensure the mirror syncing service is disabled.
  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
      prefs::kDriveFsEnableMirrorSync));
  EXPECT_FALSE(drive_service->IsMirroringEnabled());

  // Enable mirroring and ensure the integration service has it enabled.
  ToggleMirrorSync(true);
  EXPECT_TRUE(drive_service->IsMirroringEnabled());

  // Check MyFiles is being added as sync path.
  TestFuture<drive::FileError, const std::vector<base::FilePath>&> future;
  const base::FilePath my_files_path =
      file_manager::util::GetMyFilesFolderForProfile(browser()->profile());

  drive_service->GetSyncingPaths(future.GetCallback());

  EXPECT_EQ(future.Get<0>(), drive::FILE_ERROR_OK);
  EXPECT_THAT(future.Get<1>(), testing::ElementsAre(my_files_path));
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       EnableMirrorSyncWithNonExistMyFiles) {
  Profile* profile = browser()->profile();
  auto* drive_service = DriveIntegrationServiceFactory::FindForProfile(profile);

  // Replace MyFiles mount point to something not exist.
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::ScopedTempDir temp_dir;
  EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath my_files_dir = temp_dir.GetPath().Append("NotExist");
  storage::ExternalMountPoints::GetSystemInstance()->RevokeAllFileSystems();
  storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
      file_manager::util::GetDownloadsMountPointName(profile),
      storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
      my_files_dir);

  // Enable mirroring with non existed MyFiles won't turn the sync on.
  ToggleMirrorSync(true, true);
  EXPECT_FALSE(drive_service->IsMirroringEnabled());

  // Check nothing is added as sync path.
  drivefs::FakeDriveFs* fake_drivefs = GetFakeDriveFsForProfile(profile);
  TestFuture<drive::FileError, const std::vector<base::FilePath>&> future;
  fake_drivefs->GetSyncingPathsForTesting(future.GetCallback());

  EXPECT_THAT(future.Get<1>(), testing::IsEmpty());
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       DisableMirrorSync) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  // Ensure the mirror syncing service is disabled.
  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
      prefs::kDriveFsEnableMirrorSync));
  EXPECT_FALSE(drive_service->IsMirroringEnabled());

  // Enable mirror syncing.
  TestFuture<drive::FileError, const std::vector<base::FilePath>&> future;
  ToggleMirrorSync(true);
  EXPECT_TRUE(drive_service->IsMirroringEnabled());
  drive_service->GetSyncingPaths(future.GetCallback());
  const base::FilePath my_files_path =
      file_manager::util::GetMyFilesFolderForProfile(browser()->profile());
  EXPECT_THAT(future.Get<1>(), testing::ElementsAre(my_files_path));

  // Disable mirroring and ensure the integration service has it disabled.
  ToggleMirrorSync(false);
  EXPECT_FALSE(drive_service->IsMirroringEnabled());
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       ToggleSyncForPath_MirroringDisabled) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->ToggleSyncForPath(
        base::FilePath("/fake/path"), drivefs::mojom::MirrorPathStatus::kStart,
        base::BindLambdaForTesting([quit_closure](FileError status) {
          EXPECT_EQ(FILE_ERROR_SERVICE_UNAVAILABLE, status);
          quit_closure.Run();
        }));
    run_loop.Run();
  }
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       ToggleSyncForPath_MirroringEnabledFileNotFound) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  // Enable mirror sync.
  ToggleMirrorSync(true);

  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->ToggleSyncForPath(
        base::FilePath("/fake/path"), drivefs::mojom::MirrorPathStatus::kStart,
        base::BindLambdaForTesting([quit_closure](FileError status) {
          EXPECT_EQ(FILE_ERROR_NOT_FOUND, status);
          quit_closure.Run();
        }));
    run_loop.Run();
  }
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       ToggleSyncForPath_MirroringEnabled) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  // Enable mirror sync.
  ToggleMirrorSync(true);

  base::ScopedTempDir temp_dir;
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
  }

  {
    base::FilePath sync_path = temp_dir.GetPath();
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->ToggleSyncForPath(
        sync_path, drivefs::mojom::MirrorPathStatus::kStart,
        base::BindLambdaForTesting([quit_closure](FileError status) {
          EXPECT_EQ(FILE_ERROR_OK, status);
          quit_closure.Run();
        }));
    run_loop.Run();
  }

  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.Delete());
  }
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       ToggleSyncForPath_MirroringEnabledAddSamePath) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  // Enable mirror sync.
  ToggleMirrorSync(true);

  base::ScopedTempDir temp_dir;
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
  }

  {
    base::FilePath sync_path = temp_dir.GetPath();
    TestFuture<drive::FileError> toggle_sync_future;
    drive_service->ToggleSyncForPath(sync_path,
                                     drivefs::mojom::MirrorPathStatus::kStart,
                                     toggle_sync_future.GetCallback());
    EXPECT_EQ(toggle_sync_future.Get(), drive::FILE_ERROR_OK);

    // GetSyncingPath should have 2 paths: "MyFiles" and the above sync path.
    TestFuture<drive::FileError, const std::vector<base::FilePath>&>
        get_syncing_paths_future;
    drive_service->GetSyncingPaths(get_syncing_paths_future.GetCallback());
    EXPECT_EQ(get_syncing_paths_future.Get<0>(), drive::FILE_ERROR_OK);
    EXPECT_EQ(get_syncing_paths_future.Get<1>().size(), 2u);

    // Toggle sync for the same path again.
    TestFuture<drive::FileError> toggle_sync_same_path_future;
    drive_service->ToggleSyncForPath(
        sync_path, drivefs::mojom::MirrorPathStatus::kStart,
        toggle_sync_same_path_future.GetCallback());
    EXPECT_EQ(toggle_sync_same_path_future.Get(), drive::FILE_ERROR_OK);

    // GetSyncingPath should still have 2 paths because it ignores the duplicate
    // path.
    TestFuture<drive::FileError, const std::vector<base::FilePath>&>
        get_syncing_paths_again_future;
    drive_service->GetSyncingPaths(
        get_syncing_paths_again_future.GetCallback());
    EXPECT_EQ(get_syncing_paths_again_future.Get<0>(), drive::FILE_ERROR_OK);
    EXPECT_EQ(get_syncing_paths_again_future.Get<1>().size(), 2u);
  }

  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.Delete());
  }
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       GetSyncingPaths_MirroringDisabled) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  {
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    drive_service->GetSyncingPaths(base::BindLambdaForTesting(
        [quit_closure](drive::FileError status,
                       const std::vector<base::FilePath>& paths) {
          EXPECT_EQ(drive::FILE_ERROR_SERVICE_UNAVAILABLE, status);
          EXPECT_EQ(0u, paths.size());
          quit_closure.Run();
        }));
    run_loop.Run();
  }
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       GetSyncingPaths_MirroringEnabled) {
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());

  // Enable mirror sync and add |sync_path| that we expect to return from
  // |GetSyncingPaths|.
  ToggleMirrorSync(true);

  base::ScopedTempDir temp_dir;
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
  }

  {
    TestFuture<drive::FileError> toggle_sync_future;
    base::FilePath sync_path = temp_dir.GetPath();
    drive_service->ToggleSyncForPath(sync_path,
                                     drivefs::mojom::MirrorPathStatus::kStart,
                                     toggle_sync_future.GetCallback());
    EXPECT_EQ(toggle_sync_future.Get(), drive::FILE_ERROR_OK);

    TestFuture<drive::FileError, const std::vector<base::FilePath>&>
        get_syncing_paths_future;
    drive_service->GetSyncingPaths(get_syncing_paths_future.GetCallback());
    const base::FilePath my_files_path =
        file_manager::util::GetMyFilesFolderForProfile(browser()->profile());
    EXPECT_EQ(get_syncing_paths_future.Get<0>(), drive::FILE_ERROR_OK);
    EXPECT_THAT(get_syncing_paths_future.Get<1>(),
                testing::ElementsAre(my_files_path, sync_path));
  }

  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(temp_dir.Delete());
  }
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithMirrorSyncEnabled,
                       MachineRootIDPersistedAndAvailable) {
  ToggleMirrorSync(true);

  // Ensure the initial machine root ID is unset.
  EXPECT_EQ(browser()->profile()->GetPrefs()->GetString(
                prefs::kDriveFsMirrorSyncMachineRootId),
            "");

  // Invoke the delegate method to persist the machine root ID and wait for the
  // prefs key to change to the expected value.
  drivefs::FakeDriveFs* fake = GetFakeDriveFsForProfile(browser()->profile());
  fake->delegate()->PersistMachineRootID("test-machine-id");
  WaitForPrefValue(browser()->profile()->GetPrefs(),
                   prefs::kDriveFsMirrorSyncMachineRootId,
                   base::Value("test-machine-id"));

  // Setup the callback for the GetMachineRootID method to assert it gets run
  // with the "test-machine-id".
  base::RunLoop run_loop;
  base::MockOnceCallback<void(const std::string&)> machine_root_id_callback;
  EXPECT_CALL(machine_root_id_callback, Run("test-machine-id"))
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  // Kick off the GetMachineRootID method and wait for it to return
  // successfully.
  fake->delegate()->GetMachineRootID(machine_root_id_callback.Get());
  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithBulkPinningEnabled,
                       GetTotalPinnedSizeWithErrorIgnoresReturnedSize) {
  auto* drive_integration_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());
  auto* fake_drivefs = GetFakeDriveFsForProfile(browser()->profile());

  EXPECT_CALL(*fake_drivefs, GetOfflineFilesSpaceUsage(_))
      .WillOnce(RunOnceCallback<0>(drive::FILE_ERROR_FAILED, 1000));

  base::RunLoop run_loop;
  base::MockOnceCallback<void(int64_t)> mock_callback;
  EXPECT_CALL(mock_callback, Run(-1))
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  drive_integration_service->GetTotalPinnedSize(mock_callback.Get());
  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithBulkPinningEnabled,
                       GetTotalPinnedSizeReturnsCorrectSize) {
  auto* drive_integration_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());
  auto* fake_drivefs = GetFakeDriveFsForProfile(browser()->profile());

  EXPECT_CALL(*fake_drivefs, GetOfflineFilesSpaceUsage(_))
      .WillOnce(RunOnceCallback<0>(drive::FILE_ERROR_OK, 1024));

  base::RunLoop run_loop;
  base::MockOnceCallback<void(int64_t)> mock_callback;
  EXPECT_CALL(mock_callback, Run(1024))
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  drive_integration_service->GetTotalPinnedSize(mock_callback.Get());
  run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationBrowserTestWithBulkPinningEnabled,
                       GetTotalPinnedSizeReturnsCachedSizeOnNextRequest) {
  auto* drive_integration_service =
      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());
  auto* fake_drivefs = GetFakeDriveFsForProfile(browser()->profile());

  EXPECT_CALL(*fake_drivefs, GetOfflineFilesSpaceUsage(_))
      .WillOnce(RunOnceCallback<0>(drive::FILE_ERROR_OK, 1024));

  // First invocation of `GetTotalPinnedSize` should invoke the fake drivefs.
  base::RunLoop run_loop;
  base::MockOnceCallback<void(int64_t)> mock_callback;
  EXPECT_CALL(mock_callback, Run(1024))
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  drive_integration_service->GetTotalPinnedSize(mock_callback.Get());
  run_loop.Run();

  // Second invocation of `GetTotalPinnedSize` should reuse the same value but
  // cached not calling fake drivefs instead.
  base::RunLoop run_loop_2;
  base::MockOnceCallback<void(int64_t)> cached_mock_callback;
  EXPECT_CALL(cached_mock_callback, Run(1024))
      .WillOnce(RunClosure(run_loop_2.QuitClosure()));

  drive_integration_service->GetTotalPinnedSize(cached_mock_callback.Get());
  run_loop_2.Run();
}

class DriveIntegrationServiceBrowserTestLacros
    : public DriveIntegrationServiceBrowserTestBase {
 protected:
  DriveIntegrationServiceBrowserTestLacros() {
    scoped_feature_list_.InitWithFeatures(
        ash::standalone_browser::GetFeatureRefs(), {});
    scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
        ash::switches::kEnableLacrosForTesting);
  }

  // browser() does not exist in Lacros, so get the profile from ProfileManager.
  Profile* profile() { return ProfileManager::GetPrimaryUserProfile(); }

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

class MockDriveFsNativeMessageHostBridge
    : public crosapi::mojom::DriveFsNativeMessageHostBridge {
 public:
  MockDriveFsNativeMessageHostBridge() : receiver_(this) {}

  mojo::Receiver<crosapi::mojom::DriveFsNativeMessageHostBridge>* receiver() {
    return &receiver_;
  }

  MOCK_METHOD(void,
              ConnectToExtension,
              (drivefs::mojom::ExtensionConnectionParamsPtr,
               mojo::PendingReceiver<drivefs::mojom::NativeMessagingPort>,
               mojo::PendingRemote<drivefs::mojom::NativeMessagingHost>,
               ConnectToExtensionCallback),
              (override));

 private:
  mojo::Receiver<crosapi::mojom::DriveFsNativeMessageHostBridge> receiver_;
};

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTestLacros,
                       ConnectToExtensionWithPendingRequest) {
  base::RunLoop run_loop;
  base::RunLoop run_loop2;

  base::MockCallback<
      drivefs::mojom::DriveFsDelegate::ConnectToExtensionCallback>
      mock_callback;
  EXPECT_CALL(mock_callback,
              Run(drivefs::mojom::ExtensionConnectionStatus::kUnknownError))
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  mojo::PendingRemote<drivefs::mojom::NativeMessagingPort> extension_port;
  mojo::PendingReceiver<drivefs::mojom::NativeMessagingHost> drivefs_host;
  drivefs::FakeDriveFs* fake_drivefs = GetFakeDriveFsForProfile(profile());
  fake_drivefs->delegate()->ConnectToExtension(
      drivefs::mojom::ExtensionConnectionParams::New("extension_id"),
      extension_port.InitWithNewPipeAndPassReceiver(),
      drivefs_host.InitWithNewPipeAndPassRemote(), mock_callback.Get());

  // A second connection request should overwrite the first and force the first
  // request to return an error.
  base::MockCallback<
      drivefs::mojom::DriveFsDelegate::ConnectToExtensionCallback>
      mock_callback2;
  EXPECT_CALL(mock_callback2,
              Run(drivefs::mojom::ExtensionConnectionStatus::kSuccess))
      .WillOnce(RunClosure(run_loop2.QuitClosure()));

  mojo::PendingRemote<drivefs::mojom::NativeMessagingPort> extension_port2;
  mojo::PendingReceiver<drivefs::mojom::NativeMessagingHost> drivefs_host2;
  fake_drivefs->delegate()->ConnectToExtension(
      drivefs::mojom::ExtensionConnectionParams::New("extension_id2"),
      extension_port2.InitWithNewPipeAndPassReceiver(),
      drivefs_host2.InitWithNewPipeAndPassRemote(), mock_callback2.Get());

  run_loop.Run();

  // Registering the bridge should cause the second request to succeed.
  MockDriveFsNativeMessageHostBridge mock_bridge;
  EXPECT_CALL(mock_bridge, ConnectToExtension(_, _, _, _))
      .WillOnce([](drivefs::mojom::ExtensionConnectionParamsPtr params, auto,
                   auto,
                   crosapi::mojom::DriveFsNativeMessageHostBridge::
                       ConnectToExtensionCallback callback) {
        EXPECT_EQ("extension_id2", params->extension_id);
        std::move(callback).Run(
            drivefs::mojom::ExtensionConnectionStatus::kSuccess);
      });
  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(profile());
  drive_service->RegisterDriveFsNativeMessageHostBridge(
      mock_bridge.receiver()->BindNewPipeAndPassRemote());

  run_loop2.Run();
}

IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTestLacros,
                       ConnectToExtension) {
  base::RunLoop run_loop;

  MockDriveFsNativeMessageHostBridge mock_bridge;
  EXPECT_CALL(mock_bridge, ConnectToExtension(_, _, _, _))
      .WillOnce([](drivefs::mojom::ExtensionConnectionParamsPtr params, auto,
                   auto,
                   crosapi::mojom::DriveFsNativeMessageHostBridge::
                       ConnectToExtensionCallback callback) {
        EXPECT_EQ("extension_id", params->extension_id);
        std::move(callback).Run(
            drivefs::mojom::ExtensionConnectionStatus::kSuccess);
      });

  // A second connected bridge should just be ignored.
  MockDriveFsNativeMessageHostBridge mock_bridge2;
  EXPECT_CALL(mock_bridge2, ConnectToExtension(_, _, _, _)).Times(0);

  auto* drive_service =
      DriveIntegrationServiceFactory::FindForProfile(profile());
  drive_service->RegisterDriveFsNativeMessageHostBridge(
      mock_bridge.receiver()->BindNewPipeAndPassRemote());
  drive_service->RegisterDriveFsNativeMessageHostBridge(
      mock_bridge2.receiver()->BindNewPipeAndPassRemote());

  base::MockCallback<
      drivefs::mojom::DriveFsDelegate::ConnectToExtensionCallback>
      mock_callback;
  EXPECT_CALL(mock_callback,
              Run(drivefs::mojom::ExtensionConnectionStatus::kSuccess))
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  mojo::PendingRemote<drivefs::mojom::NativeMessagingPort> extension_port;
  mojo::PendingReceiver<drivefs::mojom::NativeMessagingHost> drivefs_host;
  drivefs::FakeDriveFs* fake_drivefs = GetFakeDriveFsForProfile(profile());
  fake_drivefs->delegate()->ConnectToExtension(
      drivefs::mojom::ExtensionConnectionParams::New("extension_id"),
      extension_port.InitWithNewPipeAndPassReceiver(),
      drivefs_host.InitWithNewPipeAndPassRemote(), mock_callback.Get());

  run_loop.Run();
}

}  // namespace drive