diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py index 3cf2ecb5..ee65a88e 100644 --- a/moto/s3/exceptions.py +++ b/moto/s3/exceptions.py @@ -15,6 +15,10 @@ ERROR_WITH_ARGUMENT = """{% extends 'single_error' %} {{ value }}{% endblock %} """ +ERROR_WITH_UPLOADID = """{% extends 'single_error' %} +{% block extra %}{{ upload_id }}{% endblock %} +""" + ERROR_WITH_CONDITION_NAME = """{% extends 'single_error' %} {% block extra %}{{ condition }}{% endblock %} """ @@ -418,9 +422,15 @@ class NoSystemTags(S3ClientError): class NoSuchUpload(S3ClientError): code = 404 - def __init__(self): + def __init__(self, upload_id, *args, **kwargs): + kwargs.setdefault("template", "error_uploadid") + kwargs["upload_id"] = upload_id + self.templates["error_uploadid"] = ERROR_WITH_UPLOADID super(NoSuchUpload, self).__init__( - "NoSuchUpload", "The specified multipart upload does not exist." + "NoSuchUpload", + "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.", + *args, + **kwargs ) diff --git a/moto/s3/models.py b/moto/s3/models.py index d6a98282..d50d1519 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -1604,11 +1604,13 @@ class S3Backend(BaseBackend): bucket = self.get_bucket(bucket_name) multipart_data = bucket.multiparts.get(multipart_id, None) if not multipart_data: - raise NoSuchUpload() + raise NoSuchUpload(upload_id=multipart_id) del bucket.multiparts[multipart_id] def list_multipart(self, bucket_name, multipart_id): bucket = self.get_bucket(bucket_name) + if multipart_id not in bucket.multiparts: + raise NoSuchUpload(upload_id=multipart_id) return list(bucket.multiparts[multipart_id].list_parts()) def get_all_multiparts(self, bucket_name): diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 9df7476a..7a9f2d99 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -2310,7 +2310,12 @@ def test_s3_abort_multipart_data_with_invalid_upload_and_key(): client.abort_multipart_upload( Bucket="blah", Key="foobar", UploadId="dummy_upload_id" ) - err.value.response["Error"]["Code"].should.equal("NoSuchUpload") + err = err.value.response["Error"] + err["Code"].should.equal("NoSuchUpload") + err["Message"].should.equal( + "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed." + ) + err["UploadId"].should.equal("dummy_upload_id") @mock_s3 diff --git a/tests/test_s3/test_s3_multipart.py b/tests/test_s3/test_s3_multipart.py new file mode 100644 index 00000000..03c71d25 --- /dev/null +++ b/tests/test_s3/test_s3_multipart.py @@ -0,0 +1,24 @@ +from botocore.exceptions import ClientError +from moto import mock_s3 +import boto3 +import pytest +import sure # noqa + + +@mock_s3 +def test_multipart_should_throw_nosuchupload_if_there_are_no_parts(): + bucket = boto3.resource("s3").Bucket("randombucketname") + bucket.create() + s3_object = bucket.Object("my/test2") + + multipart_upload = s3_object.initiate_multipart_upload() + multipart_upload.abort() + + with pytest.raises(ClientError) as ex: + list(multipart_upload.parts.all()) + err = ex.value.response["Error"] + err["Code"].should.equal("NoSuchUpload") + err["Message"].should.equal( + "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed." + ) + err["UploadId"].should.equal(multipart_upload.id)