Merge pull request #2946 from usmangani1/LSTACKISSUE909

SES Enhancement Adding  get_send_statistics,create_configuration_set functions.
This commit is contained in:
Bert Blommers 2020-05-04 10:07:03 +01:00 committed by GitHub
commit 2c9409faaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 225 additions and 1 deletions

View File

@ -7,3 +7,21 @@ class MessageRejectedError(RESTError):
def __init__(self, message):
super(MessageRejectedError, self).__init__("MessageRejected", message)
class ConfigurationSetDoesNotExist(RESTError):
code = 400
def __init__(self, message):
super(ConfigurationSetDoesNotExist, self).__init__(
"ConfigurationSetDoesNotExist", message
)
class EventDestinationAlreadyExists(RESTError):
code = 400
def __init__(self, message):
super(EventDestinationAlreadyExists, self).__init__(
"EventDestinationAlreadyExists", message
)

View File

@ -1,11 +1,16 @@
from __future__ import unicode_literals
import datetime
import email
from email.utils import parseaddr
from moto.core import BaseBackend, BaseModel
from moto.sns.models import sns_backends
from .exceptions import MessageRejectedError
from .exceptions import (
MessageRejectedError,
ConfigurationSetDoesNotExist,
EventDestinationAlreadyExists,
)
from .utils import get_random_message_id
from .feedback import COMMON_MAIL, BOUNCE, COMPLAINT, DELIVERY
@ -81,7 +86,11 @@ class SESBackend(BaseBackend):
self.domains = []
self.sent_messages = []
self.sent_message_count = 0
self.rejected_messages_count = 0
self.sns_topics = {}
self.config_set = {}
self.config_set_event_destination = {}
self.event_destinations = {}
def _is_verified_address(self, source):
_, address = parseaddr(source)
@ -118,6 +127,7 @@ class SESBackend(BaseBackend):
if recipient_count > RECIPIENT_LIMIT:
raise MessageRejectedError("Too many recipients.")
if not self._is_verified_address(source):
self.rejected_messages_count += 1
raise MessageRejectedError("Email address not verified %s" % source)
self.__process_sns_feedback__(source, destinations, region)
@ -135,6 +145,7 @@ class SESBackend(BaseBackend):
if recipient_count > RECIPIENT_LIMIT:
raise MessageRejectedError("Too many recipients.")
if not self._is_verified_address(source):
self.rejected_messages_count += 1
raise MessageRejectedError("Email address not verified %s" % source)
self.__process_sns_feedback__(source, destinations, region)
@ -237,5 +248,34 @@ class SESBackend(BaseBackend):
return {}
def create_configuration_set(self, configuration_set_name):
self.config_set[configuration_set_name] = 1
return {}
def create_configuration_set_event_destination(
self, configuration_set_name, event_destination
):
if self.config_set.get(configuration_set_name) is None:
raise ConfigurationSetDoesNotExist("Invalid Configuration Set Name.")
if self.event_destinations.get(event_destination["Name"]):
raise EventDestinationAlreadyExists("Duplicate Event destination Name.")
self.config_set_event_destination[configuration_set_name] = event_destination
self.event_destinations[event_destination["Name"]] = 1
return {}
def get_send_statistics(self):
statistics = {}
statistics["DeliveryAttempts"] = self.sent_message_count
statistics["Rejects"] = self.rejected_messages_count
statistics["Complaints"] = 0
statistics["Bounces"] = 0
statistics["Timestamp"] = datetime.datetime.utcnow()
return statistics
ses_backend = SESBackend()

View File

@ -133,6 +133,48 @@ class EmailResponse(BaseResponse):
template = self.response_template(SET_IDENTITY_NOTIFICATION_TOPIC_RESPONSE)
return template.render()
def get_send_statistics(self):
statistics = ses_backend.get_send_statistics()
template = self.response_template(GET_SEND_STATISTICS)
return template.render(all_statistics=[statistics])
def create_configuration_set(self):
configuration_set_name = self.querystring.get("ConfigurationSet.Name")[0]
ses_backend.create_configuration_set(
configuration_set_name=configuration_set_name
)
template = self.response_template(CREATE_CONFIGURATION_SET)
return template.render()
def create_configuration_set_event_destination(self):
configuration_set_name = self._get_param("ConfigurationSetName")
is_configuration_event_enabled = self.querystring.get(
"EventDestination.Enabled"
)[0]
configuration_event_name = self.querystring.get("EventDestination.Name")[0]
event_topic_arn = self.querystring.get(
"EventDestination.SNSDestination.TopicARN"
)[0]
event_matching_types = self._get_multi_param(
"EventDestination.MatchingEventTypes.member"
)
event_destination = {
"Name": configuration_event_name,
"Enabled": is_configuration_event_enabled,
"EventMatchingTypes": event_matching_types,
"SNSDestination": event_topic_arn,
}
ses_backend.create_configuration_set_event_destination(
configuration_set_name=configuration_set_name,
event_destination=event_destination,
)
template = self.response_template(CREATE_CONFIGURATION_SET_EVENT_DESTINATION)
return template.render()
VERIFY_EMAIL_IDENTITY = """<VerifyEmailIdentityResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<VerifyEmailIdentityResult/>
@ -248,3 +290,35 @@ SET_IDENTITY_NOTIFICATION_TOPIC_RESPONSE = """<SetIdentityNotificationTopicRespo
<RequestId>47e0ef1a-9bf2-11e1-9279-0100e8cf109a</RequestId>
</ResponseMetadata>
</SetIdentityNotificationTopicResponse>"""
GET_SEND_STATISTICS = """<GetSendStatisticsResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<SendDataPoints>
{% for statistics in all_statistics %}
<item>
<DeliveryAttempts>{{ statistics["DeliveryAttempts"] }}</DeliveryAttempts>
<Rejects>{{ statistics["Rejects"] }}</Rejects>
<Bounces>{{ statistics["Bounces"] }}</Bounces>
<Complaints>{{ statistics["Complaints"] }}</Complaints>
<Timestamp>{{ statistics["Timestamp"] }}</Timestamp>
</item>
{% endfor %}
</SendDataPoints>
<ResponseMetadata>
<RequestId>e0abcdfa-c866-11e0-b6d0-273d09173z49</RequestId>
</ResponseMetadata>
</GetSendStatisticsResponse>"""
CREATE_CONFIGURATION_SET = """<CreateConfigurationSetResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<CreateConfigurationSetResult/>
<ResponseMetadata>
<RequestId>47e0ef1a-9bf2-11e1-9279-0100e8cf109a</RequestId>
</ResponseMetadata>
</CreateConfigurationSetResponse>"""
CREATE_CONFIGURATION_SET_EVENT_DESTINATION = """<CreateConfigurationSetEventDestinationResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<CreateConfigurationSetEventDestinationResult/>
<ResponseMetadata>
<RequestId>67e0ef1a-9bf2-11e1-9279-0100e8cf109a</RequestId>
</ResponseMetadata>
</CreateConfigurationSetEventDestinationResponse>"""

View File

@ -127,3 +127,45 @@ def test_send_raw_email():
send_quota["GetSendQuotaResponse"]["GetSendQuotaResult"]["SentLast24Hours"]
)
sent_count.should.equal(1)
@mock_ses_deprecated
def test_get_send_statistics():
conn = boto.connect_ses("the_key", "the_secret")
conn.send_email.when.called_with(
"test@example.com",
"test subject",
"<span>test body</span>",
"test_to@example.com",
format="html",
).should.throw(BotoServerError)
# tests to verify rejects in get_send_statistics
result = conn.get_send_statistics()
reject_count = int(
result["GetSendStatisticsResponse"]["SendDataPoints"][0]["Rejects"]
)
delivery_count = int(
result["GetSendStatisticsResponse"]["SendDataPoints"][0]["DeliveryAttempts"]
)
reject_count.should.equal(1)
delivery_count.should.equal(0)
conn.verify_email_identity("test@example.com")
conn.send_email(
"test@example.com", "test subject", "test body", "test_to@example.com"
)
# tests to delivery attempts in get_send_statistics
result = conn.get_send_statistics()
reject_count = int(
result["GetSendStatisticsResponse"]["SendDataPoints"][0]["Rejects"]
)
delivery_count = int(
result["GetSendStatisticsResponse"]["SendDataPoints"][0]["DeliveryAttempts"]
)
reject_count.should.equal(1)
delivery_count.should.equal(1)

View File

@ -4,6 +4,8 @@ import boto3
from botocore.exceptions import ClientError
from six.moves.email_mime_multipart import MIMEMultipart
from six.moves.email_mime_text import MIMEText
from nose.tools import assert_raises
import sure # noqa
@ -227,3 +229,51 @@ def test_send_email_notification_with_encoded_sender():
Message={"Subject": {"Data": "hi",}, "Body": {"Text": {"Data": "there",}}},
)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_ses
def test_create_configuration_set():
conn = boto3.client("ses", region_name="us-east-1")
conn.create_configuration_set(ConfigurationSet=dict({"Name": "test"}))
conn.create_configuration_set_event_destination(
ConfigurationSetName="test",
EventDestination={
"Name": "snsEvent",
"Enabled": True,
"MatchingEventTypes": ["send",],
"SNSDestination": {
"TopicARN": "arn:aws:sns:us-east-1:123456789012:myTopic"
},
},
)
with assert_raises(ClientError) as ex:
conn.create_configuration_set_event_destination(
ConfigurationSetName="failtest",
EventDestination={
"Name": "snsEvent",
"Enabled": True,
"MatchingEventTypes": ["send",],
"SNSDestination": {
"TopicARN": "arn:aws:sns:us-east-1:123456789012:myTopic"
},
},
)
ex.exception.response["Error"]["Code"].should.equal("ConfigurationSetDoesNotExist")
with assert_raises(ClientError) as ex:
conn.create_configuration_set_event_destination(
ConfigurationSetName="test",
EventDestination={
"Name": "snsEvent",
"Enabled": True,
"MatchingEventTypes": ["send",],
"SNSDestination": {
"TopicARN": "arn:aws:sns:us-east-1:123456789012:myTopic"
},
},
)
ex.exception.response["Error"]["Code"].should.equal("EventDestinationAlreadyExists")