Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions google/cloud/storage/_media/_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,13 @@ def _prepare_request(self):
_CONTENT_TYPE_HEADER: self._content_type,
_helpers.CONTENT_RANGE_HEADER: content_range,
}
if (start_byte + len(payload) == self._total_bytes) and (
self._checksum_object is not None
):
local_checksum = _helpers.prepare_checksum_digest(
self._checksum_object.digest()
)
headers["x-goog-hash"] = f"{self._checksum_type}={local_checksum}"
return _PUT, self.resumable_url, payload, headers

def _update_checksum(self, start_byte, payload):
Expand Down
37 changes: 34 additions & 3 deletions tests/unit/test_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -3049,15 +3049,22 @@ def test__initiate_resumable_upload_with_client_custom_headers(self):
self._initiate_resumable_helper(client=client)

def _make_resumable_transport(
self, headers1, headers2, headers3, total_bytes, data_corruption=False
self,
headers1,
headers2,
headers3,
total_bytes,
data_corruption=False,
md5_checksum_value=None,
crc32c_checksum_value=None,
):
fake_transport = mock.Mock(spec=["request"])

fake_response1 = self._mock_requests_response(http.client.OK, headers1)
fake_response2 = self._mock_requests_response(
http.client.PERMANENT_REDIRECT, headers2
)
json_body = f'{{"size": "{total_bytes:d}"}}'
json_body = f'{{"size": "{total_bytes:d}", "md5Hash": "{md5_checksum_value}", "crc32c": "{crc32c_checksum_value}"}}'
Comment thread
chandra-siri marked this conversation as resolved.
Outdated
if data_corruption:
fake_response3 = DataCorruption(None)
else:
Expand Down Expand Up @@ -3151,6 +3158,9 @@ def _do_resumable_upload_call2(
if_metageneration_match=None,
if_metageneration_not_match=None,
timeout=None,
checksum=None,
crc32c_checksum_value=None,
md5_checksum_value=None,
):
# Third mock transport.request() does sends last chunk.
content_range = f"bytes {blob.chunk_size:d}-{total_bytes - 1:d}/{total_bytes:d}"
Expand All @@ -3161,6 +3171,11 @@ def _do_resumable_upload_call2(
"content-type": content_type,
"content-range": content_range,
}
if checksum == "crc32c":
expected_headers["x-goog-hash"] = f"crc32c={crc32c_checksum_value}"
elif checksum == "md5":
expected_headers["x-goog-hash"] = f"md5={md5_checksum_value}"

payload = data[blob.chunk_size :]
return mock.call(
"PUT",
Expand All @@ -3181,12 +3196,17 @@ def _do_resumable_helper(
timeout=None,
data_corruption=False,
retry=None,
checksum=None, # None is also a valid value, when user decides to disable checksum validation.
):
CHUNK_SIZE = 256 * 1024
USER_AGENT = "testing 1.2.3"
content_type = "text/html"
# Data to be uploaded.
data = b"<html>" + (b"A" * CHUNK_SIZE) + b"</html>"

# Data calcuated offline and entered here. (Unit test best practice).
crc32c_checksum_value = "mQ30hg=="
md5_checksum_value = "wajHeg1f2Q2u9afI6fjPOw=="
total_bytes = len(data)
if use_size:
size = total_bytes
Expand All @@ -3213,6 +3233,8 @@ def _do_resumable_helper(
headers3,
total_bytes,
data_corruption=data_corruption,
md5_checksum_value=md5_checksum_value,
crc32c_checksum_value=crc32c_checksum_value,
)

# Create some mock arguments and call the method under test.
Expand Down Expand Up @@ -3247,7 +3269,7 @@ def _do_resumable_helper(
if_generation_not_match,
if_metageneration_match,
if_metageneration_not_match,
checksum=None,
checksum=checksum,
retry=retry,
**timeout_kwarg,
)
Expand Down Expand Up @@ -3296,6 +3318,9 @@ def _do_resumable_helper(
if_metageneration_match=if_metageneration_match,
if_metageneration_not_match=if_metageneration_not_match,
timeout=expected_timeout,
checksum=checksum,
crc32c_checksum_value=crc32c_checksum_value,
md5_checksum_value=md5_checksum_value,
)
self.assertEqual(transport.request.mock_calls, [call0, call1, call2])

Expand All @@ -3308,6 +3333,12 @@ def test__do_resumable_upload_no_size(self):
def test__do_resumable_upload_with_size(self):
self._do_resumable_helper(use_size=True)

def test__do_resumable_upload_with_size_with_crc32c_checksum(self):
self._do_resumable_helper(use_size=True, checksum="crc32c")

def test__do_resumable_upload_with_size_with_md5_checksum(self):
self._do_resumable_helper(use_size=True, checksum="md5")

def test__do_resumable_upload_with_retry(self):
self._do_resumable_helper(retry=DEFAULT_RETRY)

Expand Down