1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "google/cloud/storage/client.h"
16 #include "google/cloud/storage/oauth2/google_credentials.h"
17 #include "google/cloud/storage/retry_policy.h"
18 #include "google/cloud/storage/testing/canonical_errors.h"
19 #include "google/cloud/storage/testing/mock_client.h"
20 #include "google/cloud/storage/testing/retry_tests.h"
21 #include "google/cloud/testing_util/assert_ok.h"
22 #include <gmock/gmock.h>
23
24 namespace google {
25 namespace cloud {
26 namespace storage {
27 inline namespace STORAGE_CLIENT_NS {
28 namespace {
29
30 using ::google::cloud::storage::testing::canonical_errors::TransientError;
31 using ::testing::_;
32 using ::testing::ElementsAre;
33 using ::testing::ElementsAreArray;
34 using ::testing::HasSubstr;
35 using ::testing::Return;
36 using ::testing::ReturnRef;
37
38 /**
39 * Test the functions in Storage::Client related to 'Buckets: *'.
40 *
41 * In general, this file should include for the APIs listed in:
42 *
43 * https://cloud.google.com/storage/docs/json_api/v1/buckets
44 */
45 class BucketTest : public ::testing::Test {
46 protected:
SetUp()47 void SetUp() override {
48 mock_ = std::make_shared<testing::MockClient>();
49 EXPECT_CALL(*mock_, client_options())
50 .WillRepeatedly(ReturnRef(client_options_));
51 client_.reset(new Client{
52 std::shared_ptr<internal::RawClient>(mock_),
53 LimitedErrorCountRetryPolicy(2),
54 ExponentialBackoffPolicy(std::chrono::milliseconds(1),
55 std::chrono::milliseconds(1), 2.0)});
56 }
TearDown()57 void TearDown() override {
58 client_.reset();
59 mock_.reset();
60 }
61
62 std::shared_ptr<testing::MockClient> mock_;
63 std::unique_ptr<Client> client_;
64 ClientOptions client_options_ =
65 ClientOptions(oauth2::CreateAnonymousCredentials());
66 };
67
TEST_F(BucketTest,CreateBucket)68 TEST_F(BucketTest, CreateBucket) {
69 std::string text = R"""({
70 "kind": "storage#bucket",
71 "id": "test-bucket-name",
72 "selfLink": "https://storage.googleapis.com/storage/v1/b/test-bucket-name",
73 "projectNumber": "123456789",
74 "name": "test-bucket-name",
75 "timeCreated": "2018-05-19T19:31:14Z",
76 "updated": "2018-05-19T19:31:24Z",
77 "metageneration": 7,
78 "location": "US",
79 "storageClass": "STANDARD",
80 "etag": "XYZ="
81 })""";
82 auto expected = internal::BucketMetadataParser::FromString(text).value();
83
84 ClientOptions mock_options(oauth2::CreateAnonymousCredentials());
85 mock_options.set_project_id("test-project-name");
86
87 EXPECT_CALL(*mock_, client_options()).WillRepeatedly(ReturnRef(mock_options));
88 EXPECT_CALL(*mock_, CreateBucket(_))
89 .WillOnce(Return(StatusOr<BucketMetadata>(TransientError())))
__anon02dce7db0202(internal::CreateBucketRequest const& r) 90 .WillOnce([&expected](internal::CreateBucketRequest const& r) {
91 EXPECT_EQ("test-bucket-name", r.metadata().name());
92 EXPECT_EQ("US", r.metadata().location());
93 EXPECT_EQ("STANDARD", r.metadata().storage_class());
94 EXPECT_EQ("test-project-name", r.project_id());
95 return make_status_or(expected);
96 });
97 auto actual = client_->CreateBucket(
98 "test-bucket-name",
99 BucketMetadata().set_location("US").set_storage_class("STANDARD"));
100 ASSERT_STATUS_OK(actual);
101 EXPECT_EQ(expected, *actual);
102 }
103
104 TEST_F(BucketTest, CreateBucketTooManyFailures) {
105 testing::TooManyFailuresStatusTest<BucketMetadata>(
106 mock_, EXPECT_CALL(*mock_, CreateBucket(_)),
107 [](Client& client) {
108 return client
109 .CreateBucketForProject("test-bucket-name", "test-project-name",
110 BucketMetadata())
111 .status();
112 },
113 "CreateBucket");
114 }
115
116 TEST_F(BucketTest, CreateBucketPermanentFailure) {
117 testing::PermanentFailureStatusTest<BucketMetadata>(
118 *client_, EXPECT_CALL(*mock_, CreateBucket(_)),
119 [](Client& client) {
120 return client
121 .CreateBucketForProject("test-bucket-name", "test-project-name",
122 BucketMetadata())
123 .status();
124 },
125 "CreateBucket");
126 }
127
128 TEST_F(BucketTest, GetBucketMetadata) {
129 std::string text = R"""({
130 "kind": "storage#bucket",
131 "id": "foo-bar-baz",
132 "selfLink": "https://storage.googleapis.com/storage/v1/b/foo-bar-baz",
133 "projectNumber": "123456789",
134 "name": "foo-bar-baz",
135 "timeCreated": "2018-05-19T19:31:14Z",
136 "updated": "2018-05-19T19:31:24Z",
137 "metageneration": "4",
138 "location": "US",
139 "locationType": "regional",
140 "storageClass": "STANDARD",
141 "etag": "XYZ="
142 })""";
143 auto expected = internal::BucketMetadataParser::FromString(text).value();
144
145 EXPECT_CALL(*mock_, GetBucketMetadata(_))
146 .WillOnce(Return(StatusOr<BucketMetadata>(TransientError())))
__anon02dce7db0502(internal::GetBucketMetadataRequest const& r) 147 .WillOnce([&expected](internal::GetBucketMetadataRequest const& r) {
148 EXPECT_EQ("foo-bar-baz", r.bucket_name());
149 return make_status_or(expected);
150 });
151 auto actual = client_->GetBucketMetadata("foo-bar-baz");
152 ASSERT_STATUS_OK(actual);
153 EXPECT_EQ(expected, *actual);
154 }
155
156 TEST_F(BucketTest, GetMetadataTooManyFailures) {
157 testing::TooManyFailuresStatusTest<BucketMetadata>(
158 mock_, EXPECT_CALL(*mock_, GetBucketMetadata(_)),
159 [](Client& client) {
160 return client.GetBucketMetadata("test-bucket-name").status();
161 },
162 "GetBucketMetadata");
163 }
164
165 TEST_F(BucketTest, GetMetadataPermanentFailure) {
166 testing::PermanentFailureStatusTest<BucketMetadata>(
167 *client_, EXPECT_CALL(*mock_, GetBucketMetadata(_)),
168 [](Client& client) {
169 return client.GetBucketMetadata("test-bucket-name").status();
170 },
171 "GetBucketMetadata");
172 }
173
174 TEST_F(BucketTest, DeleteBucket) {
175 EXPECT_CALL(*mock_, DeleteBucket(_))
176 .WillOnce(Return(StatusOr<internal::EmptyResponse>(TransientError())))
177 .WillOnce([](internal::DeleteBucketRequest const& r) {
178 EXPECT_EQ("foo-bar-baz", r.bucket_name());
179 return make_status_or(internal::EmptyResponse{});
180 });
181 auto status = client_->DeleteBucket("foo-bar-baz");
182 ASSERT_STATUS_OK(status);
183 }
184
185 TEST_F(BucketTest, DeleteBucketTooManyFailures) {
186 testing::TooManyFailuresStatusTest<internal::EmptyResponse>(
187 mock_, EXPECT_CALL(*mock_, DeleteBucket(_)),
188 [](Client& client) { return client.DeleteBucket("test-bucket-name"); },
189 [](Client& client) {
190 return client.DeleteBucket("test-bucket-name",
191 IfMetagenerationMatch(42));
192 },
193 "DeleteBucket");
194 }
195
196 TEST_F(BucketTest, DeleteBucketPermanentFailure) {
197 testing::PermanentFailureStatusTest<internal::EmptyResponse>(
198 *client_, EXPECT_CALL(*mock_, DeleteBucket(_)),
199 [](Client& client) { return client.DeleteBucket("test-bucket-name"); },
200 "DeleteBucket");
201 }
202
203 TEST_F(BucketTest, UpdateBucket) {
204 std::string text = R"""({
205 "kind": "storage#bucket",
206 "id": "test-bucket-name",
207 "selfLink": "https://storage.googleapis.com/storage/v1/b/test-bucket-name",
208 "projectNumber": "123456789",
209 "name": "test-bucket-name",
210 "timeCreated": "2018-05-19T19:31:14Z",
211 "updated": "2018-05-19T19:31:24Z",
212 "metageneration": 7,
213 "location": "US",
214 "locationType": "regional",
215 "storageClass": "STANDARD",
216 "etag": "XYZ="
217 })""";
218 auto expected = internal::BucketMetadataParser::FromString(text).value();
219
220 EXPECT_CALL(*mock_, UpdateBucket(_))
221 .WillOnce(Return(StatusOr<BucketMetadata>(TransientError())))
__anon02dce7db0c02(internal::UpdateBucketRequest const& r) 222 .WillOnce([&expected](internal::UpdateBucketRequest const& r) {
223 EXPECT_EQ("test-bucket-name", r.metadata().name());
224 EXPECT_EQ("US", r.metadata().location());
225 EXPECT_EQ("STANDARD", r.metadata().storage_class());
226 return make_status_or(expected);
227 });
228 auto actual = client_->UpdateBucket(
229 "test-bucket-name",
230 BucketMetadata().set_location("US").set_storage_class("STANDARD"));
231 ASSERT_STATUS_OK(actual);
232 EXPECT_EQ(expected, *actual);
233 }
234
235 TEST_F(BucketTest, UpdateBucketTooManyFailures) {
236 testing::TooManyFailuresStatusTest<BucketMetadata>(
237 mock_, EXPECT_CALL(*mock_, UpdateBucket(_)),
238 [](Client& client) {
239 return client.UpdateBucket("test-bucket-name", BucketMetadata())
240 .status();
241 },
242 [](Client& client) {
243 return client
244 .UpdateBucket("test-bucket-name", BucketMetadata(),
245 IfMetagenerationMatch(42))
246 .status();
247 },
248 "UpdateBucket");
249 }
250
251 TEST_F(BucketTest, UpdateBucketPermanentFailure) {
252 testing::PermanentFailureStatusTest<BucketMetadata>(
253 *client_, EXPECT_CALL(*mock_, UpdateBucket(_)),
254 [](Client& client) {
255 return client.UpdateBucket("test-bucket-name", BucketMetadata())
256 .status();
257 },
258 "UpdateBucket");
259 }
260
261 TEST_F(BucketTest, PatchBucket) {
262 std::string text = R"""({
263 "kind": "storage#bucket",
264 "id": "test-bucket-name",
265 "selfLink": "https://storage.googleapis.com/storage/v1/b/test-bucket-name",
266 "projectNumber": "123456789",
267 "name": "test-bucket-name",
268 "timeCreated": "2018-05-19T19:31:14Z",
269 "updated": "2018-05-19T19:31:24Z",
270 "metageneration": 7,
271 "location": "US",
272 "storageClass": "STANDARD",
273 "etag": "XYZ="
274 })""";
275 auto expected = internal::BucketMetadataParser::FromString(text).value();
276
277 EXPECT_CALL(*mock_, PatchBucket(_))
278 .WillOnce(Return(StatusOr<BucketMetadata>(TransientError())))
__anon02dce7db1002(internal::PatchBucketRequest const& r) 279 .WillOnce([&expected](internal::PatchBucketRequest const& r) {
280 EXPECT_EQ("test-bucket-name", r.bucket());
281 EXPECT_THAT(r.payload(), HasSubstr("STANDARD"));
282 return make_status_or(expected);
283 });
284 auto actual = client_->PatchBucket(
285 "test-bucket-name",
286 BucketMetadataPatchBuilder().SetStorageClass("STANDARD"));
287 ASSERT_STATUS_OK(actual);
288 EXPECT_EQ(expected, *actual);
289 }
290
291 TEST_F(BucketTest, PatchBucketTooManyFailures) {
292 testing::TooManyFailuresStatusTest<BucketMetadata>(
293 mock_, EXPECT_CALL(*mock_, PatchBucket(_)),
294 [](Client& client) {
295 return client
296 .PatchBucket("test-bucket-name", BucketMetadataPatchBuilder())
297 .status();
298 },
299 [](Client& client) {
300 return client
301 .PatchBucket("test-bucket-name", BucketMetadataPatchBuilder(),
302 IfMetagenerationMatch(42))
303 .status();
304 },
305 "PatchBucket");
306 }
307
308 TEST_F(BucketTest, PatchBucketPermanentFailure) {
309 testing::PermanentFailureStatusTest<BucketMetadata>(
310 *client_, EXPECT_CALL(*mock_, PatchBucket(_)),
311 [](Client& client) {
312 return client
313 .PatchBucket("test-bucket-name", BucketMetadataPatchBuilder())
314 .status();
315 },
316 "PatchBucket");
317 }
318
319 TEST_F(BucketTest, GetBucketIamPolicy) {
320 IamBindings bindings;
321 bindings.AddMember("roles/storage.admin", "test-user");
322 IamPolicy expected{0, bindings, "XYZ="};
323
324 EXPECT_CALL(*mock_, GetBucketIamPolicy(_))
325 .WillOnce(Return(StatusOr<IamPolicy>(TransientError())))
326 .WillOnce([&expected](internal::GetBucketIamPolicyRequest const& r) {
327 EXPECT_EQ("test-bucket-name", r.bucket_name());
328 return make_status_or(expected);
329 });
330 auto actual = client_->GetBucketIamPolicy("test-bucket-name");
331 ASSERT_STATUS_OK(actual);
332 EXPECT_EQ(expected, *actual);
333 }
334
335 TEST_F(BucketTest, GetBucketIamPolicyTooManyFailures) {
336 testing::TooManyFailuresStatusTest<IamPolicy>(
337 mock_, EXPECT_CALL(*mock_, GetBucketIamPolicy(_)),
338 [](Client& client) {
339 return client.GetBucketIamPolicy("test-bucket-name").status();
340 },
341 "GetBucketIamPolicy");
342 }
343
344 TEST_F(BucketTest, GetBucketIamPolicyPermanentFailure) {
345 testing::PermanentFailureStatusTest<IamPolicy>(
346 *client_, EXPECT_CALL(*mock_, GetBucketIamPolicy(_)),
347 [](Client& client) {
348 return client.GetBucketIamPolicy("test-bucket-name").status();
349 },
350 "GetBucketIamPolicy");
351 }
352
353 TEST_F(BucketTest, SetBucketIamPolicy) {
354 IamBindings bindings;
355 bindings.AddMember("roles/storage.admin", "test-user");
356 IamPolicy expected{0, bindings, "XYZ="};
357
358 EXPECT_CALL(*mock_, SetBucketIamPolicy(_))
359 .WillOnce(Return(StatusOr<IamPolicy>(TransientError())))
360 .WillOnce([&expected](internal::SetBucketIamPolicyRequest const& r) {
361 EXPECT_EQ("test-bucket-name", r.bucket_name());
362 EXPECT_THAT(r.json_payload(), HasSubstr("test-user"));
363 return make_status_or(expected);
364 });
365 auto actual = client_->SetBucketIamPolicy("test-bucket-name", expected);
366 ASSERT_STATUS_OK(actual);
367 EXPECT_EQ(expected, *actual);
368 }
369
370 TEST_F(BucketTest, SetBucketIamPolicyTooManyFailures) {
371 testing::TooManyFailuresStatusTest<IamPolicy>(
372 mock_, EXPECT_CALL(*mock_, SetBucketIamPolicy(_)),
373 [](Client& client) {
374 return client.SetBucketIamPolicy("test-bucket-name", IamPolicy{})
375 .status();
376 },
377 [](Client& client) {
378 return client
379 .SetBucketIamPolicy("test-bucket-name", IamPolicy{},
380 IfMatchEtag("ABC="))
381 .status();
382 },
383 "SetBucketIamPolicy");
384 }
385
386 TEST_F(BucketTest, SetBucketIamPolicyPermanentFailure) {
387 testing::PermanentFailureStatusTest<IamPolicy>(
388 *client_, EXPECT_CALL(*mock_, SetBucketIamPolicy(_)),
389 [](Client& client) {
390 return client.SetBucketIamPolicy("test-bucket-name", IamPolicy{})
391 .status();
392 },
393 "SetBucketIamPolicy");
394 }
395
396 TEST_F(BucketTest, TestBucketIamPermissions) {
397 internal::TestBucketIamPermissionsResponse expected;
398 expected.permissions.emplace_back("storage.buckets.delete");
399
400 EXPECT_CALL(*mock_, TestBucketIamPermissions(_))
401 .WillOnce(Return(StatusOr<internal::TestBucketIamPermissionsResponse>(
402 TransientError())))
403 .WillOnce(
404 [&expected](internal::TestBucketIamPermissionsRequest const& r) {
405 EXPECT_EQ("test-bucket-name", r.bucket_name());
406 EXPECT_THAT(r.permissions(), ElementsAre("storage.buckets.delete"));
407 return make_status_or(expected);
408 });
409 auto actual = client_->TestBucketIamPermissions("test-bucket-name",
410 {"storage.buckets.delete"});
411 ASSERT_STATUS_OK(actual);
412 EXPECT_THAT(*actual, ElementsAreArray(expected.permissions));
413 }
414
415 TEST_F(BucketTest, TestBucketIamPermissionsTooManyFailures) {
416 testing::TooManyFailuresStatusTest<
417 internal::TestBucketIamPermissionsResponse>(
418 mock_, EXPECT_CALL(*mock_, TestBucketIamPermissions(_)),
419 [](Client& client) {
420 return client.TestBucketIamPermissions("test-bucket-name", {}).status();
421 },
422 "TestBucketIamPermissions");
423 }
424
425 TEST_F(BucketTest, TestBucketIamPermissionsPermanentFailure) {
426 testing::PermanentFailureStatusTest<
427 internal::TestBucketIamPermissionsResponse>(
428 *client_, EXPECT_CALL(*mock_, TestBucketIamPermissions(_)),
429 [](Client& client) {
430 return client.TestBucketIamPermissions("test-bucket-name", {}).status();
431 },
432 "TestBucketIamPermissions");
433 }
434
435 TEST_F(BucketTest, LockBucketRetentionPolicy) {
436 std::string text = R"""({
437 "kind": "storage#bucket",
438 "id": "test-bucket-name",
439 "selfLink": "https://storage.googleapis.com/storage/v1/b/test-bucket-name",
440 "projectNumber": "123456789",
441 "name": "test-bucket-name",
442 "timeCreated": "2018-05-19T19:31:14Z",
443 "updated": "2018-05-19T19:31:24Z",
444 "metageneration": 7,
445 "location": "US",
446 "storageClass": "STANDARD",
447 "etag": "XYZ="
448 })""";
449 auto expected = internal::BucketMetadataParser::FromString(text).value();
450
451 EXPECT_CALL(*mock_, LockBucketRetentionPolicy(_))
452 .WillOnce(Return(StatusOr<BucketMetadata>(TransientError())))
453 .WillOnce(
__anon02dce7db1e02(internal::LockBucketRetentionPolicyRequest const& r) 454 [expected](internal::LockBucketRetentionPolicyRequest const& r) {
455 EXPECT_EQ("test-bucket-name", r.bucket_name());
456 EXPECT_EQ(42, r.metageneration());
457 return make_status_or(expected);
458 });
459 auto metadata = client_->LockBucketRetentionPolicy("test-bucket-name", 42U);
460 ASSERT_STATUS_OK(metadata);
461 EXPECT_EQ(expected, *metadata);
462 }
463
464 TEST_F(BucketTest, LockBucketRetentionPolicyTooManyFailures) {
465 testing::TooManyFailuresStatusTest<BucketMetadata>(
466 mock_, EXPECT_CALL(*mock_, LockBucketRetentionPolicy(_)),
467 [](Client& client) {
468 return client.LockBucketRetentionPolicy("test-bucket-name", 1U)
469 .status();
470 },
471 "LockBucketRetentionPolicy");
472 }
473
474 TEST_F(BucketTest, LockBucketRetentionPolicyPermanentFailure) {
475 testing::PermanentFailureStatusTest<BucketMetadata>(
476 *client_, EXPECT_CALL(*mock_, LockBucketRetentionPolicy(_)),
477 [](Client& client) {
478 return client.LockBucketRetentionPolicy("test-bucket-name", 1U)
479 .status();
480 },
481 "LockBucketRetentionPolicy");
482 }
483
484 } // namespace
485 } // namespace STORAGE_CLIENT_NS
486 } // namespace storage
487 } // namespace cloud
488 } // namespace google
489