Repository: getmoto/moto

Issue:
bucket.delete_keys not supported
The response for this just wasn't defined.  PR about to be submitted.



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
@@ -114,6 +114,9 @@ def _bucket_response_delete(self, bucket_name, headers):
             return 409, headers, template.render(bucket=removed_bucket)
 
     def _bucket_response_post(self, request, bucket_name, headers):
+        if request.path == u'/?delete':
+            return self._bucket_response_delete_keys(request, bucket_name, headers)
+
         #POST to bucket-url should create file from form
         if hasattr(request, 'form'):
             #Not HTTPretty
@@ -144,6 +147,23 @@ def _bucket_response_post(self, request, bucket_name, headers):
                 new_key.set_metadata(meta_key, metadata)
         return 200, headers, ""
 
+    def _bucket_response_delete_keys(self, request, bucket_name, headers):
+        template = Template(S3_DELETE_KEYS_RESPONSE)
+
+        keys = minidom.parseString(request.body).getElementsByTagName('Key')
+        deleted_names = []
+        error_names = []
+
+        for k in keys:
+            try:
+                key_name = k.firstChild.nodeValue
+                self.backend.delete_key(bucket_name, key_name)
+                deleted_names.append(key_name)
+            except KeyError as e:
+                error_names.append(key_name)
+
+        return 200, headers, template.render(deleted=deleted_names,delete_errors=error_names)
+
     def key_response(self, request, full_url, headers):
         response = self._key_response(request, full_url, headers)
         if isinstance(response, basestring):
@@ -391,6 +411,20 @@ def _key_response_post(self, body, parsed_url, bucket_name, query, key_name, hea
 <HostId>sdfgdsfgdsfgdfsdsfgdfs</HostId>
 </Error>"""
 
+S3_DELETE_KEYS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
+<DeleteResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
+{% for k in deleted %}
+<Deleted>
+<Key>{{k}}</Key>
+</Deleted>
+{% endfor %}
+{% for k in delete_errors %}
+<Error>
+<Key>{{k}}</Key>
+</Error>
+{% endfor %}
+</DeleteResult>"""
+
 S3_DELETE_OBJECT_SUCCESS = """<DeleteObjectResponse xmlns="http://s3.amazonaws.com/doc/2006-03-01">
   <DeleteObjectResponse>
     <Code>200</Code>


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
@@ -335,6 +335,41 @@ def test_post_with_metadata_to_bucket():
 
     bucket.get_key('the-key').get_metadata('test').should.equal('metadata')
 
+@mock_s3
+def test_delete_keys():
+    conn = boto.connect_s3('the_key', 'the_secret')
+    bucket = conn.create_bucket('foobar')
+
+    Key(bucket=bucket, name='file1').set_contents_from_string('abc')
+    Key(bucket=bucket, name='file2').set_contents_from_string('abc')
+    Key(bucket=bucket, name='file3').set_contents_from_string('abc')
+    Key(bucket=bucket, name='file4').set_contents_from_string('abc')
+
+    result = bucket.delete_keys(['file2', 'file3'])
+    
+    result.deleted.should.have.length_of(2)
+    result.errors.should.have.length_of(0)
+    keys = bucket.get_all_keys()
+    keys.should.have.length_of(2)
+    keys[0].name.should.equal('file1')
+
+@mock_s3
+def test_delete_keys_with_invalid():
+    conn = boto.connect_s3('the_key', 'the_secret')
+    bucket = conn.create_bucket('foobar')
+
+    Key(bucket=bucket, name='file1').set_contents_from_string('abc')
+    Key(bucket=bucket, name='file2').set_contents_from_string('abc')
+    Key(bucket=bucket, name='file3').set_contents_from_string('abc')
+    Key(bucket=bucket, name='file4').set_contents_from_string('abc')
+
+    result = bucket.delete_keys(['abc', 'file3'])
+
+    result.deleted.should.have.length_of(1)
+    result.errors.should.have.length_of(1)
+    keys = bucket.get_all_keys()
+    keys.should.have.length_of(3)
+    keys[0].name.should.equal('file1')
 
 @mock_s3
 def test_bucket_method_not_implemented():