Repository: localstack/localstack

Issue:
bug: max SQS batch size must be always 10 
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

I can delete messages with batch sizes greater than 10 without any errors.

The same applies to changing message visibility.

### Expected Behavior

SQS allows processing batches with a maximum size of 10 elements.

### How are you starting LocalStack?

With a `docker run` command

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

    docker run --rm -it -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

```python
import boto3

sqs = boto3.client("sqs", endpoint_url="http://localhost:4566")
queue_url = sqs.create_queue(QueueName="my-queue")["QueueUrl"]

sqs.send_message_batch(
    QueueUrl=queue_url,
    Entries=[{"Id": str(i), "MessageBody": "foo"} for i in range(10)],
)
sqs.send_message_batch(
    QueueUrl=queue_url,
    Entries=[{"Id": str(i), "MessageBody": "foo"} for i in range(10)],
)

messages = []
messages.extend(
    sqs.receive_message(
        QueueUrl=queue_url,
        AttributeNames=["All"],
        MaxNumberOfMessages=10,
    )["Messages"]
)
messages.extend(
    sqs.receive_message(
        QueueUrl=queue_url,
        AttributeNames=["All"],
        MaxNumberOfMessages=10,
    )["Messages"]
)

# This should raise an error because we're providing a batch with more than 10 elements
sqs.change_message_visibility_batch(
    QueueUrl=queue_url,
    Entries=[
        {
            "Id": str(i),
            "ReceiptHandle": msg["ReceiptHandle"],
            "VisibilityTimeout": 123,
        }
        for i, msg in enumerate(messages)
    ],
)

# This should raise an error because we're providing a batch of more than 10 elements
sqs.delete_message_batch(
    QueueUrl=queue_url,
    Entries=[
        {"Id": str(i), "ReceiptHandle": msg["ReceiptHandle"]}
        for i, msg in enumerate(messages)
    ],
)
```


### Environment

```markdown
- OS: Ubuntu 22.04
- LocalStack: latest
```


### Anything else?

_No response_


Discussion:

Resolving patch:
diff --git a/localstack/services/sqs/provider.py b/localstack/services/sqs/provider.py
--- a/localstack/services/sqs/provider.py
+++ b/localstack/services/sqs/provider.py
@@ -848,11 +848,6 @@ def send_message_batch(
     ) -> SendMessageBatchResult:
         queue = self._resolve_queue(context, queue_url=queue_url)
 
-        if entries and (no_entries := len(entries)) > 10:
-            raise TooManyEntriesInBatchRequest(
-                f"Maximum number of entries per request are 10. You have sent {no_entries}."
-            )
-
         self._assert_batch(entries)
         # check the total batch size first and raise BatchRequestTooLong id > DEFAULT_MAXIMUM_MESSAGE_SIZE.
         # This is checked before any messages in the batch are sent.  Raising the exception here should
@@ -1194,6 +1189,10 @@ def _validate_actions(self, actions: ActionNameList):
     def _assert_batch(self, batch: List) -> None:
         if not batch:
             raise EmptyBatchRequest
+        if batch and (no_entries := len(batch)) > MAX_NUMBER_OF_MESSAGES:
+            raise TooManyEntriesInBatchRequest(
+                f"Maximum number of entries per request are {MAX_NUMBER_OF_MESSAGES}. You have sent {no_entries}."
+            )
         visited = set()
         for entry in batch:
             entry_id = entry["Id"]


Test patch:
diff --git a/tests/integration/test_sqs.py b/tests/integration/test_sqs.py
--- a/tests/integration/test_sqs.py
+++ b/tests/integration/test_sqs.py
@@ -1951,6 +1951,110 @@ def test_delete_message_batch_invalid_msg_id(
             aws_client.sqs.delete_message_batch(QueueUrl=queue_url, Entries=delete_entries)
         snapshot.match("error_response", e.value.response)
 
+    @pytest.mark.aws_validated
+    def test_delete_message_batch_with_too_large_batch(
+        self, sqs_create_queue, snapshot, aws_client
+    ):
+        self._add_error_detail_transformer(snapshot)
+
+        queue_name = f"queue-{short_uid()}"
+        queue_url = sqs_create_queue(QueueName=queue_name)
+
+        message_batch = [
+            {
+                "Id": f"message-{i}",
+                "MessageBody": f"messageBody-{i}",
+            }
+            for i in range(MAX_NUMBER_OF_MESSAGES)
+        ]
+
+        result_send_batch = aws_client.sqs.send_message_batch(
+            QueueUrl=queue_url, Entries=message_batch
+        )
+        successful = result_send_batch["Successful"]
+        assert len(successful) == len(message_batch)
+
+        result_send_batch = aws_client.sqs.send_message_batch(
+            QueueUrl=queue_url, Entries=message_batch
+        )
+        successful = result_send_batch["Successful"]
+        assert len(successful) == len(message_batch)
+
+        result_recv = []
+        target_size = 2 * MAX_NUMBER_OF_MESSAGES
+
+        def _receive_all_messages():
+            result_recv.extend(
+                aws_client.sqs.receive_message(
+                    QueueUrl=queue_url,
+                    MaxNumberOfMessages=min(MAX_NUMBER_OF_MESSAGES, target_size - len(result_recv)),
+                )["Messages"]
+            )
+            assert len(result_recv) == target_size
+
+        retry(_receive_all_messages, retries=7, sleep=0.5)
+
+        delete_entries = [
+            {"Id": str(i), "ReceiptHandle": msg["ReceiptHandle"]}
+            for i, msg in enumerate(result_recv)
+        ]
+        with pytest.raises(ClientError) as e:
+            aws_client.sqs.delete_message_batch(QueueUrl=queue_url, Entries=delete_entries)
+        snapshot.match("error_response", e.value.response)
+
+    @pytest.mark.aws_validated
+    def test_change_message_visibility_batch_with_too_large_batch(
+        self, sqs_create_queue, snapshot, aws_client
+    ):
+        self._add_error_detail_transformer(snapshot)
+
+        queue_name = f"queue-{short_uid()}"
+        queue_url = sqs_create_queue(QueueName=queue_name)
+
+        message_batch = [
+            {
+                "Id": f"message-{i}",
+                "MessageBody": f"messageBody-{i}",
+            }
+            for i in range(MAX_NUMBER_OF_MESSAGES)
+        ]
+
+        result_send_batch = aws_client.sqs.send_message_batch(
+            QueueUrl=queue_url, Entries=message_batch
+        )
+        successful = result_send_batch["Successful"]
+        assert len(successful) == len(message_batch)
+
+        result_send_batch = aws_client.sqs.send_message_batch(
+            QueueUrl=queue_url, Entries=message_batch
+        )
+        successful = result_send_batch["Successful"]
+        assert len(successful) == len(message_batch)
+
+        result_recv = []
+        target_size = 2 * MAX_NUMBER_OF_MESSAGES
+
+        def _receive_all_messages():
+            result_recv.extend(
+                aws_client.sqs.receive_message(
+                    QueueUrl=queue_url,
+                    MaxNumberOfMessages=min(MAX_NUMBER_OF_MESSAGES, target_size - len(result_recv)),
+                )["Messages"]
+            )
+            assert len(result_recv) == target_size
+
+        retry(_receive_all_messages, retries=7, sleep=0.5)
+
+        change_visibility_entries = [
+            {"Id": str(i), "ReceiptHandle": msg["ReceiptHandle"], "VisibilityTimeout": 123}
+            for i, msg in enumerate(result_recv)
+        ]
+        with pytest.raises(ClientError) as e:
+            aws_client.sqs.change_message_visibility_batch(
+                QueueUrl=queue_url, Entries=change_visibility_entries
+            )
+        snapshot.match("error_response", e.value.response)
+
     @pytest.mark.aws_validated
     def test_create_and_send_to_fifo_queue(self, sqs_create_queue, aws_client):
         # Old name: test_create_fifo_queue
diff --git a/tests/integration/test_sqs.snapshot.json b/tests/integration/test_sqs.snapshot.json
--- a/tests/integration/test_sqs.snapshot.json
+++ b/tests/integration/test_sqs.snapshot.json
@@ -1012,5 +1012,37 @@
         }
       }
     }
+  },
+  "tests/integration/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch": {
+    "recorded-date": "24-06-2023, 17:14:03",
+    "recorded-content": {
+      "error_response": {
+        "Error": {
+          "Code": "AWS.SimpleQueueService.TooManyEntriesInBatchRequest",
+          "Message": "Maximum number of entries per request are 10. You have sent 20.",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
+  },
+  "tests/integration/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch": {
+    "recorded-date": "24-06-2023, 17:14:36",
+    "recorded-content": {
+      "error_response": {
+        "Error": {
+          "Code": "AWS.SimpleQueueService.TooManyEntriesInBatchRequest",
+          "Message": "Maximum number of entries per request are 10. You have sent 20.",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 400
+        }
+      }
+    }
   }
 }