/* * 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 * * http://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 "fcp/client/http/testing/test_helpers.h" #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "fcp/base/monitoring.h" #include "fcp/client/http/http_client.h" #include "fcp/client/http/http_client_util.h" namespace fcp { namespace client { namespace http { using ::google::longrunning::Operation; using ::google::protobuf::Message; using ::testing::AllOf; using ::testing::Field; using ::testing::Matcher; namespace { // A simple `HttpRequestHandle` implementation for use with // `MockableHttpClient`. class SimpleHttpRequestHandle : public HttpRequestHandle { public: SimpleHttpRequestHandle(std::unique_ptr request, std::function cancellation_listener) : request_(std::move(request)), cancellation_listener_(cancellation_listener) {} HttpRequestHandle::SentReceivedBytes TotalSentReceivedBytes() const override { return sent_received_bytes_; } void SetSentBytes(int64_t bytes) { sent_received_bytes_.sent_bytes = bytes; } void SetReceivedBytes(int64_t bytes) { sent_received_bytes_.received_bytes = bytes; } void Cancel() override { cancellation_listener_(); } HttpRequest* request() { return request_.get(); } // Marks the handle as having been passed to PerformRequests(...). Returns // true if the handle hand't previously been marked performed. bool MarkPerformed() { bool already_performed = performed_; performed_ = true; return !already_performed; } private: const std::unique_ptr request_; std::function cancellation_listener_; bool performed_ = false; HttpRequestHandle::SentReceivedBytes sent_received_bytes_ = {0, 0}; }; } // namespace std::unique_ptr MockableHttpClient::EnqueueRequest( std::unique_ptr request) { return std::make_unique( std::move(request), [this]() { this->cancellation_listener_(); }); } absl::Status MockableHttpClient::PerformRequests( std::vector> requests) { for (const auto& [generic_handle, callback] : requests) { auto handle = static_cast(generic_handle); if (!handle->MarkPerformed()) { return absl::InternalError( "MockableHttpClient: handles cannot be used more than once."); } HttpRequest* request = handle->request(); std::string request_body; if (request->HasBody()) { const HeaderList& headers = request->extra_headers(); std::optional content_length_hdr = FindHeader(headers, kContentLengthHdr); if (!content_length_hdr.has_value()) { return absl::InternalError( "MockableHttpClient only supports requests with known " "Content-Length"); } int64_t content_length; if (!absl::SimpleAtoi(*content_length_hdr, &content_length)) { return absl::InternalError(absl::StrCat( "MockableHttpClient: unexpected Content-Length value: ", content_length)); } request_body.resize(content_length); // Read the data all at once (our buffer should be big enough for it). absl::StatusOr read_result = request->ReadBody(&request_body[0], content_length); if (!read_result.ok()) { return absl::InternalError( absl::StrCat("MockableHttpClient: ReadBody failed: ", read_result.status().ToString())); } if (*read_result != content_length) { return absl::InternalError( absl::StrCat("MockableHttpClient: 1st ReadBody didn't read all the " "data. Actual: ", *read_result, ", expected: ", content_length)); } // Ensure we've hit the end of the data by checking for OUT_OF_RANGE. absl::Status read_body_result = request->ReadBody(&request_body[0], 1).status(); if (read_body_result.code() != absl::StatusCode::kOutOfRange) { return absl::InternalError( absl::StrCat("MockableHttpClient: 2nd ReadBody failed: ", read_body_result.ToString())); } } // Forward the request to the PerformSingleRequest method (which // generally will have been mocked using gMock's MOCK_METHOD). This // method will return the response that we should then deliver to the // HttpRequestCallback. SimpleHttpRequest simple_request = {std::string(request->uri()), request->method(), request->extra_headers(), request_body}; absl::StatusOr response = PerformSingleRequest(simple_request); // Mock some 'sent bytes data'. Users of this class shouldn't rely on the // exact value (just as they can't expect to predict how much data a real // `HttpClient` would send). int64_t fake_sent_bytes = request->uri().size() + request_body.size(); handle->SetSentBytes(fake_sent_bytes); sent_received_bytes_.sent_bytes += fake_sent_bytes; if (!response.ok()) { return absl::Status( response.status().code(), absl::StrCat("MockableHttpClient: PerformSingleRequest failed: ", response.status().ToString())); } // Return the response data to the callback's various methods. FCP_LOG(INFO) << "MockableHttpClient: Delivering response headers for: " << request->uri(); absl::Status response_started_result = callback->OnResponseStarted(*request, *response); if (!response_started_result.ok()) { return absl::InternalError( absl::StrCat("MockableHttpClient: OnResponseStarted failed: ", response_started_result.ToString())); } // Mock some 'received bytes data'. We add 100 bytes to ensure that even // responses with empty response bodies do increase the counter, because // generally headers will always be received. int64_t fake_received_bytes = 100 + response->body().size(); handle->SetReceivedBytes(fake_received_bytes); sent_received_bytes_.received_bytes += fake_received_bytes; FCP_LOG(INFO) << "MockableHttpClient: Delivering response body for: " << request->uri(); absl::Status response_body_result = callback->OnResponseBody(*request, *response, response->body()); if (!response_body_result.ok()) { return absl::InternalError( absl::StrCat("MockableHttpClient: OnResponseBody failed: ", response_body_result.ToString())); } FCP_LOG(INFO) << "MockableHttpClient: Delivering response completion for: " << request->uri(); callback->OnResponseCompleted(*request, *response); } return absl::OkStatus(); } Matcher SimpleHttpRequestMatcher( const Matcher& uri_matcher, const Matcher& method_matcher, const Matcher& headers_matcher, const Matcher& body_matcher) { return AllOf( Field("uri", &MockableHttpClient::SimpleHttpRequest::uri, uri_matcher), Field("method", &MockableHttpClient::SimpleHttpRequest::method, method_matcher), Field("headers", &MockableHttpClient::SimpleHttpRequest::headers, headers_matcher), Field("body", &MockableHttpClient::SimpleHttpRequest::body, body_matcher)); } Operation CreatePendingOperation(absl::string_view operation_name) { Operation operation; operation.set_done(false); operation.set_name(std::string(operation_name)); return operation; } Operation CreateDoneOperation(absl::string_view operation_name, const Message& inner_result) { Operation operation; operation.set_name(std::string(operation_name)); operation.set_done(true); operation.mutable_response()->PackFrom(inner_result); return operation; } Operation CreateErrorOperation(absl::string_view operation_name, const absl::StatusCode error_code, absl::string_view error_message) { Operation operation; operation.set_name(std::string(operation_name)); operation.set_done(true); operation.mutable_error()->set_code(static_cast(error_code)); operation.mutable_error()->set_message(std::string(error_message)); return operation; } } // namespace http } // namespace client } // namespace fcp