Repository: getmoto/moto

Issue:
Asserts in LogResponse prevent boto3 ClientError when testing
I have some code that fetches CloudWatch logs and in my tests I used moto to mock CloudWatch. I added a test that exceeded the 10,000 max events and instead of getting a `ClientError` from botocore, I got an assertion error from moto. This was not expected. I see in `LogResponse` [`get_log_events`](https://github.com/spulec/moto/blob/9eb58eea411214ded617ea45ce9a4407a8f95194/moto/logs/responses.py#L96) the statement `assert limit <= 10000`. There are a few in this class. Is this by design?

`boto3==1.16.28`
`moto==1.3.16`
`python 3.6.10`



Discussion:
Looks like a dirty workaround, @cciotti. Thanks for raising it - marking it as an enhancement to implement proper error handling.

Resolving patch:
diff --git a/moto/logs/exceptions.py b/moto/logs/exceptions.py
--- a/moto/logs/exceptions.py
+++ b/moto/logs/exceptions.py
@@ -15,8 +15,12 @@ def __init__(self, msg=None):
 
 
 class InvalidParameterException(LogsClientError):
-    def __init__(self, msg=None):
+    def __init__(self, msg=None, constraint=None, parameter=None, value=None):
         self.code = 400
+        if constraint:
+            msg = "1 validation error detected: Value '{}' at '{}' failed to satisfy constraint: {}".format(
+                value, parameter, constraint
+            )
         super().__init__(
             "InvalidParameterException", msg or "A parameter is specified incorrectly."
         )
diff --git a/moto/logs/models.py b/moto/logs/models.py
--- a/moto/logs/models.py
+++ b/moto/logs/models.py
@@ -153,6 +153,9 @@ def get_log_events(
         next_token,
         start_from_head,
     ):
+        if limit is None:
+            limit = 10000
+
         def filter_func(event):
             if start_time and event.timestamp < start_time:
                 return False
@@ -382,6 +385,8 @@ def filter_log_events(
         filter_pattern,
         interleaved,
     ):
+        if not limit:
+            limit = 10000
         streams = [
             stream
             for name, stream in self.streams.items()
@@ -531,6 +536,12 @@ def reset(self):
     def create_log_group(self, log_group_name, tags, **kwargs):
         if log_group_name in self.groups:
             raise ResourceAlreadyExistsException()
+        if len(log_group_name) > 512:
+            raise InvalidParameterException(
+                constraint="Member must have length less than or equal to 512",
+                parameter="logGroupName",
+                value=log_group_name,
+            )
         self.groups[log_group_name] = LogGroup(
             self.region_name, log_group_name, tags, **kwargs
         )
@@ -547,6 +558,12 @@ def delete_log_group(self, log_group_name):
         del self.groups[log_group_name]
 
     def describe_log_groups(self, limit, log_group_name_prefix, next_token):
+        if limit > 50:
+            raise InvalidParameterException(
+                constraint="Member must have value less than or equal to 50",
+                parameter="limit",
+                value=limit,
+            )
         if log_group_name_prefix is None:
             log_group_name_prefix = ""
 
@@ -608,6 +625,22 @@ def describe_log_streams(
     ):
         if log_group_name not in self.groups:
             raise ResourceNotFoundException()
+        if limit > 50:
+            raise InvalidParameterException(
+                constraint="Member must have value less than or equal to 50",
+                parameter="limit",
+                value=limit,
+            )
+        if order_by not in ["LogStreamName", "LastEventTime"]:
+            raise InvalidParameterException(
+                constraint="Member must satisfy enum value set: [LogStreamName, LastEventTime]",
+                parameter="orderBy",
+                value=order_by,
+            )
+        if order_by == "LastEventTime" and log_stream_name_prefix:
+            raise InvalidParameterException(
+                msg="Cannot order by LastEventTime with a logStreamNamePrefix."
+            )
         log_group = self.groups[log_group_name]
         return log_group.describe_log_streams(
             descending,
@@ -641,6 +674,12 @@ def get_log_events(
     ):
         if log_group_name not in self.groups:
             raise ResourceNotFoundException()
+        if limit and limit > 1000:
+            raise InvalidParameterException(
+                constraint="Member must have value less than or equal to 10000",
+                parameter="limit",
+                value=limit,
+            )
         log_group = self.groups[log_group_name]
         return log_group.get_log_events(
             log_group_name,
@@ -665,6 +704,12 @@ def filter_log_events(
     ):
         if log_group_name not in self.groups:
             raise ResourceNotFoundException()
+        if limit and limit > 1000:
+            raise InvalidParameterException(
+                constraint="Member must have value less than or equal to 10000",
+                parameter="limit",
+                value=limit,
+            )
         log_group = self.groups[log_group_name]
         return log_group.filter_log_events(
             log_group_name,
diff --git a/moto/logs/responses.py b/moto/logs/responses.py
--- a/moto/logs/responses.py
+++ b/moto/logs/responses.py
@@ -27,7 +27,6 @@ def create_log_group(self):
         log_group_name = self._get_param("logGroupName")
         tags = self._get_param("tags")
         kms_key_id = self._get_param("kmsKeyId")
-        assert 1 <= len(log_group_name) <= 512  # TODO: assert pattern
 
         self.logs_backend.create_log_group(log_group_name, tags, kmsKeyId=kms_key_id)
         return ""
@@ -41,7 +40,6 @@ def describe_log_groups(self):
         log_group_name_prefix = self._get_param("logGroupNamePrefix")
         next_token = self._get_param("nextToken")
         limit = self._get_param("limit", 50)
-        assert limit <= 50
         groups, next_token = self.logs_backend.describe_log_groups(
             limit, log_group_name_prefix, next_token
         )
@@ -67,13 +65,8 @@ def describe_log_streams(self):
         log_stream_name_prefix = self._get_param("logStreamNamePrefix", "")
         descending = self._get_param("descending", False)
         limit = self._get_param("limit", 50)
-        assert limit <= 50
         next_token = self._get_param("nextToken")
         order_by = self._get_param("orderBy", "LogStreamName")
-        assert order_by in {"LogStreamName", "LastEventTime"}
-
-        if order_by == "LastEventTime":
-            assert not log_stream_name_prefix
 
         streams, next_token = self.logs_backend.describe_log_streams(
             descending,
@@ -101,8 +94,7 @@ def get_log_events(self):
         log_stream_name = self._get_param("logStreamName")
         start_time = self._get_param("startTime")
         end_time = self._get_param("endTime")
-        limit = self._get_param("limit", 10000)
-        assert limit <= 10000
+        limit = self._get_param("limit")
         next_token = self._get_param("nextToken")
         start_from_head = self._get_param("startFromHead", False)
 
@@ -135,8 +127,7 @@ def filter_log_events(self):
         filter_pattern = self._get_param("filterPattern")
         interleaved = self._get_param("interleaved", False)
         end_time = self._get_param("endTime")
-        limit = self._get_param("limit", 10000)
-        assert limit <= 10000
+        limit = self._get_param("limit")
         next_token = self._get_param("nextToken")
 
         events, next_token, searched_streams = self.logs_backend.filter_log_events(


Test patch:
diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py
--- a/tests/test_logs/test_logs.py
+++ b/tests/test_logs/test_logs.py
@@ -1,6 +1,7 @@
 import json
 import os
 import time
+import sure  # noqa
 from unittest import SkipTest
 
 import boto3
@@ -806,3 +807,143 @@ def test_start_query():
     exc_value.response["Error"]["Message"].should.equal(
         "The specified log group does not exist"
     )
+
+
+@pytest.mark.parametrize("nr_of_events", [10001, 1000000])
+@mock_logs
+def test_get_too_many_log_events(nr_of_events):
+    client = boto3.client("logs", "us-east-1")
+    log_group_name = "dummy"
+    log_stream_name = "stream"
+    client.create_log_group(logGroupName=log_group_name)
+    client.create_log_stream(logGroupName=log_group_name, logStreamName=log_stream_name)
+
+    with pytest.raises(ClientError) as ex:
+        client.get_log_events(
+            logGroupName=log_group_name,
+            logStreamName=log_stream_name,
+            limit=nr_of_events,
+        )
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.contain("1 validation error detected")
+    err["Message"].should.contain(
+        "Value '{}' at 'limit' failed to satisfy constraint".format(nr_of_events)
+    )
+    err["Message"].should.contain("Member must have value less than or equal to 10000")
+
+
+@pytest.mark.parametrize("nr_of_events", [10001, 1000000])
+@mock_logs
+def test_filter_too_many_log_events(nr_of_events):
+    client = boto3.client("logs", "us-east-1")
+    log_group_name = "dummy"
+    log_stream_name = "stream"
+    client.create_log_group(logGroupName=log_group_name)
+    client.create_log_stream(logGroupName=log_group_name, logStreamName=log_stream_name)
+
+    with pytest.raises(ClientError) as ex:
+        client.filter_log_events(
+            logGroupName=log_group_name,
+            logStreamNames=[log_stream_name],
+            limit=nr_of_events,
+        )
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.contain("1 validation error detected")
+    err["Message"].should.contain(
+        "Value '{}' at 'limit' failed to satisfy constraint".format(nr_of_events)
+    )
+    err["Message"].should.contain("Member must have value less than or equal to 10000")
+
+
+@pytest.mark.parametrize("nr_of_groups", [51, 100])
+@mock_logs
+def test_describe_too_many_log_groups(nr_of_groups):
+    client = boto3.client("logs", "us-east-1")
+    with pytest.raises(ClientError) as ex:
+        client.describe_log_groups(limit=nr_of_groups)
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.contain("1 validation error detected")
+    err["Message"].should.contain(
+        "Value '{}' at 'limit' failed to satisfy constraint".format(nr_of_groups)
+    )
+    err["Message"].should.contain("Member must have value less than or equal to 50")
+
+
+@pytest.mark.parametrize("nr_of_streams", [51, 100])
+@mock_logs
+def test_describe_too_many_log_streams(nr_of_streams):
+    client = boto3.client("logs", "us-east-1")
+    log_group_name = "dummy"
+    client.create_log_group(logGroupName=log_group_name)
+    with pytest.raises(ClientError) as ex:
+        client.describe_log_streams(logGroupName=log_group_name, limit=nr_of_streams)
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.contain("1 validation error detected")
+    err["Message"].should.contain(
+        "Value '{}' at 'limit' failed to satisfy constraint".format(nr_of_streams)
+    )
+    err["Message"].should.contain("Member must have value less than or equal to 50")
+
+
+@pytest.mark.parametrize("length", [513, 1000])
+@mock_logs
+def test_create_log_group_invalid_name_length(length):
+    log_group_name = "a" * length
+    client = boto3.client("logs", "us-east-1")
+    with pytest.raises(ClientError) as ex:
+        client.create_log_group(logGroupName=log_group_name)
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.contain("1 validation error detected")
+    err["Message"].should.contain(
+        "Value '{}' at 'logGroupName' failed to satisfy constraint".format(
+            log_group_name
+        )
+    )
+    err["Message"].should.contain("Member must have length less than or equal to 512")
+
+
+@pytest.mark.parametrize("invalid_orderby", ["", "sth", "LogStreamname"])
+@mock_logs
+def test_describe_log_streams_invalid_order_by(invalid_orderby):
+    client = boto3.client("logs", "us-east-1")
+    log_group_name = "dummy"
+    client.create_log_group(logGroupName=log_group_name)
+    with pytest.raises(ClientError) as ex:
+        client.describe_log_streams(
+            logGroupName=log_group_name, orderBy=invalid_orderby
+        )
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.contain("1 validation error detected")
+    err["Message"].should.contain(
+        "Value '{}' at 'orderBy' failed to satisfy constraint".format(invalid_orderby)
+    )
+    err["Message"].should.contain(
+        "Member must satisfy enum value set: [LogStreamName, LastEventTime]"
+    )
+
+
+@mock_logs
+def test_describe_log_streams_no_prefix():
+    """
+    From the docs: If orderBy is LastEventTime , you cannot specify [logStreamNamePrefix]
+    """
+    client = boto3.client("logs", "us-east-1")
+    log_group_name = "dummy"
+    client.create_log_group(logGroupName=log_group_name)
+    with pytest.raises(ClientError) as ex:
+        client.describe_log_streams(
+            logGroupName=log_group_name,
+            orderBy="LastEventTime",
+            logStreamNamePrefix="sth",
+        )
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.equal(
+        "Cannot order by LastEventTime with a logStreamNamePrefix."
+    )