[SNS] Mock sending directly SMS (#3253)

* [SNS] Mock sending directly SMS

Proper behaviour when publishing to PhoneNumber is sending
message directly to this number, without any topic or
previous confirmation.

https://docs.aws.amazon.com/sns/latest/dg/sns-mobile-phone-number-as-subscriber.html

* Fix arguments order

* Omit checking local backend when tests in server mode
This commit is contained in:
Kamil Mańkowski 2020-08-25 14:05:49 +02:00 committed by GitHub
parent 0317602ae5
commit 8a551a9754
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 42 deletions

View File

@ -202,7 +202,7 @@ class SESBackend(BaseBackend):
if sns_topic is not None:
message = self.__generate_feedback__(msg_type)
if message:
sns_backends[region].publish(sns_topic, message)
sns_backends[region].publish(message, arn=sns_topic)
def send_raw_email(self, source, destinations, raw_data, region):
if source is not None:

View File

@ -35,6 +35,7 @@ from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
DEFAULT_PAGE_SIZE = 100
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
MAXIMUM_SMS_MESSAGE_BYTES = 1600 # Amazon limit for a single publish SMS action
class Topic(CloudFormationModel):
@ -365,6 +366,7 @@ class SNSBackend(BaseBackend):
self.platform_endpoints = {}
self.region_name = region_name
self.sms_attributes = {}
self.sms_messages = OrderedDict()
self.opt_out_numbers = [
"+447420500600",
"+447420505401",
@ -432,12 +434,6 @@ class SNSBackend(BaseBackend):
except KeyError:
raise SNSNotFoundError("Topic with arn {0} not found".format(arn))
def get_topic_from_phone_number(self, number):
for subscription in self.subscriptions.values():
if subscription.protocol == "sms" and subscription.endpoint == number:
return subscription.topic.arn
raise SNSNotFoundError("Could not find valid subscription")
def set_topic_attribute(self, topic_arn, attribute_name, attribute_value):
topic = self.get_topic(topic_arn)
setattr(topic, attribute_name, attribute_value)
@ -501,11 +497,27 @@ class SNSBackend(BaseBackend):
else:
return self._get_values_nexttoken(self.subscriptions, next_token)
def publish(self, arn, message, subject=None, message_attributes=None):
def publish(
self,
message,
arn=None,
phone_number=None,
subject=None,
message_attributes=None,
):
if subject is not None and len(subject) > 100:
# Note that the AWS docs around length are wrong: https://github.com/spulec/moto/issues/1503
raise ValueError("Subject must be less than 100 characters")
if phone_number:
# This is only an approximation. In fact, we should try to use GSM-7 or UCS-2 encoding to count used bytes
if len(message) > MAXIMUM_SMS_MESSAGE_BYTES:
raise ValueError("SMS message must be less than 1600 bytes")
message_id = six.text_type(uuid.uuid4())
self.sms_messages[message_id] = (phone_number, message)
return message_id
if len(message) > MAXIMUM_MESSAGE_LENGTH:
raise InvalidParameterValue(
"An error occurred (InvalidParameter) when calling the Publish operation: Invalid parameter: Message too long"

View File

@ -6,7 +6,7 @@ from collections import defaultdict
from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores
from .models import sns_backends
from .exceptions import SNSNotFoundError, InvalidParameterValue
from .exceptions import InvalidParameterValue
from .utils import is_e164
@ -327,6 +327,7 @@ class SNSResponse(BaseResponse):
message_attributes = self._parse_message_attributes()
arn = None
if phone_number is not None:
# Check phone is correct syntax (e164)
if not is_e164(phone_number):
@ -336,18 +337,6 @@ class SNSResponse(BaseResponse):
),
dict(status=400),
)
# Look up topic arn by phone number
try:
arn = self.backend.get_topic_from_phone_number(phone_number)
except SNSNotFoundError:
return (
self._error(
"ParameterValueInvalid",
"Could not find topic associated with phone number",
),
dict(status=400),
)
elif target_arn is not None:
arn = target_arn
else:
@ -357,7 +346,11 @@ class SNSResponse(BaseResponse):
try:
message_id = self.backend.publish(
arn, message, subject=subject, message_attributes=message_attributes
message,
arn=arn,
phone_number=phone_number,
subject=subject,
message_attributes=message_attributes,
)
except ValueError as err:
error_response = self._error("InvalidParameter", str(err))

View File

@ -11,8 +11,9 @@ import sure # noqa
import responses
from botocore.exceptions import ClientError
from nose.tools import assert_raises
from moto import mock_sns, mock_sqs
from moto import mock_sns, mock_sqs, settings
from moto.core import ACCOUNT_ID
from moto.sns import sns_backend
MESSAGE_FROM_SQS_TEMPLATE = (
'{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "my subject",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:'
@ -223,36 +224,31 @@ def test_publish_to_sqs_msg_attr_number_type():
@mock_sns
def test_publish_sms():
client = boto3.client("sns", region_name="us-east-1")
client.create_topic(Name="some-topic")
resp = client.create_topic(Name="some-topic")
arn = resp["TopicArn"]
client.subscribe(TopicArn=arn, Protocol="sms", Endpoint="+15551234567")
result = client.publish(PhoneNumber="+15551234567", Message="my message")
result.should.contain("MessageId")
if not settings.TEST_SERVER_MODE:
sns_backend.sms_messages.should.have.key(result["MessageId"]).being.equal(
("+15551234567", "my message")
)
@mock_sns
def test_publish_bad_sms():
client = boto3.client("sns", region_name="us-east-1")
client.create_topic(Name="some-topic")
resp = client.create_topic(Name="some-topic")
arn = resp["TopicArn"]
client.subscribe(TopicArn=arn, Protocol="sms", Endpoint="+15551234567")
try:
# Test invalid number
# Test invalid number
with assert_raises(ClientError) as cm:
client.publish(PhoneNumber="NAA+15551234567", Message="my message")
except ClientError as err:
err.response["Error"]["Code"].should.equal("InvalidParameter")
cm.exception.response["Error"]["Code"].should.equal("InvalidParameter")
cm.exception.response["Error"]["Message"].should.contain("not meet the E164")
try:
# Test not found number
client.publish(PhoneNumber="+44001234567", Message="my message")
except ClientError as err:
err.response["Error"]["Code"].should.equal("ParameterValueInvalid")
# Test to long ASCII message
with assert_raises(ClientError) as cm:
client.publish(PhoneNumber="+15551234567", Message="a" * 1601)
cm.exception.response["Error"]["Code"].should.equal("InvalidParameter")
cm.exception.response["Error"]["Message"].should.contain("must be less than 1600")
@mock_sqs