chromium/google_apis/tasks/tasks_api_requests_unittest.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 "google_apis/tasks/tasks_api_requests.h"

#include <memory>

#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/types/expected.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/dummy_auth_service.h"
#include "google_apis/common/request_sender.h"
#include "google_apis/common/test_util.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/gaia_urls_overrider_for_testing.h"
#include "google_apis/tasks/tasks_api_request_types.h"
#include "google_apis/tasks/tasks_api_response_types.h"
#include "google_apis/tasks/tasks_api_task_status.h"
#include "google_apis/tasks/tasks_api_url_generator_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/test/test_shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace google_apis::tasks {
namespace {

constexpr char kTaskListId[] = "random-task-list-id";
constexpr char kTaskId[] = "random-task-id";

}  // namespace

class TasksApiRequestsTest : public testing::Test {
 public:
  TasksApiRequestsTest()
      : test_shared_loader_factory_(
            base::MakeRefCounted<network::TestSharedURLLoaderFactory>(
                /*network_service=*/nullptr,
                /*is_trusted=*/true)) {}

  void SetUp() override {
    request_sender_ = std::make_unique<RequestSender>(
        std::make_unique<DummyAuthService>(), test_shared_loader_factory_,
        task_environment_.GetMainThreadTaskRunner(), "test-user-agent",
        TRAFFIC_ANNOTATION_FOR_TESTS);

    test_server_.RegisterRequestHandler(base::BindRepeating(
        &TasksApiRequestsTest::HandleDataFileRequest, base::Unretained(this)));
    ASSERT_TRUE(test_server_.Start());

    gaia_urls_overrider_ = std::make_unique<GaiaUrlsOverriderForTesting>(
        base::CommandLine::ForCurrentProcess(), "tasks_api_origin_url",
        test_server_.base_url().spec());
    ASSERT_EQ(GaiaUrls::GetInstance()->tasks_api_origin_url(),
              test_server_.base_url().spec());
  }

  RequestSender* request_sender() { return request_sender_.get(); }
  net::test_server::HttpRequest last_request() { return last_request_; }
  void set_test_file_path(const std::string& test_file_path) {
    test_file_path_ = test_file_path;
  }

 private:
  std::unique_ptr<net::test_server::HttpResponse> HandleDataFileRequest(
      const net::test_server::HttpRequest& request) {
    last_request_ = request;
    return test_util::CreateHttpResponseFromFile(
        test_util::GetTestFilePath(test_file_path_));
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::MainThreadType::IO};
  net::EmbeddedTestServer test_server_;
  std::unique_ptr<RequestSender> request_sender_;
  scoped_refptr<network::TestSharedURLLoaderFactory>
      test_shared_loader_factory_;

  std::unique_ptr<GaiaUrlsOverriderForTesting> gaia_urls_overrider_;
  net::test_server::HttpRequest last_request_;
  std::string test_file_path_;
};

TEST_F(TasksApiRequestsTest, ListTaskListsRequest) {
  set_test_file_path("tasks/task_lists.json");

  base::test::TestFuture<
      base::expected<std::unique_ptr<TaskLists>, ApiErrorCode>>
      future;
  auto request = std::make_unique<ListTaskListsRequest>(
      request_sender(), /*page_token=*/"", future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(last_request().method, net::test_server::METHOD_GET);
  EXPECT_EQ(last_request().GetURL(),
            GetListTaskListsUrl(/*max_results=*/100, /*page_token=*/""));
  EXPECT_TRUE(future.Get().value());
  EXPECT_EQ(future.Get().value()->items().size(), 2u);
}

TEST_F(TasksApiRequestsTest, ListTaskListsWithOptionalArgsRequest) {
  set_test_file_path("tasks/task_lists.json");

  base::test::TestFuture<
      base::expected<std::unique_ptr<TaskLists>, ApiErrorCode>>
      future;
  auto request = std::make_unique<ListTaskListsRequest>(
      request_sender(), /*page_token=*/"qwerty", future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(last_request().method, net::test_server::METHOD_GET);
  EXPECT_EQ(last_request().GetURL(),
            GetListTaskListsUrl(/*max_results=*/100, /*page_token=*/"qwerty"));
  EXPECT_TRUE(future.Get().value());
  EXPECT_EQ(future.Get().value()->items().size(), 2u);
}

TEST_F(TasksApiRequestsTest, ListTaskListsRequestHandlesError) {
  set_test_file_path("tasks/invalid_file_to_simulate_404_error.json");

  base::test::TestFuture<
      base::expected<std::unique_ptr<TaskLists>, ApiErrorCode>>
      future;
  auto request = std::make_unique<ListTaskListsRequest>(request_sender(),
                                                        /*page_token=*/"",
                                                        future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_THAT(future.Get(), base::test::ErrorIs(HTTP_NOT_FOUND));
}

TEST_F(TasksApiRequestsTest, ListTasksRequest) {
  set_test_file_path("tasks/tasks.json");

  base::test::TestFuture<base::expected<std::unique_ptr<Tasks>, ApiErrorCode>>
      future;
  auto request = std::make_unique<ListTasksRequest>(
      request_sender(), kTaskListId, /*page_token=*/"",
      /*include_assigned=*/false, future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(last_request().method, net::test_server::METHOD_GET);
  EXPECT_EQ(last_request().GetURL(),
            GetListTasksUrl(kTaskListId, /*include_completed=*/false,
                            /*include_assigned=*/false,
                            /*max_results=*/100,
                            /*page_token=*/""));
  EXPECT_TRUE(future.Get().value());
  EXPECT_EQ(future.Get().value()->items().size(), 2u);
}

TEST_F(TasksApiRequestsTest, ListTasksWithOptionalArgsRequest) {
  set_test_file_path("tasks/tasks.json");

  base::test::TestFuture<base::expected<std::unique_ptr<Tasks>, ApiErrorCode>>
      future;
  auto request = std::make_unique<ListTasksRequest>(
      request_sender(), kTaskListId, /*page_token=*/"qwerty",
      /*include_assigned=*/false, future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(last_request().method, net::test_server::METHOD_GET);
  EXPECT_EQ(last_request().GetURL(),
            GetListTasksUrl(kTaskListId, /*include_completed=*/false,
                            /*include_assigned=*/false,
                            /*max_results=*/100,
                            /*page_token=*/"qwerty"));
  EXPECT_TRUE(future.Get().value());
  EXPECT_EQ(future.Get().value()->items().size(), 2u);
}

TEST_F(TasksApiRequestsTest, ListTasksRequestHandlesError) {
  set_test_file_path("tasks/invalid_file_to_simulate_404_error.json");

  base::test::TestFuture<base::expected<std::unique_ptr<Tasks>, ApiErrorCode>>
      future;
  auto request = std::make_unique<ListTasksRequest>(
      request_sender(), kTaskListId, /*page_token=*/"",
      /*include_assigned=*/false, future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_THAT(future.Get(), base::test::ErrorIs(HTTP_NOT_FOUND));
}

TEST_F(TasksApiRequestsTest, PatchTaskRequest) {
  set_test_file_path("tasks/task.json");

  base::test::TestFuture<base::expected<std::unique_ptr<Task>, ApiErrorCode>>
      future;
  auto request = std::make_unique<PatchTaskRequest>(
      request_sender(), kTaskListId, kTaskId,
      TaskRequestPayload{.status = TaskStatus::kCompleted},
      future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_TRUE(future.Get().has_value());
  EXPECT_EQ(last_request().method, net::test_server::METHOD_PATCH);
  EXPECT_EQ(last_request().GetURL(), GetPatchTaskUrl(kTaskListId, kTaskId));
  EXPECT_EQ(last_request().headers.at("Content-Type"),
            "application/json; charset=utf-8");
  EXPECT_EQ(last_request().content, "{\"status\":\"completed\"}");
}

TEST_F(TasksApiRequestsTest, PatchTaskRequestHandlesError) {
  set_test_file_path("tasks/invalid_file_to_simulate_404_error.json");

  base::test::TestFuture<base::expected<std::unique_ptr<Task>, ApiErrorCode>>
      future;
  auto request = std::make_unique<PatchTaskRequest>(
      request_sender(), kTaskListId, kTaskId,
      TaskRequestPayload{.status = TaskStatus::kCompleted},
      future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_THAT(future.Get(), base::test::ErrorIs(HTTP_NOT_FOUND));
}

TEST_F(TasksApiRequestsTest, InsertTaskRequest) {
  set_test_file_path("tasks/task.json");

  base::test::TestFuture<base::expected<std::unique_ptr<Task>, ApiErrorCode>>
      future;
  auto request = std::make_unique<InsertTaskRequest>(
      request_sender(), kTaskListId, /*previous_task_id=*/"",
      TaskRequestPayload{.title = "New task",
                         .status = TaskStatus::kNeedsAction},
      future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_EQ(last_request().method, net::test_server::METHOD_POST);
  EXPECT_EQ(last_request().GetURL(),
            GetInsertTaskUrl(kTaskListId, /*previous_task_id=*/""));
  EXPECT_EQ(last_request().headers.at("Content-Type"),
            "application/json; charset=utf-8");
  EXPECT_EQ(last_request().content,
            "{\"status\":\"needsAction\",\"title\":\"New task\"}");

  ASSERT_TRUE(future.Get().has_value());
  EXPECT_EQ(future.Get().value()->id(), "qwe");
}

TEST_F(TasksApiRequestsTest, InsertTaskRequestHandlesError) {
  set_test_file_path("tasks/invalid_file_to_simulate_404_error.json");

  base::test::TestFuture<base::expected<std::unique_ptr<Task>, ApiErrorCode>>
      future;
  auto request = std::make_unique<InsertTaskRequest>(
      request_sender(), kTaskListId, /*previous_task_id=*/"",
      TaskRequestPayload{.title = "New task",
                         .status = TaskStatus::kNeedsAction},
      future.GetCallback());
  request_sender()->StartRequestWithAuthRetry(std::move(request));
  ASSERT_TRUE(future.Wait());

  EXPECT_THAT(future.Get(), base::test::ErrorIs(HTTP_NOT_FOUND));
}

}  // namespace google_apis::tasks