diff --git a/moto/s3/models.py b/moto/s3/models.py index 7079a4ce1..811f4caba 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -365,6 +365,9 @@ class FakeMultipart(BaseModel): last = part count += 1 + if count == 0: + raise MalformedXML + etag = hashlib.md5() etag.update(bytes(md5s)) return total, "{0}-{1}".format(etag.hexdigest(), count) diff --git a/tests/terraform-tests.failures.txt b/tests/terraform-tests.failures.txt index 93c990561..17860524c 100644 --- a/tests/terraform-tests.failures.txt +++ b/tests/terraform-tests.failures.txt @@ -8,5 +8,4 @@ TestAccAWSDefaultSecurityGroup_Classic_ TestAccDataSourceAwsNetworkInterface_CarrierIPAssociation TestAccAWSRouteTable_IPv4_To_LocalGateway TestAccAWSRouteTable_IPv4_To_VpcEndpoint -TestAccAWSRouteTable_VpcClassicLink -TestAccAWSS3BucketObject_updatesWithVersioningViaAccessPoint \ No newline at end of file +TestAccAWSRouteTable_VpcClassicLink \ No newline at end of file diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt index b4480733e..f6c312fdb 100644 --- a/tests/terraform-tests.success.txt +++ b/tests/terraform-tests.success.txt @@ -120,9 +120,7 @@ TestAccAWSENI_Tags TestAccAWSENI_basic TestAccAWSENI_IPv6 TestAccAWSENI_disappears -TestAccAWSS3BucketObject TestAccAWSS3BucketPolicy -TestAccAWSS3AccessPoint TestAccAWSS3BucketPublicAccessBlock TestAccAWSS3ObjectCopy TestAccAWSIAMPolicy_ diff --git a/tests/test_s3/test_s3_multipart.py b/tests/test_s3/test_s3_multipart.py index 43b682b39..ccd7aac30 100644 --- a/tests/test_s3/test_s3_multipart.py +++ b/tests/test_s3/test_s3_multipart.py @@ -1,14 +1,14 @@ +import boto3 import os import pytest +import sure # noqa # pylint: disable=unused-import + +from botocore.client import ClientError from functools import wraps from io import BytesIO -import boto3 -from botocore.client import ClientError - from moto.s3.responses import DEFAULT_REGION_NAME -import sure # noqa # pylint: disable=unused-import from moto import settings, mock_s3 import moto.s3.models as s3model @@ -839,3 +839,30 @@ def test_multipart_part_size(): for i in range(1, n_parts + 1): obj = s3.head_object(Bucket="mybucket", Key="the-key", PartNumber=i) assert obj["ContentLength"] == REDUCED_PART_SIZE + i + + +@mock_s3 +def test_complete_multipart_with_empty_partlist(): + """ + When completing a MultipartUpload with an empty part list, AWS responds with an InvalidXML-error + + Verify that we send the same error, to duplicate boto3's behaviour + """ + bucket = "testbucketthatcompletesmultipartuploadwithoutparts" + key = "test-multi-empty" + + client = boto3.client("s3", region_name=DEFAULT_REGION_NAME) + client.create_bucket(Bucket=bucket) + + response = client.create_multipart_upload(Bucket=bucket, Key=key,) + uid = response["UploadId"] + + upload = boto3.resource("s3").MultipartUpload(bucket, key, uid) + + with pytest.raises(ClientError) as exc: + upload.complete(MultipartUpload={"Parts": []}) + err = exc.value.response["Error"] + err["Code"].should.equal("MalformedXML") + err["Message"].should.equal( + "The XML you provided was not well-formed or did not validate against our published schema" + )