blob: 0780a3f51f06b298b2e9c16067fe5ec2c4d4da10 [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 <memory>
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/network/network_context.h"
#include "content/network/network_service_impl.h"
#include "content/public/common/service_names.mojom.h"
#include "content/public/test/test_url_loader_client.h"
#include "net/base/mock_network_change_notifier.h"
#include "net/proxy/proxy_config.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/interfaces/network_change_manager.mojom.h"
#include "services/network/public/interfaces/network_service.mojom.h"
#include "services/service_manager/public/cpp/service_context.h"
#include "services/service_manager/public/cpp/service_test.h"
#include "services/service_manager/public/interfaces/service_factory.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
network::mojom::NetworkContextParamsPtr CreateContextParams() {
network::mojom::NetworkContextParamsPtr params =
network::mojom::NetworkContextParams::New();
// Use a fixed proxy config, to avoid dependencies on local network
// configuration.
params->initial_proxy_config = net::ProxyConfig::CreateDirect();
return params;
}
class NetworkServiceTest : public testing::Test {
public:
NetworkServiceTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::IO),
service_(NetworkServiceImpl::CreateForTesting()) {}
~NetworkServiceTest() override {}
NetworkService* service() const { return service_.get(); }
void DestroyService() { service_.reset(); }
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<NetworkService> service_;
};
// Test shutdown in the case a NetworkContext is destroyed before the
// NetworkService.
TEST_F(NetworkServiceTest, CreateAndDestroyContext) {
network::mojom::NetworkContextPtr network_context;
service()->CreateNetworkContext(mojo::MakeRequest(&network_context),
CreateContextParams());
network_context.reset();
// Make sure the NetworkContext is destroyed.
base::RunLoop().RunUntilIdle();
}
// Test shutdown in the case there is still a live NetworkContext when the
// NetworkService is destroyed. The service should destroy the NetworkContext
// itself.
TEST_F(NetworkServiceTest, DestroyingServiceDestroysContext) {
network::mojom::NetworkContextPtr network_context;
service()->CreateNetworkContext(mojo::MakeRequest(&network_context),
CreateContextParams());
base::RunLoop run_loop;
network_context.set_connection_error_handler(run_loop.QuitClosure());
DestroyService();
// Destroying the service should destroy the context, causing a connection
// error.
run_loop.Run();
}
namespace {
class ServiceTestClient : public service_manager::test::ServiceTestClient,
public service_manager::mojom::ServiceFactory {
public:
explicit ServiceTestClient(service_manager::test::ServiceTest* test)
: service_manager::test::ServiceTestClient(test) {
registry_.AddInterface<service_manager::mojom::ServiceFactory>(base::Bind(
&ServiceTestClient::BindServiceFactoryRequest, base::Unretained(this)));
}
~ServiceTestClient() override {}
protected:
void OnBindInterface(const service_manager::BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) override {
registry_.BindInterface(interface_name, std::move(interface_pipe));
}
void CreateService(
service_manager::mojom::ServiceRequest request,
const std::string& name,
service_manager::mojom::PIDReceiverPtr pid_receiver) override {
if (name == mojom::kNetworkServiceName) {
service_context_.reset(new service_manager::ServiceContext(
NetworkServiceImpl::CreateForTesting(), std::move(request)));
}
}
void BindServiceFactoryRequest(
service_manager::mojom::ServiceFactoryRequest request) {
service_factory_bindings_.AddBinding(this, std::move(request));
}
std::unique_ptr<service_manager::ServiceContext> service_context_;
private:
service_manager::BinderRegistry registry_;
mojo::BindingSet<service_manager::mojom::ServiceFactory>
service_factory_bindings_;
};
} // namespace
class NetworkServiceTestWithService
: public service_manager::test::ServiceTest {
public:
NetworkServiceTestWithService()
: ServiceTest("content_unittests",
base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
~NetworkServiceTestWithService() override {}
void LoadURL(const GURL& url) {
network::ResourceRequest request;
request.url = url;
request.method = "GET";
request.request_initiator = url::Origin();
StartLoadingURL(request, 0);
client_->RunUntilComplete();
}
void StartLoadingURL(const network::ResourceRequest& request,
uint32_t process_id) {
client_.reset(new TestURLLoaderClient());
network::mojom::URLLoaderFactoryPtr loader_factory;
network_context_->CreateURLLoaderFactory(mojo::MakeRequest(&loader_factory),
process_id);
loader_factory->CreateLoaderAndStart(
mojo::MakeRequest(&loader_), 1, 1, network::mojom::kURLLoadOptionNone,
request, client_->CreateInterfacePtr(),
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
}
net::EmbeddedTestServer* test_server() { return &test_server_; }
TestURLLoaderClient* client() { return client_.get(); }
network::mojom::URLLoader* loader() { return loader_.get(); }
network::mojom::NetworkService* service() { return network_service_.get(); }
network::mojom::NetworkContext* context() { return network_context_.get(); }
private:
std::unique_ptr<service_manager::Service> CreateService() override {
return std::make_unique<ServiceTestClient>(this);
}
void SetUp() override {
base::FilePath content_test_data(FILE_PATH_LITERAL("content/test/data"));
test_server_.AddDefaultHandlers(content_test_data);
ASSERT_TRUE(test_server_.Start());
service_manager::test::ServiceTest::SetUp();
connector()->BindInterface(mojom::kNetworkServiceName, &network_service_);
network::mojom::NetworkContextParamsPtr context_params =
network::mojom::NetworkContextParams::New();
network_service_->CreateNetworkContext(mojo::MakeRequest(&network_context_),
std::move(context_params));
}
net::EmbeddedTestServer test_server_;
std::unique_ptr<TestURLLoaderClient> client_;
network::mojom::NetworkServicePtr network_service_;
network::mojom::NetworkContextPtr network_context_;
network::mojom::URLLoaderPtr loader_;
DISALLOW_COPY_AND_ASSIGN(NetworkServiceTestWithService);
};
// Verifies that loading a URL through the network service's mojo interface
// works.
TEST_F(NetworkServiceTestWithService, Basic) {
LoadURL(test_server()->GetURL("/echo"));
EXPECT_EQ(net::OK, client()->completion_status().error_code);
}
// Verifies that raw headers are only reported if requested.
TEST_F(NetworkServiceTestWithService, RawRequestHeadersAbsent) {
network::ResourceRequest request;
request.url = test_server()->GetURL("/server-redirect?/echo");
request.method = "GET";
request.request_initiator = url::Origin();
StartLoadingURL(request, 0);
client()->RunUntilRedirectReceived();
EXPECT_TRUE(client()->has_received_redirect());
EXPECT_TRUE(!client()->response_head().raw_request_response_info);
loader()->FollowRedirect();
client()->RunUntilComplete();
EXPECT_TRUE(!client()->response_head().raw_request_response_info);
}
TEST_F(NetworkServiceTestWithService, RawRequestHeadersPresent) {
network::ResourceRequest request;
request.url = test_server()->GetURL("/server-redirect?/echo");
request.method = "GET";
request.report_raw_headers = true;
request.request_initiator = url::Origin();
StartLoadingURL(request, 0);
client()->RunUntilRedirectReceived();
EXPECT_TRUE(client()->has_received_redirect());
{
scoped_refptr<network::HttpRawRequestResponseInfo> request_response_info =
client()->response_head().raw_request_response_info;
ASSERT_TRUE(request_response_info);
EXPECT_EQ(301, request_response_info->http_status_code);
EXPECT_EQ("Moved Permanently", request_response_info->http_status_text);
EXPECT_TRUE(base::StartsWith(request_response_info->request_headers_text,
"GET /server-redirect?/echo HTTP/1.1\r\n",
base::CompareCase::SENSITIVE));
EXPECT_GE(request_response_info->request_headers.size(), 1lu);
EXPECT_GE(request_response_info->response_headers.size(), 1lu);
EXPECT_TRUE(base::StartsWith(request_response_info->response_headers_text,
"HTTP/1.1 301 Moved Permanently\r",
base::CompareCase::SENSITIVE));
}
loader()->FollowRedirect();
client()->RunUntilComplete();
{
scoped_refptr<network::HttpRawRequestResponseInfo> request_response_info =
client()->response_head().raw_request_response_info;
EXPECT_EQ(200, request_response_info->http_status_code);
EXPECT_EQ("OK", request_response_info->http_status_text);
EXPECT_TRUE(base::StartsWith(request_response_info->request_headers_text,
"GET /echo HTTP/1.1\r\n",
base::CompareCase::SENSITIVE));
EXPECT_GE(request_response_info->request_headers.size(), 1lu);
EXPECT_GE(request_response_info->response_headers.size(), 1lu);
EXPECT_TRUE(base::StartsWith(request_response_info->response_headers_text,
"HTTP/1.1 200 OK\r",
base::CompareCase::SENSITIVE));
}
}
TEST_F(NetworkServiceTestWithService, RawRequestAccessControl) {
const uint32_t process_id = 42;
network::ResourceRequest request;
request.url = test_server()->GetURL("/nocache.html");
request.method = "GET";
request.report_raw_headers = true;
request.request_initiator = url::Origin();
StartLoadingURL(request, process_id);
client()->RunUntilComplete();
EXPECT_FALSE(client()->response_head().raw_request_response_info);
service()->SetRawHeadersAccess(process_id, true);
StartLoadingURL(request, process_id);
client()->RunUntilComplete();
{
scoped_refptr<network::HttpRawRequestResponseInfo> request_response_info =
client()->response_head().raw_request_response_info;
ASSERT_TRUE(request_response_info);
EXPECT_EQ(200, request_response_info->http_status_code);
EXPECT_EQ("OK", request_response_info->http_status_text);
}
service()->SetRawHeadersAccess(process_id, false);
StartLoadingURL(request, process_id);
client()->RunUntilComplete();
EXPECT_FALSE(client()->response_head().raw_request_response_info.get());
}
TEST_F(NetworkServiceTestWithService, SetNetworkConditions) {
network::mojom::NetworkConditionsPtr network_conditions =
network::mojom::NetworkConditions::New();
network_conditions->offline = true;
context()->SetNetworkConditions("42", std::move(network_conditions));
network::ResourceRequest request;
request.url = test_server()->GetURL("/nocache.html");
request.method = "GET";
StartLoadingURL(request, 0);
client()->RunUntilComplete();
EXPECT_EQ(net::OK, client()->completion_status().error_code);
request.headers.AddHeaderFromString(
"X-DevTools-Emulate-Network-Conditions-Client-Id: 42");
StartLoadingURL(request, 0);
client()->RunUntilComplete();
EXPECT_EQ(net::ERR_INTERNET_DISCONNECTED,
client()->completion_status().error_code);
network_conditions = network::mojom::NetworkConditions::New();
network_conditions->offline = false;
context()->SetNetworkConditions("42", std::move(network_conditions));
StartLoadingURL(request, 0);
client()->RunUntilComplete();
EXPECT_EQ(net::OK, client()->completion_status().error_code);
network_conditions = network::mojom::NetworkConditions::New();
network_conditions->offline = true;
context()->SetNetworkConditions("42", std::move(network_conditions));
request.headers.AddHeaderFromString(
"X-DevTools-Emulate-Network-Conditions-Client-Id: 42");
StartLoadingURL(request, 0);
client()->RunUntilComplete();
EXPECT_EQ(net::ERR_INTERNET_DISCONNECTED,
client()->completion_status().error_code);
context()->SetNetworkConditions("42", nullptr);
StartLoadingURL(request, 0);
client()->RunUntilComplete();
EXPECT_EQ(net::OK, client()->completion_status().error_code);
}
class TestNetworkChangeManagerClient
: public network::mojom::NetworkChangeManagerClient {
public:
explicit TestNetworkChangeManagerClient(
network::mojom::NetworkService* network_service)
: connection_type_(network::mojom::ConnectionType::CONNECTION_UNKNOWN),
binding_(this) {
network::mojom::NetworkChangeManagerPtr manager_ptr;
network::mojom::NetworkChangeManagerRequest request(
mojo::MakeRequest(&manager_ptr));
network_service->GetNetworkChangeManager(std::move(request));
network::mojom::NetworkChangeManagerClientPtr client_ptr;
network::mojom::NetworkChangeManagerClientRequest client_request(
mojo::MakeRequest(&client_ptr));
binding_.Bind(std::move(client_request));
manager_ptr->RequestNotifications(std::move(client_ptr));
}
~TestNetworkChangeManagerClient() override {}
// NetworkChangeManagerClient implementation:
void OnInitialConnectionType(network::mojom::ConnectionType type) override {
if (type == connection_type_)
run_loop_.Quit();
}
void OnNetworkChanged(network::mojom::ConnectionType type) override {
if (type == connection_type_)
run_loop_.Quit();
}
// Waits for the desired |connection_type| notification.
void WaitForNotification(network::mojom::ConnectionType type) {
connection_type_ = type;
run_loop_.Run();
}
private:
base::RunLoop run_loop_;
network::mojom::ConnectionType connection_type_;
mojo::Binding<network::mojom::NetworkChangeManagerClient> binding_;
DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeManagerClient);
};
class NetworkChangeTest : public testing::Test {
public:
NetworkChangeTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::IO) {
service_ = NetworkServiceImpl::CreateForTesting();
}
~NetworkChangeTest() override {}
NetworkService* service() const { return service_.get(); }
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
#if defined(OS_ANDROID)
// On Android, NetworkChangeNotifier setup is more involved and needs to
// to be split between UI thread and network thread. Use a mock
// NetworkChangeNotifier in tests, so the test setup is simpler.
net::test::MockNetworkChangeNotifier network_change_notifier_;
#endif
std::unique_ptr<NetworkService> service_;
};
// network::mojom:NetworkChangeManager currently doesn't support ChromeOS,
// which has a different code path to set up net::NetworkChangeNotifier.
#if defined(OS_CHROMEOS) || defined(OS_FUCHSIA)
#define MAYBE_NetworkChangeManagerRequest DISABLED_NetworkChangeManagerRequest
#else
#define MAYBE_NetworkChangeManagerRequest NetworkChangeManagerRequest
#endif
TEST_F(NetworkChangeTest, MAYBE_NetworkChangeManagerRequest) {
TestNetworkChangeManagerClient manager_client(service());
net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
net::NetworkChangeNotifier::CONNECTION_3G);
manager_client.WaitForNotification(
network::mojom::ConnectionType::CONNECTION_3G);
}
class NetworkServiceNetworkChangeTest
: public service_manager::test::ServiceTest {
public:
NetworkServiceNetworkChangeTest()
: ServiceTest("content_unittests",
base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
~NetworkServiceNetworkChangeTest() override {}
network::mojom::NetworkService* service() { return network_service_.get(); }
private:
// A ServiceTestClient that broadcasts a network change notification in the
// network service's process.
class ServiceTestClientWithNetworkChange : public ServiceTestClient {
public:
explicit ServiceTestClientWithNetworkChange(
service_manager::test::ServiceTest* test)
: ServiceTestClient(test) {}
~ServiceTestClientWithNetworkChange() override {}
protected:
void CreateService(
service_manager::mojom::ServiceRequest request,
const std::string& name,
service_manager::mojom::PIDReceiverPtr pid_receiver) override {
if (name == mojom::kNetworkServiceName) {
service_context_.reset(new service_manager::ServiceContext(
NetworkServiceImpl::CreateForTesting(), std::move(request)));
// Send a broadcast after NetworkService is actually created.
// Otherwise, this NotifyObservers is a no-op.
net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
net::NetworkChangeNotifier::CONNECTION_3G);
}
}
};
std::unique_ptr<service_manager::Service> CreateService() override {
return std::make_unique<ServiceTestClientWithNetworkChange>(this);
}
void SetUp() override {
service_manager::test::ServiceTest::SetUp();
connector()->BindInterface(mojom::kNetworkServiceName, &network_service_);
}
network::mojom::NetworkServicePtr network_service_;
#if defined(OS_ANDROID)
// On Android, NetworkChangeNotifier setup is more involved and needs
// to be split between UI thread and network thread. Use a mock
// NetworkChangeNotifier in tests, so the test setup is simpler.
net::test::MockNetworkChangeNotifier network_change_notifier_;
#endif
DISALLOW_COPY_AND_ASSIGN(NetworkServiceNetworkChangeTest);
};
TEST_F(NetworkServiceNetworkChangeTest, MAYBE_NetworkChangeManagerRequest) {
TestNetworkChangeManagerClient manager_client(service());
manager_client.WaitForNotification(
network::mojom::ConnectionType::CONNECTION_3G);
}
} // namespace
} // namespace content