blob: 816dc07f472a9a76d755d0637af55896ef06fe7a [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/httpclient/http_client.h"
#include <iostream>
#include <ostream>
#include <string>
#include <type_traits>
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/url_loader_completion_status.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 "testing/gtest/include/gtest/gtest.h"
namespace httpclient {
using ResponseProduceFlags =
network::TestURLLoaderFactory::ResponseProduceFlags;
struct TestHttpHeaders {
explicit TestHttpHeaders(std::vector<std::string> header_keys_input = {},
std::vector<std::string> header_values_input = {})
: header_keys(std::move(header_keys_input)),
header_values(std::move(header_values_input)) {}
std::vector<std::string> header_keys;
std::vector<std::string> header_values;
bool operator==(const TestHttpHeaders& other) const {
return std::equal(header_keys.begin(), header_keys.end(),
other.header_keys.begin()) &&
std::equal(header_values.begin(), header_values.end(),
other.header_values.begin());
}
friend std::ostream& operator<<(std::ostream& os,
const TestHttpHeaders& header);
};
std::ostream& operator<<(std::ostream& os, const TestHttpHeaders& header) {
os << "header_keys=[";
for (auto& key : header.header_keys) {
os << ' ' << key;
}
os << " ] "
<< "header_values=[";
for (auto& key : header.header_values) {
os << ' ' << key;
}
os << " ]";
return os;
}
struct TestHttpRequest : TestHttpHeaders {
explicit TestHttpRequest(std::string url_input,
std::string request_type = "POST",
std::string request_body = "",
std::vector<std::string> request_header_keys = {},
std::vector<std::string> request_header_values = {})
: TestHttpHeaders(request_header_keys, request_header_values),
url(url_input),
type(request_type),
body(request_body) {}
std::string url;
std::string type;
std::string body;
};
struct TestHttpResponse : TestHttpHeaders {
explicit TestHttpResponse(
int32_t response_code = net::HTTP_OK,
int32_t net_error_code_input = 0,
std::string response_body = "",
std::vector<std::string> response_header_keys = {},
std::vector<std::string> response_header_values = {})
: TestHttpHeaders(response_header_keys, response_header_values),
code(response_code),
net_error_code(net_error_code_input),
body(response_body) {}
int32_t code;
int32_t net_error_code;
std::string body;
bool operator==(const TestHttpResponse& other) const {
return TestHttpHeaders::operator==(other) && code == other.code &&
net_error_code == other.net_error_code && !body.compare(other.body);
}
friend std::ostream& operator<<(std::ostream& os,
const TestHttpResponse& response);
};
std::ostream& operator<<(std::ostream& os, const TestHttpResponse& response) {
os << "response_code=[" << base::NumberToString(response.code) << "] "
<< "net_error_code=[" << base::NumberToString(response.net_error_code)
<< "] "
<< "body=[" << response.body << "] "
<< static_cast<TestHttpHeaders>(response);
return os;
}
class MockResponseDoneCallback {
public:
MockResponseDoneCallback() = default;
void Done(int32_t response_code,
int32_t net_error_code,
std::vector<uint8_t>&& response_bytes,
std::vector<std::string>&& input_response_header_keys,
std::vector<std::string>&& input_response_header_values) {
EXPECT_FALSE(has_run);
has_run = true;
response = TestHttpResponse(
response_code, net_error_code,
std::string(response_bytes.begin(), response_bytes.end()),
std::move(input_response_header_keys),
std::move(input_response_header_values));
}
bool has_run{false};
TestHttpResponse response;
};
class HttpClientTest : public testing::Test {
public:
HttpClientTest(const HttpClientTest&) = delete;
HttpClientTest& operator=(const HttpClientTest&) = delete;
protected:
HttpClientTest() = default;
~HttpClientTest() override = default;
void SetUp() override {
shared_url_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
http_client_ = std::make_unique<HttpClient>(shared_url_loader_factory_);
}
void DestroyService() { http_client_.reset(); }
HttpClient* service() { return http_client_.get(); }
// Helper to check the next request for the network.
network::TestURLLoaderFactory::PendingRequest* GetLastPendingRequest() {
EXPECT_GT(test_url_loader_factory_.pending_requests()->size(), 0U)
<< "No pending request!";
network::TestURLLoaderFactory::PendingRequest* request =
&(test_url_loader_factory_.pending_requests()->back());
return request;
}
void Respond(
const GURL& url,
TestHttpResponse response,
network::URLLoaderCompletionStatus status =
network::URLLoaderCompletionStatus(),
ResponseProduceFlags flag = ResponseProduceFlags::kResponseDefault) {
auto head = network::mojom::URLResponseHead::New();
if (response.code >= 0) {
head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
head->headers->ReplaceStatusLine("HTTP/1.1 " +
base::NumberToString(response.code));
for (size_t i = 0; i < response.header_keys.size(); ++i) {
head->headers->SetHeader(response.header_keys[i],
response.header_values[i]);
}
status.decoded_body_length = response.body.length();
}
test_url_loader_factory_.AddResponse(
url, std::move(head), response.body, status,
network::TestURLLoaderFactory::Redirects(), flag);
task_environment_.FastForwardUntilNoTasksRemain();
}
void SendRequest(TestHttpRequest request,
MockResponseDoneCallback* done_callback) {
GURL req_url(request.url);
std::vector<uint8_t> request_body_bytes(request.body.begin(),
request.body.end());
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&HttpClient::Send, base::Unretained(service()), req_url,
request.type, std::move(request_body_bytes),
std::move(request.header_keys), std::move(request.header_values),
net::NetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS),
base::BindOnce(&MockResponseDoneCallback::Done,
base::Unretained(done_callback))));
task_environment_.RunUntilIdle();
}
void SendRequestAndRespond(TestHttpRequest request,
TestHttpResponse response,
network::URLLoaderCompletionStatus status,
MockResponseDoneCallback* done_callback) {
SendRequest(request, done_callback);
Respond(GURL(request.url), response, status);
}
void SendEmptyRequestAndResponseNoHeaders(
std::string request_url,
MockResponseDoneCallback* done_callback) {
SendRequestAndRespond(TestHttpRequest(request_url), TestHttpResponse(),
network::URLLoaderCompletionStatus(), done_callback);
}
void SendRequestAndValidateResponse(
const TestHttpRequest& request,
const TestHttpResponse& response,
network::URLLoaderCompletionStatus status =
network::URLLoaderCompletionStatus()) {
MockResponseDoneCallback done_callback;
SendRequestAndRespond(request, response, status, &done_callback);
EXPECT_TRUE(done_callback.has_run);
EXPECT_EQ(done_callback.response, response);
}
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
private:
std::unique_ptr<HttpClient> http_client_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
};
TEST_F(HttpClientTest, TestSendEmptyRequest) {
SendRequestAndValidateResponse(TestHttpRequest("http://foobar.com/survey"),
TestHttpResponse());
}
TEST_F(HttpClientTest, TestSendSimpleRequest) {
TestHttpRequest request("http://foobar.com/survey", "POST", "?bar=baz&foo=1",
{"Content-Type"}, {"application/x-protobuf"});
TestHttpResponse response(net::HTTP_OK, 0, "{key:'val'}");
SendRequestAndValidateResponse(request, response);
}
TEST_F(HttpClientTest, TestSendDifferentRequestMethod) {
std::vector<std::string> request_methods({"POST", "PUT", "PATCH"});
for (const auto& method : request_methods) {
MockResponseDoneCallback done_callback;
std::string content_type = "application/x-protobuf";
TestHttpRequest request("http://foobar.com/survey", method, "request_body",
{"Content-Type", "TestMethod"},
{content_type, method});
SendRequest(request, &done_callback);
{
auto pendingRequest = GetLastPendingRequest()->request;
EXPECT_EQ(pendingRequest.method, method);
std::string content_type_val, method_val;
pendingRequest.headers.GetHeader("TestMethod", &method_val);
pendingRequest.headers.GetHeader("Content-Type", &content_type_val);
EXPECT_EQ(content_type_val, content_type);
EXPECT_EQ(method_val, method);
}
Respond(GURL(request.url), TestHttpResponse());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(done_callback.has_run);
EXPECT_EQ(done_callback.response.code, net::HTTP_OK);
test_url_loader_factory()->ClearResponses();
}
}
TEST_F(HttpClientTest, TestSendMultipleRequests) {
MockResponseDoneCallback done_callback1;
MockResponseDoneCallback done_callback2;
MockResponseDoneCallback done_callback3;
SendEmptyRequestAndResponseNoHeaders("http://foobar.com/survey1",
&done_callback1);
SendEmptyRequestAndResponseNoHeaders("http://foobar.com/survey2",
&done_callback2);
SendEmptyRequestAndResponseNoHeaders("http://foobar.com/survey3",
&done_callback3);
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(done_callback1.has_run);
EXPECT_TRUE(done_callback2.has_run);
EXPECT_TRUE(done_callback3.has_run);
}
TEST_F(HttpClientTest, TestResponseHeader) {
TestHttpRequest request("http://foobar.com/survey");
TestHttpResponse response(
net::HTTP_OK, 0, /*response_body*/ "",
/*response_header_keys*/ {"Foo", "Bar"},
/*response_header_values*/ {"foo_value", "bar_value"});
SendRequestAndValidateResponse(request, response);
}
TEST_F(HttpClientTest, TestCancelRequest) {
MockResponseDoneCallback done_callback;
GURL url("http://foobar.com/survey");
service()->Send(
url, "GET", /*request_body*/ {}, /*header_keys*/ {},
/*header_values*/ {},
net::NetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS),
base::BindOnce(&MockResponseDoneCallback::Done,
base::Unretained(&done_callback)));
DestroyService();
Respond(url, TestHttpResponse());
EXPECT_FALSE(done_callback.has_run);
}
TEST_F(HttpClientTest, TestRequestTimeout) {
MockResponseDoneCallback done_callback;
SendRequest(TestHttpRequest("http://foobar.com/survey"), &done_callback);
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(done_callback.has_run);
EXPECT_EQ(done_callback.response.net_error_code, net::ERR_TIMED_OUT);
}
TEST_F(HttpClientTest, TestHttpError) {
std::vector<net::HttpStatusCode> error_codes(
{net::HTTP_BAD_REQUEST, net::HTTP_UNAUTHORIZED, net::HTTP_FORBIDDEN,
net::HTTP_NOT_FOUND, net::HTTP_INTERNAL_SERVER_ERROR,
net::HTTP_BAD_GATEWAY, net::HTTP_SERVICE_UNAVAILABLE});
for (const auto& code : error_codes) {
MockResponseDoneCallback done_callback;
SendRequestAndRespond(
TestHttpRequest("http://foobar.com/survey"),
TestHttpResponse(code, 0, "error_response_data",
/*response_header_keys*/ {"Foo", "Bar"},
/*response_header_values*/ {"foo_value", "bar_value"}),
network::URLLoaderCompletionStatus(), &done_callback);
// The expected response should have the code override by network error, and
// empty response body, and the same headers.
TestHttpResponse expected_response =
TestHttpResponse(code, net::ERR_HTTP_RESPONSE_CODE_FAILURE, "",
/*response_header_keys*/ {"Foo", "Bar"},
/*response_header_values*/ {"foo_value", "bar_value"});
EXPECT_TRUE(done_callback.has_run);
EXPECT_EQ(done_callback.response, expected_response);
test_url_loader_factory()->ClearResponses();
}
}
TEST_F(HttpClientTest, TestNetworkError) {
std::vector<int32_t> error_codes(
{net::ERR_CERT_COMMON_NAME_INVALID, net::ERR_CERT_DATE_INVALID,
net::ERR_CERT_WEAK_KEY, net::ERR_NAME_RESOLUTION_FAILED});
for (const auto& code : error_codes) {
MockResponseDoneCallback done_callback;
SendRequestAndRespond(
TestHttpRequest("http://foobar.com/survey"),
TestHttpResponse(net::HTTP_OK, 0, "success_response_data",
/*response_header_keys*/ {"Foo", "Bar"},
/*response_header_values*/ {"foo_value", "bar_value"}),
network::URLLoaderCompletionStatus(code), &done_callback);
// The expected response should have the code override by network error, and
// empty response body, and the same headers.
TestHttpResponse expected_response =
TestHttpResponse(0, code, "",
/*response_header_keys*/ {"Foo", "Bar"},
/*response_header_values*/ {"foo_value", "bar_value"});
EXPECT_TRUE(done_callback.has_run);
EXPECT_EQ(done_callback.response, expected_response);
test_url_loader_factory()->ClearResponses();
}
}
TEST_F(HttpClientTest, TestNetworkErrorAfterSendHeaders) {
std::vector<int32_t> error_codes(
{net::ERR_CERT_COMMON_NAME_INVALID, net::ERR_CERT_DATE_INVALID,
net::ERR_CERT_WEAK_KEY, net::ERR_NAME_RESOLUTION_FAILED});
std::string url = "http://foobar.com/survey";
GURL gurl(url);
for (const auto& code : error_codes) {
MockResponseDoneCallback done_callback;
SendRequest(TestHttpRequest(url), &done_callback);
Respond(
gurl,
TestHttpResponse(net::HTTP_OK, 0, "success_response_data",
/*response_header_keys*/ {"Foo", "Bar"},
/*response_header_values*/ {"foo_value", "bar_value"}),
network::URLLoaderCompletionStatus(code),
ResponseProduceFlags::kSendHeadersOnNetworkError);
// The expected response should have the code override by network error, and
// empty response body, and the same headers.
TestHttpResponse expected_response =
TestHttpResponse(net::HTTP_OK, code, "",
/*response_header_keys*/ {"Foo", "Bar"},
/*response_header_values*/ {"foo_value", "bar_value"});
EXPECT_TRUE(done_callback.has_run);
EXPECT_EQ(done_callback.response, expected_response);
test_url_loader_factory()->ClearResponses();
}
}
} // namespace httpclient