Add validation to put_events (#3579)
* Add validation to put_events * Add extra test for put_events * Fix Python 2.7 error
This commit is contained in:
parent
f749f583ee
commit
17d94f9e09
18
moto/events/exceptions.py
Normal file
18
moto/events/exceptions.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from moto.core.exceptions import JsonRESTError
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceNotFoundException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super(ResourceNotFoundException, self).__init__(
|
||||||
|
"ResourceNotFoundException", message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super(ValidationException, self).__init__("ValidationException", message)
|
@ -5,6 +5,7 @@ from boto3 import Session
|
|||||||
|
|
||||||
from moto.core.exceptions import JsonRESTError
|
from moto.core.exceptions import JsonRESTError
|
||||||
from moto.core import ACCOUNT_ID, BaseBackend, CloudFormationModel
|
from moto.core import ACCOUNT_ID, BaseBackend, CloudFormationModel
|
||||||
|
from moto.events.exceptions import ValidationException, ResourceNotFoundException
|
||||||
from moto.utilities.tagging_service import TaggingService
|
from moto.utilities.tagging_service import TaggingService
|
||||||
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -359,10 +360,52 @@ class EventsBackend(BaseBackend):
|
|||||||
if num_events < 1:
|
if num_events < 1:
|
||||||
raise JsonRESTError("ValidationError", "Need at least 1 event")
|
raise JsonRESTError("ValidationError", "Need at least 1 event")
|
||||||
elif num_events > 10:
|
elif num_events > 10:
|
||||||
raise JsonRESTError("ValidationError", "Can only submit 10 events at once")
|
# the exact error text is longer, the Value list consists of all the put events
|
||||||
|
raise ValidationException(
|
||||||
|
"1 validation error detected: "
|
||||||
|
"Value '[PutEventsRequestEntry]' at 'entries' failed to satisfy constraint: "
|
||||||
|
"Member must have length less than or equal to 10"
|
||||||
|
)
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for event in events:
|
||||||
|
if "Source" not in event:
|
||||||
|
entries.append(
|
||||||
|
{
|
||||||
|
"ErrorCode": "InvalidArgument",
|
||||||
|
"ErrorMessage": "Parameter Source is not valid. Reason: Source is a required argument.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif "DetailType" not in event:
|
||||||
|
entries.append(
|
||||||
|
{
|
||||||
|
"ErrorCode": "InvalidArgument",
|
||||||
|
"ErrorMessage": "Parameter DetailType is not valid. Reason: DetailType is a required argument.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif "Detail" not in event:
|
||||||
|
entries.append(
|
||||||
|
{
|
||||||
|
"ErrorCode": "InvalidArgument",
|
||||||
|
"ErrorMessage": "Parameter Detail is not valid. Reason: Detail is a required argument.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
json.loads(event["Detail"])
|
||||||
|
except ValueError: # json.JSONDecodeError exists since Python 3.5
|
||||||
|
entries.append(
|
||||||
|
{
|
||||||
|
"ErrorCode": "MalformedDetail",
|
||||||
|
"ErrorMessage": "Detail is malformed.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
entries.append({"EventId": str(uuid4())})
|
||||||
|
|
||||||
# We dont really need to store the events yet
|
# We dont really need to store the events yet
|
||||||
return [{"EventId": str(uuid4())} for _ in events]
|
return entries
|
||||||
|
|
||||||
def remove_targets(self, name, ids):
|
def remove_targets(self, name, ids):
|
||||||
rule = self.rules.get(name)
|
rule = self.rules.get(name)
|
||||||
@ -479,8 +522,8 @@ class EventsBackend(BaseBackend):
|
|||||||
name = arn.split("/")[-1]
|
name = arn.split("/")[-1]
|
||||||
if name in self.rules:
|
if name in self.rules:
|
||||||
return self.tagger.list_tags_for_resource(self.rules[name].arn)
|
return self.tagger.list_tags_for_resource(self.rules[name].arn)
|
||||||
raise JsonRESTError(
|
raise ResourceNotFoundException(
|
||||||
"ResourceNotFoundException", "An entity that you specified does not exist."
|
"Rule {0} does not exist on EventBus default.".format(name)
|
||||||
)
|
)
|
||||||
|
|
||||||
def tag_resource(self, arn, tags):
|
def tag_resource(self, arn, tags):
|
||||||
@ -488,8 +531,8 @@ class EventsBackend(BaseBackend):
|
|||||||
if name in self.rules:
|
if name in self.rules:
|
||||||
self.tagger.tag_resource(self.rules[name].arn, tags)
|
self.tagger.tag_resource(self.rules[name].arn, tags)
|
||||||
return {}
|
return {}
|
||||||
raise JsonRESTError(
|
raise ResourceNotFoundException(
|
||||||
"ResourceNotFoundException", "An entity that you specified does not exist."
|
"Rule {0} does not exist on EventBus default.".format(name)
|
||||||
)
|
)
|
||||||
|
|
||||||
def untag_resource(self, arn, tag_names):
|
def untag_resource(self, arn, tag_names):
|
||||||
@ -497,8 +540,8 @@ class EventsBackend(BaseBackend):
|
|||||||
if name in self.rules:
|
if name in self.rules:
|
||||||
self.tagger.untag_resource_using_names(self.rules[name].arn, tag_names)
|
self.tagger.untag_resource_using_names(self.rules[name].arn, tag_names)
|
||||||
return {}
|
return {}
|
||||||
raise JsonRESTError(
|
raise ResourceNotFoundException(
|
||||||
"ResourceNotFoundException", "An entity that you specified does not exist."
|
"Rule {0} does not exist on EventBus default.".format(name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,9 +9,7 @@ from botocore.exceptions import ClientError
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from moto.core import ACCOUNT_ID
|
from moto.core import ACCOUNT_ID
|
||||||
from moto.core.exceptions import JsonRESTError
|
|
||||||
from moto.events import mock_events
|
from moto.events import mock_events
|
||||||
from moto.events.models import EventsBackend
|
|
||||||
|
|
||||||
RULES = [
|
RULES = [
|
||||||
{"Name": "test1", "ScheduleExpression": "rate(5 minutes)"},
|
{"Name": "test1", "ScheduleExpression": "rate(5 minutes)"},
|
||||||
@ -333,8 +331,136 @@ def test_put_events():
|
|||||||
response["FailedEntryCount"].should.equal(0)
|
response["FailedEntryCount"].should.equal(0)
|
||||||
response["Entries"].should.have.length_of(1)
|
response["Entries"].should.have.length_of(1)
|
||||||
|
|
||||||
with pytest.raises(ClientError):
|
|
||||||
client.put_events(Entries=[event] * 20)
|
@mock_events
|
||||||
|
def test_put_events_error_too_many_entries():
|
||||||
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
with pytest.raises(ClientError) as e:
|
||||||
|
client.put_events(
|
||||||
|
Entries=[
|
||||||
|
{
|
||||||
|
"Source": "source",
|
||||||
|
"DetailType": "type",
|
||||||
|
"Detail": '{ "key1": "value1" }',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
* 11
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.value
|
||||||
|
ex.operation_name.should.equal("PutEvents")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("ValidationException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"1 validation error detected: "
|
||||||
|
"Value '[PutEventsRequestEntry]' at 'entries' failed to satisfy constraint: "
|
||||||
|
"Member must have length less than or equal to 10"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_events
|
||||||
|
def test_put_events_error_missing_argument_source():
|
||||||
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.put_events(Entries=[{}])
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["FailedEntryCount"].should.equal(1)
|
||||||
|
response["Entries"].should.have.length_of(1)
|
||||||
|
response["Entries"][0].should.equal(
|
||||||
|
{
|
||||||
|
"ErrorCode": "InvalidArgument",
|
||||||
|
"ErrorMessage": "Parameter Source is not valid. Reason: Source is a required argument.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_events
|
||||||
|
def test_put_events_error_missing_argument_detail_type():
|
||||||
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.put_events(Entries=[{"Source": "source"}])
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["FailedEntryCount"].should.equal(1)
|
||||||
|
response["Entries"].should.have.length_of(1)
|
||||||
|
response["Entries"][0].should.equal(
|
||||||
|
{
|
||||||
|
"ErrorCode": "InvalidArgument",
|
||||||
|
"ErrorMessage": "Parameter DetailType is not valid. Reason: DetailType is a required argument.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_events
|
||||||
|
def test_put_events_error_missing_argument_detail():
|
||||||
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.put_events(Entries=[{"DetailType": "type", "Source": "source"}])
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["FailedEntryCount"].should.equal(1)
|
||||||
|
response["Entries"].should.have.length_of(1)
|
||||||
|
response["Entries"][0].should.equal(
|
||||||
|
{
|
||||||
|
"ErrorCode": "InvalidArgument",
|
||||||
|
"ErrorMessage": "Parameter Detail is not valid. Reason: Detail is a required argument.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_events
|
||||||
|
def test_put_events_error_invalid_json_detail():
|
||||||
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.put_events(
|
||||||
|
Entries=[{"Detail": "detail", "DetailType": "type", "Source": "source"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["FailedEntryCount"].should.equal(1)
|
||||||
|
response["Entries"].should.have.length_of(1)
|
||||||
|
response["Entries"][0].should.equal(
|
||||||
|
{"ErrorCode": "MalformedDetail", "ErrorMessage": "Detail is malformed."}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_events
|
||||||
|
def test_put_events_with_mixed_entries():
|
||||||
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.put_events(
|
||||||
|
Entries=[
|
||||||
|
{"Source": "source"},
|
||||||
|
{"Detail": '{"key": "value"}', "DetailType": "type", "Source": "source"},
|
||||||
|
{"Detail": "detail", "DetailType": "type", "Source": "source"},
|
||||||
|
{"Detail": '{"key2": "value2"}', "DetailType": "type", "Source": "source"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["FailedEntryCount"].should.equal(2)
|
||||||
|
response["Entries"].should.have.length_of(4)
|
||||||
|
[
|
||||||
|
entry for entry in response["Entries"] if "EventId" in entry
|
||||||
|
].should.have.length_of(2)
|
||||||
|
[
|
||||||
|
entry for entry in response["Entries"] if "ErrorCode" in entry
|
||||||
|
].should.have.length_of(2)
|
||||||
|
|
||||||
|
|
||||||
@mock_events
|
@mock_events
|
||||||
@ -554,23 +680,71 @@ def test_rule_tagging_happy():
|
|||||||
|
|
||||||
|
|
||||||
@mock_events
|
@mock_events
|
||||||
def test_rule_tagging_sad():
|
def test_tag_resource_error_unknown_arn():
|
||||||
back_end = EventsBackend("us-west-2")
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
try:
|
# when
|
||||||
back_end.tag_resource("unknown", [])
|
with pytest.raises(ClientError) as e:
|
||||||
raise "tag_resource should fail if ResourceARN is not known"
|
client.tag_resource(
|
||||||
except JsonRESTError:
|
ResourceARN="arn:aws:events:eu-central-1:{0}:rule/unknown".format(
|
||||||
pass
|
ACCOUNT_ID
|
||||||
|
),
|
||||||
|
Tags=[],
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
# then
|
||||||
back_end.untag_resource("unknown", [])
|
ex = e.value
|
||||||
raise "untag_resource should fail if ResourceARN is not known"
|
ex.operation_name.should.equal("TagResource")
|
||||||
except JsonRESTError:
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
pass
|
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"Rule unknown does not exist on EventBus default."
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
back_end.list_tags_for_resource("unknown")
|
@mock_events
|
||||||
raise "list_tags_for_resource should fail if ResourceARN is not known"
|
def test_untag_resource_error_unknown_arn():
|
||||||
except JsonRESTError:
|
# given
|
||||||
pass
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
with pytest.raises(ClientError) as e:
|
||||||
|
client.untag_resource(
|
||||||
|
ResourceARN="arn:aws:events:eu-central-1:{0}:rule/unknown".format(
|
||||||
|
ACCOUNT_ID
|
||||||
|
),
|
||||||
|
TagKeys=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.value
|
||||||
|
ex.operation_name.should.equal("UntagResource")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"Rule unknown does not exist on EventBus default."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_events
|
||||||
|
def test_list_tags_for_resource_error_unknown_arn():
|
||||||
|
# given
|
||||||
|
client = boto3.client("events", "eu-central-1")
|
||||||
|
|
||||||
|
# when
|
||||||
|
with pytest.raises(ClientError) as e:
|
||||||
|
client.list_tags_for_resource(
|
||||||
|
ResourceARN="arn:aws:events:eu-central-1:{0}:rule/unknown".format(
|
||||||
|
ACCOUNT_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.value
|
||||||
|
ex.operation_name.should.equal("ListTagsForResource")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"Rule unknown does not exist on EventBus default."
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user