Repository: getmoto/moto

Issue:
delete_message_batch doesn't handle duplication correctly (incorrect exception raised)
Moto version: 2.0.8
boto3 version: 1.17.88
botocore version: 1.20.88

```python
@mock_sqs
def test_duplicate_bug() -> None:
    client = boto3.client("sqs")
    queue_url = client.create_queue(QueueName="test_inbound_queue")["QueueUrl"]
    client.send_message(QueueUrl=queue_url, MessageBody="coucou")

    messages = client.receive_message(QueueUrl=queue_url, WaitTimeSeconds=0)["Messages"]
    assert messages, "at least one msg"
    entries = [
        {"Id": msg["MessageId"], "ReceiptHandle": msg["ReceiptHandle"]}
        for msg in [messages[0], messages[0]]
    ]

    # with moto, this raises botocore.errorfactory.ReceiptHandleIsInvalid
    # however, running this code against a real AWS queue would raise
    # botocore.errorfactory.BatchEntryIdsNotDistinct
    client.delete_message_batch(QueueUrl=queue_url, Entries=entries)
```



Discussion:

Resolving patch:
diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py
--- a/moto/sqs/responses.py
+++ b/moto/sqs/responses.py
@@ -16,6 +16,7 @@
     InvalidAttributeName,
     MessageNotInflight,
     ReceiptHandleIsInvalid,
+    BatchEntryIdsNotDistinct,
 )
 from .models import sqs_backends
 from .utils import parse_message_attributes, extract_input_message_attributes
@@ -333,7 +334,8 @@ def delete_message_batch(self):
         """
         queue_name = self._get_queue_name()
 
-        message_ids = []
+        receipts = []
+
         for index in range(1, 11):
             # Loop through looking for messages
             receipt_key = "DeleteMessageBatchRequestEntry.{0}.ReceiptHandle".format(
@@ -344,12 +346,25 @@ def delete_message_batch(self):
                 # Found all messages
                 break
 
-            self.sqs_backend.delete_message(queue_name, receipt_handle[0])
-
             message_user_id_key = "DeleteMessageBatchRequestEntry.{0}.Id".format(index)
             message_user_id = self.querystring.get(message_user_id_key)[0]
-            message_ids.append(message_user_id)
+            receipts.append(
+                {"receipt_handle": receipt_handle[0], "msg_user_id": message_user_id}
+            )
+
+        receipt_seen = set()
+        for receipt_and_id in receipts:
+            receipt = receipt_and_id["receipt_handle"]
+            if receipt in receipt_seen:
+                raise BatchEntryIdsNotDistinct(receipt_and_id["msg_user_id"])
+            receipt_seen.add(receipt)
+
+        for receipt_and_id in receipts:
+            self.sqs_backend.delete_message(
+                queue_name, receipt_and_id["receipt_handle"]
+            )
 
+        message_ids = [r["msg_user_id"] for r in receipts]
         template = self.response_template(DELETE_MESSAGE_BATCH_RESPONSE)
         return template.render(message_ids=message_ids)
 


Test patch:
diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py
--- a/tests/test_sqs/test_sqs.py
+++ b/tests/test_sqs/test_sqs.py
@@ -1714,6 +1714,34 @@ def test_send_message_batch():
     )
 
 
+@mock_sqs
+def test_delete_message_batch_with_duplicates():
+    client = boto3.client("sqs", region_name="us-east-1")
+    response = client.create_queue(QueueName="test-queue")
+    queue_url = response["QueueUrl"]
+    client.send_message(QueueUrl=queue_url, MessageBody="coucou")
+
+    messages = client.receive_message(
+        QueueUrl=queue_url, WaitTimeSeconds=0, VisibilityTimeout=0
+    )["Messages"]
+    assert messages, "at least one msg"
+    entries = [
+        {"Id": msg["MessageId"], "ReceiptHandle": msg["ReceiptHandle"]}
+        for msg in [messages[0], messages[0]]
+    ]
+
+    with pytest.raises(ClientError) as e:
+        client.delete_message_batch(QueueUrl=queue_url, Entries=entries)
+    ex = e.value
+    assert ex.response["Error"]["Code"] == "BatchEntryIdsNotDistinct"
+
+    # no messages are deleted
+    messages = client.receive_message(QueueUrl=queue_url, WaitTimeSeconds=0).get(
+        "Messages", []
+    )
+    assert messages, "message still in the queue"
+
+
 @mock_sqs
 def test_message_attributes_in_receive_message():
     sqs = boto3.resource("sqs", region_name="us-east-1")