chromium/chrome/browser/ash/policy/skyvault/migration_coordinator_browsertest.cc

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

#include "chrome/browser/ash/policy/skyvault/migration_coordinator.h"

#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/policy/skyvault/local_files_migration_constants.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/ash/policy/skyvault/skyvault_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"

namespace policy::local_user_files {

using ::base::test::RunOnceCallback;

using OneDriveMigrationCoordinatorTest = SkyvaultOneDriveTest;

IN_PROC_BROWSER_TEST_F(OneDriveMigrationCoordinatorTest, SuccessfulUpload) {
  SetUpMyFiles();
  SetUpODFS();

  // Set up some files and directories.
  /**
  - MyFiles
    - foo
      - video_long.ogv
    - text.txt
  */
  const std::string file = "text.txt";
  base::FilePath file_path = CopyTestFile(file, my_files_dir());
  const std::string dir = "foo";
  base::FilePath dir_path = CreateTestDir(dir, my_files_dir());

  const std::string nested_file = "video_long.ogv";
  base::FilePath nested_file_path = CopyTestFile(nested_file, dir_path);

  MigrationCoordinator coordinator(profile());
  base::test::TestFuture<std::map<base::FilePath, MigrationUploadError>> future;
  // Upload the files.
  coordinator.Run(CloudProvider::kOneDrive, {file_path, nested_file_path},
                  kDestinationDirName, future.GetCallback());
  ASSERT_TRUE(future.Get().empty());

  // Check that all files have been moved to OneDrive in the correct place.
  CheckPathExistsOnODFS(
      base::FilePath("/").AppendASCII(kDestinationDirName).AppendASCII(file));
  CheckPathExistsOnODFS(base::FilePath("/")
                            .AppendASCII(kDestinationDirName)
                            .AppendASCII(dir)
                            .AppendASCII(nested_file));
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    CHECK(!base::PathExists(dir_path.AppendASCII(nested_file)));
    CHECK(!base::PathExists(file_path));
  }
}

IN_PROC_BROWSER_TEST_F(OneDriveMigrationCoordinatorTest,
                       FailedUpload_IOTaskError) {
  SetUpMyFiles();
  SetUpODFS();
  provided_file_system_->SetCreateFileError(
      base::File::Error::FILE_ERROR_NO_MEMORY);
  provided_file_system_->SetReauthenticationRequired(false);

  const std::string file = "video_long.ogv";
  base::FilePath file_path = CopyTestFile(file, my_files_dir());

  MigrationCoordinator coordinator(profile());
  base::test::TestFuture<std::map<base::FilePath, MigrationUploadError>> future;
  // Upload the file.
  coordinator.Run(CloudProvider::kOneDrive, {file_path}, kDestinationDirName,
                  future.GetCallback());
  auto errors = future.Get();
  ASSERT_TRUE(errors.size() == 1u);
  auto error = errors.find(file_path);
  ASSERT_NE(error, errors.end());
  ASSERT_EQ(error->second, MigrationUploadError::kCopyFailed);

  CheckPathNotFoundOnODFS(base::FilePath("/").AppendASCII(file));
}

IN_PROC_BROWSER_TEST_F(OneDriveMigrationCoordinatorTest, EmptyUrls) {
  SetUpMyFiles();
  SetUpODFS();

  MigrationCoordinator coordinator(profile());
  base::test::TestFuture<std::map<base::FilePath, MigrationUploadError>> future;
  coordinator.Run(CloudProvider::kOneDrive, {}, kDestinationDirName,
                  future.GetCallback());
  ASSERT_TRUE(future.Get().empty());
}

IN_PROC_BROWSER_TEST_F(OneDriveMigrationCoordinatorTest, StopUpload) {
  SetUpMyFiles();
  SetUpODFS();

  const std::string test_file_name = "video_long.ogv";
  base::FilePath file_path = CopyTestFile(test_file_name, my_files_dir());

  base::test::TestFuture<void> future;
  // Create directly for more control over Run() and Stop().
  OneDriveMigrationUploader uploader(profile(), {file_path},
                                     kDestinationDirName, base::DoNothing());
  // Ensure Run() doesn't finish before we call Stop().
  uploader.SetEmulateSlowForTesting(true);
  uploader.Run();
  uploader.Stop(future.GetCallback());
  ASSERT_TRUE(future.Wait());

  // Check that the source file has NOT been moved to OneDrive.
  CheckPathNotFoundOnODFS(base::FilePath("/").AppendASCII(test_file_name));
}

class GoogleDriveMigrationCoordinatorTest : public SkyvaultGoogleDriveTest {
 public:
  GoogleDriveMigrationCoordinatorTest() = default;

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

  base::FilePath observed_relative_drive_path(const FileInfo& info) override {
    base::FilePath observed_relative_drive_path;
    drive_integration_service()->GetRelativeDrivePath(
        drive_root_dir()
            .AppendASCII(kDestinationDirName)
            .Append(info.local_relative_path_),
        &observed_relative_drive_path);
    return observed_relative_drive_path;
  }

 protected:
  bool fail_sync_ = false;

 private:
  // IOTaskController::Observer:
  void OnIOTaskStatus(
      const file_manager::io_task::ProgressStatus& status) override {
    // Wait for the copy task to complete before starting the Drive sync.
    auto it = source_files_.find(status.sources[0].url.path());
    if (status.type == file_manager::io_task::OperationType::kCopy &&
        status.sources.size() == 1 && it != source_files_.end() &&
        status.state == file_manager::io_task::State::kSuccess) {
      if (fail_sync_) {
        SimulateDriveUploadFailure(it->second);
      } else {
        SimulateDriveUploadCompleted(it->second);
      }
    }
  }

  // Simulates the upload of the file to Drive by sending a series of fake
  // signals to the DriveFs delegate.
  void SimulateDriveUploadCompleted(const FileInfo& info) {
    // Set file metadata for `drivefs::mojom::DriveFs::GetMetadata`.
    drivefs::FakeMetadata metadata;
    metadata.path = observed_relative_drive_path(info);
    metadata.mime_type =
        "application/"
        "vnd.openxmlformats-officedocument.wordprocessingml.document";
    metadata.original_name = info.test_file_name_;
    metadata.alternate_url =
        "https://docs.google.com/document/d/"
        "smalldocxid?rtpof=true&usp=drive_fs";
    fake_drivefs().SetMetadata(std::move(metadata));
    // Simulate server sync events.
    drivefs::mojom::SyncingStatusPtr status =
        drivefs::mojom::SyncingStatus::New();
    status->item_events.emplace_back(
        std::in_place, 12, 34, observed_relative_drive_path(info).value(),
        drivefs::mojom::ItemEvent::State::kQueued, 123, 456,
        drivefs::mojom::ItemEventReason::kTransfer);
    drivefs_delegate()->OnSyncingStatusUpdate(status.Clone());
    drivefs_delegate().FlushForTesting();

    status = drivefs::mojom::SyncingStatus::New();
    status->item_events.emplace_back(
        std::in_place, 12, 34, observed_relative_drive_path(info).value(),
        drivefs::mojom::ItemEvent::State::kCompleted, 123, 456,
        drivefs::mojom::ItemEventReason::kTransfer);
    drivefs_delegate()->OnSyncingStatusUpdate(status.Clone());
    drivefs_delegate().FlushForTesting();
  }

  void SimulateDriveUploadFailure(const FileInfo& info) {
    // Simulate server sync events.
    drivefs::mojom::SyncingStatusPtr status =
        drivefs::mojom::SyncingStatus::New();
    status->item_events.emplace_back(
        std::in_place, 12, 34, observed_relative_drive_path(info).value(),
        drivefs::mojom::ItemEvent::State::kQueued, 123, 456,
        drivefs::mojom::ItemEventReason::kTransfer);
    drivefs_delegate()->OnSyncingStatusUpdate(status.Clone());
    drivefs_delegate().FlushForTesting();

    drivefs::mojom::SyncingStatusPtr fail_status =
        drivefs::mojom::SyncingStatus::New();
    fail_status->item_events.emplace_back(
        std::in_place, 12, 34, observed_relative_drive_path(info).value(),
        drivefs::mojom::ItemEvent::State::kFailed, 123, 456,
        drivefs::mojom::ItemEventReason::kTransfer);
    drivefs_delegate()->OnSyncingStatusUpdate(fail_status->Clone());
    drivefs_delegate().FlushForTesting();
  }
};

IN_PROC_BROWSER_TEST_F(GoogleDriveMigrationCoordinatorTest, SuccessfulUpload) {
  SetUpObservers();
  SetUpMyFiles();

  // Set up some files and directories.
  /**
  - MyFiles
    - foo
      - video_long.ogv
    - text.txt
  */
  // TODO(b/363480542): Uncomment when testing multi file syncs is supported.
  // const std::string file = "text.txt";
  // base::FilePath file_path = SetUpSourceFile(file, my_files_dir());
  const std::string dir = "foo";
  base::FilePath dir_path = CreateTestDir(dir, my_files_dir());

  const std::string nested_file = "video_long.ogv";
  base::FilePath nested_file_path = SetUpSourceFile(nested_file, dir_path);

  EXPECT_CALL(fake_drivefs(), ImmediatelyUpload)
      .WillOnce(
          base::test::RunOnceCallback<1>(drive::FileError::FILE_ERROR_OK));

  MigrationCoordinator coordinator(profile());
  base::test::TestFuture<std::map<base::FilePath, MigrationUploadError>> future;
  // Upload the files.
  coordinator.Run(CloudProvider::kGoogleDrive, {nested_file_path},
                  kDestinationDirName, future.GetCallback());
  ASSERT_TRUE(future.Get().empty());

  // Check that all files have been moved to Google Drive in the correct place.
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    // TODO(b/363480542): Uncomment when testing multi file syncs is supported.
    // EXPECT_FALSE(base::PathExists(my_files_dir().AppendASCII(file)));
    // CheckPathExistsOnDrive(
    //     observed_relative_drive_path(source_files_.find(file_path)->second));

    EXPECT_FALSE(base::PathExists(
        my_files_dir().AppendASCII(dir).AppendASCII(nested_file)));
    CheckPathExistsOnDrive(observed_relative_drive_path(
        source_files_.find(nested_file_path)->second));
  }
}

IN_PROC_BROWSER_TEST_F(GoogleDriveMigrationCoordinatorTest, FailedUpload) {
  SetUpObservers();
  SetUpMyFiles();
  fail_sync_ = true;

  const std::string file = "text.txt";
  base::FilePath file_path = SetUpSourceFile(file, my_files_dir());

  EXPECT_CALL(fake_drivefs(), ImmediatelyUpload)
      .WillOnce(
          base::test::RunOnceCallback<1>(drive::FileError::FILE_ERROR_FAILED));

  MigrationCoordinator coordinator(profile());
  base::test::TestFuture<std::map<base::FilePath, MigrationUploadError>> future;
  // Upload the files.
  coordinator.Run(CloudProvider::kGoogleDrive, {file_path}, kDestinationDirName,
                  future.GetCallback());
  auto errors = future.Get();
  ASSERT_TRUE(errors.size() == 1u);
  auto error = errors.find(file_path);
  ASSERT_NE(error, errors.end());
  ASSERT_EQ(error->second, MigrationUploadError::kCopyFailed);

  // Check that the file hasn't been moved to Google Drive.
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(base::PathExists(my_files_dir().AppendASCII(file)));
    CheckPathNotFoundOnDrive(
        observed_relative_drive_path(source_files_.find(file_path)->second));
  }
}

}  // namespace policy::local_user_files