Skip to content

Commit f84be36

Browse files
committed
Add functional test
1 parent 968031f commit f84be36

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

tests/functional/tests.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from urllib3._collections import HTTPHeaderDict
4343

4444
from minio import Minio
45+
from minio.checksum import Algorithm
4546
from minio.commonconfig import ENABLED, REPLACE, CopySource, SnowballObject
4647
from minio.datatypes import PostPolicy
4748
from minio.deleteobjects import DeleteObject
@@ -908,6 +909,111 @@ def test_negative_put_object_with_path_segment( # pylint: disable=invalid-name
908909
_client.remove_bucket(bucket_name=bucket_name)
909910

910911

912+
def test_put_object_multipart_with_checksum( # pylint: disable=invalid-name
913+
log_entry):
914+
"""Test put_object() multipart upload with checksum validation.
915+
916+
This test validates the AWS S3 compliant checksum implementation for
917+
multipart uploads:
918+
- CreateMultipartUpload receives algorithm header only (not values)
919+
- UploadPart includes checksum value headers
920+
- CompleteMultipartUpload includes checksums in XML body
921+
"""
922+
923+
# Get a unique bucket_name and object_name
924+
bucket_name = _gen_bucket_name()
925+
object_name = f"{uuid4()}-checksum"
926+
object_name_sha256 = None # Initialize for cleanup
927+
# Use 6 MB to trigger multipart upload (> 5 MB threshold)
928+
length = 6 * MB
929+
930+
log_entry["args"] = {
931+
"bucket_name": bucket_name,
932+
"object_name": object_name,
933+
"length": length,
934+
"data": "LimitedRandomReader(6 * MB)",
935+
"checksum": "Algorithm.CRC32C",
936+
}
937+
938+
try:
939+
_client.make_bucket(bucket_name=bucket_name)
940+
941+
# Upload with CRC32C checksum - triggers multipart upload
942+
reader = LimitedRandomReader(length)
943+
result = _client.put_object(
944+
bucket_name=bucket_name,
945+
object_name=object_name,
946+
data=reader,
947+
length=length,
948+
checksum=Algorithm.CRC32C,
949+
)
950+
951+
# Verify upload succeeded and returned valid result
952+
if not result.etag:
953+
raise ValueError("Upload did not return valid ETag")
954+
955+
# Verify ETag indicates multipart upload (contains dash and part count)
956+
if '-' not in result.etag:
957+
raise ValueError(
958+
f"Expected multipart ETag (with dash), got: {result.etag}")
959+
960+
# Stat the object to verify it exists and has correct size
961+
st_obj = _client.stat_object(
962+
bucket_name=bucket_name,
963+
object_name=object_name,
964+
)
965+
966+
if st_obj.size != length:
967+
raise ValueError(
968+
f"Size mismatch: expected {length}, got {st_obj.size}")
969+
970+
# Test with SHA256 checksum algorithm
971+
object_name_sha256 = f"{uuid4()}-checksum-sha256"
972+
log_entry["args"]["object_name"] = object_name_sha256
973+
log_entry["args"]["checksum"] = "Algorithm.SHA256"
974+
975+
reader = LimitedRandomReader(length)
976+
result = _client.put_object(
977+
bucket_name=bucket_name,
978+
object_name=object_name_sha256,
979+
data=reader,
980+
length=length,
981+
checksum=Algorithm.SHA256,
982+
)
983+
984+
if not result.etag:
985+
raise ValueError("Upload with SHA256 did not return valid ETag")
986+
987+
if '-' not in result.etag:
988+
raise ValueError(
989+
f"Expected multipart ETag for SHA256, got: {result.etag}")
990+
991+
st_obj = _client.stat_object(
992+
bucket_name=bucket_name,
993+
object_name=object_name_sha256,
994+
)
995+
996+
if st_obj.size != length:
997+
raise ValueError(
998+
f"Size mismatch: expected {length}, got {st_obj.size}")
999+
1000+
finally:
1001+
try:
1002+
_client.remove_object(bucket_name=bucket_name, object_name=object_name)
1003+
except: # pylint: disable=bare-except
1004+
pass
1005+
if object_name_sha256:
1006+
try:
1007+
_client.remove_object(
1008+
bucket_name=bucket_name, object_name=object_name_sha256)
1009+
except: # pylint: disable=bare-except
1010+
pass
1011+
try:
1012+
_client.remove_bucket(bucket_name=bucket_name)
1013+
except: # pylint: disable=bare-except
1014+
pass
1015+
1016+
9111017
def _test_stat_object(log_entry, sse=None, version_check=False):
9121018
"""Test stat_object()."""
9131019

@@ -2393,6 +2499,7 @@ def main():
23932499
test_copy_object_unmodified_since: None,
23942500
test_put_object: {"sse": ssec} if ssec else None,
23952501
test_negative_put_object_with_path_segment: None,
2502+
test_put_object_multipart_with_checksum: None,
23962503
test_stat_object: {"sse": ssec} if ssec else None,
23972504
test_stat_object_version: {"sse": ssec} if ssec else None,
23982505
test_get_object: {"sse": ssec} if ssec else None,

0 commit comments

Comments
 (0)