From 2650eab29533e576a984fc24aa43910d6d7cd774 Mon Sep 17 00:00:00 2001 From: Pior Bastida Date: Thu, 20 Aug 2015 11:12:25 -0400 Subject: [PATCH] Implement XML responses for SNS (for Boto3) --- moto/sns/models.py | 9 +- moto/sns/responses.py | 724 +++++++++++++++------ tests/test_sns/test_application_boto3.py | 254 ++++++++ tests/test_sns/test_publishing_boto3.py | 89 +++ tests/test_sns/test_server.py | 14 +- tests/test_sns/test_subscriptions_boto3.py | 90 +++ tests/test_sns/test_topics_boto3.py | 125 ++++ 7 files changed, 1106 insertions(+), 199 deletions(-) create mode 100644 tests/test_sns/test_application_boto3.py create mode 100644 tests/test_sns/test_publishing_boto3.py create mode 100644 tests/test_sns/test_subscriptions_boto3.py create mode 100644 tests/test_sns/test_topics_boto3.py diff --git a/moto/sns/models.py b/moto/sns/models.py index b6877e69e..fe9683d7a 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import datetime import uuid +import json import boto.sns import requests @@ -257,7 +258,7 @@ for region in boto.sns.regions(): sns_backends[region.name] = SNSBackend(region.name) -DEFAULT_TOPIC_POLICY = { +DEFAULT_TOPIC_POLICY = json.dumps({ "Version": "2008-10-17", "Id": "us-east-1/698519295917/test__default_policy_ID", "Statement": [{ @@ -284,9 +285,9 @@ DEFAULT_TOPIC_POLICY = { } } }] -} +}) -DEFAULT_EFFECTIVE_DELIVERY_POLICY = { +DEFAULT_EFFECTIVE_DELIVERY_POLICY = json.dumps({ 'http': { 'disableSubscriptionOverrides': False, 'defaultHealthyRetryPolicy': { @@ -299,4 +300,4 @@ DEFAULT_EFFECTIVE_DELIVERY_POLICY = { 'backoffFunction': 'linear' } } -} +}) diff --git a/moto/sns/responses.py b/moto/sns/responses.py index b9a17f4c7..6f90586a3 100644 --- a/moto/sns/responses.py +++ b/moto/sns/responses.py @@ -12,6 +12,10 @@ class SNSResponse(BaseResponse): def backend(self): return sns_backends[self.region] + @property + def request_json(self): + return 'JSON' in self.querystring.get('ContentType', []) + def _get_attributes(self): attributes = self._get_list_prefix('Attributes.entry') return dict( @@ -24,69 +28,85 @@ class SNSResponse(BaseResponse): name = self._get_param('Name') topic = self.backend.create_topic(name) - return json.dumps({ - 'CreateTopicResponse': { - 'CreateTopicResult': { - 'TopicArn': topic.arn, - }, - 'ResponseMetadata': { - 'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a', + if self.request_json: + return json.dumps({ + 'CreateTopicResponse': { + 'CreateTopicResult': { + 'TopicArn': topic.arn, + }, + 'ResponseMetadata': { + 'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a', + } } - } - }) + }) + + template = self.response_template(CREATE_TOPIC_TEMPLATE) + return template.render(topic=topic) def list_topics(self): next_token = self._get_param('NextToken') topics, next_token = self.backend.list_topics(next_token=next_token) - return json.dumps({ - 'ListTopicsResponse': { - 'ListTopicsResult': { - 'Topics': [{'TopicArn': topic.arn} for topic in topics], - 'NextToken': next_token, + if self.request_json: + return json.dumps({ + 'ListTopicsResponse': { + 'ListTopicsResult': { + 'Topics': [{'TopicArn': topic.arn} for topic in topics], + 'NextToken': next_token, + } + }, + 'ResponseMetadata': { + 'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a', } - }, - 'ResponseMetadata': { - 'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a', - } - }) + }) + + template = self.response_template(LIST_TOPICS_TEMPLATE) + return template.render(topics=topics, next_token=next_token) def delete_topic(self): topic_arn = self._get_param('TopicArn') self.backend.delete_topic(topic_arn) - return json.dumps({ - 'DeleteTopicResponse': { - 'ResponseMetadata': { - 'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a', + if self.request_json: + return json.dumps({ + 'DeleteTopicResponse': { + 'ResponseMetadata': { + 'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a', + } } - } - }) + }) + + template = self.response_template(DELETE_TOPIC_TEMPLATE) + return template.render() def get_topic_attributes(self): topic_arn = self._get_param('TopicArn') topic = self.backend.get_topic(topic_arn) - return json.dumps({ - "GetTopicAttributesResponse": { - "GetTopicAttributesResult": { - "Attributes": { - "Owner": topic.account_id, - "Policy": topic.policy, - "TopicArn": topic.arn, - "DisplayName": topic.display_name, - "SubscriptionsPending": topic.subscriptions_pending, - "SubscriptionsConfirmed": topic.subscriptions_confimed, - "SubscriptionsDeleted": topic.subscriptions_deleted, - "DeliveryPolicy": topic.delivery_policy, - "EffectiveDeliveryPolicy": topic.effective_delivery_policy, + if self.request_json: + return json.dumps({ + "GetTopicAttributesResponse": { + "GetTopicAttributesResult": { + "Attributes": { + "Owner": topic.account_id, + "Policy": topic.policy, + "TopicArn": topic.arn, + "DisplayName": topic.display_name, + "SubscriptionsPending": topic.subscriptions_pending, + "SubscriptionsConfirmed": topic.subscriptions_confimed, + "SubscriptionsDeleted": topic.subscriptions_deleted, + "DeliveryPolicy": topic.delivery_policy, + "EffectiveDeliveryPolicy": topic.effective_delivery_policy, + } + }, + "ResponseMetadata": { + "RequestId": "057f074c-33a7-11df-9540-99d0768312d3" } - }, - "ResponseMetadata": { - "RequestId": "057f074c-33a7-11df-9540-99d0768312d3" } - } - }) + }) + + template = self.response_template(GET_TOPIC_ATTRIBUTES_TEMPLATE) + return template.render(topic=topic) def set_topic_attributes(self): topic_arn = self._get_param('TopicArn') @@ -95,13 +115,17 @@ class SNSResponse(BaseResponse): attribute_value = self._get_param('AttributeValue') self.backend.set_topic_attribute(topic_arn, attribute_name, attribute_value) - return json.dumps({ - "SetTopicAttributesResponse": { - "ResponseMetadata": { - "RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042" + if self.request_json: + return json.dumps({ + "SetTopicAttributesResponse": { + "ResponseMetadata": { + "RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042" + } } - } - }) + }) + + template = self.response_template(SET_TOPIC_ATTRIBUTES_TEMPLATE) + return template.render() def subscribe(self): topic_arn = self._get_param('TopicArn') @@ -109,73 +133,91 @@ class SNSResponse(BaseResponse): protocol = self._get_param('Protocol') subscription = self.backend.subscribe(topic_arn, endpoint, protocol) - return json.dumps({ - "SubscribeResponse": { - "SubscribeResult": { - "SubscriptionArn": subscription.arn, - }, - "ResponseMetadata": { - "RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042" + if self.request_json: + return json.dumps({ + "SubscribeResponse": { + "SubscribeResult": { + "SubscriptionArn": subscription.arn, + }, + "ResponseMetadata": { + "RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042" + } } - } - }) + }) + + template = self.response_template(SUBSCRIBE_TEMPLATE) + return template.render(subscription=subscription) def unsubscribe(self): subscription_arn = self._get_param('SubscriptionArn') self.backend.unsubscribe(subscription_arn) - return json.dumps({ - "UnsubscribeResponse": { - "ResponseMetadata": { - "RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042" + if self.request_json: + return json.dumps({ + "UnsubscribeResponse": { + "ResponseMetadata": { + "RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042" + } } - } - }) + }) + + template = self.response_template(UNSUBSCRIBE_TEMPLATE) + return template.render() def list_subscriptions(self): next_token = self._get_param('NextToken') subscriptions, next_token = self.backend.list_subscriptions(next_token=next_token) - return json.dumps({ - "ListSubscriptionsResponse": { - "ListSubscriptionsResult": { - "Subscriptions": [{ - "TopicArn": subscription.topic.arn, - "Protocol": subscription.protocol, - "SubscriptionArn": subscription.arn, - "Owner": subscription.topic.account_id, - "Endpoint": subscription.endpoint, - } for subscription in subscriptions], - 'NextToken': next_token, - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + if self.request_json: + return json.dumps({ + "ListSubscriptionsResponse": { + "ListSubscriptionsResult": { + "Subscriptions": [{ + "TopicArn": subscription.topic.arn, + "Protocol": subscription.protocol, + "SubscriptionArn": subscription.arn, + "Owner": subscription.topic.account_id, + "Endpoint": subscription.endpoint, + } for subscription in subscriptions], + 'NextToken': next_token, + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } } - } - }) + }) + + template = self.response_template(LIST_SUBSCRIPTIONS_TEMPLATE) + return template.render(subscriptions=subscriptions, + next_token=next_token) def list_subscriptions_by_topic(self): topic_arn = self._get_param('TopicArn') next_token = self._get_param('NextToken') subscriptions, next_token = self.backend.list_subscriptions(topic_arn, next_token=next_token) - return json.dumps({ - "ListSubscriptionsByTopicResponse": { - "ListSubscriptionsByTopicResult": { - "Subscriptions": [{ - "TopicArn": subscription.topic.arn, - "Protocol": subscription.protocol, - "SubscriptionArn": subscription.arn, - "Owner": subscription.topic.account_id, - "Endpoint": subscription.endpoint, - } for subscription in subscriptions], - 'NextToken': next_token, - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + if self.request_json: + return json.dumps({ + "ListSubscriptionsByTopicResponse": { + "ListSubscriptionsByTopicResult": { + "Subscriptions": [{ + "TopicArn": subscription.topic.arn, + "Protocol": subscription.protocol, + "SubscriptionArn": subscription.arn, + "Owner": subscription.topic.account_id, + "Endpoint": subscription.endpoint, + } for subscription in subscriptions], + 'NextToken': next_token, + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } } - } - }) + }) + + template = self.response_template(LIST_SUBSCRIPTIONS_BY_TOPIC_TEMPLATE) + return template.render(subscriptions=subscriptions, + next_token=next_token) def publish(self): target_arn = self._get_param('TargetArn') @@ -184,16 +226,20 @@ class SNSResponse(BaseResponse): message = self._get_param('Message') message_id = self.backend.publish(arn, message) - return json.dumps({ - "PublishResponse": { - "PublishResult": { - "MessageId": message_id, - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + if self.request_json: + return json.dumps({ + "PublishResponse": { + "PublishResult": { + "MessageId": message_id, + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } } - } - }) + }) + + template = self.response_template(PUBLISH_TEMPLATE) + return template.render(message_id=message_id) def create_platform_application(self): name = self._get_param('Name') @@ -201,31 +247,39 @@ class SNSResponse(BaseResponse): attributes = self._get_attributes() platform_application = self.backend.create_platform_application(self.region, name, platform, attributes) - return json.dumps({ - "CreatePlatformApplicationResponse": { - "CreatePlatformApplicationResult": { - "PlatformApplicationArn": platform_application.arn, - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937b", + if self.request_json: + return json.dumps({ + "CreatePlatformApplicationResponse": { + "CreatePlatformApplicationResult": { + "PlatformApplicationArn": platform_application.arn, + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937b", + } } - } - }) + }) + + template = self.response_template(CREATE_PLATFORM_APPLICATION_TEMPLATE) + return template.render(platform_application=platform_application) def get_platform_application_attributes(self): arn = self._get_param('PlatformApplicationArn') application = self.backend.get_application(arn) - return json.dumps({ - "GetPlatformApplicationAttributesResponse": { - "GetPlatformApplicationAttributesResult": { - "Attributes": application.attributes, - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937f", + if self.request_json: + return json.dumps({ + "GetPlatformApplicationAttributesResponse": { + "GetPlatformApplicationAttributesResult": { + "Attributes": application.attributes, + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937f", + } } - } - }) + }) + + template = self.response_template(GET_PLATFORM_APPLICATION_ATTRIBUTES_TEMPLATE) + return template.render(application=application) def set_platform_application_attributes(self): arn = self._get_param('PlatformApplicationArn') @@ -233,43 +287,55 @@ class SNSResponse(BaseResponse): self.backend.set_application_attributes(arn, attributes) - return json.dumps({ - "SetPlatformApplicationAttributesResponse": { - "ResponseMetadata": { - "RequestId": "384ac68d-3775-12df-8963-01868b7c937f", + if self.request_json: + return json.dumps({ + "SetPlatformApplicationAttributesResponse": { + "ResponseMetadata": { + "RequestId": "384ac68d-3775-12df-8963-01868b7c937f", + } } - } - }) + }) + + template = self.response_template(SET_PLATFORM_APPLICATION_ATTRIBUTES_TEMPLATE) + return template.render() def list_platform_applications(self): applications = self.backend.list_platform_applications() - return json.dumps({ - "ListPlatformApplicationsResponse": { - "ListPlatformApplicationsResult": { - "PlatformApplications": [{ - "PlatformApplicationArn": application.arn, - "attributes": application.attributes, - } for application in applications], - "NextToken": None - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937c", + if self.request_json: + return json.dumps({ + "ListPlatformApplicationsResponse": { + "ListPlatformApplicationsResult": { + "PlatformApplications": [{ + "PlatformApplicationArn": application.arn, + "attributes": application.attributes, + } for application in applications], + "NextToken": None + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937c", + } } - } - }) + }) + + template = self.response_template(LIST_PLATFORM_APPLICATIONS_TEMPLATE) + return template.render(applications=applications) def delete_platform_application(self): platform_arn = self._get_param('PlatformApplicationArn') self.backend.delete_platform_application(platform_arn) - return json.dumps({ - "DeletePlatformApplicationResponse": { - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937e", + if self.request_json: + return json.dumps({ + "DeletePlatformApplicationResponse": { + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937e", + } } - } - }) + }) + + template = self.response_template(DELETE_PLATFORM_APPLICATION_TEMPLATE) + return template.render() def create_platform_endpoint(self): application_arn = self._get_param('PlatformApplicationArn') @@ -282,52 +348,64 @@ class SNSResponse(BaseResponse): platform_endpoint = self.backend.create_platform_endpoint( self.region, application, custom_user_data, token, attributes) - return json.dumps({ - "CreatePlatformEndpointResponse": { - "CreatePlatformEndpointResult": { - "EndpointArn": platform_endpoint.arn, - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3779-11df-8963-01868b7c937b", + if self.request_json: + return json.dumps({ + "CreatePlatformEndpointResponse": { + "CreatePlatformEndpointResult": { + "EndpointArn": platform_endpoint.arn, + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3779-11df-8963-01868b7c937b", + } } - } - }) + }) + + template = self.response_template(CREATE_PLATFORM_ENDPOINT_TEMPLATE) + return template.render(platform_endpoint=platform_endpoint) def list_endpoints_by_platform_application(self): application_arn = self._get_param('PlatformApplicationArn') endpoints = self.backend.list_endpoints_by_platform_application(application_arn) - return json.dumps({ - "ListEndpointsByPlatformApplicationResponse": { - "ListEndpointsByPlatformApplicationResult": { - "Endpoints": [ - { - "Attributes": endpoint.attributes, - "EndpointArn": endpoint.arn, - } for endpoint in endpoints - ], - "NextToken": None - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + if self.request_json: + return json.dumps({ + "ListEndpointsByPlatformApplicationResponse": { + "ListEndpointsByPlatformApplicationResult": { + "Endpoints": [ + { + "Attributes": endpoint.attributes, + "EndpointArn": endpoint.arn, + } for endpoint in endpoints + ], + "NextToken": None + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } } - } - }) + }) + + template = self.response_template(LIST_ENDPOINTS_BY_PLATFORM_APPLICATION_TEMPLATE) + return template.render(endpoints=endpoints) def get_endpoint_attributes(self): arn = self._get_param('EndpointArn') endpoint = self.backend.get_endpoint(arn) - return json.dumps({ - "GetEndpointAttributesResponse": { - "GetEndpointAttributesResult": { - "Attributes": endpoint.attributes, - }, - "ResponseMetadata": { - "RequestId": "384ac68d-3775-11df-8963-01868b7c937f", + if self.request_json: + return json.dumps({ + "GetEndpointAttributesResponse": { + "GetEndpointAttributesResult": { + "Attributes": endpoint.attributes, + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937f", + } } - } - }) + }) + + template = self.response_template(GET_ENDPOINT_ATTRIBUTES_TEMPLATE) + return template.render(endpoint=endpoint) def set_endpoint_attributes(self): arn = self._get_param('EndpointArn') @@ -335,10 +413,282 @@ class SNSResponse(BaseResponse): self.backend.set_endpoint_attributes(arn, attributes) - return json.dumps({ - "SetEndpointAttributesResponse": { - "ResponseMetadata": { - "RequestId": "384bc68d-3775-12df-8963-01868b7c937f", + if self.request_json: + return json.dumps({ + "SetEndpointAttributesResponse": { + "ResponseMetadata": { + "RequestId": "384bc68d-3775-12df-8963-01868b7c937f", + } } - } - }) + }) + + template = self.response_template(SET_ENDPOINT_ATTRIBUTES_TEMPLATE) + return template.render() + + +CREATE_TOPIC_TEMPLATE = """ + + {{ topic.arn }} + + + a8dec8b3-33a4-11df-8963-01868b7c937a + + """ + +LIST_TOPICS_TEMPLATE = """ + + + {% for topic in topics %} + + {{ topic.arn }} + + {% endfor %} + + {% if next_token %} + {{ next_token }} + {% endif %} + + + 3f1478c7-33a9-11df-9540-99d0768312d3 + +""" + +DELETE_TOPIC_TEMPLATE = """ + + f3aa9ac9-3c3d-11df-8235-9dab105e9c32 + +""" + +GET_TOPIC_ATTRIBUTES_TEMPLATE = """ + + + + Owner + {{ topic.account_id }} + + + Policy + {{ topic.policy }} + + + TopicArn + {{ topic.arn }} + + + DisplayName + {{ topic.display_name }} + + + SubscriptionsPending + {{ topic.subscriptions_pending }} + + + SubscriptionsConfirmed + {{ topic.subscriptions_confimed }} + + + SubscriptionsDeleted + {{ topic.subscriptions_deleted }} + + + DeliveryPolicy + {{ topic.delivery_policy }} + + + EffectiveDeliveryPolicy + {{ topic.effective_delivery_policy }} + + + + + 057f074c-33a7-11df-9540-99d0768312d3 + +""" + +SET_TOPIC_ATTRIBUTES_TEMPLATE = """ + + a8763b99-33a7-11df-a9b7-05d48da6f042 + +""" + +CREATE_PLATFORM_APPLICATION_TEMPLATE = """ + + {{ platform_application.arn }} + + + b6f0e78b-e9d4-5a0e-b973-adc04e8a4ff9 + +""" + +CREATE_PLATFORM_ENDPOINT_TEMPLATE = """ + + {{ platform_endpoint.arn }} + + + 6613341d-3e15-53f7-bf3c-7e56994ba278 + +""" + +LIST_PLATFORM_APPLICATIONS_TEMPLATE = """ + + + {% for application in applications %} + + {{ application.arn }} + + {% for attribute in application.attributes %} + + {{ attribute }} + {{ application.attributes[attribute] }} + + {% endfor %} + + + {% endfor %} + + + + 315a335e-85d8-52df-9349-791283cbb529 + +""" + +DELETE_PLATFORM_APPLICATION_TEMPLATE = """ + + 097dac18-7a77-5823-a8dd-e65476dcb037 + +""" + +GET_ENDPOINT_ATTRIBUTES_TEMPLATE = """ + + + {% for attribute in endpoint.attributes %} + + {{ attribute }} + {{ endpoint.attributes[attribute] }} + + {% endfor %} + + + + 6c725a19-a142-5b77-94f9-1055a9ea04e7 + +""" + +LIST_ENDPOINTS_BY_PLATFORM_APPLICATION_TEMPLATE = """ + + + {% for endpoint in endpoints %} + + {{ endpoint.arn }} + + {% for attribute in endpoint.attributes %} + + {{ attribute }} + {{ endpoint.attributes[attribute] }} + + {% endfor %} + + + {% endfor %} + + + + 9a48768c-dac8-5a60-aec0-3cc27ea08d96 + +""" + +GET_PLATFORM_APPLICATION_ATTRIBUTES_TEMPLATE = """ + + + {% for attribute in application.attributes %} + + {{ attribute }} + {{ application.attributes[attribute] }} + + {% endfor %} + + + + 74848df2-87f6-55ed-890c-c7be80442462 + +""" + +PUBLISH_TEMPLATE = """ + + {{ message_id }} + + + f187a3c1-376f-11df-8963-01868b7c937a + +""" + +SET_ENDPOINT_ATTRIBUTES_TEMPLATE = """ + + 2fe0bfc7-3e85-5ee5-a9e2-f58b35e85f6a + +""" + +SET_PLATFORM_APPLICATION_ATTRIBUTES_TEMPLATE = """ + + cf577bcc-b3dc-5463-88f1-3180b9412395 + +""" + +SUBSCRIBE_TEMPLATE = """ + + {{ subscription.arn }} + + + c4407779-24a4-56fa-982c-3d927f93a775 + +""" + +UNSUBSCRIBE_TEMPLATE = """ + + 18e0ac39-3776-11df-84c0-b93cc1666b84 + +""" + +LIST_SUBSCRIPTIONS_TEMPLATE = """ + + + {% for subscription in subscriptions %} + + {{ subscription.topic.arn }} + {{ subscription.protocol }} + {{ subscription.arn }} + {{ subscription.account_id }} + {{ subscription.endpoint }} + + {% endfor %} + + {% if next_token %} + {{ next_token }} + {% endif %} + + + 384ac68d-3775-11df-8963-01868b7c937a + +""" + +LIST_SUBSCRIPTIONS_BY_TOPIC_TEMPLATE = """ + + + {% for subscription in subscriptions %} + + {{ subscription.topic.arn }} + {{ subscription.protocol }} + {{ subscription.arn }} + {{ subscription.account_id }} + {{ subscription.endpoint }} + + {% endfor %} + + {% if next_token %} + {{ next_token }} + {% endif %} + + + 384ac68d-3775-11df-8963-01868b7c937a + +""" diff --git a/tests/test_sns/test_application_boto3.py b/tests/test_sns/test_application_boto3.py new file mode 100644 index 000000000..8dda67b0a --- /dev/null +++ b/tests/test_sns/test_application_boto3.py @@ -0,0 +1,254 @@ +from __future__ import unicode_literals + +import boto3 +from botocore.exceptions import ClientError +from moto import mock_sns +import sure # noqa + + +@mock_sns +def test_create_platform_application(): + conn = boto3.client('sns', region_name='us-east-1') + response = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={ + "PlatformCredential": "platform_credential", + "PlatformPrincipal": "platform_principal", + }, + ) + application_arn = response['PlatformApplicationArn'] + application_arn.should.equal('arn:aws:sns:us-east-1:123456789012:app/APNS/my-application') + + +@mock_sns +def test_get_platform_application_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + platform_application = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={ + "PlatformCredential": "platform_credential", + "PlatformPrincipal": "platform_principal", + }, + ) + arn = platform_application['PlatformApplicationArn'] + attributes = conn.get_platform_application_attributes(PlatformApplicationArn=arn)['Attributes'] + attributes.should.equal({ + "PlatformCredential": "platform_credential", + "PlatformPrincipal": "platform_principal", + }) + + +@mock_sns +def test_get_missing_platform_application_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + conn.get_platform_application_attributes.when.called_with(PlatformApplicationArn="a-fake-arn").should.throw(ClientError) + + +@mock_sns +def test_set_platform_application_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + platform_application = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={ + "PlatformCredential": "platform_credential", + "PlatformPrincipal": "platform_principal", + }, + ) + arn = platform_application['PlatformApplicationArn'] + conn.set_platform_application_attributes(PlatformApplicationArn=arn, + Attributes={"PlatformPrincipal": "other"} + ) + attributes = conn.get_platform_application_attributes(PlatformApplicationArn=arn)['Attributes'] + attributes.should.equal({ + "PlatformCredential": "platform_credential", + "PlatformPrincipal": "other", + }) + + +@mock_sns +def test_list_platform_applications(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_platform_application( + Name="application1", + Platform="APNS", + Attributes={}, + ) + conn.create_platform_application( + Name="application2", + Platform="APNS", + Attributes={}, + ) + + applications_repsonse = conn.list_platform_applications() + applications = applications_repsonse['PlatformApplications'] + applications.should.have.length_of(2) + + +@mock_sns +def test_delete_platform_application(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_platform_application( + Name="application1", + Platform="APNS", + Attributes={}, + ) + conn.create_platform_application( + Name="application2", + Platform="APNS", + Attributes={}, + ) + + applications_repsonse = conn.list_platform_applications() + applications = applications_repsonse['PlatformApplications'] + applications.should.have.length_of(2) + + application_arn = applications[0]['PlatformApplicationArn'] + conn.delete_platform_application(PlatformApplicationArn=application_arn) + + applications_repsonse = conn.list_platform_applications() + applications = applications_repsonse['PlatformApplications'] + applications.should.have.length_of(1) + + +@mock_sns +def test_create_platform_endpoint(): + conn = boto3.client('sns', region_name='us-east-1') + platform_application = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={}, + ) + application_arn = platform_application['PlatformApplicationArn'] + + endpoint = conn.create_platform_endpoint( + PlatformApplicationArn=application_arn, + Token="some_unique_id", + CustomUserData="some user data", + Attributes={ + "Enabled": 'false', + }, + ) + + endpoint_arn = endpoint['EndpointArn'] + endpoint_arn.should.contain("arn:aws:sns:us-east-1:123456789012:endpoint/APNS/my-application/") + + +@mock_sns +def test_get_list_endpoints_by_platform_application(): + conn = boto3.client('sns', region_name='us-east-1') + platform_application = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={}, + ) + application_arn = platform_application['PlatformApplicationArn'] + + endpoint = conn.create_platform_endpoint( + PlatformApplicationArn=application_arn, + Token="some_unique_id", + CustomUserData="some user data", + Attributes={ + "CustomUserData": "some data", + }, + ) + endpoint_arn = endpoint['EndpointArn'] + + endpoint_list = conn.list_endpoints_by_platform_application( + PlatformApplicationArn=application_arn + )['Endpoints'] + + endpoint_list.should.have.length_of(1) + endpoint_list[0]['Attributes']['CustomUserData'].should.equal('some data') + endpoint_list[0]['EndpointArn'].should.equal(endpoint_arn) + + +@mock_sns +def test_get_endpoint_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + platform_application = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={}, + ) + application_arn = platform_application['PlatformApplicationArn'] + + endpoint = conn.create_platform_endpoint( + PlatformApplicationArn=application_arn, + Token="some_unique_id", + CustomUserData="some user data", + Attributes={ + "Enabled": 'false', + "CustomUserData": "some data", + }, + ) + endpoint_arn = endpoint['EndpointArn'] + + attributes = conn.get_endpoint_attributes(EndpointArn=endpoint_arn)['Attributes'] + attributes.should.equal({ + "Enabled": 'false', + "CustomUserData": "some data", + }) + + +@mock_sns +def test_get_missing_endpoint_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + conn.get_endpoint_attributes.when.called_with(EndpointArn="a-fake-arn").should.throw(ClientError) + + +@mock_sns +def test_set_endpoint_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + platform_application = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={}, + ) + application_arn = platform_application['PlatformApplicationArn'] + + endpoint = conn.create_platform_endpoint( + PlatformApplicationArn=application_arn, + Token="some_unique_id", + CustomUserData="some user data", + Attributes={ + "Enabled": 'false', + "CustomUserData": "some data", + }, + ) + endpoint_arn = endpoint['EndpointArn'] + + conn.set_endpoint_attributes(EndpointArn=endpoint_arn, + Attributes={"CustomUserData": "other data"} + ) + attributes = conn.get_endpoint_attributes(EndpointArn=endpoint_arn)['Attributes'] + attributes.should.equal({ + "Enabled": 'false', + "CustomUserData": "other data", + }) + + +@mock_sns +def test_publish_to_platform_endpoint(): + conn = boto3.client('sns', region_name='us-east-1') + platform_application = conn.create_platform_application( + Name="my-application", + Platform="APNS", + Attributes={}, + ) + application_arn = platform_application['PlatformApplicationArn'] + + endpoint = conn.create_platform_endpoint( + PlatformApplicationArn=application_arn, + Token="some_unique_id", + CustomUserData="some user data", + Attributes={ + "Enabled": 'false', + }, + ) + + endpoint_arn = endpoint['EndpointArn'] + + conn.publish(Message="some message", MessageStructure="json", TargetArn=endpoint_arn) diff --git a/tests/test_sns/test_publishing_boto3.py b/tests/test_sns/test_publishing_boto3.py new file mode 100644 index 000000000..90d063971 --- /dev/null +++ b/tests/test_sns/test_publishing_boto3.py @@ -0,0 +1,89 @@ +from __future__ import unicode_literals +from six.moves.urllib.parse import parse_qs + +import boto3 +from freezegun import freeze_time +import httpretty +import sure # noqa + +from moto import mock_sns, mock_sqs + + +@mock_sqs +@mock_sns +def test_publish_to_sqs(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_topic(Name="some-topic") + response = conn.list_topics() + topic_arn = response["Topics"][0]['TopicArn'] + + sqs_conn = boto3.resource('sqs', region_name='us-east-1') + sqs_conn.create_queue(QueueName="test-queue") + + conn.subscribe(TopicArn=topic_arn, + Protocol="sqs", + Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue") + + conn.publish(TopicArn=topic_arn, Message="my message") + + queue = sqs_conn.get_queue_by_name(QueueName="test-queue") + messages = queue.receive_messages(MaxNumberOfMessages=1) + messages[0].body.should.equal('my message') + + +@mock_sqs +@mock_sns +def test_publish_to_sqs_in_different_region(): + conn = boto3.client('sns', region_name='us-west-1') + conn.create_topic(Name="some-topic") + response = conn.list_topics() + topic_arn = response["Topics"][0]['TopicArn'] + + sqs_conn = boto3.resource('sqs', region_name='us-west-2') + sqs_conn.create_queue(QueueName="test-queue") + + conn.subscribe(TopicArn=topic_arn, + Protocol="sqs", + Endpoint="arn:aws:sqs:us-west-2:123456789012:test-queue") + + conn.publish(TopicArn=topic_arn, Message="my message") + + queue = sqs_conn.get_queue_by_name(QueueName="test-queue") + messages = queue.receive_messages(MaxNumberOfMessages=1) + messages[0].body.should.equal('my message') + + +@freeze_time("2013-01-01") +@mock_sns +def test_publish_to_http(): + httpretty.HTTPretty.register_uri( + method="POST", + uri="http://example.com/foobar", + ) + + conn = boto3.client('sns', region_name='us-east-1') + conn.create_topic(Name="some-topic") + response = conn.list_topics() + topic_arn = response["Topics"][0]['TopicArn'] + + conn.subscribe(TopicArn=topic_arn, + Protocol="http", + Endpoint="http://example.com/foobar") + + response = conn.publish(TopicArn=topic_arn, Message="my message", Subject="my subject") + message_id = response['MessageId'] + + last_request = httpretty.last_request() + last_request.method.should.equal("POST") + parse_qs(last_request.body.decode('utf-8')).should.equal({ + "Type": ["Notification"], + "MessageId": [message_id], + "TopicArn": ["arn:aws:sns:{0}:123456789012:some-topic".format(conn._client_config.region_name)], + "Subject": ["my subject"], + "Message": ["my message"], + "Timestamp": ["2013-01-01T00:00:00.000Z"], + "SignatureVersion": ["1"], + "Signature": ["EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc="], + "SigningCertURL": ["https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"], + "UnsubscribeURL": ["https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"], + }) diff --git a/tests/test_sns/test_server.py b/tests/test_sns/test_server.py index 1813f7798..422763dac 100644 --- a/tests/test_sns/test_server.py +++ b/tests/test_sns/test_server.py @@ -1,8 +1,5 @@ from __future__ import unicode_literals -import json - -import re import sure # noqa import moto.server as server @@ -16,9 +13,10 @@ def test_sns_server_get(): backend = server.create_backend_app("sns") test_client = backend.test_client() - topic_data = test_client.action_json("CreateTopic", Name="test topic") - topic_arn = topic_data["CreateTopicResponse"]["CreateTopicResult"]["TopicArn"] - topics_data = test_client.action_json("ListTopics") - topics_arns = [t["TopicArn"] for t in topics_data["ListTopicsResponse"]["ListTopicsResult"]["Topics"]] + topic_data = test_client.action_data("CreateTopic", Name="test topic") + topic_data.should.contain("CreateTopicResult") + topic_data.should.contain("arn:aws:sns:us-east-1:123456789012:test topic") - assert topic_arn in topics_arns \ No newline at end of file + topics_data = test_client.action_data("ListTopics") + topics_data.should.contain("ListTopicsResult") + topic_data.should.contain("arn:aws:sns:us-east-1:123456789012:test topic") diff --git a/tests/test_sns/test_subscriptions_boto3.py b/tests/test_sns/test_subscriptions_boto3.py new file mode 100644 index 000000000..b884ca54d --- /dev/null +++ b/tests/test_sns/test_subscriptions_boto3.py @@ -0,0 +1,90 @@ +from __future__ import unicode_literals +import boto3 + +import sure # noqa + +from moto import mock_sns +from moto.sns.models import DEFAULT_PAGE_SIZE + + +@mock_sns +def test_creating_subscription(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_topic(Name="some-topic") + response = conn.list_topics() + topic_arn = response["Topics"][0]['TopicArn'] + + conn.subscribe(TopicArn=topic_arn, + Protocol="http", + Endpoint="http://example.com/") + + subscriptions = conn.list_subscriptions()["Subscriptions"] + subscriptions.should.have.length_of(1) + subscription = subscriptions[0] + subscription["TopicArn"].should.equal(topic_arn) + subscription["Protocol"].should.equal("http") + subscription["SubscriptionArn"].should.contain(topic_arn) + subscription["Endpoint"].should.equal("http://example.com/") + + # Now unsubscribe the subscription + conn.unsubscribe(SubscriptionArn=subscription["SubscriptionArn"]) + + # And there should be zero subscriptions left + subscriptions = conn.list_subscriptions()["Subscriptions"] + subscriptions.should.have.length_of(0) + + +@mock_sns +def test_getting_subscriptions_by_topic(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_topic(Name="topic1") + conn.create_topic(Name="topic2") + + response = conn.list_topics() + topics = response["Topics"] + topic1_arn = topics[0]['TopicArn'] + topic2_arn = topics[1]['TopicArn'] + + conn.subscribe(TopicArn=topic1_arn, + Protocol="http", + Endpoint="http://example1.com/") + conn.subscribe(TopicArn=topic2_arn, + Protocol="http", + Endpoint="http://example2.com/") + + topic1_subscriptions = conn.list_subscriptions_by_topic(TopicArn=topic1_arn)["Subscriptions"] + topic1_subscriptions.should.have.length_of(1) + topic1_subscriptions[0]['Endpoint'].should.equal("http://example1.com/") + + +@mock_sns +def test_subscription_paging(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_topic(Name="topic1") + + response = conn.list_topics() + topics = response["Topics"] + topic1_arn = topics[0]['TopicArn'] + + for index in range(DEFAULT_PAGE_SIZE + int(DEFAULT_PAGE_SIZE / 3)): + conn.subscribe(TopicArn=topic1_arn, + Protocol='email', + Endpoint='email_' + str(index) + '@test.com') + + all_subscriptions = conn.list_subscriptions() + all_subscriptions["Subscriptions"].should.have.length_of(DEFAULT_PAGE_SIZE) + next_token = all_subscriptions["NextToken"] + next_token.should.equal(str(DEFAULT_PAGE_SIZE)) + + all_subscriptions = conn.list_subscriptions(NextToken=next_token) + all_subscriptions["Subscriptions"].should.have.length_of(int(DEFAULT_PAGE_SIZE / 3)) + all_subscriptions.shouldnt.have("NextToken") + + topic1_subscriptions = conn.list_subscriptions_by_topic(TopicArn=topic1_arn) + topic1_subscriptions["Subscriptions"].should.have.length_of(DEFAULT_PAGE_SIZE) + next_token = topic1_subscriptions["NextToken"] + next_token.should.equal(str(DEFAULT_PAGE_SIZE)) + + topic1_subscriptions = conn.list_subscriptions_by_topic(TopicArn=topic1_arn, NextToken=next_token) + topic1_subscriptions["Subscriptions"].should.have.length_of(int(DEFAULT_PAGE_SIZE / 3)) + topic1_subscriptions.shouldnt.have("NextToken") diff --git a/tests/test_sns/test_topics_boto3.py b/tests/test_sns/test_topics_boto3.py new file mode 100644 index 000000000..b757a3750 --- /dev/null +++ b/tests/test_sns/test_topics_boto3.py @@ -0,0 +1,125 @@ +from __future__ import unicode_literals +import boto3 +import six +import json + +import sure # noqa + +from botocore.exceptions import ClientError +from moto import mock_sns +from moto.sns.models import DEFAULT_TOPIC_POLICY, DEFAULT_EFFECTIVE_DELIVERY_POLICY, DEFAULT_PAGE_SIZE + + +@mock_sns +def test_create_and_delete_topic(): + conn = boto3.client("sns", region_name="us-east-1") + conn.create_topic(Name="some-topic") + + topics_json = conn.list_topics() + topics = topics_json["Topics"] + topics.should.have.length_of(1) + topics[0]['TopicArn'].should.equal( + "arn:aws:sns:{0}:123456789012:some-topic" + .format(conn._client_config.region_name) + ) + + # Delete the topic + conn.delete_topic(TopicArn=topics[0]['TopicArn']) + + # And there should now be 0 topics + topics_json = conn.list_topics() + topics = topics_json["Topics"] + topics.should.have.length_of(0) + + +@mock_sns +def test_get_missing_topic(): + conn = boto3.client("sns", region_name="us-east-1") + conn.get_topic_attributes.when.called_with(TopicArn="a-fake-arn").should.throw(ClientError) + + +@mock_sns +def test_create_topic_in_multiple_regions(): + for region in ['us-west-1', 'us-west-2']: + conn = boto3.client("sns", region_name=region) + conn.create_topic(Name="some-topic") + list(conn.list_topics()["Topics"]).should.have.length_of(1) + + +@mock_sns +def test_topic_corresponds_to_region(): + for region in ['us-east-1', 'us-west-2']: + conn = boto3.client("sns", region_name=region) + conn.create_topic(Name="some-topic") + topics_json = conn.list_topics() + topic_arn = topics_json["Topics"][0]['TopicArn'] + topic_arn.should.equal("arn:aws:sns:{0}:123456789012:some-topic".format(region)) + + +@mock_sns +def test_topic_attributes(): + conn = boto3.client("sns", region_name="us-east-1") + conn.create_topic(Name="some-topic") + + topics_json = conn.list_topics() + topic_arn = topics_json["Topics"][0]['TopicArn'] + + attributes = conn.get_topic_attributes(TopicArn=topic_arn)['Attributes'] + attributes["TopicArn"].should.equal( + "arn:aws:sns:{0}:123456789012:some-topic" + .format(conn._client_config.region_name) + ) + attributes["Owner"].should.equal('123456789012') + attributes["Policy"].should.equal(DEFAULT_TOPIC_POLICY) + attributes["DisplayName"].should.equal("") + attributes["SubscriptionsPending"].should.equal('0') + attributes["SubscriptionsConfirmed"].should.equal('0') + attributes["SubscriptionsDeleted"].should.equal('0') + attributes["DeliveryPolicy"].should.equal("") + attributes["EffectiveDeliveryPolicy"].should.equal(DEFAULT_EFFECTIVE_DELIVERY_POLICY) + + # boto can't handle prefix-mandatory strings: + # i.e. unicode on Python 2 -- u"foobar" + # and bytes on Python 3 -- b"foobar" + if six.PY2: + policy = json.dumps({b"foo": b"bar"}) + displayname = b"My display name" + delivery = json.dumps({b"http": {b"defaultHealthyRetryPolicy": {b"numRetries": 5}}}) + else: + policy = json.dumps({u"foo": u"bar"}) + displayname = u"My display name" + delivery = json.dumps({u"http": {u"defaultHealthyRetryPolicy": {u"numRetries": 5}}}) + conn.set_topic_attributes(TopicArn=topic_arn, + AttributeName="Policy", + AttributeValue=policy) + conn.set_topic_attributes(TopicArn=topic_arn, + AttributeName="DisplayName", + AttributeValue=displayname) + conn.set_topic_attributes(TopicArn=topic_arn, + AttributeName="DeliveryPolicy", + AttributeValue=delivery) + + attributes = conn.get_topic_attributes(TopicArn=topic_arn)['Attributes'] + attributes["Policy"].should.equal('{"foo": "bar"}') + attributes["DisplayName"].should.equal("My display name") + attributes["DeliveryPolicy"].should.equal('{"http": {"defaultHealthyRetryPolicy": {"numRetries": 5}}}') + + +@mock_sns +def test_topic_paging(): + conn = boto3.client("sns", region_name="us-east-1") + for index in range(DEFAULT_PAGE_SIZE + int(DEFAULT_PAGE_SIZE / 2)): + conn.create_topic(Name="some-topic_" + str(index)) + + response = conn.list_topics() + topics_list = response["Topics"] + next_token = response["NextToken"] + + len(topics_list).should.equal(DEFAULT_PAGE_SIZE) + int(next_token).should.equal(DEFAULT_PAGE_SIZE) + + response = conn.list_topics(NextToken=next_token) + topics_list = response["Topics"] + response.shouldnt.have("NextToken") + + topics_list.should.have.length_of(int(DEFAULT_PAGE_SIZE / 2))