chromium/chrome/browser/ash/file_manager/io_task_controller_unittest.cc

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

#include "chrome/browser/ash/file_manager/io_task_controller.h"

#include <memory>

#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ash/file_manager/io_task.h"
#include "content/public/test/browser_task_environment.h"
#include "storage/browser/file_system/file_system_url.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::test::RunClosure;
using testing::_;
using testing::AllOf;
using testing::Field;
using testing::Invoke;
using testing::Property;

namespace file_manager {
namespace io_task {
namespace {

MATCHER_P(EntryStatusUrls, matcher, "") {
  std::vector<storage::FileSystemURL> urls;
  for (const auto& status : arg) {
    urls.push_back(status.url);
  }
  return testing::ExplainMatchResult(matcher, urls, result_listener);
}

storage::FileSystemURL CreateFileSystemURL(std::string url) {
  return storage::FileSystemURL::CreateForTest(GURL(url));
}

class IOTaskStatusObserver : public IOTaskController::Observer {
 public:
  MOCK_METHOD(void, OnIOTaskStatus, (const ProgressStatus&), (override));
};

class IOTaskControllerTest : public testing::Test {
 protected:
  content::BrowserTaskEnvironment browser_task_environment_;

  IOTaskController io_task_controller_;
};

TEST_F(IOTaskControllerTest, SimpleQueueing) {
  IOTaskStatusObserver observer;
  io_task_controller_.AddObserver(&observer);

  std::vector<storage::FileSystemURL> source_urls{
      CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"),
  };
  auto dest = CreateFileSystemURL(
      "filesystem:chrome-extension://abc/external/foo/dest");

  // All progress statuses should return the same |type|, |source_urls| and
  // |destination_folder| as given, so set up a base matcher to check this.
  auto base_matcher =
      AllOf(Field(&ProgressStatus::type, OperationType::kCopy),
            Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)),
            Property(&ProgressStatus::GetDestinationFolder, dest));

  // The controller should synchronously send out a progress status when queued.
  EXPECT_CALL(observer, OnIOTaskStatus(
                            AllOf(Field(&ProgressStatus::state, State::kQueued),
                                  base_matcher)));

  // The controller should also synchronously execute the I/O task, which will
  // send out another status.
  EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                            Field(&ProgressStatus::state, State::kInProgress),
                            base_matcher)));

  // Queue the I/O task, which will also synchronously execute it.
  EXPECT_EQ(0, io_task_controller_.wake_lock_counter_for_tests());
  auto task_id = io_task_controller_.Add(
      std::make_unique<DummyIOTask>(source_urls, dest, OperationType::kCopy));
  EXPECT_EQ(1, io_task_controller_.wake_lock_counter_for_tests());
  EXPECT_FALSE(io_task_controller_.TaskStatuses().empty());
  EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id);

  // Wait for the callbacks posted to the main sequence to finish.
  {
    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                              Field(&ProgressStatus::state, State::kSuccess),
                              base_matcher)))
        .WillOnce(RunClosure(run_loop.QuitClosure()));
    run_loop.Run();
  }

  EXPECT_TRUE(io_task_controller_.TaskStatuses().empty());

  // Cancel() should have no effect once a task is completed.
  io_task_controller_.Cancel(task_id);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0, io_task_controller_.wake_lock_counter_for_tests());

  io_task_controller_.RemoveObserver(&observer);
}

TEST_F(IOTaskControllerTest, Cancel) {
  IOTaskStatusObserver observer;
  io_task_controller_.AddObserver(&observer);

  std::vector<storage::FileSystemURL> source_urls{
      CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"),
  };
  auto dest = CreateFileSystemURL(
      "filesystem:chrome-extension://abc/external/foo/dest");

  // All progress statuses should return the same |type|, |source_urls| and
  // |destination_folder| given, so set up a base matcher to check this.
  auto base_matcher =
      AllOf(Field(&ProgressStatus::type, OperationType::kMove),
            Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)),
            Property(&ProgressStatus::GetDestinationFolder, dest));

  // The controller should synchronously send out a progress status when queued.
  EXPECT_CALL(observer, OnIOTaskStatus(
                            AllOf(Field(&ProgressStatus::state, State::kQueued),
                                  base_matcher)));

  // The controller should also synchronously execute the I/O task, which will
  // send out another status.
  EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                            Field(&ProgressStatus::state, State::kInProgress),
                            base_matcher)));

  auto task_id = io_task_controller_.Add(
      std::make_unique<DummyIOTask>(source_urls, dest, OperationType::kMove));
  EXPECT_FALSE(io_task_controller_.TaskStatuses().empty());
  EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id);

  // Cancel should synchronously send a progress status.
  EXPECT_CALL(observer,
              OnIOTaskStatus(AllOf(
                  Field(&ProgressStatus::state, State::kCancelled),
                  Field(&ProgressStatus::task_id, task_id), base_matcher)));

  io_task_controller_.Cancel(task_id);
  EXPECT_TRUE(io_task_controller_.TaskStatuses().empty());

  // No more observer notifications should come after Cancel() as the task is
  // deleted.
  base::RunLoop().RunUntilIdle();

  io_task_controller_.RemoveObserver(&observer);
}

TEST_F(IOTaskControllerTest, PauseResume) {
  IOTaskStatusObserver observer;
  io_task_controller_.AddObserver(&observer);

  std::vector<storage::FileSystemURL> source_urls{
      CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"),
  };
  auto dest = CreateFileSystemURL(
      "filesystem:chrome-extension://abc/external/foo/dest");

  // All progress statuses should return the same |type|, |source_urls| and
  // |destination_folder| given, so set up a base matcher to check this.
  auto base_matcher =
      AllOf(Field(&ProgressStatus::type, OperationType::kMove),
            Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)),
            Property(&ProgressStatus::GetDestinationFolder, dest));

  // The controller should synchronously send out a progress status when queued.
  EXPECT_CALL(observer, OnIOTaskStatus(
                            AllOf(Field(&ProgressStatus::state, State::kQueued),
                                  base_matcher)));

  // The controller should also synchronously execute the I/O task, which will
  // send out another status.
  EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                            Field(&ProgressStatus::state, State::kInProgress),
                            base_matcher)));

  auto task_id = io_task_controller_.Add(
      std::make_unique<DummyIOTask>(source_urls, dest, OperationType::kMove));
  EXPECT_FALSE(io_task_controller_.TaskStatuses().empty());
  EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id);

  // Pause should synchronously send a progress status.
  PauseParams pause_params;
  pause_params.policy_params = PolicyPauseParams(policy::Policy::kDlp);
  EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                            Field(&ProgressStatus::state, State::kPaused),
                            Field(&ProgressStatus::task_id, task_id),
                            Field(&ProgressStatus::pause_params, pause_params),
                            base_matcher)));
  io_task_controller_.Pause(task_id, pause_params);
  EXPECT_FALSE(io_task_controller_.TaskStatuses().empty());

  // ProgressPausedTasks should synchronously send another paused status update.
  EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                            Field(&ProgressStatus::state, State::kPaused),
                            Field(&ProgressStatus::task_id, task_id),
                            Field(&ProgressStatus::pause_params, pause_params),
                            base_matcher)));
  io_task_controller_.ProgressPausedTasks();

  // Resume should synchronously send a progress status.
  EXPECT_CALL(observer,
              OnIOTaskStatus(AllOf(
                  Field(&ProgressStatus::state, State::kInProgress),
                  Field(&ProgressStatus::task_id, task_id), base_matcher)));
  io_task_controller_.Resume(task_id, ResumeParams());
  EXPECT_FALSE(io_task_controller_.TaskStatuses().empty());

  // Wait for the task to finish successfully.
  EXPECT_CALL(observer,
              OnIOTaskStatus(AllOf(
                  Field(&ProgressStatus::state, State::kSuccess),
                  Field(&ProgressStatus::task_id, task_id), base_matcher)));
  base::RunLoop().RunUntilIdle();

  // ProgressPausedTasks shouldn't send any more updates after task completes.
  EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                            Field(&ProgressStatus::state, State::kPaused),
                            Field(&ProgressStatus::task_id, task_id),
                            Field(&ProgressStatus::pause_params, pause_params),
                            base_matcher)))
      .Times(0);
  io_task_controller_.ProgressPausedTasks();

  io_task_controller_.RemoveObserver(&observer);
}

TEST_F(IOTaskControllerTest, CompleteWithError) {
  IOTaskStatusObserver observer;
  io_task_controller_.AddObserver(&observer);

  std::vector<storage::FileSystemURL> source_urls{
      CreateFileSystemURL("filesystem:chrome-extension://abc/external/foo/src"),
  };
  auto dest = CreateFileSystemURL(
      "filesystem:chrome-extension://abc/external/foo/dest");

  // All progress statuses should return the same |type|, |source_urls| and
  // |destination_folder| given, so set up a base matcher to check this.
  auto base_matcher =
      AllOf(Field(&ProgressStatus::type, OperationType::kMove),
            Field(&ProgressStatus::sources, EntryStatusUrls(source_urls)),
            Property(&ProgressStatus::GetDestinationFolder, dest));

  // The controller should synchronously send out a progress status when queued.
  EXPECT_CALL(observer, OnIOTaskStatus(
                            AllOf(Field(&ProgressStatus::state, State::kQueued),
                                  base_matcher)));

  // The controller should also synchronously execute the I/O task, which will
  // send out another status.
  EXPECT_CALL(observer, OnIOTaskStatus(AllOf(
                            Field(&ProgressStatus::state, State::kInProgress),
                            base_matcher)));

  auto task_id = io_task_controller_.Add(std::make_unique<DummyIOTask>(
      source_urls, dest, OperationType::kMove,
      /*show_notification=*/true, /*progress_succeeds=*/false));
  EXPECT_FALSE(io_task_controller_.TaskStatuses().empty());
  EXPECT_EQ(io_task_controller_.TaskStatuses()[0].get().task_id, task_id);

  // CompleteWithError should synchronously send a progress status.
  EXPECT_CALL(observer,
              OnIOTaskStatus(AllOf(Field(&ProgressStatus::state, State::kError),
                                   Field(&ProgressStatus::task_id, task_id),
                                   Field(&ProgressStatus::policy_error,
                                         PolicyError(PolicyErrorType::kDlp,
                                                     /*blocked_files=*/2)),
                                   base_matcher)));
  io_task_controller_.CompleteWithError(
      task_id, PolicyError(PolicyErrorType::kDlp, /*blocked_files=*/2));

  EXPECT_TRUE(io_task_controller_.TaskStatuses().empty());

  // No more observer notifications should come after CompleteWithError as the
  // task is deleted.
  base::RunLoop().RunUntilIdle();

  io_task_controller_.RemoveObserver(&observer);
}

}  // namespace
}  // namespace io_task
}  // namespace file_manager