blob: 6f374787dce1eca2f2fa6f1aae9f09c5c28e8e21 [file] [log] [blame]
// Copyright 2018 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 "components/update_client/url_loader_post_interceptor.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind_test_util.h"
#include "components/update_client/test_configurator.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 "services/network/public/cpp/resource_request.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace update_client {
URLLoaderPostInterceptor::URLLoaderPostInterceptor(
network::TestURLLoaderFactory* url_loader_factory)
: url_loader_factory_(url_loader_factory) {
filtered_urls_.push_back(
GURL(base::StringPrintf("%s://%s%s", POST_INTERCEPT_SCHEME,
POST_INTERCEPT_HOSTNAME, POST_INTERCEPT_PATH)));
InitializeWithInterceptor();
}
URLLoaderPostInterceptor::URLLoaderPostInterceptor(
std::vector<GURL> supported_urls,
network::TestURLLoaderFactory* url_loader_factory)
: url_loader_factory_(url_loader_factory) {
DCHECK_LT(0u, supported_urls.size());
filtered_urls_.swap(supported_urls);
InitializeWithInterceptor();
}
URLLoaderPostInterceptor::URLLoaderPostInterceptor(
std::vector<GURL> supported_urls,
net::test_server::EmbeddedTestServer* embedded_test_server)
: embedded_test_server_(embedded_test_server) {
DCHECK_LT(0u, supported_urls.size());
filtered_urls_.swap(supported_urls);
InitializeWithRequestHandler();
}
URLLoaderPostInterceptor::~URLLoaderPostInterceptor() {}
bool URLLoaderPostInterceptor::ExpectRequest(
std::unique_ptr<RequestMatcher> request_matcher) {
return ExpectRequest(std::move(request_matcher), net::HTTP_OK);
}
bool URLLoaderPostInterceptor::ExpectRequest(
std::unique_ptr<RequestMatcher> request_matcher,
net::HttpStatusCode response_code) {
expectations_.push(
{std::move(request_matcher), ExpectationResponse(response_code, "")});
return true;
}
bool URLLoaderPostInterceptor::ExpectRequest(
std::unique_ptr<RequestMatcher> request_matcher,
const base::FilePath& filepath) {
std::string response;
if (filepath.empty() || !base::ReadFileToString(filepath, &response))
return false;
expectations_.push({std::move(request_matcher),
ExpectationResponse(net::HTTP_OK, response)});
return true;
}
// Returns how many requests have been intercepted and matched by
// an expectation. One expectation can only be matched by one request.
int URLLoaderPostInterceptor::GetHitCount() const {
return hit_count_;
}
// Returns how many requests in total have been captured by the interceptor.
int URLLoaderPostInterceptor::GetCount() const {
return static_cast<int>(requests_.size());
}
// Returns all requests that have been intercepted, matched or not.
std::vector<URLLoaderPostInterceptor::InterceptedRequest>
URLLoaderPostInterceptor::GetRequests() const {
return requests_;
}
// Return the body of the n-th request, zero-based.
std::string URLLoaderPostInterceptor::GetRequestBody(size_t n) const {
return std::get<0>(requests_[n]);
}
// Returns the joined bodies of all requests for debugging purposes.
std::string URLLoaderPostInterceptor::GetRequestsAsString() const {
const std::vector<InterceptedRequest> requests = GetRequests();
std::string s = "Requests are:";
int i = 0;
for (auto it = requests.cbegin(); it != requests.cend(); ++it)
s.append(base::StringPrintf("\n [%d]: %s", ++i, std::get<0>(*it).c_str()));
return s;
}
// Resets the state of the interceptor so that new expectations can be set.
void URLLoaderPostInterceptor::Reset() {
hit_count_ = 0;
requests_.clear();
base::queue<Expectation>().swap(expectations_);
}
void URLLoaderPostInterceptor::Pause() {
is_paused_ = true;
}
void URLLoaderPostInterceptor::Resume() {
is_paused_ = false;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
if (!pending_expectations_.size())
return;
PendingExpectation expectation =
std::move(pending_expectations_.front());
pending_expectations_.pop();
url_loader_factory_->AddResponse(expectation.first.spec(),
expectation.second.response_body,
expectation.second.response_code);
}));
}
void URLLoaderPostInterceptor::url_job_request_ready_callback(
UrlJobRequestReadyCallback url_job_request_ready_callback) {
url_job_request_ready_callback_ = std::move(url_job_request_ready_callback);
}
int URLLoaderPostInterceptor::GetHitCountForURL(const GURL& url) {
int hit_count = 0;
const std::vector<InterceptedRequest> requests = GetRequests();
for (auto it = requests.cbegin(); it != requests.cend(); ++it) {
GURL url_no_query = std::get<2>(*it);
if (url_no_query.has_query()) {
GURL::Replacements replacements;
replacements.ClearQuery();
url_no_query = url_no_query.ReplaceComponents(replacements);
}
if (url_no_query == url)
hit_count++;
}
return hit_count;
}
void URLLoaderPostInterceptor::InitializeWithInterceptor() {
DCHECK(url_loader_factory_);
url_loader_factory_->SetInterceptor(
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
GURL url = request.url;
if (url.has_query()) {
GURL::Replacements replacements;
replacements.ClearQuery();
url = url.ReplaceComponents(replacements);
}
auto it = std::find_if(
filtered_urls_.begin(), filtered_urls_.end(),
[url](const GURL& filtered_url) { return filtered_url == url; });
if (it == filtered_urls_.end())
return;
std::string request_body = network::GetUploadData(request);
requests_.push_back({request_body, request.headers, request.url});
if (expectations_.empty())
return;
const auto& expectation = expectations_.front();
if (expectation.first->Match(request_body)) {
const net::HttpStatusCode response_code(
expectation.second.response_code);
const std::string response_body(expectation.second.response_body);
if (url_job_request_ready_callback_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, std::move(url_job_request_ready_callback_));
}
if (!is_paused_) {
url_loader_factory_->AddResponse(request.url.spec(), response_body,
response_code);
} else {
pending_expectations_.push({request.url, expectation.second});
}
expectations_.pop();
++hit_count_;
}
}));
}
void URLLoaderPostInterceptor::InitializeWithRequestHandler() {
DCHECK(embedded_test_server_);
DCHECK(!url_loader_factory_);
embedded_test_server_->RegisterRequestHandler(base::BindRepeating(
&URLLoaderPostInterceptor::RequestHandler, base::Unretained(this)));
}
std::unique_ptr<net::test_server::HttpResponse>
URLLoaderPostInterceptor::RequestHandler(
const net::test_server::HttpRequest& request) {
// Only intercepts POST.
if (request.method != net::test_server::METHOD_POST)
return nullptr;
GURL url = request.GetURL();
if (url.has_query()) {
GURL::Replacements replacements;
replacements.ClearQuery();
url = url.ReplaceComponents(replacements);
}
auto it = std::find_if(
filtered_urls_.begin(), filtered_urls_.end(),
[url](const GURL& filtered_url) { return filtered_url == url; });
if (it == filtered_urls_.end())
return nullptr;
std::string request_body = request.content;
net::HttpRequestHeaders headers;
for (auto it : request.headers)
headers.SetHeader(it.first, it.second);
requests_.push_back({request_body, headers, url});
if (expectations_.empty())
return nullptr;
const auto& expectation = expectations_.front();
if (expectation.first->Match(request_body)) {
const net::HttpStatusCode response_code(expectation.second.response_code);
const std::string response_body(expectation.second.response_body);
expectations_.pop();
++hit_count_;
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(response_code);
http_response->set_content(response_body);
return http_response;
}
return nullptr;
}
bool PartialMatch::Match(const std::string& actual) const {
return actual.find(expected_) != std::string::npos;
}
bool AnyMatch::Match(const std::string& actual) const {
return true;
}
} // namespace update_client