chromium/ash/api/tasks/fake_tasks_client.cc

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

#include "ash/api/tasks/fake_tasks_client.h"

#include <list>
#include <memory>
#include <optional>
#include <utility>

#include "ash/api/tasks/tasks_client.h"
#include "ash/api/tasks/tasks_types.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "google_apis/common/api_error_codes.h"
#include "url/gurl.h"

namespace ash::api {
namespace {

size_t RunPendingCallbacks(std::list<base::OnceClosure>& pending_callbacks) {
  std::list<base::OnceClosure> callbacks;
  pending_callbacks.swap(callbacks);
  for (auto& callback : callbacks) {
    std::move(callback).Run();
  }
  return callbacks.size();
}

bool IsHttpErrorSuccess(google_apis::ApiErrorCode http_error) {
  return http_error == google_apis::ApiErrorCode::HTTP_SUCCESS;
}

}  // namespace

FakeTasksClient::FakeTasksClient()
    : task_lists_(std::make_unique<ui::ListModel<TaskList>>()),
      cached_task_lists_(std::make_unique<ui::ListModel<TaskList>>()) {}

FakeTasksClient::~FakeTasksClient() = default;

bool FakeTasksClient::IsDisabledByAdmin() const {
  return is_disabled_by_admin_;
}

const ui::ListModel<api::TaskList>* FakeTasksClient::GetCachedTaskLists() {
  if (cached_task_lists_->item_count() == 0) {
    return nullptr;
  }

  return cached_task_lists_.get();
}

void FakeTasksClient::GetTaskLists(bool force_fetch,
                                   GetTaskListsCallback callback) {
  const bool need_fetch = force_fetch || cached_task_lists_->item_count() == 0;
  auto* task_lists_returned =
      need_fetch ? task_lists_.get() : cached_task_lists_.get();

  if (paused_ || (paused_on_fetch_ && need_fetch)) {
    pending_get_task_lists_callbacks_.push_back(base::BindOnce(
        [](ui::ListModel<TaskList>* task_lists, GetTaskListsCallback callback,
           bool success, std::optional<google_apis::ApiErrorCode> http_error) {
          std::move(callback).Run(success, http_error, task_lists);
        },
        task_lists_returned, std::move(callback), !get_task_lists_error_,
        http_error_));
  } else {
    std::move(callback).Run(/*success=*/!get_task_lists_error_, http_error_,
                            task_lists_returned);
  }
}

const ui::ListModel<api::Task>* FakeTasksClient::GetCachedTasksInTaskList(
    const std::string& task_list_id) {
  // TODO(b/321789067): Update `cached_tasks_` to a map of list id to tasks to
  // adapt more complicated tests.
  if (task_list_id != cached_task_list_id_) {
    return nullptr;
  }

  return cached_tasks_.get();
}

void FakeTasksClient::GetTasks(const std::string& task_list_id,
                               bool force_fetch,
                               GetTasksCallback callback) {
  auto iter = tasks_in_task_lists_.find(task_list_id);
  CHECK(iter != tasks_in_task_lists_.end());

  const bool need_fetch = force_fetch || task_list_id != cached_task_list_id_;
  auto* tasks_returned = need_fetch ? iter->second.get() : cached_tasks_.get();
  cached_task_list_id_ = task_list_id;

  if (paused_ || (paused_on_fetch_ && need_fetch)) {
    pending_get_tasks_callbacks_.push_back(base::BindOnce(
        [](ui::ListModel<Task>* tasks, GetTasksCallback callback, bool success,
           std::optional<google_apis::ApiErrorCode> http_error) {
          std::move(callback).Run(success, http_error, tasks);
        },
        tasks_returned, std::move(callback), !get_tasks_error_, http_error_));
  } else {
    std::move(callback).Run(/*success=*/!get_tasks_error_, http_error_,
                            tasks_returned);
  }
}

void FakeTasksClient::MarkAsCompleted(const std::string& task_list_id,
                                      const std::string& task_id,
                                      bool completed) {
  if (completed) {
    pending_completed_tasks_.push_back(
        base::JoinString({task_list_id, task_id}, ":"));
  } else {
    pending_completed_tasks_.erase(
        base::ranges::find(pending_completed_tasks_,
                           base::JoinString({task_list_id, task_id}, ":")));
  }
}

void FakeTasksClient::AddTask(const std::string& task_list_id,
                              const std::string& title,
                              TasksClient::OnTaskSavedCallback callback) {
  if (paused_) {
    pending_add_task_callbacks_.push_back(
        base::BindOnce(&FakeTasksClient::AddTaskImpl, base::Unretained(this),
                       task_list_id, title, std::move(callback)));
  } else {
    AddTaskImpl(task_list_id, title, std::move(callback));
  }
}

void FakeTasksClient::UpdateTask(const std::string& task_list_id,
                                 const std::string& task_id,
                                 const std::string& title,
                                 bool completed,
                                 TasksClient::OnTaskSavedCallback callback) {
  if (paused_) {
    pending_update_task_callbacks_.push_back(base::BindOnce(
        &FakeTasksClient::UpdateTaskImpl, base::Unretained(this), task_list_id,
        task_id, title, completed, std::move(callback)));
  } else {
    UpdateTaskImpl(task_list_id, task_id, title, completed,
                   std::move(callback));
  }
}

std::optional<base::Time> FakeTasksClient::GetTasksLastUpdateTime(
    const std::string& task_list_id) const {
  return last_updated_time_;
}

void FakeTasksClient::OnGlanceablesBubbleClosed(base::OnceClosure callback) {
  ++bubble_closed_count_;
  RunPendingGetTaskListsCallbacks();
  RunPendingGetTasksCallbacks();
  RunPendingAddTaskCallbacks();
  RunPendingUpdateTaskCallbacks();
  completed_tasks_ += pending_completed_tasks_.size();
  pending_completed_tasks_.clear();
  CacheTaskLists();
  CacheTasks();
  std::move(callback).Run();
}

void FakeTasksClient::AddTaskList(std::unique_ptr<TaskList> task_list_data) {
  CHECK(!base::Contains(*task_lists_, task_list_data->id, &TaskList::id));
  tasks_in_task_lists_.emplace(task_list_data->id,
                               std::make_unique<ui::ListModel<Task>>());
  task_lists_->Add(std::move(task_list_data));
}

void FakeTasksClient::AddTask(const std::string& task_list_id,
                              std::unique_ptr<Task> task_data) {
  auto task_list_iter = tasks_in_task_lists_.find(task_list_id);
  CHECK(task_list_iter != tasks_in_task_lists_.end());

  auto& tasks = task_list_iter->second;
  CHECK(!base::Contains(*tasks, task_data->id, &Task::id));
  tasks->Add(std::move(task_data));
}

void FakeTasksClient::DeleteTaskList(const std::string& task_list_id) {
  // Find the task list iterator with id `task_list_id`.
  auto iter = std::find_if(
      task_lists_->begin(), task_lists_->end(),
      [task_list_id](const auto& list) { return list->id == task_list_id; });
  if (iter == task_lists_->end()) {
    return;
  }

  task_lists_->DeleteAt(iter - task_lists_->begin());
}

void FakeTasksClient::SetTasksLastUpdateTime(base::Time time) {
  last_updated_time_ = time;
}

int FakeTasksClient::GetAndResetBubbleClosedCount() {
  int result = bubble_closed_count_;
  bubble_closed_count_ = 0;
  return result;
}

size_t FakeTasksClient::RunPendingGetTasksCallbacks() {
  return RunPendingCallbacks(pending_get_tasks_callbacks_);
}

size_t FakeTasksClient::RunPendingGetTaskListsCallbacks() {
  return RunPendingCallbacks(pending_get_task_lists_callbacks_);
}

size_t FakeTasksClient::RunPendingAddTaskCallbacks() {
  return RunPendingCallbacks(pending_add_task_callbacks_);
}

size_t FakeTasksClient::RunPendingUpdateTaskCallbacks() {
  return RunPendingCallbacks(pending_update_task_callbacks_);
}

void FakeTasksClient::AddTaskImpl(const std::string& task_list_id,
                                  const std::string& title,
                                  TasksClient::OnTaskSavedCallback callback) {
  CHECK(http_error_);
  if (!IsHttpErrorSuccess(http_error_.value())) {
    // Simulate there is an error when requesting data through the Google Task
    // API.
    std::move(callback).Run(http_error_.value(), /*task=*/nullptr);
    return;
  }

  auto task_list_iter = tasks_in_task_lists_.find(task_list_id);
  CHECK(task_list_iter != tasks_in_task_lists_.end());

  const auto new_task_id = base::Uuid::GenerateRandomV4().AsLowercaseString();
  auto pending_task = std::make_unique<Task>(
      new_task_id, title,
      /*due=*/std::nullopt, /*completed=*/false,
      /*has_subtasks=*/false, /*has_email_link=*/false,
      /*has_notes=*/false,
      /*updated=*/base::Time::Now(),
      /*web_view_link=*/
      GURL(base::StrCat({"https://tasks.google.com/task/", new_task_id})),
      Task::OriginSurfaceType::kRegular);

  const auto* const task = task_list_iter->second->AddAt(
      /*index=*/0, std::move(pending_task));
  // Simulate update the task successfully through the Google Task API.
  std::move(callback).Run(http_error_.value(), task);
}

void FakeTasksClient::UpdateTaskImpl(
    const std::string& task_list_id,
    const std::string& task_id,
    const std::string& title,
    bool completed,
    TasksClient::OnTaskSavedCallback callback) {
  CHECK(http_error_);
  if (!IsHttpErrorSuccess(http_error_.value())) {
    // Simulate there is an error when requesting data through the Google Task
    // API.
    std::move(callback).Run(http_error_.value(), /*task=*/nullptr);
    return;
  }

  auto task_list_iter = tasks_in_task_lists_.find(task_list_id);
  CHECK(task_list_iter != tasks_in_task_lists_.end());

  const auto task_iter = std::find_if(
      task_list_iter->second->begin(), task_list_iter->second->end(),
      [&task_id](const auto& task) { return task->id == task_id; });
  CHECK(task_iter != task_list_iter->second->end());

  Task* task = task_iter->get();
  task->title = title;
  task->completed = completed;
  // Simulate update the task successfully through the Google Task API.
  std::move(callback).Run(http_error_.value(), task);
}

void FakeTasksClient::CacheTaskLists() {
  cached_task_lists_->DeleteAll();
  for (const auto& list : *task_lists_) {
    cached_task_lists_->Add(
        std::make_unique<TaskList>(list->id, list->title, list->updated));
  }
}

void FakeTasksClient::CacheTasks() {
  auto iter = tasks_in_task_lists_.find(cached_task_list_id_);
  if (iter == tasks_in_task_lists_.end()) {
    return;
  }

  if (!cached_tasks_) {
    cached_tasks_ = std::make_unique<ui::ListModel<Task>>();
  } else {
    cached_tasks_->DeleteAll();
  }

  for (const auto& task : *iter->second) {
    cached_tasks_->Add(std::make_unique<Task>(
        task->id, task->title, task->due, task->completed, task->has_subtasks,
        task->has_email_link, task->has_notes, task->updated,
        task->web_view_link, Task::OriginSurfaceType::kRegular));
  }
}

}  // namespace ash::api