blob: c252df4121ab5091e34db3c7afe35455f1c391f7 [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 "services/network/cors/preflight_controller.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.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/test/embedded_test_server/request_handler_util.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/cors/cors_url_loader_factory.h"
#include "services/network/network_service.h"
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/test/fake_test_cert_verifier_params_factory.h"
#include "services/network/test/test_network_service_client.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace network {
namespace cors {
namespace {
using WithTrustedHeaderClient = PreflightController::WithTrustedHeaderClient;
TEST(PreflightControllerCreatePreflightRequestTest, LexicographicalOrder) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader("Orange", "Orange");
request.headers.SetHeader("Apple", "Red");
request.headers.SetHeader("Kiwifruit", "Green");
request.headers.SetHeader(net::HttpRequestHeaders::kContentType,
"application/octet-stream");
request.headers.SetHeader("Strawberry", "Red");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
std::string header;
EXPECT_TRUE(
preflight->headers.GetHeader(net::HttpRequestHeaders::kOrigin, &header));
EXPECT_EQ("null", header);
EXPECT_TRUE(preflight->headers.GetHeader(
header_names::kAccessControlRequestHeaders, &header));
EXPECT_EQ("apple,content-type,kiwifruit,orange,strawberry", header);
}
TEST(PreflightControllerCreatePreflightRequestTest, ExcludeSimpleHeaders) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader("Accept", "everything");
request.headers.SetHeader(net::HttpRequestHeaders::kAcceptLanguage,
"everything");
request.headers.SetHeader("Content-Language", "everything");
request.headers.SetHeader("Save-Data", "on");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
// Do not emit empty-valued headers; an empty list of non-"CORS safelisted"
// request headers should cause "Access-Control-Request-Headers:" to be
// left out in the preflight request.
std::string header;
EXPECT_FALSE(preflight->headers.GetHeader(
header_names::kAccessControlRequestHeaders, &header));
}
TEST(PreflightControllerCreatePreflightRequestTest, Credentials) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kInclude;
request.request_initiator = url::Origin();
request.headers.SetHeader("Orange", "Orange");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
EXPECT_EQ(mojom::CredentialsMode::kOmit, preflight->credentials_mode);
}
TEST(PreflightControllerCreatePreflightRequestTest,
ExcludeSimpleContentTypeHeader) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader(net::HttpRequestHeaders::kContentType,
"text/plain");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
// Empty list also; see comment in test above.
std::string header;
EXPECT_FALSE(preflight->headers.GetHeader(
header_names::kAccessControlRequestHeaders, &header));
}
TEST(PreflightControllerCreatePreflightRequestTest, IncludeSecFetchModeHeader) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader("X-Custom-Header", "foobar");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
std::string header;
EXPECT_TRUE(preflight->headers.GetHeader("Sec-Fetch-Mode", &header));
EXPECT_EQ("cors", header);
}
TEST(PreflightControllerCreatePreflightRequestTest, IncludeNonSimpleHeader) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader("X-Custom-Header", "foobar");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
std::string header;
EXPECT_TRUE(preflight->headers.GetHeader(
header_names::kAccessControlRequestHeaders, &header));
EXPECT_EQ("x-custom-header", header);
}
TEST(PreflightControllerCreatePreflightRequestTest,
IncludeNonSimpleContentTypeHeader) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader(net::HttpRequestHeaders::kContentType,
"application/octet-stream");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
std::string header;
EXPECT_TRUE(preflight->headers.GetHeader(
header_names::kAccessControlRequestHeaders, &header));
EXPECT_EQ("content-type", header);
}
TEST(PreflightControllerCreatePreflightRequestTest, ExcludeForbiddenHeaders) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader("referer", "https://www.google.com/");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
std::string header;
EXPECT_FALSE(preflight->headers.GetHeader(
header_names::kAccessControlRequestHeaders, &header));
}
TEST(PreflightControllerCreatePreflightRequestTest, Tainted) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin::Create(GURL("https://example.com"));
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request, true);
std::string header;
EXPECT_TRUE(
preflight->headers.GetHeader(net::HttpRequestHeaders::kOrigin, &header));
EXPECT_EQ(header, "null");
}
TEST(PreflightControllerCreatePreflightRequestTest, FetchWindowId) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader(net::HttpRequestHeaders::kContentType,
"application/octet-stream");
request.fetch_window_id = base::UnguessableToken::Create();
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
EXPECT_EQ(request.fetch_window_id, preflight->fetch_window_id);
}
TEST(PreflightControllerCreatePreflightRequestTest, RenderFrameId) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.request_initiator = url::Origin();
request.headers.SetHeader(net::HttpRequestHeaders::kContentType,
"application/octet-stream");
request.render_frame_id = 99;
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequestForTesting(request);
EXPECT_EQ(request.render_frame_id, preflight->render_frame_id);
}
TEST(PreflightControllerOptionsTest, CheckOptions) {
base::test::TaskEnvironment task_environment_(
base::test::TaskEnvironment::MainThreadType::IO);
TestURLLoaderFactory url_loader_factory;
PreflightController preflight_controller(
{} /* extra_safelisted_header_names */, nullptr /* network_service */);
network::ResourceRequest request;
request.url = GURL("https://example.com/");
request.request_initiator = url::Origin();
preflight_controller.PerformPreflightCheck(
base::BindOnce([](int, base::Optional<CorsErrorStatus>) {}), request,
WithTrustedHeaderClient(false), false /* tainted */,
TRAFFIC_ANNOTATION_FOR_TESTS, &url_loader_factory, 0 /* process_id */,
net::IsolationInfo());
preflight_controller.PerformPreflightCheck(
base::BindOnce([](int, base::Optional<CorsErrorStatus>) {}), request,
WithTrustedHeaderClient(true), false /* tainted */,
TRAFFIC_ANNOTATION_FOR_TESTS, &url_loader_factory, 0 /* process_id */,
net::IsolationInfo());
ASSERT_EQ(2, url_loader_factory.NumPending());
EXPECT_EQ(mojom::kURLLoadOptionAsCorsPreflight,
url_loader_factory.GetPendingRequest(0)->options);
EXPECT_EQ(mojom::kURLLoadOptionAsCorsPreflight |
mojom::kURLLoadOptionUseHeaderClient,
url_loader_factory.GetPendingRequest(1)->options);
}
class MockNetworkServiceClient : public TestNetworkServiceClient {
public:
explicit MockNetworkServiceClient(
mojo::PendingReceiver<mojom::NetworkServiceClient> receiver)
: TestNetworkServiceClient(std::move(receiver)) {}
~MockNetworkServiceClient() override = default;
MockNetworkServiceClient(const MockNetworkServiceClient&) = delete;
MockNetworkServiceClient& operator=(const MockNetworkServiceClient&) = delete;
void WaitUntilRequestCompleted() {
if (completed_)
return;
base::RunLoop run_loop;
wait_for_completed_ = run_loop.QuitClosure();
run_loop.Run();
}
bool on_raw_request_called() const { return on_raw_request_called_; }
bool on_raw_response_called() const { return on_raw_response_called_; }
const base::Optional<network::ResourceRequest>& preflight_request() const {
return preflight_request_;
}
const network::mojom::URLResponseHeadPtr& preflight_response() const {
return preflight_response_;
}
const base::Optional<network::URLLoaderCompletionStatus>& preflight_status()
const {
return preflight_status_;
}
private:
// mojom::NetworkServiceClient:
void OnRawRequest(
int32_t process_id,
int32_t routing_id,
const std::string& devtools_request_id,
const net::CookieAccessResultList& cookies_with_access_result,
std::vector<network::mojom::HttpRawHeaderPairPtr> headers) override {
on_raw_request_called_ = true;
}
void OnRawResponse(
int32_t process_id,
int32_t routing_id,
const std::string& devtools_request_id,
const net::CookieAndLineStatusList& cookies_with_status,
std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
const base::Optional<std::string>& raw_response_headers) override {
on_raw_response_called_ = true;
}
void OnCorsPreflightRequest(int32_t process_id,
int32_t routing_id,
const base::UnguessableToken& devtool_request_id,
const network::ResourceRequest& request,
const GURL& initiator_url) override {
preflight_request_ = request;
}
void OnCorsPreflightResponse(
int32_t process_id,
int32_t routing_id,
const base::UnguessableToken& devtool_request_id,
const GURL& url,
network::mojom::URLResponseHeadPtr head) override {
preflight_response_ = std::move(head);
}
void OnCorsPreflightRequestCompleted(
int32_t process_id,
int32_t routing_id,
const base::UnguessableToken& devtool_request_id,
const network::URLLoaderCompletionStatus& status) override {
completed_ = true;
preflight_status_ = status;
if (wait_for_completed_)
std::move(wait_for_completed_).Run();
}
bool completed_ = false;
base::OnceClosure wait_for_completed_;
bool on_raw_request_called_ = false;
bool on_raw_response_called_ = false;
base::Optional<network::ResourceRequest> preflight_request_;
network::mojom::URLResponseHeadPtr preflight_response_;
base::Optional<network::URLLoaderCompletionStatus> preflight_status_;
};
class PreflightControllerTest : public testing::Test {
public:
PreflightControllerTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
test_initiator_origin_(
url::Origin::Create(GURL("http://example.com/"))),
access_control_allow_origin_(test_initiator_origin_) {
CorsURLLoaderFactory::SetAllowExternalPreflightsForTesting(true);
mojo::Remote<mojom::NetworkService> network_service_remote;
network_service_ = NetworkService::Create(
network_service_remote.BindNewPipeAndPassReceiver());
auto context_params = mojom::NetworkContextParams::New();
// Use a dummy CertVerifier that always passes cert verification, since
// these unittests don't need to test CertVerifier behavior.
context_params->cert_verifier_params =
FakeTestCertVerifierParamsFactory::GetCertVerifierParams();
network_service_remote->CreateNetworkContext(
network_context_remote_.BindNewPipeAndPassReceiver(),
std::move(context_params));
network::mojom::URLLoaderFactoryParamsPtr params =
network::mojom::URLLoaderFactoryParams::New();
params->process_id = mojom::kBrowserProcessId;
params->is_corb_enabled = false;
network_context_remote_->CreateURLLoaderFactory(
url_loader_factory_remote_.BindNewPipeAndPassReceiver(),
std::move(params));
}
~PreflightControllerTest() override {
CorsURLLoaderFactory::SetAllowExternalPreflightsForTesting(false);
}
protected:
void HandleRequestCompletion(int net_error,
base::Optional<CorsErrorStatus> status) {
net_error_ = net_error;
status_ = status;
run_loop_->Quit();
}
GURL GetURL(const std::string& path) { return test_server_.GetURL(path); }
void PerformPreflightCheck(
const ResourceRequest& request,
bool tainted = false,
net::IsolationInfo isolation_info = net::IsolationInfo()) {
DCHECK(preflight_controller_);
run_loop_ = std::make_unique<base::RunLoop>();
preflight_controller_->PerformPreflightCheck(
base::BindOnce(&PreflightControllerTest::HandleRequestCompletion,
base::Unretained(this)),
request, WithTrustedHeaderClient(false), tainted,
TRAFFIC_ANNOTATION_FOR_TESTS, url_loader_factory_remote_.get(),
0 /* process_id */, isolation_info);
run_loop_->Run();
}
void SetAccessControlAllowOrigin(const url::Origin origin) {
access_control_allow_origin_ = origin;
}
const url::Origin& test_initiator_origin() const {
return test_initiator_origin_;
}
const url::Origin& access_control_allow_origin() const {
return access_control_allow_origin_;
}
int net_error() const { return net_error_; }
base::Optional<CorsErrorStatus> status() { return status_; }
base::Optional<CorsErrorStatus> success() { return base::nullopt; }
size_t access_count() { return access_count_; }
NetworkService* network_service() { return network_service_.get(); }
private:
void SetUp() override {
SetAccessControlAllowOrigin(test_initiator_origin_);
preflight_controller_ = std::make_unique<PreflightController>(
std::vector<std::string>(), network_service_.get());
test_server_.RegisterRequestHandler(base::BindRepeating(
&PreflightControllerTest::ServePreflight, base::Unretained(this)));
EXPECT_TRUE(test_server_.Start());
}
std::unique_ptr<net::test_server::HttpResponse> ServePreflight(
const net::test_server::HttpRequest& request) {
access_count_++;
std::unique_ptr<net::test_server::BasicHttpResponse> response;
if (request.method != net::test_server::METHOD_OPTIONS)
return response;
response = std::make_unique<net::test_server::BasicHttpResponse>();
if (net::test_server::ShouldHandle(request, "/404") ||
net::test_server::ShouldHandle(request, "/allow") ||
net::test_server::ShouldHandle(request, "/tainted")) {
response->set_code(net::test_server::ShouldHandle(request, "/404")
? net::HTTP_NOT_FOUND
: net::HTTP_OK);
const url::Origin origin =
net::test_server::ShouldHandle(request, "/tainted")
? url::Origin()
: access_control_allow_origin();
response->AddCustomHeader(header_names::kAccessControlAllowOrigin,
origin.Serialize());
response->AddCustomHeader(header_names::kAccessControlAllowMethods,
"GET, OPTIONS");
response->AddCustomHeader(header_names::kAccessControlMaxAge, "1000");
response->AddCustomHeader(net::HttpRequestHeaders::kCacheControl,
"no-store");
}
return response;
}
base::test::TaskEnvironment task_environment_;
const url::Origin test_initiator_origin_;
url::Origin access_control_allow_origin_;
std::unique_ptr<base::RunLoop> run_loop_;
std::unique_ptr<NetworkService> network_service_;
mojo::Remote<mojom::NetworkContext> network_context_remote_;
mojo::Remote<mojom::URLLoaderFactory> url_loader_factory_remote_;
net::test_server::EmbeddedTestServer test_server_;
size_t access_count_ = 0;
std::unique_ptr<PreflightController> preflight_controller_;
int net_error_ = net::OK;
base::Optional<CorsErrorStatus> status_;
};
TEST_F(PreflightControllerTest, CheckInvalidRequest) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.url = GetURL("/404");
request.request_initiator = test_initiator_origin();
PerformPreflightCheck(request);
EXPECT_EQ(net::ERR_FAILED, net_error());
ASSERT_TRUE(status());
EXPECT_EQ(mojom::CorsError::kPreflightInvalidStatus, status()->cors_error);
EXPECT_EQ(1u, access_count());
}
TEST_F(PreflightControllerTest, CheckValidRequest) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.url = GetURL("/allow");
request.request_initiator = test_initiator_origin();
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count());
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count()); // Should be from the preflight cache.
// Verify if cache related flags work to skip the preflight cache.
request.load_flags = net::LOAD_VALIDATE_CACHE;
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(2u, access_count());
request.load_flags = net::LOAD_BYPASS_CACHE;
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(3u, access_count());
request.load_flags = net::LOAD_DISABLE_CACHE;
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(4u, access_count());
}
TEST_F(PreflightControllerTest, CheckRequestNetworkIsolationKey) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.url = GetURL("/allow");
const url::Origin& origin = test_initiator_origin();
request.request_initiator = origin;
ResourceRequest::TrustedParams trusted_params;
trusted_params.isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RedirectMode::kUpdateNothing, origin, origin,
net::SiteForCookies());
request.trusted_params = {trusted_params};
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count());
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count()); // Should be from the preflight cache.
url::Origin second_origin = url::Origin::Create(GURL("https://example.com/"));
request.request_initiator = second_origin;
SetAccessControlAllowOrigin(second_origin);
request.trusted_params->isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RedirectMode::kUpdateNothing, origin, second_origin,
net::SiteForCookies());
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(2u, access_count());
}
TEST_F(PreflightControllerTest, CheckFactoryNetworkIsolationKey) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.url = GetURL("/allow");
const url::Origin& origin = test_initiator_origin();
request.request_initiator = origin;
const net::IsolationInfo isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RedirectMode::kUpdateNothing, origin, origin,
net::SiteForCookies());
PerformPreflightCheck(request, false, isolation_info);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count());
PerformPreflightCheck(request, false, isolation_info);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count()); // Should be from the preflight cache.
PerformPreflightCheck(request, false, net::IsolationInfo());
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(2u, access_count()); // Should not be from the preflight cache.
}
TEST_F(PreflightControllerTest, CheckTaintedRequest) {
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.url = GetURL("/tainted");
request.request_initiator = test_initiator_origin();
PerformPreflightCheck(request, true /* tainted */);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count());
}
TEST_F(PreflightControllerTest, CheckResponseWithNullHeaders) {
GURL url = GURL("https://google.com/finullurl");
const mojom::URLResponseHead response_head;
ResourceRequest request;
request.url = url;
request.request_initiator = test_initiator_origin();
const bool tainted = false;
base::Optional<CorsErrorStatus> detected_error_status;
EXPECT_FALSE(response_head.headers);
std::unique_ptr<PreflightResult> result =
PreflightController::CreatePreflightResultForTesting(
url, response_head, request, tainted, &detected_error_status);
EXPECT_FALSE(result);
}
TEST_F(PreflightControllerTest, DevToolsEvents) {
mojo::PendingRemote<network::mojom::NetworkServiceClient>
network_service_client_remote;
std::unique_ptr<MockNetworkServiceClient> network_service_client =
std::make_unique<MockNetworkServiceClient>(
network_service_client_remote.InitWithNewPipeAndPassReceiver());
network_service()->SetClient(std::move(network_service_client_remote),
network::mojom::NetworkServiceParams::New());
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
request.credentials_mode = mojom::CredentialsMode::kOmit;
request.url = GetURL("/allow");
request.request_initiator = test_initiator_origin();
// Set the devtools id to trigger the DevTools event call on
// NetworkServiceClient.
request.devtools_request_id = "TEST";
PerformPreflightCheck(request);
EXPECT_EQ(net::OK, net_error());
ASSERT_FALSE(status());
EXPECT_EQ(1u, access_count());
// Check the DevTools event results.
network_service_client->WaitUntilRequestCompleted();
EXPECT_TRUE(network_service_client->on_raw_request_called());
EXPECT_TRUE(network_service_client->on_raw_response_called());
ASSERT_TRUE(network_service_client->preflight_request().has_value());
EXPECT_EQ(request.url, network_service_client->preflight_request()->url);
EXPECT_EQ("OPTIONS", network_service_client->preflight_request()->method);
ASSERT_TRUE(network_service_client->preflight_response());
ASSERT_TRUE(network_service_client->preflight_response()->headers);
EXPECT_EQ(
200,
network_service_client->preflight_response()->headers->response_code());
ASSERT_TRUE(network_service_client->preflight_status().has_value());
EXPECT_EQ(net::OK, network_service_client->preflight_status()->error_code);
}
} // namespace
} // namespace cors
} // namespace network