chromium/chrome/browser/ash/printing/oauth2/http_exchange_unittest.cc

// Copyright 2022 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/printing/oauth2/http_exchange.h"

#include <string>
#include <utility>

#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/task_environment.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace ash {
namespace {

// The structure holding objects/variables defining HTTP response. The default
// constructor creates correct response returning empty JSON object.
struct HttpExchangeDefinition {
  explicit HttpExchangeDefinition(
      net::HttpStatusCode status = net::HttpStatusCode::HTTP_OK)
      : url("http://a.b/c"),
        response_head(network::CreateURLResponseHead(status)),
        response_content("{}") {
    response_head->headers->SetHeader("Content-Type", "application/json");
  }
  std::string url;
  network::mojom::URLResponseHeadPtr response_head;
  std::string response_content;
  network::URLLoaderCompletionStatus compl_status;
};

class PrintingOAuth2HttpExchangeTest : public testing::Test {
 public:
  PrintingOAuth2HttpExchangeTest()
      : url_loader_factory_(),
        http_exchange_(url_loader_factory_.GetSafeWeakWrapper()) {}
  ~PrintingOAuth2HttpExchangeTest() override {}
  // Helper method calling http_exchange_.Exchange("GET", ...).
  printing::oauth2::StatusCode Exchange(
      HttpExchangeDefinition def,
      net::HttpStatusCode success_code = net::HttpStatusCode::HTTP_OK,
      net::HttpStatusCode error_code = net::HttpStatusCode::HTTP_BAD_REQUEST) {
    url_loader_factory_.AddResponse(GURL(def.url), std::move(def.response_head),
                                    def.response_content, def.compl_status);
    http_exchange_.Exchange(
        "GET", GURL(def.url), printing::oauth2::ContentFormat::kEmpty,
        static_cast<int>(success_code), static_cast<int>(error_code),
        PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
        base::BindOnce(&PrintingOAuth2HttpExchangeTest::ExchangeCallback,
                       base::Unretained(this)));
    task_environment_.RunUntilIdle();
    return callback_status_;
  }
  // This callback is used by the method above.
  void ExchangeCallback(printing::oauth2::StatusCode status) {
    callback_status_ = status;
  }

 protected:
  network::TestURLLoaderFactory url_loader_factory_;
  printing::oauth2::HttpExchange http_exchange_;
  base::test::TaskEnvironment task_environment_;
  printing::oauth2::StatusCode callback_status_;
};

constexpr char kExampleContent[] = R"({
  "arr1": ["v1", "v2"],
  "arr2": ["x1", 12, "x2"],
  "int": 123,
  "url1": "http://a:123/b",
  "url2": "https://abc.de/12",
  "str1": "123",
  "str2": "abc",
  "emptyStr": "" })";

TEST_F(PrintingOAuth2HttpExchangeTest, ConnectionError) {
  HttpExchangeDefinition def;
  def.compl_status.error_code = -100;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kConnectionError);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 0);
}

TEST_F(PrintingOAuth2HttpExchangeTest, ServerError) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_INTERNAL_SERVER_ERROR);
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kServerError);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 500 /*HTTP_INTERNAL_SERVER_ERROR*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, ServerTemporarilyUnavailable) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_SERVICE_UNAVAILABLE);
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status,
            printing::oauth2::StatusCode::kServerTemporarilyUnavailable);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 503 /*HTTP_SERVICE_UNAVAILABLE*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, InvalidResponseUnknownStatus) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_NO_CONTENT);
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kInvalidResponse);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 204 /*HTTP_NO_CONTENT*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, InvalidResponseNoJson) {
  HttpExchangeDefinition def;
  def.response_content = "abcdef";
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kInvalidResponse);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 200 /*HTTP_OK*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, InvalidResponseNoErrorField) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_BAD_REQUEST);
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kInvalidResponse);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 400 /*HTTP_BAD_REQUEST*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, InvalidAccessToken) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_BAD_REQUEST);
  def.response_content = "{\"error\": \"invalid_grant\" }";
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kInvalidAccessToken);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 400 /*HTTP_BAD_REQUEST*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, AccessDenied) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_BAD_REQUEST);
  def.response_content = "{\"error\": \"whatever\" }";
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kAccessDenied);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 400 /*HTTP_BAD_REQUEST*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, CorrectEmptyResponse) {
  HttpExchangeDefinition def;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  EXPECT_TRUE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 200 /*HTTP_OK*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, NonStandardHttpStatus1) {
  HttpExchangeDefinition def;
  auto status = Exchange(std::move(def), net::HttpStatusCode::HTTP_NO_CONTENT);
  EXPECT_EQ(status, printing::oauth2::StatusCode::kInvalidResponse);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 200 /*HTTP_OK*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, NonStandardHttpStatus2) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_NO_CONTENT);
  auto status = Exchange(std::move(def), net::HttpStatusCode::HTTP_NO_CONTENT);
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  EXPECT_TRUE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 204 /*HTTP_NO_CONTENT*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, NonStandardHttpStatus3) {
  HttpExchangeDefinition def;
  def.response_content = "{\"error\": \"whatever\" }";
  auto status = Exchange(std::move(def), net::HttpStatusCode::HTTP_NO_CONTENT,
                         net::HttpStatusCode::HTTP_OK);
  // Obtained HTTP status (200) means error message in this case.
  EXPECT_EQ(status, printing::oauth2::StatusCode::kAccessDenied);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 200 /*HTTP_OK*/);
}

TEST_F(PrintingOAuth2HttpExchangeTest, Clear) {
  HttpExchangeDefinition def(net::HttpStatusCode::HTTP_BAD_REQUEST);
  def.response_content = "{\"aaa\": \"bbb\" }";
  auto status = Exchange(std::move(def));
  // Obtained HTTP status (400) means error message. It is invalid because the
  // field "error" is missing.
  EXPECT_EQ(status, printing::oauth2::StatusCode::kInvalidResponse);
  EXPECT_FALSE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 400 /*HTTP_BAD_REQUEST*/);
  std::string aaa;
  EXPECT_TRUE(http_exchange_.ParamStringGet("aaa", true, &aaa));
  EXPECT_EQ(aaa, "bbb");
  http_exchange_.Clear();
  EXPECT_TRUE(http_exchange_.GetErrorMessage().empty());
  EXPECT_EQ(http_exchange_.GetHttpStatus(), 0);
  aaa.clear();
  EXPECT_FALSE(http_exchange_.ParamStringGet("aaa", true, &aaa));
  EXPECT_EQ(aaa, "");
}

TEST_F(PrintingOAuth2HttpExchangeTest, MissingResponseParam) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // Missing parameter is an error <=> `required` == true
  EXPECT_FALSE(http_exchange_.ParamArrayStringContains("miss1", true, "123"));
  EXPECT_TRUE(http_exchange_.ParamArrayStringContains("miss1", false, "123"));
  EXPECT_FALSE(http_exchange_.ParamArrayStringEquals("miss2", true, {}));
  EXPECT_TRUE(http_exchange_.ParamArrayStringEquals("miss2", false, {}));
  std::string value = "abc123";
  EXPECT_FALSE(http_exchange_.ParamStringGet("miss3", true, &value));
  EXPECT_TRUE(http_exchange_.ParamStringGet("miss3", false, &value));
  EXPECT_EQ(value, "abc123");
  EXPECT_FALSE(http_exchange_.ParamStringEquals("miss4", true, "123"));
  EXPECT_TRUE(http_exchange_.ParamStringEquals("miss4", false, "123"));
  GURL url("http://abc123/d");
  EXPECT_FALSE(http_exchange_.ParamURLGet("miss5", true, &url));
  EXPECT_TRUE(http_exchange_.ParamURLGet("miss5", false, &url));
  EXPECT_EQ(url.spec(), "http://abc123/d");
  EXPECT_FALSE(http_exchange_.ParamURLEquals("miss6", true, url));
  EXPECT_TRUE(http_exchange_.ParamURLEquals("miss6", false, url));
}

class ParamRequired : public PrintingOAuth2HttpExchangeTest,
                      public testing::WithParamInterface<bool> {};

TEST_P(ParamRequired, ResponseParamTypeMismatch) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // The type mismatch is always an error, even if `required` == false.
  const bool req = GetParam();
  EXPECT_FALSE(http_exchange_.ParamArrayStringContains("int", req, "123"));
  EXPECT_FALSE(http_exchange_.ParamArrayStringContains("str1", req, "123"));
  EXPECT_FALSE(http_exchange_.ParamArrayStringEquals("int", req, {"123"}));
  EXPECT_FALSE(http_exchange_.ParamArrayStringEquals("str1", req, {"123"}));
  std::string value = "abc123";
  EXPECT_FALSE(http_exchange_.ParamStringGet("arr1", req, &value));
  EXPECT_FALSE(http_exchange_.ParamStringGet("int", req, &value));
  EXPECT_EQ(value, "abc123");
  EXPECT_FALSE(http_exchange_.ParamStringEquals("arr1", req, "v0"));
  EXPECT_FALSE(http_exchange_.ParamStringEquals("int", req, "v0"));
  GURL url("http://abc123/d");
  EXPECT_FALSE(http_exchange_.ParamURLGet("arr2", req, &url));
  EXPECT_FALSE(http_exchange_.ParamURLGet("str2", req, &url));
  EXPECT_EQ(url.spec(), "http://abc123/d");
  EXPECT_FALSE(http_exchange_.ParamURLEquals("arr1", req, url));
  EXPECT_FALSE(http_exchange_.ParamURLEquals("str1", req, url));
}

TEST_P(ParamRequired, ParamArrayStringContains) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // If the param exists, these methods work the same way for any `required`.
  const bool req = GetParam();
  EXPECT_TRUE(http_exchange_.ParamArrayStringContains("arr1", req, "v1"));
  EXPECT_FALSE(http_exchange_.ParamArrayStringContains("arr1", req, "v0"));
  EXPECT_TRUE(http_exchange_.ParamArrayStringContains("arr2", req, "x2"));
  EXPECT_FALSE(http_exchange_.ParamArrayStringContains("arr2", req, "12"));
}

TEST_P(ParamRequired, ParamArrayStringEquals) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // If the param exists, these methods work the same way for any `required`.
  const bool req = GetParam();
  EXPECT_FALSE(
      http_exchange_.ParamArrayStringEquals("arr1", req, {"v2", "v1"}));
  EXPECT_TRUE(http_exchange_.ParamArrayStringEquals("arr1", req, {"v1", "v2"}));
  EXPECT_FALSE(
      http_exchange_.ParamArrayStringEquals("arr2", req, {"x1", "12", "x2"}));
}

TEST_P(ParamRequired, ParamStringGet) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // If the param exists and is non-empty, this method works the same way for
  // any `required`.
  const bool req = GetParam();
  std::string value;
  EXPECT_TRUE(http_exchange_.ParamStringGet("str1", req, &value));
  EXPECT_EQ(value, "123");
  EXPECT_TRUE(http_exchange_.ParamStringGet("str2", req, nullptr));
  // Empty string is not allowed when the parameter is required.
  const bool out = http_exchange_.ParamStringGet("emptyStr", req, &value);
  if (req) {
    EXPECT_FALSE(out);
    EXPECT_EQ(value, "123");  // the previous value was preserved
  } else {
    EXPECT_TRUE(out);
    EXPECT_EQ(value, "");
  }
}

TEST_P(ParamRequired, ParamStringEquals) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // If the param exists, these methods work the same way for any `required`.
  EXPECT_FALSE(http_exchange_.ParamStringEquals("str1", GetParam(), "abc"));
  EXPECT_TRUE(http_exchange_.ParamStringEquals("str1", GetParam(), "123"));
}

TEST_P(ParamRequired, ParamURLGet) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // If the param exists, these methods work the same way for any `required`.
  GURL value;
  EXPECT_TRUE(http_exchange_.ParamURLGet("url2", GetParam(), &value));
  EXPECT_EQ(value.spec(), "https://abc.de/12");
  EXPECT_TRUE(http_exchange_.ParamURLGet("url2", GetParam(), nullptr));
  EXPECT_FALSE(http_exchange_.ParamURLGet("url1", GetParam(), &value));
}

TEST_P(ParamRequired, ParamURLEquals) {
  HttpExchangeDefinition def;
  def.response_content = kExampleContent;
  auto status = Exchange(std::move(def));
  EXPECT_EQ(status, printing::oauth2::StatusCode::kOK);
  // If the param exists, these methods work the same way for any `required`.
  const bool req = GetParam();
  EXPECT_TRUE(
      http_exchange_.ParamURLEquals("url2", req, GURL("https://abc.de/12")));
  EXPECT_FALSE(
      http_exchange_.ParamURLEquals("url2", req, GURL("https://abc.de")));
  EXPECT_TRUE(
      http_exchange_.ParamURLEquals("url1", req, GURL("http://a:123/b")));
}

INSTANTIATE_TEST_SUITE_P(PrintingOAuth2HttpExchangeWithParamTest,
                         ParamRequired,
                         testing::Values(true, false));

}  // namespace
}  // namespace ash