Repository: getmoto/moto

Issue:
delete_object_tagging deletes the entire object
Expected behaviour:
Issuing delete_object_tagging on an object to delete the tags on the object but not the object itself.

Actual behaviour:
Object in S3 is actually deleted when the tags are deleted

Test case:
```
@mock_s3
def test_get_put_delete_object_tagging_boto3():
    makedir("/tmp/test", "/tmp/test")
    local_file = f"/tmp/test/local"
    touch(local_file)
    session = boto3.Session(profile_name="moto", region_name="eu-west-1")
    s3_client = session.client('s3')
    s3_client.create_bucket(Bucket="bucket")
    s3_client.put_object(
        Bucket="bucket",
        Key="remote",
        Body="".encode('utf8')
    )

    print("Listing before deleting tags")
    for obj in s3_client.list_objects_v2(Bucket="bucket").get("Contents", []):
        print(obj.get("Key"))

    s3_client.delete_object_tagging(
        Bucket="bucket",
        Key="remote"
    )

    print("Listing after deleting tags")
    for obj in s3_client.list_objects_v2(Bucket="bucket").get("Contents", []):
        print(obj.get("Key"))
```

Output:
```
Listing before deleting tags
remote
Listing after deleting tags
```
I was expecting to still see the file there after removing the tags.
I'm using the python mocks.

Let me know if you need more information.


Discussion:
Thanks; setting as a feature request.

Resolving patch:
diff --git a/moto/s3/models.py b/moto/s3/models.py
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -1566,6 +1566,10 @@ def _set_delete_marker(self, bucket_name, key_name):
         bucket = self.get_bucket(bucket_name)
         bucket.keys[key_name] = FakeDeleteMarker(key=bucket.keys[key_name])
 
+    def delete_object_tagging(self, bucket_name, key_name, version_id=None):
+        key = self.get_object(bucket_name, key_name, version_id=version_id)
+        self.tagger.delete_all_tags_for_resource(key.arn)
+
     def delete_object(self, bucket_name, key_name, version_id=None):
         key_name = clean_key_name(key_name)
         bucket = self.get_bucket(bucket_name)
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -1618,6 +1618,12 @@ def _key_response_delete(self, bucket_name, query, key_name):
             self.backend.cancel_multipart(bucket_name, upload_id)
             return 204, {}, ""
         version_id = query.get("versionId", [None])[0]
+        if "tagging" in query:
+            self.backend.delete_object_tagging(
+                bucket_name, key_name, version_id=version_id
+            )
+            template = self.response_template(S3_DELETE_KEY_TAGGING_RESPONSE)
+            return 204, {}, template.render(version_id=version_id)
         self.backend.delete_object(bucket_name, key_name, version_id=version_id)
         return 204, {}, ""
 
@@ -1935,6 +1941,12 @@ def _key_response_post(self, request, body, bucket_name, query, key_name):
 {% endfor %}
 </DeleteResult>"""
 
+S3_DELETE_KEY_TAGGING_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
+<DeleteObjectTaggingResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
+<VersionId>{{version_id}}</VersionId>
+</DeleteObjectTaggingResult>
+"""
+
 S3_OBJECT_ACL_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
     <AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
       <Owner>


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
@@ -2424,9 +2424,13 @@ def test_boto3_put_object_with_tagging():
 
     s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="foo=bar")
 
-    resp = s3.get_object_tagging(Bucket=bucket_name, Key=key)
+    s3.get_object_tagging(Bucket=bucket_name, Key=key)["TagSet"].should.contain(
+        {"Key": "foo", "Value": "bar"}
+    )
+
+    s3.delete_object_tagging(Bucket=bucket_name, Key=key)
 
-    resp["TagSet"].should.contain({"Key": "foo", "Value": "bar"})
+    s3.get_object_tagging(Bucket=bucket_name, Key=key)["TagSet"].should.equal([])
 
 
 @mock_s3