blob: 745bffca667fbdeaedc13e3e4b42b5b24198bbe8 [file] [log] [blame]
// Copyright 2017 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 "services/network/public/cpp/simple_url_loader.h"
#include <stdint.h>
#include <list>
#include <string>
#include <utility>
#include <vector>
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_task_environment.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/redirect_info.h"
#include "services/network/network_service.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace network {
namespace {
// Server path that returns a response containing as many a's as are specified
// in the query part of the URL.
const char kResponseSizePath[] = "/response-size";
// Server path that returns a gzip response with a non-gzipped body.
const char kInvalidGzipPath[] = "/invalid-gzip";
// Server path that returns truncated response (Content-Length less than body
// size).
const char kTruncatedBodyPath[] = "/truncated-body";
// The body of the truncated response (After truncation).
const char kTruncatedBody[] = "Truncated Body";
// Server path returns a 5xx error once, then returns the request body.
const char kFailOnceThenEchoBody[] = "/fail-once-then-echo-body";
// Used in string upload tests.
const char kShortUploadBody[] =
"Though this upload be but little, it is fierce.";
// Returns a string longer than
// SimpleURLLoader::kMaxUploadStringAsStringLength, to test the path where
// strings are streamed to the URLLoader.
std::string GetLongUploadBody() {
std::string long_string;
long_string.reserve(SimpleURLLoader::kMaxUploadStringSizeToCopy);
while (long_string.length() <= SimpleURLLoader::kMaxUploadStringSizeToCopy) {
long_string.append(kShortUploadBody);
}
return long_string;
}
// Class to make it easier to start a SimpleURLLoader, wait for it to complete,
// and check the result.
class SimpleLoaderTestHelper : public SimpleURLLoaderStreamConsumer {
public:
// What the response should be downloaded to. Running all tests for all types
// is more than strictly needed, but simplest just to cover all cases.
enum class DownloadType { TO_STRING, TO_FILE, TO_TEMP_FILE, AS_STREAM };
explicit SimpleLoaderTestHelper(
std::unique_ptr<network::ResourceRequest> resource_request,
DownloadType download_type)
: download_type_(download_type),
simple_url_loader_(
SimpleURLLoader::Create(std::move(resource_request),
TRAFFIC_ANNOTATION_FOR_TESTS)) {
// Create a desistination directory, if downloading to a file.
if (download_type_ == DownloadType::TO_FILE) {
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
dest_path_ = temp_dir_.GetPath().AppendASCII("foo");
}
}
~SimpleLoaderTestHelper() override {
base::ScopedAllowBlockingForTesting allow_blocking;
if (temp_dir_.IsValid())
EXPECT_TRUE(temp_dir_.Delete());
if (!on_destruction_callback_.is_null())
std::move(on_destruction_callback_).Run();
}
// File path that will be written to.
const base::FilePath& dest_path() const {
DCHECK_EQ(DownloadType::TO_FILE, download_type_);
return dest_path_;
}
// Starts a SimpleURLLoader using the method corresponding to the
// DownloadType, but does not wait for it to complete. The default
// |max_body_size| of -1 means don't use a max body size (Use
// DownloadToStringOfUnboundedSizeUntilCrashAndDie for string downloads, and
// don't specify a size for other types of downloads).
void StartSimpleLoader(network::mojom::URLLoaderFactory* url_loader_factory,
int64_t max_body_size = -1) {
EXPECT_FALSE(done_);
switch (download_type_) {
case DownloadType::TO_STRING:
if (max_body_size < 0) {
simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory,
base::BindOnce(&SimpleLoaderTestHelper::DownloadedToString,
base::Unretained(this)));
} else {
simple_url_loader_->DownloadToString(
url_loader_factory,
base::BindOnce(&SimpleLoaderTestHelper::DownloadedToString,
base::Unretained(this)),
max_body_size);
}
break;
case DownloadType::TO_FILE:
if (max_body_size < 0) {
simple_url_loader_->DownloadToFile(
url_loader_factory,
base::BindOnce(&SimpleLoaderTestHelper::DownloadedToFile,
base::Unretained(this)),
dest_path_);
} else {
simple_url_loader_->DownloadToFile(
url_loader_factory,
base::BindOnce(&SimpleLoaderTestHelper::DownloadedToFile,
base::Unretained(this)),
dest_path_, max_body_size);
}
break;
case DownloadType::TO_TEMP_FILE:
if (max_body_size < 0) {
simple_url_loader_->DownloadToTempFile(
url_loader_factory,
base::BindOnce(&SimpleLoaderTestHelper::DownloadedToFile,
base::Unretained(this)));
} else {
simple_url_loader_->DownloadToTempFile(
url_loader_factory,
base::BindOnce(&SimpleLoaderTestHelper::DownloadedToFile,
base::Unretained(this)),
max_body_size);
}
break;
case DownloadType::AS_STREAM:
// Downloading to stream doesn't support a max body size.
DCHECK_LT(max_body_size, 0);
simple_url_loader_->DownloadAsStream(url_loader_factory, this);
break;
}
}
// Starts the SimpleURLLoader waits for completion.
void StartSimpleLoaderAndWait(
network::mojom::URLLoaderFactory* url_loader_factory,
int64_t max_body_size = -1) {
StartSimpleLoader(url_loader_factory, max_body_size);
Wait();
}
// Waits until the request is completed. Automatically called by
// StartSimpleLoaderAndWait, but exposed so some tests can start the
// SimpleURLLoader directly.
void Wait() { run_loop_.Run(); }
// Sets whether a file should still exists on download-to-file errors.
// Defaults to false.
void set_expect_path_exists_on_error(bool expect_path_exists_on_error) {
EXPECT_EQ(DownloadType::TO_FILE, download_type_);
expect_path_exists_on_error_ = expect_path_exists_on_error;
}
// Sets whether reading is resumed asynchronously when downloading as a
// stream. Defaults to false.
void set_download_to_stream_async_resume(
bool download_to_stream_async_resume) {
download_to_stream_async_resume_ = download_to_stream_async_resume;
}
// Sets whether the helper should destroy the SimpleURLLoader in
// OnDataReceived.
void set_download_to_stream_destroy_on_data_received(
bool download_to_stream_destroy_on_data_received) {
download_to_stream_destroy_on_data_received_ =
download_to_stream_destroy_on_data_received;
}
// Sets whether retrying is done asynchronously when downloading as a stream.
// Defaults to false.
void set_download_to_stream_async_retry(bool download_to_stream_async_retry) {
download_to_stream_async_retry_ = download_to_stream_async_retry;
}
// Sets whether the helper should destroy the SimpleURLLoader in OnRetry.
void set_download_to_stream_destroy_on_retry(
bool download_to_stream_destroy_on_retry) {
download_to_stream_destroy_on_retry_ = download_to_stream_destroy_on_retry;
}
// Sets whether the SimpleURLLoader should be destroyed when invoking the
// completion callback. When enabled, it will be destroyed before touching the
// completion data, to make sure it's still available after the destruction of
// the SimpleURLLoader.
void set_destroy_loader_on_complete(bool destroy_loader_on_complete) {
destroy_loader_on_complete_ = destroy_loader_on_complete;
}
// Received response body, if any. Returns nullptr if no body was received
// (Which is different from a 0-length body). For DownloadType::TO_STRING,
// this is just the value passed to the callback. For DownloadType::TO_FILE,
// it is nullptr if an empty FilePath was passed to the callback, or the
// contents of the file, otherwise.
const std::string* response_body() const {
EXPECT_TRUE(done_);
return response_body_.get();
}
// Returns true if the callback has been invoked.
bool done() const { return done_; }
SimpleURLLoader* simple_url_loader() { return simple_url_loader_.get(); }
// Destroys the SimpleURLLoader. Useful in tests where the SimpleURLLoader is
// destroyed while it still has an open file, as the file needs to be closed
// before the SimpleLoaderTestHelper's destructor tries to clean up the temp
// directory.
void DestroySimpleURLLoader() { simple_url_loader_.reset(); }
void SetAllowPartialResults(bool allow_partial_results) {
simple_url_loader_->SetAllowPartialResults(allow_partial_results);
allow_http_error_results_ = allow_partial_results;
}
// Returns the HTTP response code. Fails if there isn't one.
int GetResponseCode() const {
EXPECT_TRUE(done_);
if (!simple_url_loader_->ResponseInfo()) {
ADD_FAILURE() << "No response info.";
return -1;
}
if (!simple_url_loader_->ResponseInfo()->headers) {
ADD_FAILURE() << "No response headers.";
return -1;
}
return simple_url_loader_->ResponseInfo()->headers->response_code();
}
// Returns the number of retries. Number of retries are only exposed when
// downloading as a stream.
int download_as_stream_retries() const {
DCHECK_EQ(DownloadType::AS_STREAM, download_type_);
return download_as_stream_retries_;
}
private:
void DownloadedToString(std::unique_ptr<std::string> response_body) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EXPECT_FALSE(done_);
EXPECT_EQ(DownloadType::TO_STRING, download_type_);
EXPECT_FALSE(response_body_);
if (destroy_loader_on_complete_)
simple_url_loader_.reset();
response_body_ = std::move(response_body);
done_ = true;
run_loop_.Quit();
}
void DownloadedToFile(base::FilePath file_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EXPECT_FALSE(done_);
EXPECT_TRUE(download_type_ == DownloadType::TO_FILE ||
download_type_ == DownloadType::TO_TEMP_FILE);
EXPECT_FALSE(response_body_);
if (destroy_loader_on_complete_)
simple_url_loader_.reset();
base::ScopedAllowBlockingForTesting allow_blocking;
if (!file_path.empty()) {
EXPECT_TRUE(base::PathExists(file_path));
response_body_ = std::make_unique<std::string>();
EXPECT_TRUE(base::ReadFileToString(file_path, response_body_.get()));
}
// Can do some additional checks in the TO_FILE case. Unfortunately, in the
// temp file case, can't check that temp files were cleaned up, since it's
// a shared directory.
if (download_type_ == DownloadType::TO_FILE) {
// Make sure the destination file exists if |file_path| is non-empty, or
// the file is expected to exist on error.
EXPECT_EQ(!file_path.empty() || expect_path_exists_on_error_,
base::PathExists(dest_path_));
if (!file_path.empty())
EXPECT_EQ(dest_path_, file_path);
}
// Clean up file, so tests don't leave around files in the temp directory.
// Only matters in the TO_TEMP_FILE case.
if (!file_path.empty())
base::DeleteFile(file_path, false);
done_ = true;
run_loop_.Quit();
}
// SimpleURLLoaderStreamConsumer implementation:
void OnDataReceived(base::StringPiece string_piece,
base::OnceClosure resume) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EXPECT_FALSE(done_);
EXPECT_EQ(DownloadType::AS_STREAM, download_type_);
// If destroying the stream on data received, destroy the stream before
// reading the data, to make sure that works.
if (download_to_stream_destroy_on_data_received_) {
simple_url_loader_.reset();
done_ = true;
}
download_as_stream_response_body_.append(string_piece.as_string());
if (download_to_stream_destroy_on_data_received_) {
run_loop_.Quit();
return;
}
if (download_to_stream_async_resume_) {
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(resume));
return;
}
std::move(resume).Run();
}
void OnComplete(bool success) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EXPECT_FALSE(done_);
EXPECT_EQ(DownloadType::AS_STREAM, download_type_);
EXPECT_FALSE(response_body_);
// If headers weren't received for the final request, the response body
// should be empty.
if (!simple_url_loader_->ResponseInfo())
DCHECK(download_as_stream_response_body_.empty());
// This makes behavior of downloading a response to a stream more closely
// resemble other DownloadTypes, so most test logic can be shared.
if (success ||
(allow_http_error_results_ && simple_url_loader_->ResponseInfo())) {
response_body_ =
std::make_unique<std::string>(download_as_stream_response_body_);
}
if (destroy_loader_on_complete_)
simple_url_loader_.reset();
done_ = true;
run_loop_.Quit();
}
void OnRetry(base::OnceClosure start_retry) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EXPECT_FALSE(done_);
EXPECT_EQ(DownloadType::AS_STREAM, download_type_);
++download_as_stream_retries_;
download_as_stream_response_body_.clear();
if (download_to_stream_destroy_on_retry_) {
simple_url_loader_.reset();
done_ = true;
run_loop_.Quit();
return;
}
if (download_to_stream_async_retry_) {
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(start_retry));
return;
}
std::move(start_retry).Run();
}
DownloadType download_type_;
bool done_ = false;
bool expect_path_exists_on_error_ = false;
std::unique_ptr<SimpleURLLoader> simple_url_loader_;
base::RunLoop run_loop_;
// Response body, regardless of DownloadType. Only populated on completion.
// Null on error.
std::unique_ptr<std::string> response_body_;
base::OnceClosure on_destruction_callback_;
// Response data when downloading as stream:
std::string download_as_stream_response_body_;
int download_as_stream_retries_ = 0;
bool download_to_stream_async_resume_ = false;
bool download_to_stream_destroy_on_data_received_ = false;
bool download_to_stream_async_retry_ = false;
bool download_to_stream_destroy_on_retry_ = false;
bool destroy_loader_on_complete_ = false;
bool allow_http_error_results_ = false;
base::ScopedTempDir temp_dir_;
base::FilePath dest_path_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(SimpleLoaderTestHelper);
};
// Request handler for the embedded test server that returns a response body
// with the length indicated by the query string.
std::unique_ptr<net::test_server::HttpResponse> HandleResponseSize(
const net::test_server::HttpRequest& request) {
if (request.GetURL().path_piece() != kResponseSizePath)
return nullptr;
std::unique_ptr<net::test_server::BasicHttpResponse> response =
std::make_unique<net::test_server::BasicHttpResponse>();
uint32_t length;
if (!base::StringToUint(request.GetURL().query(), &length)) {
ADD_FAILURE() << "Invalid length: " << request.GetURL();
} else {
response->set_content(std::string(length, 'a'));
}
return std::move(response);
}
// Request handler for the embedded test server that returns a an invalid gzip
// response body. No body bytes will be read successfully.
std::unique_ptr<net::test_server::HttpResponse> HandleInvalidGzip(
const net::test_server::HttpRequest& request) {
if (request.GetURL().path_piece() != kInvalidGzipPath)
return nullptr;
std::unique_ptr<net::test_server::BasicHttpResponse> response =
std::make_unique<net::test_server::BasicHttpResponse>();
response->AddCustomHeader("Content-Encoding", "gzip");
response->set_content("Not gzipped");
return std::move(response);
}
// Request handler for the embedded test server that returns a response with a
// truncated body. Consumer should see an error after reading some data.
std::unique_ptr<net::test_server::HttpResponse> HandleTruncatedBody(
const net::test_server::HttpRequest& request) {
if (request.GetURL().path_piece() != kTruncatedBodyPath)
return nullptr;
std::unique_ptr<net::test_server::RawHttpResponse> response =
std::make_unique<net::test_server::RawHttpResponse>(
base::StringPrintf("HTTP/1.1 200 OK\r\n"
"Content-Length: %" PRIuS "\r\n",
strlen(kTruncatedBody) + 4),
kTruncatedBody);
return std::move(response);
}
// Request handler for the embedded test server that returns a 5xx error once,
// and on future requests, has a response body matching the request body.
std::unique_ptr<net::test_server::HttpResponse> FailOnceThenEchoBody(
bool* has_failed_request,
const net::test_server::HttpRequest& request) {
if (request.GetURL().path_piece() != kFailOnceThenEchoBody)
return nullptr;
if (!*has_failed_request) {
EXPECT_FALSE(request.content.empty());
*has_failed_request = true;
std::unique_ptr<net::test_server::BasicHttpResponse> response =
std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_INTERNAL_SERVER_ERROR);
return response;
}
std::unique_ptr<net::test_server::BasicHttpResponse> response =
std::make_unique<net::test_server::BasicHttpResponse>();
response->set_content(request.content);
return std::move(response);
}
// Base class with shared setup logic.
class SimpleURLLoaderTestBase {
public:
SimpleURLLoaderTestBase()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::IO) {
network::mojom::NetworkServicePtr network_service_ptr;
network::mojom::NetworkServiceRequest network_service_request =
mojo::MakeRequest(&network_service_ptr);
network_service_ =
network::NetworkService::Create(std::move(network_service_request),
/*netlog=*/nullptr);
network::mojom::NetworkContextParamsPtr context_params =
network::mojom::NetworkContextParams::New();
context_params->enable_data_url_support = true;
network_service_ptr->CreateNetworkContext(
mojo::MakeRequest(&network_context_), std::move(context_params));
mojom::URLLoaderFactoryParamsPtr params =
mojom::URLLoaderFactoryParams::New();
params->process_id = mojom::kBrowserProcessId;
params->is_corb_enabled = false;
network_context_->CreateURLLoaderFactory(
mojo::MakeRequest(&url_loader_factory_), std::move(params));
test_server_.AddDefaultHandlers(base::FilePath(FILE_PATH_LITERAL("")));
test_server_.RegisterRequestHandler(
base::BindRepeating(&HandleResponseSize));
test_server_.RegisterRequestHandler(
base::BindRepeating(&HandleInvalidGzip));
test_server_.RegisterRequestHandler(
base::BindRepeating(&HandleTruncatedBody));
test_server_.RegisterRequestHandler(base::BindRepeating(
&FailOnceThenEchoBody, base::Owned(new bool(false))));
EXPECT_TRUE(test_server_.Start());
// Can only create this after blocking calls. Creating the network stack
// has some, as does starting the test server.
disallow_blocking_ = std::make_unique<base::ScopedDisallowBlocking>();
}
virtual ~SimpleURLLoaderTestBase() {}
// Returns the path of a file that can be used in upload tests.
static base::FilePath GetTestFilePath() {
base::FilePath test_data_dir;
base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir);
return test_data_dir.AppendASCII("services/test/data/title1.html");
}
static std::string GetTestFileContents() {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string file_contents;
EXPECT_TRUE(base::ReadFileToString(GetTestFilePath(), &file_contents));
return file_contents;
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<network::mojom::NetworkService> network_service_;
network::mojom::NetworkContextPtr network_context_;
network::mojom::URLLoaderFactoryPtr url_loader_factory_;
net::test_server::EmbeddedTestServer test_server_;
std::unique_ptr<base::ScopedDisallowBlocking> disallow_blocking_;
};
class SimpleURLLoaderTest
: public SimpleURLLoaderTestBase,
public testing::TestWithParam<SimpleLoaderTestHelper::DownloadType> {
public:
SimpleURLLoaderTest() {}
~SimpleURLLoaderTest() override {}
std::unique_ptr<SimpleLoaderTestHelper> CreateHelper(
std::unique_ptr<network::ResourceRequest> resource_request) {
EXPECT_TRUE(resource_request);
return std::make_unique<SimpleLoaderTestHelper>(std::move(resource_request),
GetParam());
}
std::unique_ptr<SimpleLoaderTestHelper> CreateHelperForURL(
const GURL& url,
const char* method = "GET") {
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
resource_request->url = url;
resource_request->method = method;
return std::make_unique<SimpleLoaderTestHelper>(std::move(resource_request),
GetParam());
}
};
TEST_P(SimpleURLLoaderTest, BasicRequest) {
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
// Use a more interesting request than "/echo", just to verify more than the
// request URL is hooked up.
resource_request->url = test_server_.GetURL("/echoheader?foo");
resource_request->headers.SetHeader("foo", "Expected Response");
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelper(std::move(resource_request));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("Expected Response", *test_helper->response_body());
}
// Test that SimpleURLLoader handles data URLs, which don't have headers.
TEST_P(SimpleURLLoaderTest, DataURL) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("data:text/plain,foo"));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->simple_url_loader()->ResponseInfo());
EXPECT_FALSE(test_helper->simple_url_loader()->ResponseInfo()->headers);
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("foo", *test_helper->response_body());
}
// Make sure the class works when the size of the encoded and decoded bodies are
// different.
TEST_P(SimpleURLLoaderTest, GzipBody) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/gzip-body?foo"));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("foo", *test_helper->response_body());
}
// Make sure redirects are followed.
TEST_P(SimpleURLLoaderTest, Redirect) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
"/server-redirect?" + test_server_.GetURL("/echo").spec()));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("Echo", *test_helper->response_body());
}
// Make sure OnRedirectCallback is invoked on a redirect.
TEST_P(SimpleURLLoaderTest, OnRedirectCallback) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
"/server-redirect?" + test_server_.GetURL("/echo").spec()));
int num_redirects = 0;
net::RedirectInfo redirect_info;
network::ResourceResponseHead response_head;
test_helper->simple_url_loader()->SetOnRedirectCallback(base::BindRepeating(
[](int* num_redirects, net::RedirectInfo* redirect_info_ptr,
network::ResourceResponseHead* response_head_ptr,
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
++*num_redirects;
*redirect_info_ptr = redirect_info;
*response_head_ptr = response_head;
},
base::Unretained(&num_redirects), base::Unretained(&redirect_info),
base::Unretained(&response_head)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("Echo", *test_helper->response_body());
EXPECT_EQ(1, num_redirects);
EXPECT_EQ(test_server_.GetURL("/echo"), redirect_info.new_url);
ASSERT_TRUE(response_head.headers);
EXPECT_EQ(301, response_head.headers->response_code());
}
// Make sure OnRedirectCallback is invoked on each redirect.
TEST_P(SimpleURLLoaderTest, OnRedirectCallbackTwoRedirects) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
"/server-redirect?" +
test_server_
.GetURL("/server-redirect?" + test_server_.GetURL("/echo").spec())
.spec()));
int num_redirects = 0;
test_helper->simple_url_loader()->SetOnRedirectCallback(base::BindRepeating(
[](int* num_redirects, const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
++*num_redirects;
},
base::Unretained(&num_redirects)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("Echo", *test_helper->response_body());
EXPECT_EQ(2, num_redirects);
}
TEST_P(SimpleURLLoaderTest, DeleteInOnRedirectCallback) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
"/server-redirect?" + test_server_.GetURL("/echo").spec()));
SimpleLoaderTestHelper* unowned_test_helper = test_helper.get();
base::RunLoop run_loop;
unowned_test_helper->simple_url_loader()->SetOnRedirectCallback(
base::BindRepeating(
[](std::unique_ptr<SimpleLoaderTestHelper> test_helper,
base::RunLoop* run_loop, const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
run_loop->Quit();
},
base::Passed(std::move(test_helper)), &run_loop));
unowned_test_helper->StartSimpleLoader(url_loader_factory_.get());
run_loop.Run();
}
TEST_P(SimpleURLLoaderTest, UploadShortStringWithRedirect) {
// Use a 307 redirect to preserve the body across the redirect.
std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelperForURL(
test_server_.GetURL("/server-redirect-307?" +
test_server_.GetURL("/echo").spec()),
"POST");
test_helper->simple_url_loader()->AttachStringForUpload(kShortUploadBody,
"text/plain");
int num_redirects = 0;
test_helper->simple_url_loader()->SetOnRedirectCallback(base::BindRepeating(
[](int* num_redirects, const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
++*num_redirects;
},
base::Unretained(&num_redirects)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kShortUploadBody, *test_helper->response_body());
// Make sure request really was redirected.
EXPECT_EQ(1, num_redirects);
}
TEST_P(SimpleURLLoaderTest, UploadLongStringWithRedirect) {
// Use a 307 redirect to preserve the body across the redirect.
std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelperForURL(
test_server_.GetURL("/server-redirect-307?" +
test_server_.GetURL("/echo").spec()),
"POST");
test_helper->simple_url_loader()->AttachStringForUpload(GetLongUploadBody(),
"text/plain");
int num_redirects = 0;
test_helper->simple_url_loader()->SetOnRedirectCallback(base::BindRepeating(
[](int* num_redirects, const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
++*num_redirects;
},
base::Unretained(&num_redirects)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(GetLongUploadBody(), *test_helper->response_body());
// Make sure request really was redirected.
EXPECT_EQ(1, num_redirects);
}
TEST_P(SimpleURLLoaderTest, OnResponseStartedCallback) {
GURL url = test_server_.GetURL("/set-header?foo: bar");
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
resource_request->url = test_server_.GetURL("/server-redirect?" + url.spec());
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelper(std::move(resource_request));
base::RunLoop run_loop;
GURL actual_url;
std::string foo_header_value;
test_helper->simple_url_loader()->SetOnResponseStartedCallback(base::BindOnce(
[](GURL* out_final_url, std::string* foo_header_value,
base::OnceClosure quit_closure, const GURL& final_url,
const ResourceResponseHead& response_head) {
*out_final_url = final_url;
if (response_head.headers) {
response_head.headers->EnumerateHeader(/*iter=*/nullptr, "foo",
foo_header_value);
}
std::move(quit_closure).Run();
},
&actual_url, &foo_header_value, run_loop.QuitClosure()));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
run_loop.Run();
EXPECT_EQ(url, actual_url);
EXPECT_EQ("bar", foo_header_value);
}
TEST_P(SimpleURLLoaderTest, DeleteInOnResponseStartedCallback) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo"));
SimpleLoaderTestHelper* unowned_test_helper = test_helper.get();
base::RunLoop run_loop;
unowned_test_helper->simple_url_loader()->SetOnResponseStartedCallback(
base::BindOnce(
[](std::unique_ptr<SimpleLoaderTestHelper> test_helper,
base::OnceClosure quit_closure, const GURL& final_url,
const ResourceResponseHead& response_head) {
// Delete the SimpleURLLoader.
test_helper.reset();
// Access the parameters to trigger a memory error if they have been
// deleted. (ASAN build should catch it)
EXPECT_FALSE(response_head.request_start.is_null());
EXPECT_TRUE(final_url.is_valid());
std::move(quit_closure).Run();
},
base::Passed(std::move(test_helper)), run_loop.QuitClosure()));
unowned_test_helper->StartSimpleLoader(url_loader_factory_.get());
run_loop.Run();
}
// Check the case where the SimpleURLLoader is deleted in the completion
// callback.
TEST_P(SimpleURLLoaderTest, DestroyLoaderInOnComplete) {
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
// Use a more interesting request than "/echo", just to verify more than the
// request URL is hooked up.
resource_request->url = test_server_.GetURL("/echoheader?foo");
resource_request->headers.SetHeader("foo", "Expected Response");
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelper(std::move(resource_request));
test_helper->set_destroy_loader_on_complete(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("Expected Response", *test_helper->response_body());
}
// Check the case where a URLLoaderFactory with a closed Mojo pipe was passed
// in.
TEST_P(SimpleURLLoaderTest, DisconnectedURLLoader) {
// Destroy the NetworkContext, and wait for the Mojo URLLoaderFactory proxy to
// be informed of the closed pipe.
network_context_.reset();
base::RunLoop().RunUntilIdle();
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
resource_request->url = test_server_.GetURL("/echoheader?foo");
resource_request->headers.SetHeader("foo", "Expected Response");
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelper(std::move(resource_request));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_FAILED, test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->simple_url_loader()->ResponseInfo());
}
// Check that no body is returned with an HTTP error response.
TEST_P(SimpleURLLoaderTest, HttpErrorStatusCodeResponse) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo?status=400"));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_FAILED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(400, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
// Check that the body is returned with an HTTP error response, when
// SetAllowHttpErrorResults(true) is called.
TEST_P(SimpleURLLoaderTest, HttpErrorStatusCodeResponseAllowed) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo?status=400"));
test_helper->simple_url_loader()->SetAllowHttpErrorResults(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(400, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("Echo", *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, EmptyResponseBody) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/nocontent"));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(204, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
// A response body is sent from the NetworkService, but it's empty.
EXPECT_EQ("", *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, BigResponseBody) {
// Big response that requires multiple reads, and exceeds the maximum size
// limit of SimpleURLLoader::DownloadToString(). That is, this test make sure
// that DownloadToStringOfUnboundedSizeUntilCrashAndDie() can receive strings
// longer than DownloadToString() allows.
const uint32_t kResponseSize = 2 * 1024 * 1024;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kResponseSize, test_helper->response_body()->length());
EXPECT_EQ(std::string(kResponseSize, 'a'), *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, ResponseBodyWithSizeMatchingLimit) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 16;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kResponseSize);
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kResponseSize, test_helper->response_body()->length());
EXPECT_EQ(std::string(kResponseSize, 'a'), *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, ResponseBodyWithSizeBelowLimit) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 16;
const uint32_t kMaxResponseSize = kResponseSize + 1;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kMaxResponseSize);
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kResponseSize, test_helper->response_body()->length());
EXPECT_EQ(std::string(kResponseSize, 'a'), *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, ResponseBodyWithSizeAboveLimit) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 16;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kResponseSize - 1);
EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES,
test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->response_body());
}
// Same as above, but with setting allow_partial_results to true.
TEST_P(SimpleURLLoaderTest, ResponseBodyWithSizeAboveLimitPartialResponse) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 16;
const uint32_t kMaxResponseSize = kResponseSize - 1;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->SetAllowPartialResults(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kMaxResponseSize);
EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES,
test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(std::string(kMaxResponseSize, 'a'), *test_helper->response_body());
EXPECT_EQ(kMaxResponseSize, test_helper->response_body()->length());
}
// The next 4 tests duplicate the above 4, but with larger response sizes. This
// means the size limit will not be exceeded on the first read.
TEST_P(SimpleURLLoaderTest, BigResponseBodyWithSizeMatchingLimit) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 512 * 1024;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kResponseSize);
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kResponseSize, test_helper->response_body()->length());
EXPECT_EQ(std::string(kResponseSize, 'a'), *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, BigResponseBodyWithSizeBelowLimit) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 512 * 1024;
const uint32_t kMaxResponseSize = kResponseSize + 1;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kMaxResponseSize);
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kResponseSize, test_helper->response_body()->length());
EXPECT_EQ(std::string(kResponseSize, 'a'), *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, BigResponseBodyWithSizeAboveLimit) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 512 * 1024;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kResponseSize - 1);
EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES,
test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, BigResponseBodyWithSizeAboveLimitPartialResponse) {
// Download to stream doesn't support response sizes.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
return;
const uint32_t kResponseSize = 512 * 1024;
const uint32_t kMaxResponseSize = kResponseSize - 1;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->SetAllowPartialResults(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get(),
kMaxResponseSize);
EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES,
test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(std::string(kMaxResponseSize, 'a'), *test_helper->response_body());
EXPECT_EQ(kMaxResponseSize, test_helper->response_body()->length());
}
TEST_P(SimpleURLLoaderTest, NetErrorBeforeHeaders) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/close-socket"));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_EMPTY_RESPONSE,
test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->simple_url_loader()->ResponseInfo());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, NetErrorBeforeHeadersWithPartialResults) {
// Allow response body on error. There should still be no response body, since
// the error is before body reading starts.
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/close-socket"));
test_helper->SetAllowPartialResults(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_FALSE(test_helper->response_body());
EXPECT_EQ(net::ERR_EMPTY_RESPONSE,
test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->simple_url_loader()->ResponseInfo());
}
TEST_P(SimpleURLLoaderTest, NetErrorAfterHeaders) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kInvalidGzipPath));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED,
test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, NetErrorAfterHeadersWithPartialResults) {
// Allow response body on error. This case results in a 0-byte response body.
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kInvalidGzipPath));
test_helper->SetAllowPartialResults(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED,
test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("", *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, TruncatedBody) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kTruncatedBodyPath));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_CONTENT_LENGTH_MISMATCH,
test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, TruncatedBodyWithPartialResults) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kTruncatedBodyPath));
test_helper->SetAllowPartialResults(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_CONTENT_LENGTH_MISMATCH,
test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kTruncatedBody, *test_helper->response_body());
}
// Test case where NetworkService is destroyed before headers are received (and
// before the request is even made, for that matter).
TEST_P(SimpleURLLoaderTest, DestroyServiceBeforeResponseStarts) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/hung"));
test_helper->StartSimpleLoader(url_loader_factory_.get());
{
// Destroying the NetworkService may result in blocking operations on some
// platforms, like joining threads.
base::ScopedAllowBlockingForTesting allow_blocking;
network_service_.reset();
}
test_helper->Wait();
EXPECT_EQ(net::ERR_FAILED, test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->response_body());
ASSERT_FALSE(test_helper->simple_url_loader()->ResponseInfo());
}
TEST_P(SimpleURLLoaderTest, UploadShortString) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo"), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(kShortUploadBody,
"text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kShortUploadBody, *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, UploadLongString) {
std::string long_string = GetLongUploadBody();
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo"), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(long_string,
"text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(long_string, *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, UploadEmptyString) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo"), "POST");
test_helper->simple_url_loader()->AttachStringForUpload("", "text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("", *test_helper->response_body());
// Also make sure the correct method was sent, with the right content-type.
test_helper = CreateHelperForURL(test_server_.GetURL("/echoall"), "POST");
test_helper->simple_url_loader()->AttachStringForUpload("", "text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_NE(std::string::npos,
test_helper->response_body()->find("Content-Type: text/plain"));
EXPECT_NE(std::string::npos, test_helper->response_body()->find("POST /"));
EXPECT_EQ(std::string::npos, test_helper->response_body()->find("PUT /"));
}
TEST_P(SimpleURLLoaderTest, UploadShortStringWithRetry) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kFailOnceThenEchoBody), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(kShortUploadBody,
"text/plain");
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_5XX);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kShortUploadBody, *test_helper->response_body());
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
EXPECT_EQ(1, test_helper->download_as_stream_retries());
}
TEST_P(SimpleURLLoaderTest, UploadLongStringWithRetry) {
std::string long_string = GetLongUploadBody();
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kFailOnceThenEchoBody), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(long_string,
"text/plain");
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_5XX);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(long_string, *test_helper->response_body());
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
EXPECT_EQ(1, test_helper->download_as_stream_retries());
}
TEST_P(SimpleURLLoaderTest, UploadFile) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo"), "POST");
test_helper->simple_url_loader()->AttachFileForUpload(GetTestFilePath(),
"text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(GetTestFileContents(), *test_helper->response_body());
// Also make sure the correct method was sent, with the right content-type.
test_helper = CreateHelperForURL(test_server_.GetURL("/echoall"), "POST");
test_helper->simple_url_loader()->AttachFileForUpload(GetTestFilePath(),
"text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_NE(std::string::npos,
test_helper->response_body()->find("Content-Type: text/plain"));
EXPECT_NE(std::string::npos, test_helper->response_body()->find("POST /"));
EXPECT_EQ(std::string::npos, test_helper->response_body()->find("PUT /"));
}
TEST_P(SimpleURLLoaderTest, UploadFileWithPut) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo"), "PUT");
test_helper->simple_url_loader()->AttachFileForUpload(GetTestFilePath(),
"text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(GetTestFileContents(), *test_helper->response_body());
// Also make sure the correct method was sent, with the right content-type.
test_helper = CreateHelperForURL(test_server_.GetURL("/echoall"), "PUT");
test_helper->simple_url_loader()->AttachFileForUpload(GetTestFilePath(),
"text/salted");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_NE(std::string::npos,
test_helper->response_body()->find("Content-Type: text/salted"));
EXPECT_EQ(std::string::npos,
test_helper->response_body()->find("Content-Type: text/plain"));
EXPECT_EQ(std::string::npos, test_helper->response_body()->find("POST /"));
EXPECT_NE(std::string::npos, test_helper->response_body()->find("PUT /"));
}
TEST_P(SimpleURLLoaderTest, UploadFileWithRetry) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kFailOnceThenEchoBody), "POST");
test_helper->simple_url_loader()->AttachFileForUpload(GetTestFilePath(),
"text/plain");
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_5XX);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(GetTestFileContents(), *test_helper->response_body());
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
EXPECT_EQ(1, test_helper->download_as_stream_retries());
}
TEST_P(SimpleURLLoaderTest, UploadNonexistantFile) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/echo"), "POST");
// Path to a file that doesn't exist. Start with the test directory just to
// get a valid absolute path the test has access to.
base::FilePath path_to_nonexistent_file =
GetTestFilePath().DirName().AppendASCII("this/file/does/not/exist");
// Appending a path to the end of a file should guarantee no such file exists.
test_helper->simple_url_loader()->AttachFileForUpload(
path_to_nonexistent_file, "text/plain");
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->simple_url_loader()->ResponseInfo());
EXPECT_FALSE(test_helper->response_body());
}
// Test case where uploading a file is canceled before the URLLoader is started
// (But after the SimpleURLLoader is started).
TEST_P(SimpleURLLoaderTest, UploadFileCanceledBeforeLoaderStarted) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("http://does_not_matter:7/"), "POST");
test_helper->simple_url_loader()->AttachFileForUpload(GetTestFilePath(),
"text/plain");
test_helper->StartSimpleLoader(url_loader_factory_.get());
test_helper.reset();
scoped_task_environment_.RunUntilIdle();
}
TEST_P(SimpleURLLoaderTest, UploadFileCanceledWithRetry) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/hung"), "POST");
test_helper->simple_url_loader()->AttachFileForUpload(GetTestFilePath(),
"text/plain");
test_helper->simple_url_loader()->SetRetryOptions(
2, SimpleURLLoader::RETRY_ON_5XX);
test_helper->StartSimpleLoader(url_loader_factory_.get());
scoped_task_environment_.RunUntilIdle();
test_helper.reset();
scoped_task_environment_.RunUntilIdle();
}
enum class TestLoaderEvent {
// States related to reading the long upload body (Returned by
// GetLongUploadBody()). They expect the ResourceRequest to have a request
// body with a single DataPipeGetter.
// Call Read() on the DataPipeGetter.
kStartReadLongUploadBody,
// Wait for Read() to complete, expecting it to succeed and return the size of
// the string returned by GetLongUploadBody().
kWaitForLongUploadBodySize,
// Read the entire body, expecting it to equal the string returned by
// GetLongUploadBody().
kReadLongUploadBody,
// Read the first byte of the upload body. Cannot be followed by a call to
// kReadLongUploadBody.
kReadFirstByteOfLongUploadBody,
kReceivedRedirect,
// Receive a response with a 200 status code.
kReceivedResponse,
// Receive a response with a 401 status code.
kReceived401Response,
// Receive a response with a 501 status code.
kReceived501Response,
kBodyBufferReceived,
kBodyDataRead,
// ResponseComplete indicates a success.
kResponseComplete,
// ResponseComplete is passed a network error (net::ERR_TIMED_OUT).
kResponseCompleteFailed,
// ResponseComplete is passed net::ERR_NETWORK_CHANGED.
kResponseCompleteNetworkChanged,
// Less body data is received than is expected.
kResponseCompleteTruncated,
// More body data is received than is expected.
kResponseCompleteWithExtraData,
kClientPipeClosed,
kBodyBufferClosed,
};
// URLLoader that the test fixture can control. This allows finer grained
// control over event order over when a pipe is closed, and in ordering of
// events where there are multiple pipes. It also allows sending events in
// unexpected order, to test handling of events from less trusted processes.
class MockURLLoader : public network::mojom::URLLoader {
public:
MockURLLoader(base::test::ScopedTaskEnvironment* scoped_task_environment,
network::mojom::URLLoaderRequest url_loader_request,
network::mojom::URLLoaderClientPtr client,
std::vector<TestLoaderEvent> test_events,
scoped_refptr<network::ResourceRequestBody> request_body)
: scoped_task_environment_(scoped_task_environment),
binding_(this, std::move(url_loader_request)),
client_(std::move(client)),
test_events_(std::move(test_events)),
weak_factory_for_data_pipe_callbacks_(this) {
if (request_body && request_body->elements()->size() == 1 &&
(*request_body->elements())[0].type() ==
network::DataElement::TYPE_DATA_PIPE) {
data_pipe_getter_ = (*request_body->elements())[0].CloneDataPipeGetter();
DCHECK(data_pipe_getter_);
}
}
void RunTest() {
for (auto test_event : test_events_) {
switch (test_event) {
case TestLoaderEvent::kStartReadLongUploadBody: {
ASSERT_TRUE(data_pipe_getter_);
upload_data_pipe_.reset();
weak_factory_for_data_pipe_callbacks_.InvalidateWeakPtrs();
read_run_loop_ = std::make_unique<base::RunLoop>();
mojo::DataPipe data_pipe;
data_pipe_getter_->Read(
std::move(data_pipe.producer_handle),
base::BindOnce(
&MockURLLoader::OnReadComplete,
weak_factory_for_data_pipe_callbacks_.GetWeakPtr()));
upload_data_pipe_ = std::move(data_pipe.consumer_handle);
// Continue instead of break, to avoid spinning the message loop -
// only wait for the response if next step indicates to do so.
continue;
}
case TestLoaderEvent::kWaitForLongUploadBodySize: {
ASSERT_TRUE(data_pipe_getter_);
ASSERT_TRUE(read_run_loop_);
read_run_loop_->Run();
break;
}
case TestLoaderEvent::kReadLongUploadBody: {
ASSERT_TRUE(data_pipe_getter_);
ASSERT_TRUE(upload_data_pipe_.is_valid());
std::string upload_body;
while (true) {
char read_buffer[32 * 1024];
uint32_t read_size = sizeof(read_buffer);
MojoResult result = upload_data_pipe_->ReadData(
read_buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
base::RunLoop().RunUntilIdle();
continue;
}
if (result != MOJO_RESULT_OK)
break;
upload_body.append(read_buffer, read_size);
}
EXPECT_EQ(GetLongUploadBody(), upload_body);
break;
}
case TestLoaderEvent::kReadFirstByteOfLongUploadBody: {
ASSERT_TRUE(data_pipe_getter_);
ASSERT_TRUE(upload_data_pipe_.is_valid());
MojoResult result;
char byte;
uint32_t read_size;
while (true) {
read_size = 1;
result = upload_data_pipe_->ReadData(&byte, &read_size,
MOJO_READ_DATA_FLAG_NONE);
if (result != MOJO_RESULT_SHOULD_WAIT)
break;
base::RunLoop().RunUntilIdle();
}
if (result != MOJO_RESULT_OK) {
ADD_FAILURE() << "Expected to read one byte of data.";
break;
}
EXPECT_EQ(1u, read_size);
EXPECT_EQ(GetLongUploadBody()[0], byte);
break;
}
case TestLoaderEvent::kReceivedRedirect: {
net::RedirectInfo redirect_info;
redirect_info.new_method = "GET";
redirect_info.new_url = GURL("bar://foo/");
redirect_info.status_code = 301;
network::ResourceResponseHead response_info;
std::string headers(
"HTTP/1.0 301 The Response Has Moved to Another Server\n"
"Location: bar://foo/");
response_info.headers =
new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
headers.c_str(), headers.size()));
client_->OnReceiveRedirect(redirect_info, response_info);
break;
}
case TestLoaderEvent::kReceivedResponse: {
network::ResourceResponseHead response_info;
std::string headers("HTTP/1.0 200 OK");
response_info.headers =
new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
headers.c_str(), headers.size()));
client_->OnReceiveResponse(response_info);
break;
}
case TestLoaderEvent::kReceived401Response: {
network::ResourceResponseHead response_info;
std::string headers("HTTP/1.0 401 Client Borkage");
response_info.headers =
new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
headers.c_str(), headers.size()));
client_->OnReceiveResponse(response_info);
break;
}
case TestLoaderEvent::kReceived501Response: {
network::ResourceResponseHead response_info;
std::string headers("HTTP/1.0 501 Server Borkage");
response_info.headers =
new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
headers.c_str(), headers.size()));
client_->OnReceiveResponse(response_info);
break;
}
case TestLoaderEvent::kBodyBufferReceived: {
mojo::DataPipe data_pipe(1024);
body_stream_ = std::move(data_pipe.producer_handle);
client_->OnStartLoadingResponseBody(
std::move(data_pipe.consumer_handle));
break;
}
case TestLoaderEvent::kBodyDataRead: {
uint32_t num_bytes = 1;
// Writing one byte should always succeed synchronously, for the
// amount of data these tests send.
EXPECT_EQ(MOJO_RESULT_OK,
body_stream_->WriteData("a", &num_bytes,
MOJO_WRITE_DATA_FLAG_NONE));
EXPECT_EQ(num_bytes, 1u);
break;
}
case TestLoaderEvent::kResponseComplete: {
network::URLLoaderCompletionStatus status;
status.error_code = net::OK;
status.decoded_body_length = CountBytesToSend();
client_->OnComplete(status);
break;
}
case TestLoaderEvent::kResponseCompleteFailed: {
network::URLLoaderCompletionStatus status;
// Use an error that SimpleURLLoader doesn't create itself, so clear
// when this is the source of the error code.
status.error_code = net::ERR_TIMED_OUT;
status.decoded_body_length = CountBytesToSend();
client_->OnComplete(status);
break;
}
case TestLoaderEvent::kResponseCompleteNetworkChanged: {
network::URLLoaderCompletionStatus status;
status.error_code = net::ERR_NETWORK_CHANGED;
status.decoded_body_length = CountBytesToSend();
client_->OnComplete(status);
break;
}
case TestLoaderEvent::kResponseCompleteTruncated: {
network::URLLoaderCompletionStatus status;
status.error_code = net::OK;
status.decoded_body_length = CountBytesToSend() + 1;
client_->OnComplete(status);
break;
}
case TestLoaderEvent::kResponseCompleteWithExtraData: {
// Make sure |decoded_body_length| doesn't underflow.
DCHECK_GT(CountBytesToSend(), 0u);
network::URLLoaderCompletionStatus status;
status.error_code = net::OK;
status.decoded_body_length = CountBytesToSend() - 1;
client_->OnComplete(status);
break;
}
case TestLoaderEvent::kClientPipeClosed: {
DCHECK(client_);
EXPECT_TRUE(binding_.is_bound());
client_.reset();
break;
}
case TestLoaderEvent::kBodyBufferClosed: {
body_stream_.reset();
break;
}
}
// Wait for Mojo to pass along the message, to ensure expected ordering.
scoped_task_environment_->RunUntilIdle();
}
}
~MockURLLoader() override {}
// network::mojom::URLLoader implementation:
void FollowRedirect(const base::Optional<std::vector<std::string>>&
to_be_removed_request_headers,
const base::Optional<net::HttpRequestHeaders>&
modified_request_headers) override {}
void ProceedWithResponse() override {}
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {
NOTREACHED();
}
void PauseReadingBodyFromNet() override {}
void ResumeReadingBodyFromNet() override {}
network::mojom::URLLoaderClient* client() const { return client_.get(); }
private:
// Counts the total number of bytes that will be sent over the course of
// running the request. Includes both those that have been sent already, and
// those that have yet to be sent.
uint32_t CountBytesToSend() const {
int total_bytes = 0;
for (auto test_event : test_events_) {
if (test_event == TestLoaderEvent::kBodyDataRead)
++total_bytes;
}
return total_bytes;
}
void OnReadComplete(int32_t status, uint64_t size) {
EXPECT_EQ(net::OK, status);
EXPECT_EQ(GetLongUploadBody().size(), size);
read_run_loop_->Quit();
}
base::test::ScopedTaskEnvironment* scoped_task_environment_;
std::unique_ptr<net::URLRequest> url_request_;
mojo::Binding<network::mojom::URLLoader> binding_;
network::mojom::URLLoaderClientPtr client_;
std::vector<TestLoaderEvent> test_events_;
mojo::ScopedDataPipeProducerHandle body_stream_;
network::mojom::DataPipeGetterPtr data_pipe_getter_;
mojo::ScopedDataPipeConsumerHandle upload_data_pipe_;
std::unique_ptr<base::RunLoop> read_run_loop_;
base::WeakPtrFactory<MockURLLoader> weak_factory_for_data_pipe_callbacks_;
DISALLOW_COPY_AND_ASSIGN(MockURLLoader);
};
class MockURLLoaderFactory : public network::mojom::URLLoaderFactory {
public:
explicit MockURLLoaderFactory(
base::test::ScopedTaskEnvironment* scoped_task_environment)
: scoped_task_environment_(scoped_task_environment) {}
~MockURLLoaderFactory() override {}
// network::mojom::URLLoaderFactory implementation:
void CreateLoaderAndStart(network::mojom::URLLoaderRequest url_loader_request,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& url_request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag&
traffic_annotation) override {
ASSERT_FALSE(test_events_.empty());
requested_urls_.push_back(url_request.url);
url_loaders_.push_back(std::make_unique<MockURLLoader>(
scoped_task_environment_, std::move(url_loader_request),
std::move(client), test_events_.front(), url_request.request_body));
test_events_.pop_front();
url_loader_queue_.push_back(url_loaders_.back().get());
}
void Clone(network::mojom::URLLoaderFactoryRequest request) override {
mojo::BindingId id = binding_set_.AddBinding(this, std::move(request));
if (close_new_binding_on_clone_)
binding_set_.RemoveBinding(id);
}
// Makes clone fail close the newly created binding after bining the request,
// Simulating the case where the network service goes away before the cloned
// interface is used.
void set_close_new_binding_on_clone(bool close_new_binding_on_clone) {
close_new_binding_on_clone_ = close_new_binding_on_clone;
}
// Adds a events that will be returned by a single MockURLLoader. Mutliple
// calls mean multiple MockURLLoaders are expected to be created. Each will
// run to completion before the next one is expected to be created.
void AddEvents(const std::vector<TestLoaderEvent> events) {
DCHECK(url_loaders_.empty());
test_events_.push_back(events);
}
// Runs all events for all created URLLoaders, in order.
void RunTest(SimpleLoaderTestHelper* test_helper,
bool wait_for_completion = true) {
network::mojom::URLLoaderFactoryPtr factory;
binding_set_.AddBinding(this, mojo::MakeRequest(&factory));
test_helper->StartSimpleLoader(factory.get());
// Wait for the first URLLoader to start receiving messages.
base::RunLoop().RunUntilIdle();
while (!url_loader_queue_.empty()) {
url_loader_queue_.front()->RunTest();
url_loader_queue_.pop_front();
}
if (wait_for_completion)
test_helper->Wait();
// All loads with added events should have been created and had their
// RunTest() methods called by the time the above loop completes.
EXPECT_TRUE(test_events_.empty());
}
const std::list<GURL>& requested_urls() const { return requested_urls_; }
private:
base::test::ScopedTaskEnvironment* scoped_task_environment_;
std::list<std::unique_ptr<MockURLLoader>> url_loaders_;
std::list<std::vector<TestLoaderEvent>> test_events_;
bool close_new_binding_on_clone_ = false;
// Queue of URLLoaders that have yet to had their RunTest method called.
// Separate list than |url_loaders_| so that old pipes aren't destroyed.
std::list<MockURLLoader*> url_loader_queue_;
std::list<GURL> requested_urls_;
mojo::BindingSet<network::mojom::URLLoaderFactory> binding_set_;
DISALLOW_COPY_AND_ASSIGN(MockURLLoaderFactory);
};
// Check that the request fails if OnComplete() is called before anything else.
TEST_P(SimpleURLLoaderTest, ResponseCompleteBeforeReceivedResponse) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents({TestLoaderEvent::kResponseComplete});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->simple_url_loader()->ResponseInfo());
EXPECT_FALSE(test_helper->response_body());
}
// Check that the request fails if OnComplete() is called before the body pipe
// is received.
TEST_P(SimpleURLLoaderTest, ResponseCompleteAfterReceivedResponse) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kResponseComplete});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, CloseClientPipeBeforeBodyStarts) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kClientPipeClosed});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_FAILED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
// This test tries closing the client pipe / completing the request in most
// possible valid orders relative to read events (Which always occur in the same
// order).
TEST_P(SimpleURLLoaderTest, CloseClientPipeOrder) {
enum class ClientCloseOrder {
kBeforeData,
kDuringData,
kAfterData,
kAfterBufferClosed,
};
// In what order the URLLoaderClient pipe is closed, relative to read events.
// Order of other main events can't vary, relative to each other (Getting body
// pipe, reading body bytes, closing body pipe).
const ClientCloseOrder kClientCloseOrder[] = {
ClientCloseOrder::kBeforeData, ClientCloseOrder::kDuringData,
ClientCloseOrder::kAfterData, ClientCloseOrder::kAfterBufferClosed,
};
const TestLoaderEvent kClientCloseEvents[] = {
TestLoaderEvent::kResponseComplete,
TestLoaderEvent::kResponseCompleteFailed,
TestLoaderEvent::kResponseCompleteTruncated,
TestLoaderEvent::kClientPipeClosed,
};
for (const auto close_client_order : kClientCloseOrder) {
for (const auto close_client_event : kClientCloseEvents) {
for (uint32_t bytes_received = 0; bytes_received < 3; bytes_received++) {
for (int allow_partial_results = 0; allow_partial_results < 2;
allow_partial_results++) {
if (close_client_order == ClientCloseOrder::kDuringData &&
bytes_received < 2) {
continue;
}
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
std::vector<TestLoaderEvent> events;
events.push_back(TestLoaderEvent::kReceivedResponse);
events.push_back(TestLoaderEvent::kBodyBufferReceived);
if (close_client_order == ClientCloseOrder::kBeforeData)
events.push_back(close_client_event);
for (uint32_t i = 0; i < bytes_received; ++i) {
events.push_back(TestLoaderEvent::kBodyDataRead);
if (i == 0 && close_client_order == ClientCloseOrder::kDuringData)
events.push_back(close_client_event);
}
if (close_client_order == ClientCloseOrder::kAfterData)
events.push_back(close_client_event);
events.push_back(TestLoaderEvent::kBodyBufferClosed);
if (close_client_order == ClientCloseOrder::kAfterBufferClosed)
events.push_back(close_client_event);
loader_factory.AddEvents(events);
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
test_helper->SetAllowPartialResults(allow_partial_results);
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(200, test_helper->GetResponseCode());
if (close_client_event != TestLoaderEvent::kResponseComplete) {
if (close_client_event ==
TestLoaderEvent::kResponseCompleteFailed) {
EXPECT_EQ(net::ERR_TIMED_OUT,
test_helper->simple_url_loader()->NetError());
} else {
EXPECT_EQ(net::ERR_FAILED,
test_helper->simple_url_loader()->NetError());
}
if (!allow_partial_results) {
EXPECT_FALSE(test_helper->response_body());
} else {
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(std::string(bytes_received, 'a'),
*test_helper->response_body());
}
} else {
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(std::string(bytes_received, 'a'),
*test_helper->response_body());
}
}
}
}
}
}
// Make sure the close client pipe message doesn't cause any issues.
TEST_P(SimpleURLLoaderTest, ErrorAndCloseClientPipeBeforeBodyStarts) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents({TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kResponseCompleteFailed,
TestLoaderEvent::kClientPipeClosed});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_TIMED_OUT, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
// Make sure the close client pipe message doesn't cause any issues.
TEST_P(SimpleURLLoaderTest, SuccessAndCloseClientPipeBeforeBodyComplete) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kResponseComplete, TestLoaderEvent::kClientPipeClosed,
TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kBodyBufferClosed});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("a", *test_helper->response_body());
}
// Make sure the close client pipe message doesn't cause any issues.
TEST_P(SimpleURLLoaderTest, SuccessAndCloseClientPipeAfterBodyComplete) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kResponseComplete, TestLoaderEvent::kClientPipeClosed});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("a", *test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, DoubleReceivedResponse) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kReceivedResponse});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, RedirectAfterReseivedResponse) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kReceivedRedirect});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, UnexpectedBodyBufferReceived) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents({TestLoaderEvent::kBodyBufferReceived});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->simple_url_loader()->ResponseInfo());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, DoubleBodyBufferReceived) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents({TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyBufferReceived});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, UnexpectedMessageAfterBodyStarts) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kReceivedRedirect});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, UnexpectedMessageAfterBodyStarts2) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kBodyBufferReceived});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, UnexpectedMessageAfterBodyComplete) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kBodyBufferReceived});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, MoreDataThanExpected) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kBodyDataRead,
TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kResponseCompleteWithExtraData});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_UNEXPECTED, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
TEST_P(SimpleURLLoaderTest, RetryOn5xx) {
const GURL kInitialURL("foo://bar/initial");
struct TestCase {
// Parameters passed to SetRetryOptions.
int max_retries;
int retry_mode;
// Number of 5xx responses before a successful response.
int num_5xx;
// Whether the request is expected to succeed in the end.
bool expect_success;
// Expected times the url should be requested.
int expected_num_requests;
} const kTestCases[] = {
// No retry on 5xx when retries disabled.
{0, SimpleURLLoader::RETRY_NEVER, 1, false, 1},
// No retry on 5xx when retries enabled on network change.
{1, SimpleURLLoader::RETRY_ON_NETWORK_CHANGE, 1, false, 1},
// As many retries allowed as 5xx errors.
{1, SimpleURLLoader::RETRY_ON_5XX, 1, true, 2},
{1,
SimpleURLLoader::RETRY_ON_5XX | SimpleURLLoader::RETRY_ON_NETWORK_CHANGE,
1, true, 2},
{2, SimpleURLLoader::RETRY_ON_5XX, 2, true, 3},
// More retries than 5xx errors.
{2, SimpleURLLoader::RETRY_ON_5XX, 1, true, 2},
// Fewer retries than 5xx errors.
{1, SimpleURLLoader::RETRY_ON_5XX, 2, false, 2},
};
for (const auto& test_case : kTestCases) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
for (int i = 0; i < test_case.num_5xx; i++) {
loader_factory.AddEvents({TestLoaderEvent::kReceived501Response});
}
if (test_case.expect_success) {
// Valid response with a 1-byte body.
loader_factory.AddEvents({TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead,
TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kResponseComplete});
}
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL(kInitialURL));
test_helper->simple_url_loader()->SetRetryOptions(test_case.max_retries,
test_case.retry_mode);
loader_factory.RunTest(test_helper.get());
if (test_case.expect_success) {
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(1u, test_helper->response_body()->size());
} else {
EXPECT_EQ(501, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
}
EXPECT_EQ(static_cast<size_t>(test_case.expected_num_requests),
loader_factory.requested_urls().size());
for (const auto& url : loader_factory.requested_urls()) {
EXPECT_EQ(kInitialURL, url);
}
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM) {
EXPECT_EQ(test_case.expected_num_requests - 1,
test_helper->download_as_stream_retries());
}
}
}
// Test that when retrying on 5xx is enabled, there's no retry on a 4xx error.
TEST_P(SimpleURLLoaderTest, NoRetryOn4xx) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents({TestLoaderEvent::kReceived401Response});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_5XX);
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(401, test_helper->GetResponseCode());
EXPECT_FALSE(test_helper->response_body());
EXPECT_EQ(1u, loader_factory.requested_urls().size());
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
EXPECT_EQ(0, test_helper->download_as_stream_retries());
}
// Checks that retrying after a redirect works. The original URL should be
// re-requested.
TEST_P(SimpleURLLoaderTest, RetryAfterRedirect) {
const GURL kInitialURL("foo://bar/initial");
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents({TestLoaderEvent::kReceivedRedirect,
TestLoaderEvent::kReceived501Response});
loader_factory.AddEvents(
{TestLoaderEvent::kReceivedRedirect, TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kResponseComplete});
int num_redirects = 0;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(kInitialURL);
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_5XX);
test_helper->simple_url_loader()->SetOnRedirectCallback(base::BindRepeating(
[](int* num_redirects, const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) {
++*num_redirects;
},
base::Unretained(&num_redirects)));
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(200, test_helper->GetResponseCode());
EXPECT_TRUE(test_helper->response_body());
EXPECT_EQ(2, num_redirects);
EXPECT_EQ(2u, loader_factory.requested_urls().size());
for (const auto& url : loader_factory.requested_urls()) {
EXPECT_EQ(kInitialURL, url);
}
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
EXPECT_EQ(1, test_helper->download_as_stream_retries());
}
TEST_P(SimpleURLLoaderTest, RetryOnNetworkChange) {
// TestLoaderEvents up to (and including) a network change. Since
// SimpleURLLoader always waits for the body buffer to be closed before
// retrying, everything that has a kBodyBufferReceived message must also have
// a kBodyBufferClosed message. Each test case will be tried against each of
// these event sequences.
const std::vector<std::vector<TestLoaderEvent>> kNetworkChangedEvents = {
{TestLoaderEvent::kResponseCompleteNetworkChanged},
{TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kResponseCompleteNetworkChanged},
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kResponseCompleteNetworkChanged},
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kResponseCompleteNetworkChanged,
TestLoaderEvent::kBodyBufferClosed},
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kResponseCompleteNetworkChanged},
{TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead,
TestLoaderEvent::kResponseCompleteNetworkChanged,
TestLoaderEvent::kBodyBufferClosed},
{TestLoaderEvent::kReceivedRedirect,
TestLoaderEvent::kResponseCompleteNetworkChanged},
};
const GURL kInitialURL("foo://bar/initial");
// Test cases in which to try each entry in kNetworkChangedEvents.
struct TestCase {
// Parameters passed to SetRetryOptions.
int max_retries;
int retry_mode;
// Number of network changes responses before a successful response.
// For each network change, the entire sequence of an entry in
// kNetworkChangedEvents is repeated.
int num_network_changes;
// Whether the request is expected to succeed in the end.
bool expect_success;
// Expected times the url should be requested.
int expected_num_requests;
} const kTestCases[] = {
// No retry on network change when retries disabled.
{0, SimpleURLLoader::RETRY_NEVER, 1, false, 1},
// No retry on network change when retries enabled on 5xx response.
{1, SimpleURLLoader::RETRY_ON_5XX, 1, false, 1},
// As many retries allowed as network changes.
{1, SimpleURLLoader::RETRY_ON_NETWORK_CHANGE, 1, true, 2},
{1,
SimpleURLLoader::RETRY_ON_NETWORK_CHANGE | SimpleURLLoader::RETRY_ON_5XX,
1, true, 2},
{2, SimpleURLLoader::RETRY_ON_NETWORK_CHANGE, 2, true, 3},
// More retries than network changes.
{2, SimpleURLLoader::RETRY_ON_NETWORK_CHANGE, 1, true, 2},
// Fewer retries than network changes.
{1, SimpleURLLoader::RETRY_ON_NETWORK_CHANGE, 2, false, 2},
};
for (const auto& network_events : kNetworkChangedEvents) {
for (const auto& test_case : kTestCases) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
for (int i = 0; i < test_case.num_network_changes; i++) {
loader_factory.AddEvents(network_events);
}
if (test_case.expect_success) {
// Valid response with a 1-byte body.
loader_factory.AddEvents({TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kBodyBufferReceived,
TestLoaderEvent::kBodyDataRead,
TestLoaderEvent::kBodyBufferClosed,
TestLoaderEvent::kResponseComplete});
}
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL(kInitialURL));
test_helper->simple_url_loader()->SetRetryOptions(test_case.max_retries,
test_case.retry_mode);
loader_factory.RunTest(test_helper.get());
if (test_case.expect_success) {
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(1u, test_helper->response_body()->size());
} else {
EXPECT_EQ(net::ERR_NETWORK_CHANGED,
test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->response_body());
}
EXPECT_EQ(static_cast<size_t>(test_case.expected_num_requests),
loader_factory.requested_urls().size());
for (const auto& url : loader_factory.requested_urls()) {
EXPECT_EQ(kInitialURL, url);
}
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM) {
EXPECT_EQ(test_case.expected_num_requests - 1,
test_helper->download_as_stream_retries());
}
}
}
// Check that there's no retry for each entry in kNetworkChangedEvents when an
// error other than a network change is received.
for (const auto& network_events : kNetworkChangedEvents) {
std::vector<TestLoaderEvent> modifed_network_events = network_events;
for (auto& test_loader_event : modifed_network_events) {
if (test_loader_event == TestLoaderEvent::kResponseCompleteNetworkChanged)
test_loader_event = TestLoaderEvent::kResponseCompleteFailed;
}
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(modifed_network_events);
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL(kInitialURL));
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_TIMED_OUT, test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->response_body());
EXPECT_EQ(1u, loader_factory.requested_urls().size());
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
EXPECT_EQ(0, test_helper->download_as_stream_retries());
}
}
// Check the case where the URLLoaderFactory has been disconnected before the
// request is retried.
TEST_P(SimpleURLLoaderTest, RetryWithUnboundFactory) {
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents({TestLoaderEvent::kResponseCompleteNetworkChanged});
// Since clone fails asynchronously, this shouldn't be any different from
// the new URLLoaderFactory being disconnected in some other way.
loader_factory.set_close_new_binding_on_clone(true);
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::ERR_FAILED, test_helper->simple_url_loader()->NetError());
EXPECT_FALSE(test_helper->response_body());
// Retry is still called, before the dead factory is discovered.
if (GetParam() == SimpleLoaderTestHelper::DownloadType::AS_STREAM)
EXPECT_EQ(1, test_helper->download_as_stream_retries());
}
// Test the case where DataPipeGetter::Read is called twice in a row,
// with no intervening reads of the data on the pipe.
TEST_P(SimpleURLLoaderTest, UploadLongStringStartReadTwice) {
std::string long_string = GetLongUploadBody();
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kStartReadLongUploadBody,
TestLoaderEvent::kStartReadLongUploadBody,
TestLoaderEvent::kWaitForLongUploadBodySize,
TestLoaderEvent::kReadLongUploadBody, TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kResponseComplete,
TestLoaderEvent::kBodyBufferClosed});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(long_string,
"text/plain");
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("", *test_helper->response_body());
}
// Test the case where DataPipeGetter::Read is called a second time, after only
// reading part of the response, with no intervening reads of the data on the
// pipe.
TEST_P(SimpleURLLoaderTest,
UploadLongStringReadPartOfUploadBodyBeforeRestartBodyRead) {
std::string long_string = GetLongUploadBody();
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
loader_factory.AddEvents(
{TestLoaderEvent::kStartReadLongUploadBody,
TestLoaderEvent::kWaitForLongUploadBodySize,
TestLoaderEvent::kReadFirstByteOfLongUploadBody,
TestLoaderEvent::kStartReadLongUploadBody,
TestLoaderEvent::kWaitForLongUploadBodySize,
TestLoaderEvent::kReadLongUploadBody, TestLoaderEvent::kReceivedResponse,
TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kResponseComplete,
TestLoaderEvent::kBodyBufferClosed});
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(long_string,
"text/plain");
loader_factory.RunTest(test_helper.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("", *test_helper->response_body());
}
// Test for GetFinalURL.
TEST_P(SimpleURLLoaderTest, GetFinalURL) {
GURL url = test_server_.GetURL("/echo");
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
resource_request->url = url;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelper(std::move(resource_request));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(url, test_helper->simple_url_loader()->GetFinalURL());
}
// Test for GetFinalURL with a redirect.
TEST_P(SimpleURLLoaderTest, GetFinalURLAfterRedirect) {
GURL url = test_server_.GetURL("/echo");
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/server-redirect?" + url.spec()));
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(url, test_helper->simple_url_loader()->GetFinalURL());
}
INSTANTIATE_TEST_CASE_P(
/* No prefix */,
SimpleURLLoaderTest,
testing::Values(SimpleLoaderTestHelper::DownloadType::TO_STRING,
SimpleLoaderTestHelper::DownloadType::TO_FILE,
SimpleLoaderTestHelper::DownloadType::TO_TEMP_FILE,
SimpleLoaderTestHelper::DownloadType::AS_STREAM));
class SimpleURLLoaderFileTest : public SimpleURLLoaderTestBase,
public testing::Test {
public:
SimpleURLLoaderFileTest() {}
~SimpleURLLoaderFileTest() override {}
std::unique_ptr<SimpleLoaderTestHelper> CreateHelperForURL(const GURL& url) {
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
resource_request->url = url;
return std::make_unique<SimpleLoaderTestHelper>(
std::move(resource_request),
SimpleLoaderTestHelper::DownloadType::TO_FILE);
}
};
// Make sure that an existing file will be completely overwritten.
TEST_F(SimpleURLLoaderFileTest, OverwriteFile) {
std::string junk_data(100, '!');
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("data:text/plain,foo"));
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_EQ(static_cast<int>(junk_data.size()),
base::WriteFile(test_helper->dest_path(), junk_data.data(),
junk_data.size()));
ASSERT_TRUE(base::PathExists(test_helper->dest_path()));
}
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->simple_url_loader()->ResponseInfo());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ("foo", *test_helper->response_body());
}
// Make sure that file creation errors are handled correctly.
TEST_F(SimpleURLLoaderFileTest, FileCreateError) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("data:text/plain,foo"));
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(base::CreateDirectory(test_helper->dest_path()));
ASSERT_TRUE(base::PathExists(test_helper->dest_path()));
}
// The directory should still exist after the download fails.
test_helper->set_expect_path_exists_on_error(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::ERR_ACCESS_DENIED,
test_helper->simple_url_loader()->NetError());
EXPECT_TRUE(test_helper->simple_url_loader()->ResponseInfo());
EXPECT_FALSE(test_helper->response_body());
}
// Make sure that destroying the loader destroys a partially downloaded file.
TEST_F(SimpleURLLoaderFileTest, DeleteLoaderDuringRequestDestroysFile) {
for (bool body_data_read : {false, true}) {
for (bool body_buffer_closed : {false, true}) {
for (bool client_pipe_closed : {false, true}) {
// If both pipes were closed cleanly, the file shouldn't be deleted, as
// that indicates success.
if (body_buffer_closed && client_pipe_closed)
continue;
MockURLLoaderFactory loader_factory(&scoped_task_environment_);
std::vector<TestLoaderEvent> events;
events.push_back(TestLoaderEvent::kReceivedResponse);
events.push_back(TestLoaderEvent::kBodyBufferReceived);
if (body_data_read)
events.push_back(TestLoaderEvent::kBodyDataRead);
if (body_buffer_closed)
events.push_back(TestLoaderEvent::kBodyBufferClosed);
if (client_pipe_closed)
events.push_back(TestLoaderEvent::kClientPipeClosed);
loader_factory.AddEvents(events);
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(GURL("foo://bar/"));
// Run events without waiting for the request to complete, since the
// request will hang.
loader_factory.RunTest(test_helper.get(),
false /* wait_for_completion */);
// Wait for the request to advance as far as it's going to.
scoped_task_environment_.RunUntilIdle();
// Destination file should have been created, and request should still
// be in progress.
base::FilePath dest_path = test_helper->dest_path();
{
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(dest_path));
EXPECT_FALSE(test_helper->done());
}
// Destroying the SimpleURLLoader now should post a task to destroy the
// file.
test_helper->DestroySimpleURLLoader();
scoped_task_environment_.RunUntilIdle();
{
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_FALSE(base::PathExists(dest_path));
}
}
}
}
}
// Used for testing stream-specific features.
class SimpleURLLoaderStreamTest : public SimpleURLLoaderTestBase,
public testing::Test {
public:
SimpleURLLoaderStreamTest() {}
~SimpleURLLoaderStreamTest() override {}
std::unique_ptr<SimpleLoaderTestHelper> CreateHelperForURL(
const GURL& url,
const std::string& method = "GET") {
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
resource_request->url = url;
resource_request->method = method;
return std::make_unique<SimpleLoaderTestHelper>(
std::move(resource_request),
SimpleLoaderTestHelper::DownloadType::AS_STREAM);
}
};
TEST_F(SimpleURLLoaderStreamTest, OnDataReceivedCompletesAsync) {
const uint32_t kResponseSize = 2 * 1024 * 1024;
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(
base::StringPrintf("/response-size?%u", kResponseSize)));
test_helper->set_download_to_stream_async_resume(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
EXPECT_EQ(200, test_helper->GetResponseCode());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kResponseSize, test_helper->response_body()->length());
EXPECT_EQ(std::string(kResponseSize, 'a'), *test_helper->response_body());
EXPECT_EQ(0, test_helper->download_as_stream_retries());
}
// Test case where class is destroyed during OnDataReceived. Main purpose is to
// make sure there's not a crash.
TEST_F(SimpleURLLoaderStreamTest, OnDataReceivedDestruction) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL("/response-size?1"));
test_helper->set_download_to_stream_destroy_on_data_received(true);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_FALSE(test_helper->simple_url_loader());
// Make sure no pending task results in a crash.
base::RunLoop().RunUntilIdle();
}
TEST_F(SimpleURLLoaderStreamTest, OnRetryCompletesAsync) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kFailOnceThenEchoBody), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(kShortUploadBody,
"text/plain");
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_5XX);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError());
ASSERT_TRUE(test_helper->response_body());
EXPECT_EQ(kShortUploadBody, *test_helper->response_body());
EXPECT_EQ(1, test_helper->download_as_stream_retries());
}
// Test case where class is destroyed during OnRetry. While setting the loader
// to retry and then destroying it on retry is perhaps a bit strange, seems best
// to be consistent in the provided API. Main purpose of this test is to make
// sure there's not a crash.
TEST_F(SimpleURLLoaderStreamTest, OnRetryDestruction) {
std::unique_ptr<SimpleLoaderTestHelper> test_helper =
CreateHelperForURL(test_server_.GetURL(kFailOnceThenEchoBody), "POST");
test_helper->simple_url_loader()->AttachStringForUpload(kShortUploadBody,
"text/plain");
test_helper->set_download_to_stream_destroy_on_retry(true);
test_helper->simple_url_loader()->SetRetryOptions(
1, SimpleURLLoader::RETRY_ON_5XX);
test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
EXPECT_FALSE(test_helper->simple_url_loader());
// Make sure no pending task results in a crash.
base::RunLoop().RunUntilIdle();
}
} // namespace
} // namespace network