xref: /aosp_15_r20/external/federated-compute/fcp/client/http/testing/test_helpers.cc (revision 14675a029014e728ec732f129a32e299b2da0601)
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "fcp/client/http/testing/test_helpers.h"
17 
18 #include <cstdint>
19 #include <functional>
20 #include <memory>
21 #include <optional>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 #include "gmock/gmock.h"
27 #include "gtest/gtest.h"
28 #include "absl/status/status.h"
29 #include "absl/status/statusor.h"
30 #include "absl/strings/numbers.h"
31 #include "absl/strings/str_cat.h"
32 #include "absl/strings/string_view.h"
33 #include "fcp/base/monitoring.h"
34 #include "fcp/client/http/http_client.h"
35 #include "fcp/client/http/http_client_util.h"
36 
37 namespace fcp {
38 namespace client {
39 namespace http {
40 
41 using ::google::longrunning::Operation;
42 using ::google::protobuf::Message;
43 using ::testing::AllOf;
44 using ::testing::Field;
45 using ::testing::Matcher;
46 
47 namespace {
48 
49 // A simple `HttpRequestHandle` implementation for use with
50 // `MockableHttpClient`.
51 class SimpleHttpRequestHandle : public HttpRequestHandle {
52  public:
SimpleHttpRequestHandle(std::unique_ptr<HttpRequest> request,std::function<void ()> cancellation_listener)53   SimpleHttpRequestHandle(std::unique_ptr<HttpRequest> request,
54                           std::function<void()> cancellation_listener)
55       : request_(std::move(request)),
56         cancellation_listener_(cancellation_listener) {}
57 
TotalSentReceivedBytes() const58   HttpRequestHandle::SentReceivedBytes TotalSentReceivedBytes() const override {
59     return sent_received_bytes_;
60   }
SetSentBytes(int64_t bytes)61   void SetSentBytes(int64_t bytes) { sent_received_bytes_.sent_bytes = bytes; }
SetReceivedBytes(int64_t bytes)62   void SetReceivedBytes(int64_t bytes) {
63     sent_received_bytes_.received_bytes = bytes;
64   }
65 
Cancel()66   void Cancel() override { cancellation_listener_(); }
67 
request()68   HttpRequest* request() { return request_.get(); }
69 
70   // Marks the handle as having been passed to PerformRequests(...). Returns
71   // true if the handle hand't previously been marked performed.
MarkPerformed()72   bool MarkPerformed() {
73     bool already_performed = performed_;
74     performed_ = true;
75     return !already_performed;
76   }
77 
78  private:
79   const std::unique_ptr<HttpRequest> request_;
80   std::function<void()> cancellation_listener_;
81   bool performed_ = false;
82   HttpRequestHandle::SentReceivedBytes sent_received_bytes_ = {0, 0};
83 };
84 
85 }  // namespace
86 
EnqueueRequest(std::unique_ptr<HttpRequest> request)87 std::unique_ptr<HttpRequestHandle> MockableHttpClient::EnqueueRequest(
88     std::unique_ptr<HttpRequest> request) {
89   return std::make_unique<SimpleHttpRequestHandle>(
90       std::move(request), [this]() { this->cancellation_listener_(); });
91 }
92 
PerformRequests(std::vector<std::pair<HttpRequestHandle *,HttpRequestCallback * >> requests)93 absl::Status MockableHttpClient::PerformRequests(
94     std::vector<std::pair<HttpRequestHandle*, HttpRequestCallback*>> requests) {
95   for (const auto& [generic_handle, callback] : requests) {
96     auto handle = static_cast<SimpleHttpRequestHandle*>(generic_handle);
97     if (!handle->MarkPerformed()) {
98       return absl::InternalError(
99           "MockableHttpClient: handles cannot be used more than once.");
100     }
101 
102     HttpRequest* request = handle->request();
103 
104     std::string request_body;
105     if (request->HasBody()) {
106       const HeaderList& headers = request->extra_headers();
107       std::optional<std::string> content_length_hdr =
108           FindHeader(headers, kContentLengthHdr);
109       if (!content_length_hdr.has_value()) {
110         return absl::InternalError(
111             "MockableHttpClient only supports requests with known "
112             "Content-Length");
113       }
114       int64_t content_length;
115       if (!absl::SimpleAtoi(*content_length_hdr, &content_length)) {
116         return absl::InternalError(absl::StrCat(
117             "MockableHttpClient: unexpected Content-Length value: ",
118             content_length));
119       }
120       request_body.resize(content_length);
121 
122       // Read the data all at once (our buffer should be big enough for it).
123       absl::StatusOr<int64_t> read_result =
124           request->ReadBody(&request_body[0], content_length);
125       if (!read_result.ok()) {
126         return absl::InternalError(
127             absl::StrCat("MockableHttpClient: ReadBody failed: ",
128                          read_result.status().ToString()));
129       }
130       if (*read_result != content_length) {
131         return absl::InternalError(
132             absl::StrCat("MockableHttpClient: 1st ReadBody didn't read all the "
133                          "data. Actual: ",
134                          *read_result, ", expected: ", content_length));
135       }
136 
137       // Ensure we've hit the end of the data by checking for OUT_OF_RANGE.
138       absl::Status read_body_result =
139           request->ReadBody(&request_body[0], 1).status();
140       if (read_body_result.code() != absl::StatusCode::kOutOfRange) {
141         return absl::InternalError(
142             absl::StrCat("MockableHttpClient: 2nd ReadBody failed: ",
143                          read_body_result.ToString()));
144       }
145     }
146 
147     // Forward the request to the PerformSingleRequest method (which
148     // generally will have been mocked using gMock's MOCK_METHOD). This
149     // method will return the response that we should then deliver to the
150     // HttpRequestCallback.
151     SimpleHttpRequest simple_request = {std::string(request->uri()),
152                                         request->method(),
153                                         request->extra_headers(), request_body};
154     absl::StatusOr<FakeHttpResponse> response =
155         PerformSingleRequest(simple_request);
156 
157     // Mock some 'sent bytes data'. Users of this class shouldn't rely on the
158     // exact value (just as they can't expect to predict how much data a real
159     // `HttpClient` would send).
160     int64_t fake_sent_bytes = request->uri().size() + request_body.size();
161     handle->SetSentBytes(fake_sent_bytes);
162     sent_received_bytes_.sent_bytes += fake_sent_bytes;
163 
164     if (!response.ok()) {
165       return absl::Status(
166           response.status().code(),
167           absl::StrCat("MockableHttpClient: PerformSingleRequest failed: ",
168                        response.status().ToString()));
169     }
170 
171     // Return the response data to the callback's various methods.
172     FCP_LOG(INFO) << "MockableHttpClient: Delivering response headers for: "
173                   << request->uri();
174     absl::Status response_started_result =
175         callback->OnResponseStarted(*request, *response);
176     if (!response_started_result.ok()) {
177       return absl::InternalError(
178           absl::StrCat("MockableHttpClient: OnResponseStarted failed: ",
179                        response_started_result.ToString()));
180     }
181 
182     // Mock some 'received bytes data'. We add 100 bytes to ensure that even
183     // responses with empty response bodies do increase the counter, because
184     // generally headers will always be received.
185     int64_t fake_received_bytes = 100 + response->body().size();
186     handle->SetReceivedBytes(fake_received_bytes);
187     sent_received_bytes_.received_bytes += fake_received_bytes;
188 
189     FCP_LOG(INFO) << "MockableHttpClient: Delivering response body for: "
190                   << request->uri();
191     absl::Status response_body_result =
192         callback->OnResponseBody(*request, *response, response->body());
193     if (!response_body_result.ok()) {
194       return absl::InternalError(
195           absl::StrCat("MockableHttpClient: OnResponseBody failed: ",
196                        response_body_result.ToString()));
197     }
198 
199     FCP_LOG(INFO) << "MockableHttpClient: Delivering response completion for: "
200                   << request->uri();
201     callback->OnResponseCompleted(*request, *response);
202   }
203   return absl::OkStatus();
204 }
205 
SimpleHttpRequestMatcher(const Matcher<std::string> & uri_matcher,const Matcher<HttpRequest::Method> & method_matcher,const Matcher<HeaderList> & headers_matcher,const Matcher<std::string> & body_matcher)206 Matcher<MockableHttpClient::SimpleHttpRequest> SimpleHttpRequestMatcher(
207     const Matcher<std::string>& uri_matcher,
208     const Matcher<HttpRequest::Method>& method_matcher,
209     const Matcher<HeaderList>& headers_matcher,
210     const Matcher<std::string>& body_matcher) {
211   return AllOf(
212       Field("uri", &MockableHttpClient::SimpleHttpRequest::uri, uri_matcher),
213       Field("method", &MockableHttpClient::SimpleHttpRequest::method,
214             method_matcher),
215       Field("headers", &MockableHttpClient::SimpleHttpRequest::headers,
216             headers_matcher),
217       Field("body", &MockableHttpClient::SimpleHttpRequest::body,
218             body_matcher));
219 }
220 
CreatePendingOperation(absl::string_view operation_name)221 Operation CreatePendingOperation(absl::string_view operation_name) {
222   Operation operation;
223   operation.set_done(false);
224   operation.set_name(std::string(operation_name));
225   return operation;
226 }
227 
CreateDoneOperation(absl::string_view operation_name,const Message & inner_result)228 Operation CreateDoneOperation(absl::string_view operation_name,
229                               const Message& inner_result) {
230   Operation operation;
231   operation.set_name(std::string(operation_name));
232   operation.set_done(true);
233   operation.mutable_response()->PackFrom(inner_result);
234   return operation;
235 }
236 
CreateErrorOperation(absl::string_view operation_name,const absl::StatusCode error_code,absl::string_view error_message)237 Operation CreateErrorOperation(absl::string_view operation_name,
238                                const absl::StatusCode error_code,
239                                absl::string_view error_message) {
240   Operation operation;
241   operation.set_name(std::string(operation_name));
242   operation.set_done(true);
243   operation.mutable_error()->set_code(static_cast<int>(error_code));
244   operation.mutable_error()->set_message(std::string(error_message));
245   return operation;
246 }
247 
248 }  // namespace http
249 }  // namespace client
250 }  // namespace fcp
251