diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py index cb8661035..c1169b707 100644 --- a/moto/s3/exceptions.py +++ b/moto/s3/exceptions.py @@ -10,6 +10,11 @@ ERROR_WITH_KEY_NAME = """{% extends 'single_error' %} {% block extra %}{{ key_name }}{% endblock %} """ +ERROR_WITH_ARGUMENT = """{% extends 'single_error' %} +{% block extra %}{{ name }} +{{ value }}{% endblock %} +""" + ERROR_WITH_CONDITION_NAME = """{% extends 'single_error' %} {% block extra %}{{ condition }}{% endblock %} """ @@ -68,6 +73,19 @@ class MissingKey(S3ClientError): ) +class MissingVersion(S3ClientError): + code = 404 + + def __init__(self, version_id, *args, **kwargs): + kwargs.setdefault("template", "argument_error") + kwargs["name"] = "versionId" + kwargs["value"] = version_id + self.templates["argument_error"] = ERROR_WITH_ARGUMENT + super(MissingVersion, self).__init__( + "InvalidArgument", "Invalid version id specified", *args, **kwargs + ) + + class ObjectNotInActiveTierError(S3ClientError): code = 403 diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 8b1dbe9b5..06aeb8e28 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -29,6 +29,7 @@ from .exceptions import ( S3ClientError, MissingBucket, MissingKey, + MissingVersion, InvalidPartOrder, MalformedXML, MalformedACLError, @@ -1171,8 +1172,10 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): if_unmodified_since = headers.get("If-Unmodified-Since", None) key = self.backend.get_object(bucket_name, key_name, version_id=version_id) - if key is None: + if key is None and version_id is None: raise MissingKey(key_name) + elif key is None: + raise MissingVersion(version_id) if if_unmodified_since: if_unmodified_since = str_to_rfc_1123_datetime(if_unmodified_since) diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 0f5624f96..8c2b8130c 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -4939,6 +4939,25 @@ def test_request_partial_content_should_contain_actual_content_length(): e.response["Error"]["RangeRequested"].should.equal(requested_range) +@mock_s3 +def test_get_unknown_version_should_throw_specific_error(): + bucket_name = "my_bucket" + object_key = "hello.txt" + s3 = boto3.resource("s3", region_name="us-east-1") + client = boto3.client("s3", region_name="us-east-1") + bucket = s3.create_bucket(Bucket=bucket_name) + bucket.Versioning().enable() + content = "some text" + s3.Object(bucket_name, object_key).put(Body=content) + + with pytest.raises(ClientError) as e: + client.get_object(Bucket=bucket_name, Key=object_key, VersionId="unknown") + e.value.response["Error"]["Code"].should.equal("InvalidArgument") + e.value.response["Error"]["Message"].should.equal("Invalid version id specified") + e.value.response["Error"]["ArgumentName"].should.equal("versionId") + e.value.response["Error"]["ArgumentValue"].should.equal("unknown") + + @mock_s3 def test_request_partial_content_without_specifying_range_should_return_full_object(): bucket = "bucket"