Add exact Number, exact String.Array and attribute key matching to SNS subscription filter policy and validate filter policy
This commit is contained in:
		
							parent
							
								
									94fd5c4128
								
							
						
					
					
						commit
						d8a922811c
					
				| @ -40,3 +40,11 @@ class InvalidParameterValue(RESTError): | |||||||
|     def __init__(self, message): |     def __init__(self, message): | ||||||
|         super(InvalidParameterValue, self).__init__( |         super(InvalidParameterValue, self).__init__( | ||||||
|             "InvalidParameterValue", message) |             "InvalidParameterValue", message) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class InternalError(RESTError): | ||||||
|  |     code = 500 | ||||||
|  | 
 | ||||||
|  |     def __init__(self, message): | ||||||
|  |         super(InternalError, self).__init__( | ||||||
|  |             "InternalFailure", message) | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ from moto.awslambda import lambda_backends | |||||||
| 
 | 
 | ||||||
| from .exceptions import ( | from .exceptions import ( | ||||||
|     SNSNotFoundError, DuplicateSnsEndpointError, SnsEndpointDisabled, SNSInvalidParameter, |     SNSNotFoundError, DuplicateSnsEndpointError, SnsEndpointDisabled, SNSInvalidParameter, | ||||||
|     InvalidParameterValue |     InvalidParameterValue, InternalError | ||||||
| ) | ) | ||||||
| from .utils import make_arn_for_topic, make_arn_for_subscription | from .utils import make_arn_for_topic, make_arn_for_subscription | ||||||
| 
 | 
 | ||||||
| @ -131,13 +131,47 @@ class Subscription(BaseModel): | |||||||
|             message_attributes = {} |             message_attributes = {} | ||||||
| 
 | 
 | ||||||
|         def _field_match(field, rules, message_attributes): |         def _field_match(field, rules, message_attributes): | ||||||
|             if field not in message_attributes: |  | ||||||
|                 return False |  | ||||||
|             for rule in rules: |             for rule in rules: | ||||||
|  |                 #  TODO: boolean value matching is not supported, SNS behavior unknown | ||||||
|                 if isinstance(rule, six.string_types): |                 if isinstance(rule, six.string_types): | ||||||
|                     # only string value matching is supported |                     if field not in message_attributes: | ||||||
|  |                         return False | ||||||
|                     if message_attributes[field]['Value'] == rule: |                     if message_attributes[field]['Value'] == rule: | ||||||
|                         return True |                         return True | ||||||
|  |                     try: | ||||||
|  |                         json_data = json.loads(message_attributes[field]['Value']) | ||||||
|  |                         if rule in json_data: | ||||||
|  |                             return True | ||||||
|  |                     except (ValueError, TypeError): | ||||||
|  |                         pass | ||||||
|  |                 if isinstance(rule, (six.integer_types, float)): | ||||||
|  |                     if field not in message_attributes: | ||||||
|  |                         return False | ||||||
|  |                     if message_attributes[field]['Type'] == 'Number': | ||||||
|  |                         attribute_values = [message_attributes[field]['Value']] | ||||||
|  |                     elif message_attributes[field]['Type'] == 'String.Array': | ||||||
|  |                         try: | ||||||
|  |                             attribute_values = json.loads(message_attributes[field]['Value']) | ||||||
|  |                             if not isinstance(attribute_values, list): | ||||||
|  |                                 attribute_values = [attribute_values] | ||||||
|  |                         except (ValueError, TypeError): | ||||||
|  |                             return False | ||||||
|  |                     else: | ||||||
|  |                         return False | ||||||
|  | 
 | ||||||
|  |                     for attribute_values in attribute_values: | ||||||
|  |                         # Even the offical documentation states a 5 digits of accuracy after the decimal point for numerics, in reality it is 6 | ||||||
|  |                         # https://docs.aws.amazon.com/sns/latest/dg/sns-subscription-filter-policies.html#subscription-filter-policy-constraints | ||||||
|  |                         if int(attribute_values * 1000000) == int(rule * 1000000): | ||||||
|  |                             return True | ||||||
|  |                 if isinstance(rule, dict): | ||||||
|  |                     keyword = list(rule.keys())[0] | ||||||
|  |                     attributes = list(rule.values())[0] | ||||||
|  |                     if keyword == 'exists': | ||||||
|  |                         if attributes and field in message_attributes: | ||||||
|  |                             return True | ||||||
|  |                         elif not attributes and field not in message_attributes: | ||||||
|  |                             return True | ||||||
|             return False |             return False | ||||||
| 
 | 
 | ||||||
|         return all(_field_match(field, rules, message_attributes) |         return all(_field_match(field, rules, message_attributes) | ||||||
| @ -421,7 +455,49 @@ class SNSBackend(BaseBackend): | |||||||
|         subscription.attributes[name] = value |         subscription.attributes[name] = value | ||||||
| 
 | 
 | ||||||
|         if name == 'FilterPolicy': |         if name == 'FilterPolicy': | ||||||
|             subscription._filter_policy = json.loads(value) |             filter_policy = json.loads(value) | ||||||
|  |             self._validate_filter_policy(filter_policy) | ||||||
|  |             subscription._filter_policy = filter_policy | ||||||
|  | 
 | ||||||
|  |     def _validate_filter_policy(self, value): | ||||||
|  |         # TODO: extend validation checks | ||||||
|  |         combinations = 1 | ||||||
|  |         for rules in six.itervalues(value): | ||||||
|  |             combinations *= len(rules) | ||||||
|  |         # Even the offical documentation states the total combination of values must not exceed 100, in reality it is 150 | ||||||
|  |         # https://docs.aws.amazon.com/sns/latest/dg/sns-subscription-filter-policies.html#subscription-filter-policy-constraints | ||||||
|  |         if combinations > 150: | ||||||
|  |             raise SNSInvalidParameter("Invalid parameter: FilterPolicy: Filter policy is too complex") | ||||||
|  | 
 | ||||||
|  |         for field, rules in six.iteritems(value): | ||||||
|  |             for rule in rules: | ||||||
|  |                 if rule is None: | ||||||
|  |                     continue | ||||||
|  |                 if isinstance(rule, six.string_types): | ||||||
|  |                     continue | ||||||
|  |                 if isinstance(rule, bool): | ||||||
|  |                     continue | ||||||
|  |                 if isinstance(rule, (six.integer_types, float)): | ||||||
|  |                     if rule <= -1000000000 or rule >= 1000000000: | ||||||
|  |                         raise InternalError("Unknown") | ||||||
|  |                     continue | ||||||
|  |                 if isinstance(rule, dict): | ||||||
|  |                     keyword = list(rule.keys())[0] | ||||||
|  |                     attributes = list(rule.values())[0] | ||||||
|  |                     if keyword == 'anything-but': | ||||||
|  |                         continue | ||||||
|  |                     elif keyword == 'exists': | ||||||
|  |                         if not isinstance(attributes, bool): | ||||||
|  |                             raise SNSInvalidParameter("Invalid parameter: FilterPolicy: exists match pattern must be either true or false.") | ||||||
|  |                         continue | ||||||
|  |                     elif keyword == 'numeric': | ||||||
|  |                         continue | ||||||
|  |                     elif keyword == 'prefix': | ||||||
|  |                         continue | ||||||
|  |                     else: | ||||||
|  |                         raise SNSInvalidParameter("Invalid parameter: FilterPolicy: Unrecognized match type {type}".format(type=keyword)) | ||||||
|  | 
 | ||||||
|  |                 raise SNSInvalidParameter("Invalid parameter: FilterPolicy: Match value must be String, number, true, false, or null") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| sns_backends = {} | sns_backends = {} | ||||||
|  | |||||||
| @ -57,7 +57,16 @@ class SNSResponse(BaseResponse): | |||||||
| 
 | 
 | ||||||
|             transform_value = None |             transform_value = None | ||||||
|             if 'StringValue' in value: |             if 'StringValue' in value: | ||||||
|                 transform_value = value['StringValue'] |                 if data_type == 'Number': | ||||||
|  |                     try: | ||||||
|  |                         transform_value = float(value['StringValue']) | ||||||
|  |                     except ValueError: | ||||||
|  |                         raise InvalidParameterValue( | ||||||
|  |                             "An error occurred (ParameterValueInvalid) " | ||||||
|  |                             "when calling the Publish operation: " | ||||||
|  |                             "Could not cast message attribute '{0}' value to number.".format(name)) | ||||||
|  |                 else: | ||||||
|  |                     transform_value = value['StringValue'] | ||||||
|             elif 'BinaryValue' in value: |             elif 'BinaryValue' in value: | ||||||
|                 transform_value = value['BinaryValue'] |                 transform_value = value['BinaryValue'] | ||||||
|             if not transform_value: |             if not transform_value: | ||||||
|  | |||||||
| @ -109,6 +109,17 @@ def test_publish_to_sqs_bad(): | |||||||
|             }}) |             }}) | ||||||
|     except ClientError as err: |     except ClientError as err: | ||||||
|         err.response['Error']['Code'].should.equal('InvalidParameterValue') |         err.response['Error']['Code'].should.equal('InvalidParameterValue') | ||||||
|  |     try: | ||||||
|  |         # Test Number DataType, with a non numeric value | ||||||
|  |         conn.publish( | ||||||
|  |             TopicArn=topic_arn, Message=message, | ||||||
|  |             MessageAttributes={'price': { | ||||||
|  |                 'DataType': 'Number', | ||||||
|  |                 'StringValue': 'error' | ||||||
|  |             }}) | ||||||
|  |     except ClientError as err: | ||||||
|  |         err.response['Error']['Code'].should.equal('InvalidParameterValue') | ||||||
|  |         err.response['Error']['Message'].should.equal("An error occurred (ParameterValueInvalid) when calling the Publish operation: Could not cast message attribute 'price' value to number.") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @mock_sqs | @mock_sqs | ||||||
| @ -487,3 +498,380 @@ def test_filtering_exact_string_no_attributes_no_match(): | |||||||
|     message_attributes = [ |     message_attributes = [ | ||||||
|         json.loads(m.body)['MessageAttributes'] for m in messages] |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|     message_attributes.should.equal([]) |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_exact_number_int(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'Number', | ||||||
|  |                                      'StringValue': '100'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'price': {'Type': 'Number', 'Value': 100}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_exact_number_float(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100.1]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'Number', | ||||||
|  |                                      'StringValue': '100.1'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'price': {'Type': 'Number', 'Value': 100.1}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_exact_number_float_accuracy(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100.123456789]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'Number', | ||||||
|  |                                      'StringValue': '100.1234561'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'price': {'Type': 'Number', 'Value': 100.1234561}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_exact_number_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'Number', | ||||||
|  |                                      'StringValue': '101'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_exact_number_with_string_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'String', | ||||||
|  |                                      'StringValue': '100'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_string_array_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'customer_interests': ['basketball', 'baseball']}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'customer_interests': {'DataType': 'String.Array', | ||||||
|  |                                      'StringValue': json.dumps(['basketball', 'rugby'])}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'customer_interests': {'Type': 'String.Array', 'Value': json.dumps(['basketball', 'rugby'])}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_string_array_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'customer_interests': ['baseball']}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no_match', | ||||||
|  |         MessageAttributes={'customer_interests': {'DataType': 'String.Array', | ||||||
|  |                                      'StringValue': json.dumps(['basketball', 'rugby'])}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_string_array_with_number_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100, 500]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'String.Array', | ||||||
|  |                                      'StringValue': json.dumps([100, 50])}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'price': {'Type': 'String.Array', 'Value': json.dumps([100, 50])}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_string_array_with_number_float_accuracy_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100.123456789, 500]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'String.Array', | ||||||
|  |                                      'StringValue': json.dumps([100.1234561, 50])}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'price': {'Type': 'String.Array', 'Value': json.dumps([100.1234561, 50])}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | # this is the correct behavior from SNS | ||||||
|  | def test_filtering_string_array_with_number_no_array_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100, 500]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'String.Array', | ||||||
|  |                                      'StringValue': '100'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'price': {'Type': 'String.Array', 'Value': '100'}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_string_array_with_number_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [500]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no_match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'String.Array', | ||||||
|  |                                      'StringValue': json.dumps([100, 50])}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | # this is the correct behavior from SNS | ||||||
|  | def test_filtering_string_array_with_string_no_array_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'price': [100]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no_match', | ||||||
|  |         MessageAttributes={'price': {'DataType': 'String.Array', | ||||||
|  |                                      'StringValue': 'one hundread'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_attribute_key_exists_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'store': [{'exists': True}]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'store': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'example_corp'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'store': {'Type': 'String', 'Value': 'example_corp'}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_attribute_key_exists_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'store': [{'exists': True}]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no match', | ||||||
|  |         MessageAttributes={'event': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'order_cancelled'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_attribute_key_not_exists_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'store': [{'exists': False}]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'event': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'order_cancelled'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal(['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal( | ||||||
|  |         [{'event': {'Type': 'String', 'Value': 'order_cancelled'}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_attribute_key_not_exists_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'store': [{'exists': False}]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no match', | ||||||
|  |         MessageAttributes={'store': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'example_corp'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_all_AND_matching_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'store': [{'exists': True}], | ||||||
|  |          'event': ['order_cancelled'], | ||||||
|  |          'customer_interests': ['basketball', 'baseball'], | ||||||
|  |          'price': [100]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='match', | ||||||
|  |         MessageAttributes={'store': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'example_corp'}, | ||||||
|  |                            'event': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'order_cancelled'}, | ||||||
|  |                            'customer_interests': {'DataType': 'String.Array', | ||||||
|  |                                                   'StringValue': json.dumps(['basketball', 'rugby'])}, | ||||||
|  |                            'price': {'DataType': 'Number', | ||||||
|  |                                      'StringValue': '100'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal( | ||||||
|  |         ['match']) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([{ | ||||||
|  |         'store': {'Type': 'String', 'Value': 'example_corp'}, | ||||||
|  |         'event': {'Type': 'String', 'Value': 'order_cancelled'}, | ||||||
|  |         'customer_interests': {'Type': 'String.Array', 'Value': json.dumps(['basketball', 'rugby'])}, | ||||||
|  |         'price': {'Type': 'Number', 'Value': 100}}]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_sqs | ||||||
|  | @mock_sns | ||||||
|  | def test_filtering_all_AND_matching_no_match(): | ||||||
|  |     topic, subscription, queue = _setup_filter_policy_test( | ||||||
|  |         {'store': [{'exists': True}], | ||||||
|  |          'event': ['order_cancelled'], | ||||||
|  |          'customer_interests': ['basketball', 'baseball'], | ||||||
|  |          'price': [100], | ||||||
|  |          "encrypted": [False]}) | ||||||
|  | 
 | ||||||
|  |     topic.publish( | ||||||
|  |         Message='no match', | ||||||
|  |         MessageAttributes={'store': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'example_corp'}, | ||||||
|  |                            'event': {'DataType': 'String', | ||||||
|  |                                      'StringValue': 'order_cancelled'}, | ||||||
|  |                            'customer_interests': {'DataType': 'String.Array', | ||||||
|  |                                                   'StringValue': json.dumps(['basketball', 'rugby'])}, | ||||||
|  |                            'price': {'DataType': 'Number', | ||||||
|  |                                      'StringValue': '100'}}) | ||||||
|  | 
 | ||||||
|  |     messages = queue.receive_messages(MaxNumberOfMessages=5) | ||||||
|  |     message_bodies = [json.loads(m.body)['Message'] for m in messages] | ||||||
|  |     message_bodies.should.equal([]) | ||||||
|  |     message_attributes = [ | ||||||
|  |         json.loads(m.body)['MessageAttributes'] for m in messages] | ||||||
|  |     message_attributes.should.equal([]) | ||||||
|  | |||||||
| @ -201,7 +201,9 @@ def test_creating_subscription_with_attributes(): | |||||||
|         "store": ["example_corp"], |         "store": ["example_corp"], | ||||||
|         "event": ["order_cancelled"], |         "event": ["order_cancelled"], | ||||||
|         "encrypted": [False], |         "encrypted": [False], | ||||||
|         "customer_interests": ["basketball", "baseball"] |         "customer_interests": ["basketball", "baseball"], | ||||||
|  |         "price": [100, 100.12], | ||||||
|  |         "error": [None] | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     conn.subscribe(TopicArn=topic_arn, |     conn.subscribe(TopicArn=topic_arn, | ||||||
| @ -294,7 +296,9 @@ def test_set_subscription_attributes(): | |||||||
|         "store": ["example_corp"], |         "store": ["example_corp"], | ||||||
|         "event": ["order_cancelled"], |         "event": ["order_cancelled"], | ||||||
|         "encrypted": [False], |         "encrypted": [False], | ||||||
|         "customer_interests": ["basketball", "baseball"] |         "customer_interests": ["basketball", "baseball"], | ||||||
|  |         "price": [100, 100.12], | ||||||
|  |         "error": [None] | ||||||
|     }) |     }) | ||||||
|     conn.set_subscription_attributes( |     conn.set_subscription_attributes( | ||||||
|         SubscriptionArn=subscription_arn, |         SubscriptionArn=subscription_arn, | ||||||
| @ -332,6 +336,77 @@ def test_set_subscription_attributes(): | |||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @mock_sns | ||||||
|  | def test_subscribe_invalid_filter_policy(): | ||||||
|  |     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'] | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         conn.subscribe(TopicArn = topic_arn, | ||||||
|  |                        Protocol = 'http', | ||||||
|  |                        Endpoint = 'http://example.com/', | ||||||
|  |                        Attributes = { | ||||||
|  |                            'FilterPolicy': json.dumps({ | ||||||
|  |                                'store': [str(i) for i in range(151)] | ||||||
|  |                            }) | ||||||
|  |                        }) | ||||||
|  |     except ClientError as err: | ||||||
|  |         err.response['Error']['Code'].should.equal('InvalidParameter') | ||||||
|  |         err.response['Error']['Message'].should.equal('Invalid parameter: FilterPolicy: Filter policy is too complex') | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         conn.subscribe(TopicArn = topic_arn, | ||||||
|  |                        Protocol = 'http', | ||||||
|  |                        Endpoint = 'http://example.com/', | ||||||
|  |                        Attributes = { | ||||||
|  |                            'FilterPolicy': json.dumps({ | ||||||
|  |                                'store': [['example_corp']] | ||||||
|  |                            }) | ||||||
|  |                        }) | ||||||
|  |     except ClientError as err: | ||||||
|  |         err.response['Error']['Code'].should.equal('InvalidParameter') | ||||||
|  |         err.response['Error']['Message'].should.equal('Invalid parameter: FilterPolicy: Match value must be String, number, true, false, or null') | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         conn.subscribe(TopicArn = topic_arn, | ||||||
|  |                        Protocol = 'http', | ||||||
|  |                        Endpoint = 'http://example.com/', | ||||||
|  |                        Attributes = { | ||||||
|  |                            'FilterPolicy': json.dumps({ | ||||||
|  |                                'store': [{'exists': None}] | ||||||
|  |                            }) | ||||||
|  |                        }) | ||||||
|  |     except ClientError as err: | ||||||
|  |         err.response['Error']['Code'].should.equal('InvalidParameter') | ||||||
|  |         err.response['Error']['Message'].should.equal('Invalid parameter: FilterPolicy: exists match pattern must be either true or false.') | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         conn.subscribe(TopicArn = topic_arn, | ||||||
|  |                        Protocol = 'http', | ||||||
|  |                        Endpoint = 'http://example.com/', | ||||||
|  |                        Attributes = { | ||||||
|  |                            'FilterPolicy': json.dumps({ | ||||||
|  |                                 'store': [{'error': True}] | ||||||
|  |                             }) | ||||||
|  |                        }) | ||||||
|  |     except ClientError as err: | ||||||
|  |         err.response['Error']['Code'].should.equal('InvalidParameter') | ||||||
|  |         err.response['Error']['Message'].should.equal('Invalid parameter: FilterPolicy: Unrecognized match type error') | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         conn.subscribe(TopicArn = topic_arn, | ||||||
|  |                        Protocol = 'http', | ||||||
|  |                        Endpoint = 'http://example.com/', | ||||||
|  |                        Attributes = { | ||||||
|  |                            'FilterPolicy': json.dumps({ | ||||||
|  |                                'store': [1000000001] | ||||||
|  |                            }) | ||||||
|  |                        }) | ||||||
|  |     except ClientError as err: | ||||||
|  |         err.response['Error']['Code'].should.equal('InternalFailure') | ||||||
|  | 
 | ||||||
| @mock_sns | @mock_sns | ||||||
| def test_check_not_opted_out(): | def test_check_not_opted_out(): | ||||||
|     conn = boto3.client('sns', region_name='us-east-1') |     conn = boto3.client('sns', region_name='us-east-1') | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user