xref: /aosp_15_r20/external/federated-compute/fcp/client/http/http_client_util_test.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/http_client_util.h"
17 
18 #include <optional>
19 #include <string>
20 
21 #include "google/rpc/status.pb.h"
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24 #include "absl/status/status.h"
25 #include "fcp/base/monitoring.h"
26 #include "fcp/testing/testing.h"
27 
28 namespace fcp::client::http {
29 namespace {
30 
31 using ::fcp::IsCode;
32 using ::testing::HasSubstr;
33 using ::testing::Optional;
34 using ::testing::StrEq;
35 
TEST(ConvertHttpCodeToStatusTest,ConvertsKnownCodesCorrectly)36 TEST(ConvertHttpCodeToStatusTest, ConvertsKnownCodesCorrectly) {
37   EXPECT_OK(ConvertHttpCodeToStatus(kHttpOk));
38   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpBadRequest),
39               IsCode(INVALID_ARGUMENT));
40   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpForbidden),
41               IsCode(PERMISSION_DENIED));
42   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpNotFound), IsCode(NOT_FOUND));
43   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpConflict), IsCode(ABORTED));
44   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpTooManyRequests),
45               IsCode(RESOURCE_EXHAUSTED));
46   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpClientClosedRequest),
47               IsCode(CANCELLED));
48   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpGatewayTimeout),
49               IsCode(DEADLINE_EXCEEDED));
50   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpNotImplemented),
51               IsCode(UNIMPLEMENTED));
52   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpServiceUnavailable),
53               IsCode(UNAVAILABLE));
54   EXPECT_THAT(ConvertHttpCodeToStatus(kHttpUnauthorized),
55               IsCode(UNAUTHENTICATED));
56 }
57 
TEST(ConvertHttpCodeToStatusTest,ConvertsUnknown200CodesToOk)58 TEST(ConvertHttpCodeToStatusTest, ConvertsUnknown200CodesToOk) {
59   EXPECT_OK(ConvertHttpCodeToStatus(201));
60   EXPECT_OK(ConvertHttpCodeToStatus(210));
61   EXPECT_OK(ConvertHttpCodeToStatus(299));
62 }
63 
TEST(ConvertHttpCodeToStatusTest,ConvertsUnknown400CodesToFailedPrecondition)64 TEST(ConvertHttpCodeToStatusTest, ConvertsUnknown400CodesToFailedPrecondition) {
65   // Note: 400, 401, and 499 are known errors codes that map to other values. We
66   // hence test a few other values in the 400 range that aren't "known".
67   EXPECT_THAT(ConvertHttpCodeToStatus(402), IsCode(FAILED_PRECONDITION));
68   EXPECT_THAT(ConvertHttpCodeToStatus(405), IsCode(FAILED_PRECONDITION));
69   EXPECT_THAT(ConvertHttpCodeToStatus(410), IsCode(FAILED_PRECONDITION));
70   EXPECT_THAT(ConvertHttpCodeToStatus(498), IsCode(FAILED_PRECONDITION));
71 }
72 
TEST(ConvertHttpCodeToStatusTest,ConvertsUnknown500CodesToInternal)73 TEST(ConvertHttpCodeToStatusTest, ConvertsUnknown500CodesToInternal) {
74   // note: 501 is a known error code that maps to other values. We hence test
75   // 502 instead.
76   EXPECT_THAT(ConvertHttpCodeToStatus(500), IsCode(INTERNAL));
77   EXPECT_THAT(ConvertHttpCodeToStatus(502), IsCode(INTERNAL));
78   EXPECT_THAT(ConvertHttpCodeToStatus(510), IsCode(INTERNAL));
79   EXPECT_THAT(ConvertHttpCodeToStatus(599), IsCode(INTERNAL));
80 }
81 
TEST(ConvertHttpCodeToStatusTest,ConvertsUnknownAllOtherCodesToUnknown)82 TEST(ConvertHttpCodeToStatusTest, ConvertsUnknownAllOtherCodesToUnknown) {
83   EXPECT_THAT(ConvertHttpCodeToStatus(300), IsCode(UNKNOWN));
84   EXPECT_THAT(ConvertHttpCodeToStatus(301), IsCode(UNKNOWN));
85   EXPECT_THAT(ConvertHttpCodeToStatus(310), IsCode(UNKNOWN));
86   EXPECT_THAT(ConvertHttpCodeToStatus(399), IsCode(UNKNOWN));
87   EXPECT_THAT(ConvertHttpCodeToStatus(0), IsCode(UNKNOWN));
88   EXPECT_THAT(ConvertHttpCodeToStatus(1), IsCode(UNKNOWN));
89   EXPECT_THAT(ConvertHttpCodeToStatus(10), IsCode(UNKNOWN));
90   EXPECT_THAT(ConvertHttpCodeToStatus(99), IsCode(UNKNOWN));
91   EXPECT_THAT(ConvertHttpCodeToStatus(600), IsCode(UNKNOWN));
92 }
93 
94 // Test to ensure that when we map an 'unknown' HTTP response to its fallback
95 // catch-all StatusCode, we keep the original error code in the error message
96 // (to aid in debugging).
TEST(ConvertHttpCodeToStatusTest,IncludesOriginalErrorCodeInMessage)97 TEST(ConvertHttpCodeToStatusTest, IncludesOriginalErrorCodeInMessage) {
98   EXPECT_THAT(ConvertHttpCodeToStatus(400).message(), HasSubstr("code: 400"));
99   EXPECT_THAT(ConvertHttpCodeToStatus(402).message(), HasSubstr("code: 402"));
100   EXPECT_THAT(ConvertHttpCodeToStatus(502).message(), HasSubstr("code: 502"));
101   EXPECT_THAT(ConvertHttpCodeToStatus(503).message(), HasSubstr("code: 503"));
102   EXPECT_THAT(ConvertHttpCodeToStatus(300).message(), HasSubstr("code: 300"));
103 }
104 
ExpectRpcStatusMatchAbslStatus(absl::StatusCode code)105 void ExpectRpcStatusMatchAbslStatus(absl::StatusCode code) {
106   ::google::rpc::Status rpc_status;
107   *rpc_status.mutable_message() = "the_message";
108   rpc_status.set_code(static_cast<int>(code));
109   absl::Status converted_status = ConvertRpcStatusToAbslStatus(rpc_status);
110   EXPECT_THAT(converted_status, IsCode(code));
111   // OK Status objects always have empty messages.
112   EXPECT_EQ(converted_status.message(),
113             code == absl::StatusCode::kOk ? "" : "the_message");
114 }
115 
TEST(ConvertRpcStatusToAbslStatusTest,ValidCodesShouldConvertSuccessfully)116 TEST(ConvertRpcStatusToAbslStatusTest, ValidCodesShouldConvertSuccessfully) {
117   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kOk);
118   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kCancelled);
119   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kUnknown);
120   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kInvalidArgument);
121   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kDeadlineExceeded);
122   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kNotFound);
123   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kAlreadyExists);
124   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kPermissionDenied);
125   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kResourceExhausted);
126   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kFailedPrecondition);
127   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kAborted);
128   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kOutOfRange);
129   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kUnimplemented);
130   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kInternal);
131   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kUnavailable);
132   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kDataLoss);
133   ExpectRpcStatusMatchAbslStatus(absl::StatusCode::kUnauthenticated);
134 }
135 
TEST(ConvertRpcStatusToAbslStatusTest,InvalidCodesShouldConvertToUnknown)136 TEST(ConvertRpcStatusToAbslStatusTest, InvalidCodesShouldConvertToUnknown) {
137   ::google::rpc::Status rpc_status;
138   *rpc_status.mutable_message() = "the_message";
139   rpc_status.set_code(100);  // 100 is not a valid status code.
140   absl::Status converted_status = ConvertRpcStatusToAbslStatus(rpc_status);
141   EXPECT_THAT(converted_status, IsCode(UNKNOWN));
142   EXPECT_EQ(converted_status.message(), "the_message");
143 }
144 
TEST(FindHeaderTest,DoesNotFindMissingHeader)145 TEST(FindHeaderTest, DoesNotFindMissingHeader) {
146   EXPECT_EQ(FindHeader({}, "Header-One"), std::nullopt);
147   EXPECT_EQ(FindHeader({{"Header-Two", "bar"}}, "Header-One"), std::nullopt);
148   EXPECT_EQ(FindHeader({{"Header-Two", "bar"}, {"Header-Three", "baz"}},
149                        "Header-One"),
150             std::nullopt);
151 }
TEST(FindHeaderTest,EmptyNeedleDoesNotFindAnyHeader)152 TEST(FindHeaderTest, EmptyNeedleDoesNotFindAnyHeader) {
153   EXPECT_EQ(FindHeader({}, ""), std::nullopt);
154   EXPECT_EQ(FindHeader({{"Header-Two", "bar"}}, ""), std::nullopt);
155 }
156 
TEST(FindHeaderTest,FindsHeaderInList)157 TEST(FindHeaderTest, FindsHeaderInList) {
158   EXPECT_THAT(FindHeader({{"Header-One", "foo"},
159                           {"Header-Two", "bar"},
160                           {"Header-Three", "baz"}},
161                          "Header-Two"),
162               Optional(StrEq("bar")));
163 
164   EXPECT_THAT(FindHeader({{"Header-Two", "BAR"},
165                           {"Header-One", "foo"},
166                           {"Header-Three", "baz"}},
167                          "Header-Two"),
168               Optional(StrEq("BAR")));
169 
170   EXPECT_THAT(FindHeader({{"Header-One", "foo"},
171                           {"Header-Three", "baz"},
172                           {"Header-Two", "BaR"}},
173                          "Header-Two"),
174               Optional(StrEq("BaR")));
175 
176   EXPECT_THAT(FindHeader({{"Header-Two", "bar"}}, "Header-Two"),
177               Optional(StrEq("bar")));
178 }
179 
TEST(FindHeaderTest,FindsHeaderInListDespiteMixedCase)180 TEST(FindHeaderTest, FindsHeaderInListDespiteMixedCase) {
181   // In each of these scenarios, the fact that the list or needle are not the
182   // same case should not affect the result.
183   EXPECT_THAT(FindHeader({{"Header-One", "foo"},
184                           {"header-two", "bar"},
185                           {"Header-Three", "baz"}},
186                          "Header-Two"),
187               Optional(StrEq("bar")));
188 
189   EXPECT_THAT(FindHeader({{"Header-One", "foo"},
190                           {"Header-Two", "bar"},
191                           {"Header-Three", "baz"}},
192                          "header-two"),
193               Optional(StrEq("bar")));
194 }
195 
TEST(FindHeaderTest,ReturnsFirstMatch)196 TEST(FindHeaderTest, ReturnsFirstMatch) {
197   // In each of these scenarios, the first matching header value should be
198   // returned.
199   EXPECT_THAT(
200       FindHeader(
201           {{"Header-One", "foo"}, {"Header-Two", "bar"}, {"Header-Two", "baz"}},
202           "Header-Two"),
203       Optional(StrEq("bar")));
204 
205   EXPECT_THAT(
206       FindHeader(
207           {{"Header-One", "foo"}, {"Header-Two", "bar"}, {"Header-Two", "baz"}},
208           "Header-Two"),
209       Optional(StrEq("bar")));
210 
211   EXPECT_THAT(
212       FindHeader(
213           {{"Header-One", "foo"}, {"header-two", "bar"}, {"HEADER-TWO", "baz"}},
214           "HEADER-TWO"),
215       Optional(StrEq("bar")));
216 }
217 
TEST(JoinBaseUriWithSuffixTest,ReturnsJoinedUri)218 TEST(JoinBaseUriWithSuffixTest, ReturnsJoinedUri) {
219   // No trailing slash in base URI.
220   auto result = JoinBaseUriWithSuffix("https://foo", "/bar");
221   ASSERT_OK(result);
222   EXPECT_THAT(*result, StrEq("https://foo/bar"));
223 
224   // Trailing slash in base URI.
225   result = JoinBaseUriWithSuffix("https://foo/", "/bar");
226   ASSERT_OK(result);
227   EXPECT_THAT(*result, StrEq("https://foo/bar"));
228 
229   // Additional URI components are correctly merged.
230   result = JoinBaseUriWithSuffix("https://foo:123", "/bar/baz");
231   ASSERT_OK(result);
232   EXPECT_THAT(*result, StrEq("https://foo:123/bar/baz"));
233 
234   result = JoinBaseUriWithSuffix("https://foo:123/", "/bar/baz");
235   ASSERT_OK(result);
236   EXPECT_THAT(*result, StrEq("https://foo:123/bar/baz"));
237 
238   // Empty suffixes should be allowed.
239   result = JoinBaseUriWithSuffix("https://foo", "");
240   ASSERT_OK(result);
241   EXPECT_THAT(*result, StrEq("https://foo/"));
242 
243   // Trailing slash in base URI.
244   result = JoinBaseUriWithSuffix("https://foo/", "");
245   ASSERT_OK(result);
246   EXPECT_THAT(*result, StrEq("https://foo/"));
247 }
248 
TEST(JoinBaseUriWithSuffixTest,NoLeadingSlashInSuffixFails)249 TEST(JoinBaseUriWithSuffixTest, NoLeadingSlashInSuffixFails) {
250   // No leading slash in the URI suffix, should result in error.
251   auto result = JoinBaseUriWithSuffix("https://foo", "bar");
252   EXPECT_THAT(result.status(), IsCode(INVALID_ARGUMENT));
253   result = JoinBaseUriWithSuffix("https://foo/", "bar");
254   EXPECT_THAT(result.status(), IsCode(INVALID_ARGUMENT));
255 }
256 
TEST(EncodeUriTest,UnencodedCharsShouldRemainUnencoded)257 TEST(EncodeUriTest, UnencodedCharsShouldRemainUnencoded) {
258   std::string unencoded_single_path_segment_chars =
259       "-_.~0123456789abcxyzABCXYZ";
260   auto result = EncodeUriSinglePathSegment(unencoded_single_path_segment_chars);
261   ASSERT_OK(result);
262   EXPECT_THAT(*result, StrEq(unencoded_single_path_segment_chars));
263 
264   std::string unencoded_multi_path_segment_chars =
265       "-_.~/01234567899abcxyzABCXYZ";
266   result = EncodeUriMultiplePathSegments(unencoded_multi_path_segment_chars);
267   ASSERT_OK(result);
268   EXPECT_THAT(*result, StrEq(unencoded_multi_path_segment_chars));
269 }
270 
TEST(EncodeUriTest,OtherCharsShouldBeEncoded)271 TEST(EncodeUriTest, OtherCharsShouldBeEncoded) {
272   auto result = EncodeUriSinglePathSegment("#?+%/");
273   ASSERT_OK(result);
274   EXPECT_THAT(*result, StrEq("%23%3F%2B%25%2F"));
275 
276   // For the "multiple path segments" version the slash should remain unencoded.
277   result = EncodeUriMultiplePathSegments("#?+%/");
278   ASSERT_OK(result);
279   EXPECT_THAT(*result, StrEq("%23%3F%2B%25/"));
280 
281   // Non-encodable characters before/in between/after the encodable characters
282   // should remain unencoded.
283   result = EncodeUriSinglePathSegment("abc#?123+%/XYZ");
284   ASSERT_OK(result);
285   EXPECT_THAT(*result, StrEq("abc%23%3F123%2B%25%2FXYZ"));
286 
287   result = EncodeUriMultiplePathSegments("abc#?123+%/XYZ");
288   ASSERT_OK(result);
289   EXPECT_THAT(*result, StrEq("abc%23%3F123%2B%25/XYZ"));
290 }
291 
TEST(EncodeUriTest,EmptyStringShouldReturnEmptyString)292 TEST(EncodeUriTest, EmptyStringShouldReturnEmptyString) {
293   auto result = EncodeUriSinglePathSegment("");
294   ASSERT_OK(result);
295   EXPECT_THAT(*result, StrEq(""));
296 
297   // For the "multiple path segments" version the slash should remain unencoded.
298   result = EncodeUriMultiplePathSegments("");
299   ASSERT_OK(result);
300   EXPECT_THAT(*result, StrEq(""));
301 }
302 
TEST(EncodeUriTest,NonAsciiStringShouldReturnError)303 TEST(EncodeUriTest, NonAsciiStringShouldReturnError) {
304   auto result = EncodeUriSinglePathSegment("€");
305   EXPECT_THAT(result, IsCode(INVALID_ARGUMENT));
306 
307   // For the "multiple path segments" version the slash should remain unencoded.
308   result = EncodeUriMultiplePathSegments("€");
309   EXPECT_THAT(result, IsCode(INVALID_ARGUMENT));
310 }
311 
TEST(CreateByteStreamUriTest,HappyCase)312 TEST(CreateByteStreamUriTest, HappyCase) {
313   auto result = CreateByteStreamUploadUriSuffix("my/resource");
314   ASSERT_OK(result);
315   EXPECT_THAT(*result, "/upload/v1/media/my/resource");
316 }
317 
TEST(CreateByteStreamUriTest,NonAsciiResourceNameShouldReturnError)318 TEST(CreateByteStreamUriTest, NonAsciiResourceNameShouldReturnError) {
319   EXPECT_THAT(CreateByteStreamUploadUriSuffix("€"), IsCode(INVALID_ARGUMENT));
320 }
321 
322 }  // namespace
323 }  // namespace fcp::client::http
324