From a8b6fdee591b6cf5d61b7592b841b6ea1d8264bf Mon Sep 17 00:00:00 2001 From: Qin Wang Date: Thu, 29 Jun 2023 18:38:34 -0700 Subject: [PATCH] Implement fake GetResponse and migrate fake_http_client to third_party/nearby/internal/test PiperOrigin-RevId: 544518775 --- Package.swift | 1 + internal/test/BUILD | 11 ++ internal/test/fake_http_client.h | 186 +++++++++++++++++++++++ internal/test/fake_http_client_factory.h | 37 +++++ internal/test/fake_http_client_test.cc | 163 ++++++++++++++++++++ 5 files changed, 398 insertions(+) create mode 100644 internal/test/fake_http_client.h create mode 100644 internal/test/fake_http_client_factory.h create mode 100644 internal/test/fake_http_client_test.cc diff --git a/Package.swift b/Package.swift index 6553676a..a887a0b9 100644 --- a/Package.swift +++ b/Package.swift @@ -543,6 +543,7 @@ let package = Package( "internal/test/fake_device_info_test.cc", "internal/test/fake_task_runner_test.cc", "internal/test/fake_data_set_test.cc", + "internal/test/fake_http_client_test.cc", "internal/weave/base_socket_test.cc", "internal/weave/control_packet_write_request_test.cc", "internal/weave/message_write_request_test.cc", diff --git a/internal/test/BUILD b/internal/test/BUILD index dbfbc938..e8c5bf70 100644 --- a/internal/test/BUILD +++ b/internal/test/BUILD @@ -27,6 +27,8 @@ cc_library( "fake_clock.h", "fake_data_set.h", "fake_device_info.h", + "fake_http_client.h", + "fake_http_client_factory.h", "fake_single_thread_executor.h", "fake_task_runner.h", "fake_timer.h", @@ -39,11 +41,14 @@ cc_library( deps = [ "//internal/base:bluetooth_address", "//internal/data:data_manager", + "//internal/network:types", "//internal/platform:comm", "//internal/platform:types", "//internal/platform/implementation:types", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", @@ -59,6 +64,7 @@ cc_test( "fake_clock_test.cc", "fake_data_set_test.cc", "fake_device_info_test.cc", + "fake_http_client_test.cc", "fake_task_runner_test.cc", "fake_timer_test.cc", ], @@ -69,9 +75,14 @@ cc_test( deps = [ ":test", "//internal/data:data_manager", + "//internal/network:types", "//internal/platform/implementation:types", "//internal/platform/implementation/g3", # fixdeps: keep "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", "@com_google_googletest//:gtest_main", diff --git a/internal/test/fake_http_client.h b/internal/test/fake_http_client.h new file mode 100644 index 00000000..14e04a80 --- /dev/null +++ b/internal/test/fake_http_client.h @@ -0,0 +1,186 @@ +// Copyright 2021-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_HTTP_CLIENT_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_HTTP_CLIENT_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "internal/network/http_client.h" +#include "internal/network/http_request.h" +#include "internal/network/http_response.h" +#include "internal/network/http_status_code.h" + +namespace nearby { +namespace network { + +class FakeHttpClient : public HttpClient { + public: + struct RequestInfo { + HttpRequest request; + std::function&)> callback; + }; + + FakeHttpClient() = default; + explicit FakeHttpClient(const HttpRequest& request); + ~FakeHttpClient() override = default; + + FakeHttpClient(const FakeHttpClient&) = default; + FakeHttpClient& operator=(const FakeHttpClient&) = default; + FakeHttpClient(FakeHttpClient&&) = default; + FakeHttpClient& operator=(FakeHttpClient&&) = default; + + void StartRequest(const HttpRequest& request, + std::function&)> + callback) override { + RequestInfo request_info; + request_info.request = request; + request_info.callback = callback; + request_infos_.push_back(std::move(request_info)); + } + + void StartCancellableRequest( + std::unique_ptr request, + std::function&)> callback) + override { + RequestInfo request_info; + request_info.request = request->http_request(); + request_info.callback = callback; + request_infos_.push_back(std::move(request_info)); + } + + absl::StatusOr GetResponse( + const HttpRequest& request) override { + if (sync_responses_.empty()) { + return absl::FailedPreconditionError("No response."); + } + auto response = sync_responses_.front(); + sync_responses_.erase(sync_responses_.begin()); + return response; + } + + void SetResponseForSyncRequest(const absl::StatusOr& response) { + sync_responses_.push_back(response); + } + + // Mock methods + void CompleteRequest(const absl::StatusOr& response, + size_t pos = 0) { + if (pos >= request_infos_.size()) { + return; + } + auto request_info = request_infos_.at(pos); + if (request_info.callback != nullptr) { + request_info.callback(response); + } + + request_infos_.erase(request_infos_.begin() + pos); + } + + void CompleteRequest( + int error, + std::optional response_code = + std::nullopt, + const std::optional& response_string = std::nullopt, + size_t pos = 0) { + absl::StatusOr result; + + nearby::network::HttpResponse response; + if (error == 0) { + absl::Status status = + HTTPCodeToStatus(static_cast(*response_code), ""); + if (status.ok()) { + if (response_code.has_value()) { + response.SetStatusCode(*response_code); + } + + if (response_string.has_value()) { + response.SetBody(*response_string); + } + + response.SetHeaders({{"Content-type", {"text/html"}}}); + result = response; + } else { + result = status; + } + } else { + result = absl::Status(absl::StatusCode::kFailedPrecondition, + std::to_string(error)); + } + + CompleteRequest(result, pos); + } + + absl::Status HTTPCodeToStatus(int status_code, + absl::string_view status_message) { + absl::Status status = absl::OkStatus(); + switch (status_code) { + case 400: + return absl::InvalidArgumentError(status_message); + case 401: + return absl::UnauthenticatedError(status_message); + case 403: + return absl::PermissionDeniedError(status_message); + case 404: + return absl::NotFoundError(status_message); + case 409: + return absl::AbortedError(status_message); + case 416: + return absl::OutOfRangeError(status_message); + case 429: + return absl::ResourceExhaustedError(status_message); + case 499: + return absl::CancelledError(status_message); + case 504: + return absl::DeadlineExceededError(status_message); + case 501: + return absl::UnimplementedError(status_message); + case 503: + return absl::UnavailableError(status_message); + default: + break; + } + if (status_code >= 200 && status_code < 300) { + return absl::OkStatus(); + } else if (status_code >= 400 && status_code < 500) { + return absl::FailedPreconditionError(status_message); + } else if (status_code >= 500 && status_code < 600) { + return absl::InternalError(status_message); + } + return absl::UnknownError(status_message); + } + + const std::vector& GetPendingRequest() { return request_infos_; } + + private: + std::vector request_infos_; + + // Response for sync request + std::vector> sync_responses_; +}; + +} // namespace network +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_HTTP_CLIENT_H_ diff --git a/internal/test/fake_http_client_factory.h b/internal/test/fake_http_client_factory.h new file mode 100644 index 00000000..1f903819 --- /dev/null +++ b/internal/test/fake_http_client_factory.h @@ -0,0 +1,37 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_HTTP_CLIENT_FACTORY_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_HTTP_CLIENT_FACTORY_H_ + +#include + +#include "internal/network/http_client.h" +#include "internal/network/http_client_factory.h" +#include "internal/test/fake_http_client.h" + +namespace nearby { +namespace network { + +class FakeHttpClientFactory : public HttpClientFactory { + public: + std::unique_ptr CreateInstance() override { + return std::make_unique(); + } +}; + +} // namespace network +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_TEST_FAKE_HTTP_CLIENT_FACTORY_H_ diff --git a/internal/test/fake_http_client_test.cc b/internal/test/fake_http_client_test.cc new file mode 100644 index 00000000..2c498b52 --- /dev/null +++ b/internal/test/fake_http_client_test.cc @@ -0,0 +1,163 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/test/fake_http_client.h" + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "internal/network/http_request.h" +#include "internal/network/http_response.h" +#include "internal/network/http_status_code.h" +#include "internal/network/url.h" + +namespace nearby { +namespace network { +namespace { + +class FakekHttpClientTest : public ::testing::Test { + public: + void StartRequest( + absl::string_view request_url, + std::function&)> callback) { + HttpRequest request; + Url url; + url.SetUrlPath(request_url); + request.SetUrl(url); + request.SetMethod(HttpRequestMethod::kPost); + request.SetBody("request body"); + client_.StartRequest(request, callback); + } + + void CompleteRequest( + HttpStatusCode status, std::string status_message = "", + absl::flat_hash_map> headers = {}, + std::string body = "", int pos = 0) { + HttpResponse response; + response.SetStatusCode(status); + response.SetReasonPhrase(status_message); + response.SetHeaders(headers); + response.SetBody(body); + client_.CompleteRequest(response, pos); + } + + void CompleteRequest(int error, int status = 200, int pos = 0) { + client_.CompleteRequest(error, static_cast(status), + std::nullopt, pos); + } + + void CheckHttpError(int status, absl::StatusCode expected_status) { + absl::StatusOr response; + StartRequest("http://www.google.com", + [&response](const absl::StatusOr& res) { + response = res; + }); + + client_.CompleteRequest(0, static_cast(status), + std::nullopt); + if (status >= 200 & status < 300) { + ASSERT_TRUE(response.ok()); + } else { + ASSERT_FALSE(response.ok()); + } + + EXPECT_EQ(response.status().code(), expected_status); + } + + protected: + FakeHttpClient client_; +}; + +TEST_F(FakekHttpClientTest, TestMockHttpResponse) { + absl::StatusOr response; + StartRequest( + "http://www.google.com", + [&response](const absl::StatusOr& res) { response = res; }); + + CompleteRequest(HttpStatusCode::kHttpOk); + ASSERT_OK(response); + EXPECT_EQ(response->GetStatusCode(), HttpStatusCode::kHttpOk); +} + +TEST_F(FakekHttpClientTest, TestGetResponse) { + // Set up HttpRequest + HttpRequest request; + Url url; + url.SetUrlPath("http://www.google.com"); + request.SetUrl(url); + request.SetMethod(HttpRequestMethod::kGet); + request.SetBody("request body"); + // Set up HttpResponse + HttpResponse response; + response.SetStatusCode(HttpStatusCode::kHttpOk); + client_.SetResponseForSyncRequest(response); + // Sync GetResponse + absl::StatusOr result = client_.GetResponse(request); + + ASSERT_OK(result); + EXPECT_EQ(result->GetStatusCode(), HttpStatusCode::kHttpOk); +} + +TEST_F(FakekHttpClientTest, TestSystemError) { + absl::StatusOr response; + StartRequest( + "http://www.google.com", + [&response](const absl::StatusOr& res) { response = res; }); + + CompleteRequest(0xffff); + ASSERT_FALSE(response.ok()); + EXPECT_EQ(response.status().code(), absl::StatusCode::kFailedPrecondition); +} + +TEST_F(FakekHttpClientTest, TestHttpErrorCodes) { + CheckHttpError(200, absl::StatusCode::kOk); + CheckHttpError(400, absl::StatusCode::kInvalidArgument); + CheckHttpError(401, absl::StatusCode::kUnauthenticated); + CheckHttpError(403, absl::StatusCode::kPermissionDenied); + CheckHttpError(404, absl::StatusCode::kNotFound); + CheckHttpError(409, absl::StatusCode::kAborted); + CheckHttpError(416, absl::StatusCode::kOutOfRange); + CheckHttpError(429, absl::StatusCode::kResourceExhausted); + CheckHttpError(499, absl::StatusCode::kCancelled); + CheckHttpError(504, absl::StatusCode::kDeadlineExceeded); + CheckHttpError(501, absl::StatusCode::kUnimplemented); + CheckHttpError(503, absl::StatusCode::kUnavailable); + CheckHttpError(488, absl::StatusCode::kFailedPrecondition); + CheckHttpError(522, absl::StatusCode::kInternal); + CheckHttpError(777, absl::StatusCode::kUnknown); +} + +TEST_F(FakekHttpClientTest, TestCompleteNotExistingRequest) { + int count = 0; + StartRequest("http://www.google.com", + [&count](const absl::StatusOr& res) { ++count; }); + + CompleteRequest(0xffff); + EXPECT_EQ(count, 1); + CompleteRequest(0xffff); + EXPECT_EQ(count, 1); +} + +} // namespace +} // namespace network +} // namespace nearby