From 726775678cadc53f7340fa69e8e7c4effefa39df Mon Sep 17 00:00:00 2001 From: gruebel Date: Sat, 12 Oct 2019 20:36:15 +0200 Subject: [PATCH] Add sns.tag_resource --- IMPLEMENTATION_COVERAGE.md | 4 +- moto/sns/exceptions.py | 16 ++++ moto/sns/models.py | 17 +++- moto/sns/responses.py | 19 +++- tests/test_sns/test_topics_boto3.py | 138 +++++++++++++++++++++++++++- 5 files changed, 183 insertions(+), 11 deletions(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 62565a067..972031ad2 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -5866,7 +5866,7 @@ - [ ] update_job ## sns -52% implemented +55% implemented - [ ] add_permission - [ ] check_if_phone_number_is_opted_out - [ ] confirm_subscription @@ -5897,7 +5897,7 @@ - [X] set_subscription_attributes - [ ] set_topic_attributes - [X] subscribe -- [ ] tag_resource +- [x] tag_resource - [X] unsubscribe - [ ] untag_resource diff --git a/moto/sns/exceptions.py b/moto/sns/exceptions.py index 706b3b5cc..6d29e7acb 100644 --- a/moto/sns/exceptions.py +++ b/moto/sns/exceptions.py @@ -10,6 +10,14 @@ class SNSNotFoundError(RESTError): "NotFound", message) +class ResourceNotFoundError(RESTError): + code = 404 + + def __init__(self): + super(ResourceNotFoundError, self).__init__( + 'ResourceNotFound', 'Resource does not exist') + + class DuplicateSnsEndpointError(RESTError): code = 400 @@ -42,6 +50,14 @@ class InvalidParameterValue(RESTError): "InvalidParameterValue", message) +class TagLimitExceededError(RESTError): + code = 400 + + def __init__(self): + super(TagLimitExceededError, self).__init__( + 'TagLimitExceeded', 'Could not complete request: tag quota of per resource exceeded') + + class InternalError(RESTError): code = 500 diff --git a/moto/sns/models.py b/moto/sns/models.py index d7f15338b..55b243e45 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -18,7 +18,7 @@ from moto.awslambda import lambda_backends from .exceptions import ( SNSNotFoundError, DuplicateSnsEndpointError, SnsEndpointDisabled, SNSInvalidParameter, - InvalidParameterValue, InternalError + InvalidParameterValue, InternalError, ResourceNotFoundError, TagLimitExceededError ) from .utils import make_arn_for_topic, make_arn_for_subscription @@ -504,8 +504,23 @@ class SNSBackend(BaseBackend): raise SNSInvalidParameter("Invalid parameter: FilterPolicy: Match value must be String, number, true, false, or null") def list_tags_for_resource(self, resource_arn): + if resource_arn not in self.topics: + raise ResourceNotFoundError + return self.topics[resource_arn]._tags + def tag_resource(self, resource_arn, tags): + if resource_arn not in self.topics: + raise ResourceNotFoundError + + updated_tags = self.topics[resource_arn]._tags.copy() + updated_tags.update(tags) + + if len(updated_tags) > 50: + raise TagLimitExceededError + + self.topics[resource_arn]._tags = updated_tags + sns_backends = {} for region in Session().get_available_regions('sns'): diff --git a/moto/sns/responses.py b/moto/sns/responses.py index 7cc085be1..3b8383d1f 100644 --- a/moto/sns/responses.py +++ b/moto/sns/responses.py @@ -699,15 +699,19 @@ class SNSResponse(BaseResponse): def list_tags_for_resource(self): arn = self._get_param('ResourceArn') - if arn not in self.backend.topics: - error_response = self._error('ResourceNotFound', 'Resource does not exist') - return error_response, dict(status=404) - result = self.backend.list_tags_for_resource(arn) template = self.response_template(LIST_TAGS_FOR_RESOURCE_TEMPLATE) return template.render(tags=result) + def tag_resource(self): + arn = self._get_param('ResourceArn') + tags = self._get_tags() + + self.backend.tag_resource(arn, tags) + + return self.response_template(TAG_RESOURCE_TEMPLATE).render() + CREATE_TOPIC_TEMPLATE = """ @@ -1105,3 +1109,10 @@ LIST_TAGS_FOR_RESOURCE_TEMPLATE = """ + + + fd4ab1da-692f-50a7-95ad-e7c665877d98 + +""" diff --git a/tests/test_sns/test_topics_boto3.py b/tests/test_sns/test_topics_boto3.py index 4a994e724..ace825af5 100644 --- a/tests/test_sns/test_topics_boto3.py +++ b/tests/test_sns/test_topics_boto3.py @@ -47,8 +47,8 @@ def test_create_topic_with_attributes(): @mock_sns def test_create_topic_with_tags(): conn = boto3.client("sns", region_name="us-east-1") - conn.create_topic( - Name='some-topic-with-attribute', + response = conn.create_topic( + Name='some-topic-with-tags', Tags=[ { 'Key': 'tag_key_1', @@ -60,8 +60,7 @@ def test_create_topic_with_tags(): } ] ) - topics_json = conn.list_topics() - topic_arn = topics_json["Topics"][0]['TopicArn'] + topic_arn = response['TopicArn'] conn.list_tags_for_resource(ResourceArn=topic_arn)['Tags'].should.equal([ { @@ -231,3 +230,134 @@ def test_add_remove_permissions(): TopicArn=response['TopicArn'], Label='Test1234' ) + + +@mock_sns +def test_tag_topic(): + conn = boto3.client('sns', region_name='us-east-1') + response = conn.create_topic( + Name = 'some-topic-with-tags' + ) + topic_arn = response['TopicArn'] + + conn.tag_resource( + ResourceArn=topic_arn, + Tags=[ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_1' + } + ] + ) + conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_1' + } + ]) + + conn.tag_resource( + ResourceArn=topic_arn, + Tags=[ + { + 'Key': 'tag_key_2', + 'Value': 'tag_value_2' + } + ] + ) + conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_1' + }, + { + 'Key': 'tag_key_2', + 'Value': 'tag_value_2' + } + ]) + + conn.tag_resource( + ResourceArn = topic_arn, + Tags = [ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_X' + } + ] + ) + conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_X' + }, + { + 'Key': 'tag_key_2', + 'Value': 'tag_value_2' + } + ]) + + +@mock_sns +def test_list_tags_for_resource_error(): + conn = boto3.client('sns', region_name = 'us-east-1') + conn.create_topic( + Name = 'some-topic-with-tags', + Tags = [ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_X' + } + ] + ) + + conn.list_tags_for_resource.when.called_with( + ResourceArn = 'not-existing-topic' + ).should.throw( + ClientError, + 'Resource does not exist' + ) + + +@mock_sns +def test_tag_resource_errors(): + conn = boto3.client('sns', region_name = 'us-east-1') + response = conn.create_topic( + Name = 'some-topic-with-tags', + Tags = [ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_X' + } + ] + ) + topic_arn = response['TopicArn'] + + conn.tag_resource.when.called_with( + ResourceArn = 'not-existing-topic', + Tags = [ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_1' + } + ] + ).should.throw( + ClientError, + 'Resource does not exist' + ) + + too_many_tags = [{'Key': 'tag_key_{}'.format(i), 'Value': 'tag_value_{}'.format(i)} for i in range(51)] + conn.tag_resource.when.called_with( + ResourceArn = topic_arn, + Tags = too_many_tags + ).should.throw( + ClientError, + 'Could not complete request: tag quota of per resource exceeded' + ) + + # when the request fails, the tags should not be updated + conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([ + { + 'Key': 'tag_key_1', + 'Value': 'tag_value_X' + } + ])