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