Repository: getmoto/moto

Issue:
Sending SMS via SNS without topic fails
Moto version: 1.3.8
Boto3 version: 1.9.42

Example:
```
import boto3
from moto import mock_sns
mock = mock_sns()
mock.start()
client = boto3.client('sns')
phone_number = "<insert valid E164 formatted phone number>"
client.publish(PhoneNumber=phone_number, Message='Test message'))
```

Expected:
No errors

Actual:
```
botocore.errorfactory.InvalidParameterValueException: An error occurred (ParameterValueInvalid) when calling the Publish operation: Could not find topic associated with phone number
```

Extra info:
The same code without Moto works and sends an SMS to my phone, also the AWS cli tool accepts only a phone number and message.

It seems moto tries to connect a phone number to a SNS topic, while this is not required by AWS. See https://github.com/spulec/moto/blob/master/moto/sns/responses.py#L304 


Discussion:
Thanks; setting this as a feature request to allow phone numbers without topics.
I am also running into this.
Running into this issue as well.  Is someone working on an issue?  If not what would be a good solution for this?
Found a workaround by creating a subscription with the number I need to send with in moto.

It seems like at this time, creating subscriptions do not require confirmation.
I'm also having this issue
Same here

Resolving patch:
diff --git a/moto/ses/models.py b/moto/ses/models.py
--- a/moto/ses/models.py
+++ b/moto/ses/models.py
@@ -202,7 +202,7 @@ def __process_sns_feedback__(self, source, destinations, region):
                 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:
diff --git a/moto/sns/models.py b/moto/sns/models.py
--- a/moto/sns/models.py
+++ b/moto/sns/models.py
@@ -35,6 +35,7 @@
 
 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 @@ def __init__(self, region_name):
         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 @@ def get_topic(self, arn):
         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 @@ def list_subscriptions(self, topic_arn=None, next_token=None):
         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"
diff --git a/moto/sns/responses.py b/moto/sns/responses.py
--- a/moto/sns/responses.py
+++ b/moto/sns/responses.py
@@ -6,7 +6,7 @@
 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 @@ def publish(self):
 
         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 @@ def publish(self):
                     ),
                     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 @@ def publish(self):
 
         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))


Test patch:
diff --git a/tests/test_sns/test_publishing_boto3.py b/tests/test_sns/test_publishing_boto3.py
--- a/tests/test_sns/test_publishing_boto3.py
+++ b/tests/test_sns/test_publishing_boto3.py
@@ -11,8 +11,9 @@
 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")
-
-    try:
-        # Test not found number
-        client.publish(PhoneNumber="+44001234567", Message="my message")
-    except ClientError as err:
-        err.response["Error"]["Code"].should.equal("ParameterValueInvalid")
+    cm.exception.response["Error"]["Code"].should.equal("InvalidParameter")
+    cm.exception.response["Error"]["Message"].should.contain("not meet the E164")
+
+    # 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