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"