1 // Copyright 2019 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/testing/canonical_errors.h"
17 #include "google/cloud/storage/testing/storage_integration_test.h"
18 #include "google/cloud/internal/getenv.h"
19 #include "google/cloud/log.h"
20 #include "google/cloud/testing_util/assert_ok.h"
21 #include "google/cloud/testing_util/capture_log_lines_backend.h"
22 #include "google/cloud/testing_util/expect_exception.h"
23 #include <gmock/gmock.h>
24 #include <regex>
25
26 namespace google {
27 namespace cloud {
28 namespace storage {
29 inline namespace STORAGE_CLIENT_NS {
30 namespace {
31
32 using ::testing::HasSubstr;
33 using ::testing::StartsWith;
34
35 class ObjectHashIntegrationTest
36 : public google::cloud::storage::testing::StorageIntegrationTest {
37 protected:
SetUp()38 void SetUp() override {
39 bucket_name_ = google::cloud::internal::GetEnv(
40 "GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME")
41 .value_or("");
42 ASSERT_FALSE(bucket_name_.empty());
43 }
44
45 std::string bucket_name_;
46 };
47
48 /// @test Verify that MD5 hashes are disabled by default.
TEST_F(ObjectHashIntegrationTest,DefaultMD5HashXML)49 TEST_F(ObjectHashIntegrationTest, DefaultMD5HashXML) {
50 auto client_options = ClientOptions::CreateDefaultClientOptions();
51 ASSERT_STATUS_OK(client_options);
52 Client client((*client_options)
53 .set_enable_raw_client_tracing(true)
54 .set_enable_http_tracing(true));
55 auto object_name = MakeRandomObjectName();
56
57 auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
58 auto id = LogSink::Instance().AddBackend(backend);
59 StatusOr<ObjectMetadata> insert_meta =
60 client.InsertObject(bucket_name_, object_name, LoremIpsum(),
61 IfGenerationMatch(0), Fields(""));
62 ASSERT_STATUS_OK(insert_meta);
63
64 LogSink::Instance().RemoveBackend(id);
65
66 EXPECT_THAT(backend->ClearLogLines(),
67 Not(Contains(StartsWith("x-goog-hash: md5="))));
68
69 auto status = client.DeleteObject(bucket_name_, object_name);
70 ASSERT_STATUS_OK(status);
71 }
72
73 /// @test Verify that MD5 hashes are disabled by default.
TEST_F(ObjectHashIntegrationTest,DefaultMD5HashJSON)74 TEST_F(ObjectHashIntegrationTest, DefaultMD5HashJSON) {
75 auto client_options = ClientOptions::CreateDefaultClientOptions();
76 ASSERT_STATUS_OK(client_options);
77 Client client((*client_options)
78 .set_enable_raw_client_tracing(true)
79 .set_enable_http_tracing(true));
80 auto object_name = MakeRandomObjectName();
81
82 auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
83 auto id = LogSink::Instance().AddBackend(backend);
84 StatusOr<ObjectMetadata> insert_meta = client.InsertObject(
85 bucket_name_, object_name, LoremIpsum(), IfGenerationMatch(0));
86 ASSERT_STATUS_OK(insert_meta);
87
88 LogSink::Instance().RemoveBackend(id);
89
90 // This is a big indirect, we detect if the upload changed to
91 // multipart/related, and if so, we assume the hash value is being used.
92 // Unfortunately I (@coryan) cannot think of a way to examine the upload
93 // contents.
94 EXPECT_THAT(
95 backend->ClearLogLines(),
96 Contains(StartsWith("content-type: multipart/related; boundary=")));
97
98 if (insert_meta->has_metadata("x_emulator_upload")) {
99 // When running against the emulator, we have some more information to
100 // verify the right upload type and contents were sent.
101 EXPECT_EQ("multipart", insert_meta->metadata("x_emulator_upload"));
102 ASSERT_FALSE(insert_meta->has_metadata("x_emulator_md5"));
103 }
104
105 auto status = client.DeleteObject(bucket_name_, object_name);
106 ASSERT_STATUS_OK(status);
107 }
108
109 /// @test Verify that `DisableMD5Hash` actually disables the header.
TEST_F(ObjectHashIntegrationTest,DisableMD5HashXML)110 TEST_F(ObjectHashIntegrationTest, DisableMD5HashXML) {
111 auto client_options = ClientOptions::CreateDefaultClientOptions();
112 ASSERT_STATUS_OK(client_options);
113 Client client((*client_options)
114 .set_enable_raw_client_tracing(true)
115 .set_enable_http_tracing(true));
116 auto object_name = MakeRandomObjectName();
117
118 auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
119 auto id = LogSink::Instance().AddBackend(backend);
120 StatusOr<ObjectMetadata> insert_meta = client.InsertObject(
121 bucket_name_, object_name, LoremIpsum(), IfGenerationMatch(0),
122 DisableMD5Hash(true), Fields(""));
123 ASSERT_STATUS_OK(insert_meta);
124
125 LogSink::Instance().RemoveBackend(id);
126
127 EXPECT_THAT(backend->ClearLogLines(),
128 Not(Contains(StartsWith("x-goog-hash: md5="))));
129
130 auto status = client.DeleteObject(bucket_name_, object_name);
131 ASSERT_STATUS_OK(status);
132 }
133
134 /// @test Verify that `DisableMD5Hash` actually disables the payload.
TEST_F(ObjectHashIntegrationTest,DisableMD5HashJSON)135 TEST_F(ObjectHashIntegrationTest, DisableMD5HashJSON) {
136 auto client_options = ClientOptions::CreateDefaultClientOptions();
137 ASSERT_STATUS_OK(client_options);
138 Client client((*client_options)
139 .set_enable_raw_client_tracing(true)
140 .set_enable_http_tracing(true));
141 auto object_name = MakeRandomObjectName();
142
143 auto backend = std::make_shared<testing_util::CaptureLogLinesBackend>();
144 auto id = LogSink::Instance().AddBackend(backend);
145 StatusOr<ObjectMetadata> insert_meta =
146 client.InsertObject(bucket_name_, object_name, LoremIpsum(),
147 IfGenerationMatch(0), DisableMD5Hash(true));
148 ASSERT_STATUS_OK(insert_meta);
149
150 LogSink::Instance().RemoveBackend(id);
151
152 // This is a big indirect, we detect if the upload changed to
153 // multipart/related, and if so, we assume the hash value is being used.
154 // Unfortunately I (@coryan) cannot think of a way to examine the upload
155 // contents.
156 EXPECT_THAT(
157 backend->ClearLogLines(),
158 Contains(StartsWith("content-type: multipart/related; boundary=")));
159
160 if (insert_meta->has_metadata("x_emulator_upload")) {
161 // When running against the emulator, we have some more information to
162 // verify the right upload type and contents were sent.
163 EXPECT_EQ("multipart", insert_meta->metadata("x_emulator_upload"));
164 ASSERT_FALSE(insert_meta->has_metadata("x_emulator_md5"));
165 }
166
167 auto status = client.DeleteObject(bucket_name_, object_name);
168 ASSERT_STATUS_OK(status);
169 }
170
171 /// @test Verify that MD5 hashes are disabled by default on downloads.
TEST_F(ObjectHashIntegrationTest,DefaultMD5StreamingReadXML)172 TEST_F(ObjectHashIntegrationTest, DefaultMD5StreamingReadXML) {
173 StatusOr<Client> client = MakeIntegrationTestClient();
174 ASSERT_STATUS_OK(client);
175
176 auto object_name = MakeRandomObjectName();
177
178 // Create an object and a stream to read it back.
179 StatusOr<ObjectMetadata> meta =
180 client->InsertObject(bucket_name_, object_name, LoremIpsum(),
181 IfGenerationMatch(0), Projection::Full());
182 ASSERT_STATUS_OK(meta);
183
184 auto stream = client->ReadObject(bucket_name_, object_name);
185 std::string actual(std::istreambuf_iterator<char>{stream}, {});
186 ASSERT_FALSE(stream.IsOpen());
187 ASSERT_FALSE(actual.empty());
188
189 EXPECT_EQ(stream.received_hash(), stream.computed_hash());
190 EXPECT_THAT(stream.received_hash(), Not(HasSubstr(meta->md5_hash())));
191
192 auto status = client->DeleteObject(bucket_name_, object_name);
193 ASSERT_STATUS_OK(status);
194 }
195
196 /// @test Verify that MD5 hashes are disabled by default on downloads.
TEST_F(ObjectHashIntegrationTest,DefaultMD5StreamingReadJSON)197 TEST_F(ObjectHashIntegrationTest, DefaultMD5StreamingReadJSON) {
198 StatusOr<Client> client = MakeIntegrationTestClient();
199 ASSERT_STATUS_OK(client);
200
201 auto object_name = MakeRandomObjectName();
202
203 // Create an object and a stream to read it back.
204 StatusOr<ObjectMetadata> meta =
205 client->InsertObject(bucket_name_, object_name, LoremIpsum(),
206 IfGenerationMatch(0), Projection::Full());
207 ASSERT_STATUS_OK(meta);
208
209 auto stream = client->ReadObject(bucket_name_, object_name,
210 IfMetagenerationNotMatch(0));
211 std::string actual(std::istreambuf_iterator<char>{stream}, {});
212 ASSERT_FALSE(stream.IsOpen());
213 ASSERT_FALSE(actual.empty());
214
215 EXPECT_EQ(stream.received_hash(), stream.computed_hash());
216 EXPECT_THAT(stream.received_hash(), Not(HasSubstr(meta->md5_hash())));
217
218 auto status = client->DeleteObject(bucket_name_, object_name);
219 ASSERT_STATUS_OK(status);
220 }
221
222 /// @test Verify that hashes and checksums can be disabled on downloads.
TEST_F(ObjectHashIntegrationTest,DisableHashesStreamingReadXML)223 TEST_F(ObjectHashIntegrationTest, DisableHashesStreamingReadXML) {
224 StatusOr<Client> client = MakeIntegrationTestClient();
225 ASSERT_STATUS_OK(client);
226
227 auto object_name = MakeRandomObjectName();
228
229 // Create an object and a stream to read it back.
230 StatusOr<ObjectMetadata> meta =
231 client->InsertObject(bucket_name_, object_name, LoremIpsum(),
232 IfGenerationMatch(0), Projection::Full());
233 ASSERT_STATUS_OK(meta);
234
235 auto stream =
236 client->ReadObject(bucket_name_, object_name, DisableMD5Hash(true),
237 DisableCrc32cChecksum(true));
238 std::string actual(std::istreambuf_iterator<char>{stream}, {});
239 ASSERT_FALSE(stream.IsOpen());
240 ASSERT_FALSE(actual.empty());
241
242 EXPECT_TRUE(stream.computed_hash().empty());
243 EXPECT_TRUE(stream.received_hash().empty());
244
245 auto status = client->DeleteObject(bucket_name_, object_name);
246 ASSERT_STATUS_OK(status);
247 }
248
249 /// @test Verify that hashes and checksums can be disabled on downloads.
TEST_F(ObjectHashIntegrationTest,DisableHashesStreamingReadJSON)250 TEST_F(ObjectHashIntegrationTest, DisableHashesStreamingReadJSON) {
251 StatusOr<Client> client = MakeIntegrationTestClient();
252 ASSERT_STATUS_OK(client);
253
254 auto object_name = MakeRandomObjectName();
255
256 // Create an object and a stream to read it back.
257 StatusOr<ObjectMetadata> meta =
258 client->InsertObject(bucket_name_, object_name, LoremIpsum(),
259 IfGenerationMatch(0), Projection::Full());
260 ASSERT_STATUS_OK(meta);
261
262 auto stream = client->ReadObject(
263 bucket_name_, object_name, DisableMD5Hash(true),
264 DisableCrc32cChecksum(true), IfMetagenerationNotMatch(0));
265 std::string actual(std::istreambuf_iterator<char>{stream}, {});
266 ASSERT_FALSE(stream.IsOpen());
267 ASSERT_FALSE(actual.empty());
268
269 EXPECT_TRUE(stream.computed_hash().empty());
270 EXPECT_TRUE(stream.received_hash().empty());
271
272 auto status = client->DeleteObject(bucket_name_, object_name);
273 ASSERT_STATUS_OK(status);
274 }
275
276 /// @test Verify that MD5 hashes are disabled by default on uploads.
TEST_F(ObjectHashIntegrationTest,DefaultMD5StreamingWriteJSON)277 TEST_F(ObjectHashIntegrationTest, DefaultMD5StreamingWriteJSON) {
278 StatusOr<Client> client = MakeIntegrationTestClient();
279 ASSERT_STATUS_OK(client);
280
281 auto object_name = MakeRandomObjectName();
282
283 // Create the object, but only if it does not exist already.
284 auto os =
285 client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0));
286 os.exceptions(std::ios_base::failbit);
287 // We will construct the expected response while streaming the data up.
288 std::ostringstream expected;
289 WriteRandomLines(os, expected);
290
291 auto expected_md5hash = ComputeMD5Hash(expected.str());
292
293 os.Close();
294 ObjectMetadata meta = os.metadata().value();
295 EXPECT_EQ(os.received_hash(), os.computed_hash());
296 EXPECT_THAT(os.received_hash(), Not(HasSubstr(expected_md5hash)));
297
298 auto status = client->DeleteObject(bucket_name_, object_name);
299 ASSERT_STATUS_OK(status);
300 }
301
302 /// @test Verify MD5 hash value before upload.
TEST_F(ObjectHashIntegrationTest,VerifyValidMD5StreamingWriteJSON)303 TEST_F(ObjectHashIntegrationTest, VerifyValidMD5StreamingWriteJSON) {
304 StatusOr<Client> client = MakeIntegrationTestClient();
305 ASSERT_STATUS_OK(client);
306
307 auto object_name = MakeRandomObjectName();
308
309 std::string expected = LoremIpsum();
310 auto expected_md5hash = ComputeMD5Hash(expected);
311
312 // Create the object, but only if it does not exist already.
313 auto os = client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
314 MD5HashValue(expected_md5hash));
315
316 os.exceptions(std::ios_base::failbit);
317 os << expected;
318 os.Close();
319
320 ObjectMetadata meta = os.metadata().value();
321 EXPECT_EQ(os.received_hash(), os.computed_hash());
322 EXPECT_THAT(os.received_hash(), Not(HasSubstr(expected_md5hash)));
323
324 auto status = client->DeleteObject(bucket_name_, object_name);
325 ASSERT_STATUS_OK(status);
326 }
327
328 /// @test Verify invalid MD5 hash value before upload.
TEST_F(ObjectHashIntegrationTest,InvalidMD5StreamingWriteJSON)329 TEST_F(ObjectHashIntegrationTest, InvalidMD5StreamingWriteJSON) {
330 StatusOr<Client> client = MakeIntegrationTestClient();
331 ASSERT_STATUS_OK(client);
332
333 auto object_name = MakeRandomObjectName();
334
335 std::string expected = LoremIpsum();
336
337 // Create the object, but only if it does not exist already. Dummy MD5HasValue
338 // is passed during WriteObject.
339 auto os = client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
340 MD5HashValue("AAAAAAAAAA+AAAAAAAAAAA=="));
341
342 os.exceptions(std::ios_base::failbit);
343 os << expected;
344 os.Close();
345
346 EXPECT_TRUE(os.bad());
347 EXPECT_FALSE(os.metadata().status().ok());
348 }
349
350 /// @test Verify MD5 hashes before upload.
TEST_F(ObjectHashIntegrationTest,InvalidMD5StreamingWriteXML)351 TEST_F(ObjectHashIntegrationTest, InvalidMD5StreamingWriteXML) {
352 StatusOr<Client> client = MakeIntegrationTestClient();
353 ASSERT_STATUS_OK(client);
354
355 auto object_name = MakeRandomObjectName();
356
357 std::string expected = LoremIpsum();
358
359 // Create the object, but only if it does not exist already. Dummy MD5HasValue
360 // is passed during WriteObject.
361 auto os = client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
362 Projection::Full(),
363 MD5HashValue("AAAAAAAAAA+AAAAAAAAAAA=="));
364 os.exceptions(std::ios_base::failbit);
365 os << expected;
366 os.Close();
367
368 EXPECT_TRUE(os.bad());
369 EXPECT_FALSE(os.metadata().status().ok());
370 }
371
372 /// @test Verify that hashes and checksums can be disabled in uploads.
TEST_F(ObjectHashIntegrationTest,DisableHashesStreamingWriteJSON)373 TEST_F(ObjectHashIntegrationTest, DisableHashesStreamingWriteJSON) {
374 StatusOr<Client> client = MakeIntegrationTestClient();
375 ASSERT_STATUS_OK(client);
376
377 auto object_name = MakeRandomObjectName();
378
379 // Create the object, but only if it does not exist already.
380 auto os =
381 client->WriteObject(bucket_name_, object_name, IfGenerationMatch(0),
382 DisableMD5Hash(true), DisableCrc32cChecksum(true));
383 os.exceptions(std::ios_base::failbit);
384 // We will construct the expected response while streaming the data up.
385 std::ostringstream expected;
386 WriteRandomLines(os, expected);
387
388 os.Close();
389 ObjectMetadata meta = os.metadata().value();
390 EXPECT_TRUE(os.received_hash().empty());
391 EXPECT_TRUE(os.computed_hash().empty());
392
393 auto status = client->DeleteObject(bucket_name_, object_name);
394 ASSERT_STATUS_OK(status);
395 }
396
397 /// @test Verify that MD5 hash mismatches are reported by default on downloads.
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadXML)398 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadXML) {
399 // This test is disabled when not using the emulator as it relies on the
400 // emulator to inject faults.
401 if (!UsingEmulator()) GTEST_SKIP();
402 StatusOr<Client> client = MakeIntegrationTestClient();
403 ASSERT_STATUS_OK(client);
404
405 auto object_name = MakeRandomObjectName();
406
407 // Create an object and a stream to read it back.
408 StatusOr<ObjectMetadata> meta = client->InsertObject(
409 bucket_name_, object_name, LoremIpsum(), EnableMD5Hash(),
410 IfGenerationMatch(0), Projection::Full());
411 ASSERT_STATUS_OK(meta);
412
413 auto stream = client->ReadObject(
414 bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
415 CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
416
417 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
418 EXPECT_THROW(
419 try {
420 std::string actual(std::istreambuf_iterator<char>{stream},
421 std::istreambuf_iterator<char>{});
422 } catch (HashMismatchError const& ex) {
423 EXPECT_NE(ex.received_hash(), ex.computed_hash());
424 EXPECT_THAT(ex.what(), HasSubstr("mismatched hashes"));
425 throw;
426 },
427 HashMismatchError);
428 #else
429 std::string actual(std::istreambuf_iterator<char>{stream}, {});
430 EXPECT_NE(stream.received_hash(), stream.computed_hash());
431 EXPECT_EQ(stream.received_hash(), meta->md5_hash());
432 EXPECT_FALSE(stream.status().ok());
433 #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
434
435 auto status = client->DeleteObject(bucket_name_, object_name);
436 EXPECT_STATUS_OK(status);
437 }
438
439 /// @test Verify that MD5 hash mismatches are reported by default on downloads.
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadJSON)440 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadJSON) {
441 // This test is disabled when not using the emulator as it relies on the
442 // emulator to inject faults.
443 if (!UsingEmulator()) GTEST_SKIP();
444 StatusOr<Client> client = MakeIntegrationTestClient();
445 ASSERT_STATUS_OK(client);
446
447 auto object_name = MakeRandomObjectName();
448
449 // Create an object and a stream to read it back.
450 StatusOr<ObjectMetadata> meta = client->InsertObject(
451 bucket_name_, object_name, LoremIpsum(), EnableMD5Hash(),
452 IfGenerationMatch(0), Projection::Full());
453 ASSERT_STATUS_OK(meta);
454
455 auto stream = client->ReadObject(
456 bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
457 IfMetagenerationNotMatch(0),
458 CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
459
460 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
461 EXPECT_THROW(
462 try {
463 std::string actual(std::istreambuf_iterator<char>{stream},
464 std::istreambuf_iterator<char>{});
465 } catch (HashMismatchError const& ex) {
466 EXPECT_NE(ex.received_hash(), ex.computed_hash());
467 EXPECT_THAT(ex.what(), HasSubstr("mismatched hashes"));
468 throw;
469 },
470 HashMismatchError);
471 #else
472 std::string actual(std::istreambuf_iterator<char>{stream}, {});
473 EXPECT_FALSE(stream.received_hash().empty());
474 EXPECT_FALSE(stream.computed_hash().empty());
475 EXPECT_NE(stream.received_hash(), stream.computed_hash());
476 #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
477
478 auto status = client->DeleteObject(bucket_name_, object_name);
479 EXPECT_STATUS_OK(status);
480 }
481
482 /// @test Verify that MD5 hash mismatches are reported when using .read().
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadXMLRead)483 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadXMLRead) {
484 // This test is disabled when not using the emulator as it relies on the
485 // emulator to inject faults.
486 if (!UsingEmulator()) GTEST_SKIP();
487 StatusOr<Client> client = MakeIntegrationTestClient();
488 ASSERT_STATUS_OK(client);
489
490 auto object_name = MakeRandomObjectName();
491 auto contents = MakeRandomData(1024 * 1024);
492
493 // Create an object and a stream to read it back.
494 StatusOr<ObjectMetadata> meta =
495 client->InsertObject(bucket_name_, object_name, contents, EnableMD5Hash(),
496 IfGenerationMatch(0), Projection::Full());
497 ASSERT_STATUS_OK(meta);
498
499 auto stream = client->ReadObject(
500 bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
501 CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
502
503 // Create a buffer large enough to hold the results and read pas EOF.
504 std::vector<char> buffer(2 * contents.size());
505 stream.read(buffer.data(), buffer.size());
506 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
507 EXPECT_TRUE(stream.bad());
508 #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
509 EXPECT_EQ(StatusCode::kDataLoss, stream.status().code());
510 EXPECT_NE(stream.received_hash(), stream.computed_hash());
511 EXPECT_EQ(stream.received_hash(), meta->md5_hash());
512
513 auto status = client->DeleteObject(bucket_name_, object_name);
514 EXPECT_STATUS_OK(status);
515 }
516
517 /// @test Verify that MD5 hash mismatches are reported when using .read().
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingReadJSONRead)518 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingReadJSONRead) {
519 // This test is disabled when not using the emulator as it relies on the
520 // emulator to inject faults.
521 if (!UsingEmulator()) GTEST_SKIP();
522 StatusOr<Client> client = MakeIntegrationTestClient();
523 ASSERT_STATUS_OK(client);
524
525 auto object_name = MakeRandomObjectName();
526 auto contents = MakeRandomData(1024 * 1024);
527
528 // Create an object and a stream to read it back.
529 StatusOr<ObjectMetadata> meta =
530 client->InsertObject(bucket_name_, object_name, contents, EnableMD5Hash(),
531 IfGenerationMatch(0), Projection::Full());
532 ASSERT_STATUS_OK(meta);
533
534 auto stream = client->ReadObject(
535 bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
536 IfMetagenerationNotMatch(0),
537 CustomHeader("x-goog-emulator-instructions", "return-corrupted-data"));
538
539 // Create a buffer large enough to hold the results and read pas EOF.
540 std::vector<char> buffer(2 * contents.size());
541 stream.read(buffer.data(), buffer.size());
542 #if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
543 EXPECT_TRUE(stream.bad());
544 #endif // GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS
545 EXPECT_EQ(StatusCode::kDataLoss, stream.status().code());
546 EXPECT_NE(stream.received_hash(), stream.computed_hash());
547 EXPECT_EQ(stream.received_hash(), meta->md5_hash());
548
549 auto status = client->DeleteObject(bucket_name_, object_name);
550 EXPECT_STATUS_OK(status);
551 }
552
553 /// @test Verify that MD5 hash mismatches are reported by default on downloads.
TEST_F(ObjectHashIntegrationTest,MismatchedMD5StreamingWriteJSON)554 TEST_F(ObjectHashIntegrationTest, MismatchedMD5StreamingWriteJSON) {
555 // This test is disabled when not using the emulator as it relies on the
556 // emulator to inject faults.
557 if (!UsingEmulator()) GTEST_SKIP();
558 StatusOr<Client> client = MakeIntegrationTestClient();
559 ASSERT_STATUS_OK(client);
560
561 auto object_name = MakeRandomObjectName();
562
563 // Create a stream to upload an object.
564 ObjectWriteStream stream = client->WriteObject(
565 bucket_name_, object_name, DisableCrc32cChecksum(true), EnableMD5Hash(),
566 IfGenerationMatch(0),
567 CustomHeader("x-goog-emulator-instructions", "inject-upload-data-error"));
568 stream << LoremIpsum() << "\n";
569 stream << LoremIpsum();
570
571 stream.Close();
572 EXPECT_TRUE(stream.bad());
573 EXPECT_STATUS_OK(stream.metadata());
574 EXPECT_NE(stream.received_hash(), stream.computed_hash());
575
576 auto status = client->DeleteObject(bucket_name_, object_name);
577 EXPECT_STATUS_OK(status);
578 }
579
580 } // anonymous namespace
581 } // namespace STORAGE_CLIENT_NS
582 } // namespace storage
583 } // namespace cloud
584 } // namespace google
585