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