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