Repository: getmoto/moto

Issue:
mock_s3 get_object should not return VersionId when bucket doesn't have versioning
Even though the S3 document says that "Objects that are stored in your bucket before you set the versioning state have a version ID of null." However, when you do [get_object](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/get_object.html), if the bucket doesn't have versioning, the response doesn't have the key ``"VersionId"``. However, moto will return ``"VersionId": "null"`` (a string).

I believe you should change this line of code https://github.com/getmoto/moto/blob/master/moto/s3/models.py#L1812


Discussion:
Thanks for raising this @MacHu-GWU - marking it as a bug.
@bblommers assign this to me and I will take a look
No need @rafcio19 - I should be able to raise a PR shortly. Appreciate the offer!

Resolving patch:
diff --git a/moto/s3/models.py b/moto/s3/models.py
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -95,7 +95,7 @@ def __init__(
         storage: Optional[str] = "STANDARD",
         etag: Optional[str] = None,
         is_versioned: bool = False,
-        version_id: int = 0,
+        version_id: str = "null",
         max_buffer_size: Optional[int] = None,
         multipart: Optional["FakeMultipart"] = None,
         bucket_name: Optional[str] = None,
@@ -156,7 +156,7 @@ def safe_name(self, encoding_type: Optional[str] = None) -> str:
         return self.name
 
     @property
-    def version_id(self) -> int:
+    def version_id(self) -> str:
         return self._version_id
 
     @property
@@ -1856,6 +1856,7 @@ def put_object(
             storage=storage,
             etag=etag,
             is_versioned=bucket.is_versioned,
+            # AWS uses VersionId=null in both requests and responses
             version_id=str(random.uuid4()) if bucket.is_versioned else "null",  # type: ignore
             multipart=multipart,
             encryption=encryption,
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -1348,7 +1348,7 @@ def _key_response_get(
         elif key is None:
             raise MissingVersion()
 
-        if key.version_id:
+        if key.version_id != "null":
             response_headers["x-amz-version-id"] = key.version_id
 
         if key.storage_class in ARCHIVE_STORAGE_CLASSES:
@@ -1386,8 +1386,6 @@ def _key_response_get(
             attributes_to_get = headers.get("x-amz-object-attributes", "").split(",")
             response_keys = self.backend.get_object_attributes(key, attributes_to_get)
 
-            if key.version_id == "null":  # type: ignore
-                response_headers.pop("x-amz-version-id")
             response_headers["Last-Modified"] = key.last_modified_ISO8601
 
             template = self.response_template(S3_OBJECT_ATTRIBUTES_RESPONSE)


Test patch:
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -2476,6 +2476,7 @@ def test_list_object_versions_with_versioning_disabled():
 
     # Test latest object version is returned
     response = s3.get_object(Bucket=bucket_name, Key=key)
+    assert "VersionId" not in response["ResponseMetadata"]["HTTPHeaders"]
     response["Body"].read().should.equal(items[-1])