| // Copyright 2018 The Fuchsia 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 "lib/network_wrapper/network_wrapper_impl.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include <fuchsia/net/oldhttp/cpp/fidl.h> |
| #include <fuchsia/netstack/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/zx/socket.h> |
| |
| #include "gtest/gtest.h" |
| #include "lib/backoff/testing/test_backoff.h" |
| #include "lib/fidl/cpp/binding.h" |
| #include "lib/fidl/cpp/optional.h" |
| #include "lib/fit/defer.h" |
| #include "lib/fsl/socket/strings.h" |
| #include "src/lib/fxl/macros.h" |
| #include "lib/gtest/test_loop_fixture.h" |
| |
| namespace network_wrapper { |
| namespace { |
| |
| namespace http = ::fuchsia::net::oldhttp; |
| |
| const char kRedirectUrl[] = "http://example.com/redirect"; |
| |
| // Url loader that stores the url request for inspection in |request_received|, |
| // and returns response indicated in |response_to_return|. |response_to_return| |
| // is moved out in ::Start(). |
| class FakeURLLoader : public http::URLLoader { |
| public: |
| FakeURLLoader(fidl::InterfaceRequest<http::URLLoader> request, |
| http::URLResponse response_to_return, |
| http::URLRequest* request_received) |
| : binding_(this, std::move(request)), |
| response_to_return_(std::move(response_to_return)), |
| request_received_(request_received) {} |
| ~FakeURLLoader() override {} |
| |
| // URLLoader: |
| void Start(http::URLRequest request, StartCallback callback) override { |
| *request_received_ = std::move(request); |
| callback(std::move(response_to_return_)); |
| } |
| void FollowRedirect(FollowRedirectCallback) override {} |
| void QueryStatus(QueryStatusCallback) override {} |
| |
| private: |
| fidl::Binding<http::URLLoader> binding_; |
| http::URLResponse response_to_return_; |
| http::URLRequest* request_received_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(FakeURLLoader); |
| }; |
| |
| // Fake implementation of network service, allowing to inspect the last request |
| // passed to any url loader and set the response that url loaders need to |
| // return. Response is moved out when url request starts, and needs to be set |
| // each time. |
| class FakeNetworkWrapper : public http::HttpService { |
| public: |
| explicit FakeNetworkWrapper(fidl::InterfaceRequest<http::HttpService> request) |
| : binding_(this, std::move(request)) {} |
| ~FakeNetworkWrapper() override {} |
| |
| http::URLRequest* GetRequest() { return &request_received_; } |
| |
| void SetResponse(http::URLResponse response) { |
| response_to_return_ = std::move(response); |
| } |
| |
| // NetworkService: |
| void CreateURLLoader( |
| fidl::InterfaceRequest<http::URLLoader> loader) override { |
| loaders_.push_back(std::make_unique<FakeURLLoader>( |
| std::move(loader), std::move(response_to_return_), &request_received_)); |
| } |
| |
| private: |
| fidl::Binding<http::HttpService> binding_; |
| std::vector<std::unique_ptr<FakeURLLoader>> loaders_; |
| http::URLRequest request_received_; |
| http::URLResponse response_to_return_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(FakeNetworkWrapper); |
| }; |
| |
| class NetworkWrapperImplTest : public gtest::TestLoopFixture { |
| public: |
| NetworkWrapperImplTest() |
| : network_service_(dispatcher(), |
| std::make_unique<backoff::TestBackoff>(zx::sec(0)), |
| [this] { return NewHttpService(); }) {} |
| |
| void SetSocketResponse(zx::socket body, uint32_t status_code) { |
| http::URLResponse server_response; |
| server_response.body = http::URLBody::New(); |
| server_response.body->set_stream(std::move(body)); |
| server_response.status_code = status_code; |
| http::HttpHeader header; |
| header.name = "Location"; |
| header.value = kRedirectUrl; |
| server_response.headers.push_back(std::move(header)); |
| if (fake_network_service_) { |
| fake_network_service_->SetResponse(std::move(server_response)); |
| } else { |
| response_ = fidl::MakeOptional(std::move(server_response)); |
| } |
| } |
| |
| void SetStringResponse(const std::string& body, uint32_t status_code) { |
| SetSocketResponse(fsl::WriteStringToSocket(body), status_code); |
| } |
| |
| http::URLRequest NewRequest(const std::string& method, |
| const std::string& url) { |
| http::URLRequest request; |
| request.method = method; |
| request.url = url; |
| return request; |
| } |
| |
| private: |
| http::HttpServicePtr NewHttpService() { |
| http::HttpServicePtr result; |
| fake_network_service_ = |
| std::make_unique<FakeNetworkWrapper>(result.NewRequest()); |
| if (response_) { |
| fake_network_service_->SetResponse(std::move(*response_)); |
| } |
| return result; |
| } |
| |
| protected: |
| NetworkWrapperImpl network_service_; |
| std::unique_ptr<FakeNetworkWrapper> fake_network_service_; |
| http::URLResponsePtr response_; |
| }; |
| |
| TEST_F(NetworkWrapperImplTest, SimpleRequest) { |
| bool callback_destroyed = false; |
| http::URLResponse response; |
| network_service_.Request( |
| [this]() { |
| SetStringResponse("Hello", 200); |
| return NewRequest("GET", "http://example.com"); |
| }, |
| [destroy_watcher = |
| fit::defer([&callback_destroyed] { callback_destroyed = true; }), |
| &response](http::URLResponse received_response) { |
| response = std::move(received_response); |
| }); |
| EXPECT_FALSE(callback_destroyed); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(callback_destroyed); |
| EXPECT_EQ(200u, response.status_code); |
| } |
| |
| TEST_F(NetworkWrapperImplTest, CancelRequest) { |
| bool callback_destroyed = false; |
| bool received_response = false; |
| auto cancel = network_service_.Request( |
| [this]() { |
| SetStringResponse("Hello", 200); |
| return NewRequest("GET", "http://example.com"); |
| }, |
| [&received_response, destroy_watcher = fit::defer([&callback_destroyed] { |
| callback_destroyed = true; |
| })](http::URLResponse) { |
| received_response = true; |
| }); |
| |
| async::PostTask(dispatcher(), [cancel] { cancel->Cancel(); }); |
| cancel = nullptr; |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(received_response); |
| EXPECT_TRUE(callback_destroyed); |
| } |
| |
| TEST_F(NetworkWrapperImplTest, NetworkDeleted) { |
| int request_count = 0; |
| http::URLResponse response; |
| network_service_.Request( |
| [this, &request_count]() { |
| if (request_count == 0) { |
| fake_network_service_.reset(); |
| } |
| ++request_count; |
| SetStringResponse("Hello", 200); |
| return NewRequest("GET", "http://example.com"); |
| }, |
| [&response](http::URLResponse received_response) { |
| response = std::move(received_response); |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(2, request_count); |
| EXPECT_EQ(200u, response.status_code); |
| } |
| |
| TEST_F(NetworkWrapperImplTest, Redirection) { |
| int request_count = 0; |
| http::URLResponse response; |
| network_service_.Request( |
| [this, &request_count]() { |
| if (request_count == 0) { |
| SetStringResponse("Hello", 307); |
| } else { |
| SetStringResponse("Hello", 200); |
| } |
| ++request_count; |
| return NewRequest("GET", "http://example.com"); |
| }, |
| [&response](http::URLResponse received_response) { |
| response = std::move(received_response); |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(2, request_count); |
| EXPECT_EQ(200u, response.status_code); |
| EXPECT_EQ(kRedirectUrl, fake_network_service_->GetRequest()->url); |
| } |
| |
| TEST_F(NetworkWrapperImplTest, CancelOnCallback) { |
| fxl::RefPtr<callback::Cancellable> request; |
| http::URLResponsePtr response; |
| request = network_service_.Request( |
| [this] { |
| SetStringResponse("Hello", 200); |
| return NewRequest("GET", "http://example.com"); |
| }, |
| [&request, &response](http::URLResponse received_response) mutable { |
| response = fidl::MakeOptional(std::move(received_response)); |
| request->Cancel(); |
| request = nullptr; |
| }); |
| EXPECT_FALSE(response); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(response); |
| } |
| |
| } // namespace |
| } // namespace network_wrapper |