Repository: getmoto/moto

Issue:
s3 delete_objects incorrectly raises errors for non-existing keys
When asking S3 to delete a series of keys in a bucket (via delete_objects) no errors are raised if the keys do not exist. Sample response from s3:

`{u'Deleted': [{u'Key': 'key1'}, {u'Key': 'key2'}, {u'Key': 'key3'}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'RetryAttempts': 0, 'HostId': '<omitted>', 'RequestId': '<omitted>', 'HTTPHeaders': {'x-amz-id-2': '<omitted>', 'server': 'AmazonS3', 'transfer-encoding': 'chunked', 'connection': 'close', 'x-amz-request-id': '<omitted>', 'date': 'Thu, 19 Apr 2018 08:48:56 GMT', 'content-type': 'application/xml'}}}`

However, when using moto, attempts to delete non-existent keys are incorrectly raised as errors:

`{u'Deleted': [{u'Key': 'key1'}], u'Errors': [{u'Key': 'key2'}, {u'Key': 'key3'}], 'ResponseMetadata': {'RetryAttempts': 0, 'HTTPStatusCode': 200, 'HTTPHeaders': {u'Content-Type': u'text/plain'}}}`

In the above examples, key1 exists, but key2 and key3 do not. S3 says all three are deleted, moto says key2 and key3 errored (but does not say what the errors are).


Discussion:

Resolving patch:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -943,13 +943,10 @@ def _bucket_response_delete_keys(self, request, body, bucket_name):
             key_name = object_["Key"]
             version_id = object_.get("VersionId", None)
 
-            success, _ = self.backend.delete_object(
+            self.backend.delete_object(
                 bucket_name, undo_clean_key_name(key_name), version_id=version_id
             )
-            if success:
-                deleted_objects.append((key_name, version_id))
-            else:
-                error_names.append(key_name)
+            deleted_objects.append((key_name, version_id))
 
         return (
             200,


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
@@ -689,8 +689,7 @@ def test_delete_keys_invalid():
     # non-existing key case
     result = bucket.delete_keys(["abc", "file3"])
 
-    result.deleted.should.have.length_of(1)
-    result.errors.should.have.length_of(1)
+    result.deleted.should.have.length_of(2)
     keys = bucket.get_all_keys()
     keys.should.have.length_of(3)
     keys[0].name.should.equal("file1")
@@ -4215,6 +4214,22 @@ def assert_deleted():
     assert_deleted()
 
 
+@mock_s3
+def test_delete_objects_unknown_key():
+    bucket_name = "test-moto-issue-1581"
+    client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
+    client.create_bucket(Bucket=bucket_name)
+    client.put_object(Bucket=bucket_name, Key="file1", Body="body")
+
+    s = client.delete_objects(
+        Bucket=bucket_name, Delete={"Objects": [{"Key": "file1"}, {"Key": "file2"}]}
+    )
+    s["Deleted"].should.have.length_of(2)
+    s["Deleted"].should.contain({"Key": "file1"})
+    s["Deleted"].should.contain({"Key": "file2"})
+    client.delete_bucket(Bucket=bucket_name)
+
+
 @mock_s3
 @mock_config
 def test_public_access_block():
diff --git a/tests/test_s3bucket_path/test_s3bucket_path.py b/tests/test_s3bucket_path/test_s3bucket_path.py
--- a/tests/test_s3bucket_path/test_s3bucket_path.py
+++ b/tests/test_s3bucket_path/test_s3bucket_path.py
@@ -315,8 +315,8 @@ def test_delete_keys_with_invalid():
 
     result = bucket.delete_keys(["abc", "file3"])
 
-    result.deleted.should.have.length_of(1)
-    result.errors.should.have.length_of(1)
+    result.deleted.should.have.length_of(2)
+    result.errors.should.have.length_of(0)
     keys = bucket.get_all_keys()
     keys.should.have.length_of(3)
     keys[0].name.should.equal("file1")