| Index: headless/public/util/generic_url_request_job_test.cc
|
| diff --git a/headless/public/util/generic_url_request_job_test.cc b/headless/public/util/generic_url_request_job_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e687a6864191674762f2541c0c235741033337a7
|
| --- /dev/null
|
| +++ b/headless/public/util/generic_url_request_job_test.cc
|
| @@ -0,0 +1,346 @@
|
| +// Copyright 2016 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 "headless/public/util/generic_url_request_job.h"
|
| +
|
| +#include <memory>
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/json/json_reader.h"
|
| +#include "base/json/json_writer.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/run_loop.h"
|
| +#include "base/single_thread_task_runner.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "base/values.h"
|
| +#include "headless/public/util/expedited_dispatcher.h"
|
| +#include "headless/public/util/testing/generic_url_request_mocks.h"
|
| +#include "headless/public/util/url_fetcher.h"
|
| +#include "net/http/http_response_headers.h"
|
| +#include "net/url_request/url_request_job_factory_impl.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +std::ostream& operator<<(std::ostream& os, const base::Value& value) {
|
| + std::string json;
|
| + base::JSONWriter::WriteWithOptions(
|
| + value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
|
| + os << json;
|
| + return os;
|
| +}
|
| +
|
| +MATCHER_P(MatchesJson, json, json.c_str()) {
|
| + std::unique_ptr<base::Value> expected(
|
| + base::JSONReader::Read(json, base::JSON_PARSE_RFC));
|
| + return arg.Equals(expected.get());
|
| +}
|
| +
|
| +namespace headless {
|
| +
|
| +namespace {
|
| +
|
| +class MockFetcher : public URLFetcher {
|
| + public:
|
| + MockFetcher(base::DictionaryValue* fetch_request,
|
| + const std::string& json_reply)
|
| + : fetch_reply_(base::JSONReader::Read(json_reply, base::JSON_PARSE_RFC)),
|
| + fetch_request_(fetch_request) {
|
| + CHECK(fetch_reply_) << "Invalid json: " << json_reply;
|
| + }
|
| +
|
| + ~MockFetcher() override {}
|
| +
|
| + void StartFetch(const GURL& url,
|
| + const net::HttpRequestHeaders& request_headers,
|
| + ResultListener* result_listener) override {
|
| + // Record the request.
|
| + fetch_request_->SetString("url", url.spec());
|
| + std::unique_ptr<base::DictionaryValue> headers(new base::DictionaryValue);
|
| + for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
|
| + headers->SetString(it.name(), it.value());
|
| + }
|
| + fetch_request_->Set("headers", std::move(headers));
|
| +
|
| + // Return the canned response.
|
| + base::DictionaryValue* reply_dictionary;
|
| + ASSERT_TRUE(fetch_reply_->GetAsDictionary(&reply_dictionary));
|
| + std::string final_url;
|
| + ASSERT_TRUE(reply_dictionary->GetString("url", &final_url));
|
| + int http_response_code;
|
| + ASSERT_TRUE(reply_dictionary->GetInteger("http_response_code",
|
| + &http_response_code));
|
| + ASSERT_TRUE(reply_dictionary->GetString("data", &response_data_));
|
| + base::DictionaryValue* reply_headers_dictionary;
|
| + ASSERT_TRUE(
|
| + reply_dictionary->GetDictionary("headers", &reply_headers_dictionary));
|
| + scoped_refptr<net::HttpResponseHeaders> response_headers(
|
| + new net::HttpResponseHeaders(""));
|
| + for (base::DictionaryValue::Iterator it(*reply_headers_dictionary);
|
| + !it.IsAtEnd(); it.Advance()) {
|
| + std::string value;
|
| + ASSERT_TRUE(it.value().GetAsString(&value));
|
| + response_headers->AddHeader(
|
| + base::StringPrintf("%s: %s", it.key().c_str(), value.c_str()));
|
| + }
|
| + result_listener->OnFetchComplete(
|
| + GURL(final_url), http_response_code, std::move(response_headers),
|
| + response_data_.c_str(), response_data_.size());
|
| + }
|
| +
|
| + private:
|
| + std::unique_ptr<base::Value> fetch_reply_;
|
| + base::DictionaryValue* fetch_request_; // NOT OWNED
|
| + std::string response_data_; // Here to ensure the required lifetime.
|
| +};
|
| +
|
| +class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
|
| + public:
|
| + // Details of the fetch will be stored in |fetch_request|.
|
| + // The fetch response will be created from parsing |json_fetch_reply_|.
|
| + MockProtocolHandler(base::DictionaryValue* fetch_request,
|
| + std::string* json_fetch_reply,
|
| + URLRequestDispatcher* dispatcher,
|
| + GenericURLRequestJob::Delegate* job_delegate)
|
| + : fetch_request_(fetch_request),
|
| + json_fetch_reply_(json_fetch_reply),
|
| + job_delegate_(job_delegate),
|
| + dispatcher_(dispatcher) {}
|
| +
|
| + // net::URLRequestJobFactory::ProtocolHandler override.
|
| + net::URLRequestJob* MaybeCreateJob(
|
| + net::URLRequest* request,
|
| + net::NetworkDelegate* network_delegate) const override {
|
| + return new GenericURLRequestJob(
|
| + request, network_delegate, dispatcher_,
|
| + base::MakeUnique<MockFetcher>(fetch_request_, *json_fetch_reply_),
|
| + job_delegate_);
|
| + }
|
| +
|
| + private:
|
| + base::DictionaryValue* fetch_request_; // NOT OWNED
|
| + std::string* json_fetch_reply_; // NOT OWNED
|
| + GenericURLRequestJob::Delegate* job_delegate_; // NOT OWNED
|
| + URLRequestDispatcher* dispatcher_; // NOT OWNED
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class GenericURLRequestJobTest : public testing::Test {
|
| + public:
|
| + GenericURLRequestJobTest() : dispatcher_(message_loop_.task_runner()) {
|
| + url_request_job_factory_.SetProtocolHandler(
|
| + "https", base::WrapUnique(new MockProtocolHandler(
|
| + &fetch_request_, &json_fetch_reply_, &dispatcher_,
|
| + &job_delegate_)));
|
| + url_request_context_.set_job_factory(&url_request_job_factory_);
|
| + url_request_context_.set_cookie_store(&cookie_store_);
|
| + }
|
| +
|
| + std::unique_ptr<net::URLRequest> CreateAndCompleteJob(
|
| + const GURL& url,
|
| + const std::string& json_reply) {
|
| + json_fetch_reply_ = json_reply;
|
| +
|
| + std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
|
| + url, net::DEFAULT_PRIORITY, &request_delegate_));
|
| + request->Start();
|
| + message_loop_.RunUntilIdle();
|
| + return request;
|
| + }
|
| +
|
| + protected:
|
| + base::MessageLoop message_loop_;
|
| + ExpeditedDispatcher dispatcher_;
|
| +
|
| + net::URLRequestJobFactoryImpl url_request_job_factory_;
|
| + net::URLRequestContext url_request_context_;
|
| + MockCookieStore cookie_store_;
|
| +
|
| + MockURLRequestDelegate request_delegate_;
|
| + base::DictionaryValue fetch_request_; // The request sent to MockFetcher.
|
| + std::string json_fetch_reply_; // The reply to be sent by MockFetcher.
|
| + MockGenericURLRequestJobDelegate job_delegate_;
|
| +};
|
| +
|
| +TEST_F(GenericURLRequestJobTest, BasicRequestParams) {
|
| + // TODO(alexclarke): Lobby for raw string literals and use them here!
|
| + json_fetch_reply_ =
|
| + "{\"url\":\"https://example.com\","
|
| + " \"http_response_code\":200,"
|
| + " \"data\":\"Reply\","
|
| + " \"headers\":{\"Content-Type\":\"text/plain\"}}";
|
| +
|
| + std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
|
| + GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_));
|
| + request->SetReferrer("https://referrer.example.com");
|
| + request->SetExtraRequestHeaderByName("Extra-Header", "Value", true);
|
| + request->SetExtraRequestHeaderByName("User-Agent", "TestBrowser", true);
|
| + request->SetExtraRequestHeaderByName("Accept", "text/plain", true);
|
| + request->Start();
|
| + message_loop_.RunUntilIdle();
|
| +
|
| + std::string expected_request_json =
|
| + "{\"url\": \"https://example.com/\","
|
| + " \"headers\": {"
|
| + " \"Accept\": \"text/plain\","
|
| + " \"Cookie\": \"\","
|
| + " \"Extra-Header\": \"Value\","
|
| + " \"Referer\": \"https://referrer.example.com/\","
|
| + " \"User-Agent\": \"TestBrowser\""
|
| + " }"
|
| + "}";
|
| +
|
| + EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
|
| +}
|
| +
|
| +TEST_F(GenericURLRequestJobTest, BasicRequestProperties) {
|
| + std::string reply =
|
| + "{\"url\":\"https://example.com\","
|
| + " \"http_response_code\":200,"
|
| + " \"data\":\"Reply\","
|
| + " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
|
| +
|
| + std::unique_ptr<net::URLRequest> request(
|
| + CreateAndCompleteJob(GURL("https://example.com"), reply));
|
| +
|
| + EXPECT_EQ(200, request->GetResponseCode());
|
| +
|
| + std::string mime_type;
|
| + request->GetMimeType(&mime_type);
|
| + EXPECT_EQ("text/html", mime_type);
|
| +
|
| + std::string charset;
|
| + request->GetCharset(&charset);
|
| + EXPECT_EQ("utf-8", charset);
|
| +
|
| + std::string content_type;
|
| + EXPECT_TRUE(request->response_info().headers->GetNormalizedHeader(
|
| + "Content-Type", &content_type));
|
| + EXPECT_EQ("text/html; charset=UTF-8", content_type);
|
| +}
|
| +
|
| +TEST_F(GenericURLRequestJobTest, BasicRequestContents) {
|
| + std::string reply =
|
| + "{\"url\":\"https://example.com\","
|
| + " \"http_response_code\":200,"
|
| + " \"data\":\"Reply\","
|
| + " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
|
| +
|
| + std::unique_ptr<net::URLRequest> request(
|
| + CreateAndCompleteJob(GURL("https://example.com"), reply));
|
| +
|
| + const int kBufferSize = 256;
|
| + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
|
| + int bytes_read;
|
| + EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
|
| + EXPECT_EQ(5, bytes_read);
|
| + EXPECT_EQ("Reply", std::string(buffer->data(), 5));
|
| +}
|
| +
|
| +TEST_F(GenericURLRequestJobTest, ReadInParts) {
|
| + std::string reply =
|
| + "{\"url\":\"https://example.com\","
|
| + " \"http_response_code\":200,"
|
| + " \"data\":\"Reply\","
|
| + " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
|
| +
|
| + std::unique_ptr<net::URLRequest> request(
|
| + CreateAndCompleteJob(GURL("https://example.com"), reply));
|
| +
|
| + const int kBufferSize = 3;
|
| + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
|
| + int bytes_read;
|
| + EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
|
| + EXPECT_EQ(3, bytes_read);
|
| + EXPECT_EQ("Rep", std::string(buffer->data(), bytes_read));
|
| +
|
| + EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
|
| + EXPECT_EQ(2, bytes_read);
|
| + EXPECT_EQ("ly", std::string(buffer->data(), bytes_read));
|
| +
|
| + EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
|
| + EXPECT_EQ(0, bytes_read);
|
| +}
|
| +
|
| +TEST_F(GenericURLRequestJobTest, RequestWithCookies) {
|
| + net::CookieList* cookies = cookie_store_.cookies();
|
| +
|
| + // Basic matching cookie.
|
| + cookies->push_back(*net::CanonicalCookie::Create(
|
| + GURL("https://example.com"), "basic_cookie", "1", "example.com", "/",
|
| + base::Time(), base::Time(),
|
| + /* secure */ false,
|
| + /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
|
| + /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
|
| +
|
| + // Matching secure cookie.
|
| + cookies->push_back(*net::CanonicalCookie::Create(
|
| + GURL("https://example.com"), "secure_cookie", "2", "example.com", "/",
|
| + base::Time(), base::Time(),
|
| + /* secure */ true,
|
| + /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
|
| + /* enforce_strict_secure */ true, net::COOKIE_PRIORITY_DEFAULT));
|
| +
|
| + // Matching http-only cookie.
|
| + cookies->push_back(*net::CanonicalCookie::Create(
|
| + GURL("https://example.com"), "http_only_cookie", "3", "example.com", "/",
|
| + base::Time(), base::Time(),
|
| + /* secure */ false,
|
| + /* http_only */ true, net::CookieSameSite::NO_RESTRICTION,
|
| + /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
|
| +
|
| + // Matching cookie with path.
|
| + cookies->push_back(*net::CanonicalCookie::Create(
|
| + GURL("https://example.com"), "cookie_with_path", "4", "example.com",
|
| + "/widgets", base::Time(), base::Time(),
|
| + /* secure */ false,
|
| + /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
|
| + /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
|
| +
|
| + // Matching cookie with subdomain.
|
| + cookies->push_back(*net::CanonicalCookie::Create(
|
| + GURL("https://cdn.example.com"), "bad_subdomain_cookie", "5",
|
| + "cdn.example.com", "/", base::Time(), base::Time(),
|
| + /* secure */ false,
|
| + /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
|
| + /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
|
| +
|
| + // Non-matching cookie (different site).
|
| + cookies->push_back(*net::CanonicalCookie::Create(
|
| + GURL("https://zombo.com"), "bad_site_cookie", "6", "zombo.com", "/",
|
| + base::Time(), base::Time(),
|
| + /* secure */ false,
|
| + /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
|
| + /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
|
| +
|
| + // Non-matching cookie (different path).
|
| + cookies->push_back(*net::CanonicalCookie::Create(
|
| + GURL("https://example.com"), "bad_path_cookie", "7", "example.com",
|
| + "/gadgets", base::Time(), base::Time(),
|
| + /* secure */ false,
|
| + /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
|
| + /* enforce_strict_secure */ false, net::COOKIE_PRIORITY_DEFAULT));
|
| +
|
| + std::string reply =
|
| + "{\"url\":\"https://example.com\","
|
| + " \"http_response_code\":200,"
|
| + " \"data\":\"Reply\","
|
| + " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
|
| +
|
| + std::unique_ptr<net::URLRequest> request(
|
| + CreateAndCompleteJob(GURL("https://example.com"), reply));
|
| +
|
| + std::string expected_request_json =
|
| + "{\"url\": \"https://example.com/\","
|
| + " \"headers\": {"
|
| + " \"Cookie\": \"basic_cookie=1; secure_cookie=2; http_only_cookie=3\","
|
| + " \"Referer\": \"\""
|
| + " }"
|
| + "}";
|
| +
|
| + EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
|
| +}
|
| +
|
| +} // namespace headless
|
|
|