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