commit
f34298cffd
10
.travis.yml
10
.travis.yml
@ -7,6 +7,7 @@ python:
|
|||||||
- 2.7
|
- 2.7
|
||||||
- 3.6
|
- 3.6
|
||||||
- 3.7
|
- 3.7
|
||||||
|
- 3.8
|
||||||
env:
|
env:
|
||||||
- TEST_SERVER_MODE=false
|
- TEST_SERVER_MODE=false
|
||||||
- TEST_SERVER_MODE=true
|
- TEST_SERVER_MODE=true
|
||||||
@ -17,7 +18,14 @@ install:
|
|||||||
python setup.py sdist
|
python setup.py sdist
|
||||||
|
|
||||||
if [ "$TEST_SERVER_MODE" = "true" ]; then
|
if [ "$TEST_SERVER_MODE" = "true" ]; then
|
||||||
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${TRAVIS_PYTHON_VERSION}-stretch /moto/travis_moto_server.sh &
|
if [ "$TRAVIS_PYTHON_VERSION" = "3.8" ]; then
|
||||||
|
# Python 3.8 does not provide Stretch images yet [1]
|
||||||
|
# [1] https://github.com/docker-library/python/issues/428
|
||||||
|
PYTHON_DOCKER_TAG=${TRAVIS_PYTHON_VERSION}-buster
|
||||||
|
else
|
||||||
|
PYTHON_DOCKER_TAG=${TRAVIS_PYTHON_VERSION}-stretch
|
||||||
|
fi
|
||||||
|
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${PYTHON_DOCKER_TAG} /moto/travis_moto_server.sh &
|
||||||
fi
|
fi
|
||||||
travis_retry pip install boto==2.45.0
|
travis_retry pip install boto==2.45.0
|
||||||
travis_retry pip install boto3
|
travis_retry pip install boto3
|
||||||
|
@ -700,6 +700,7 @@
|
|||||||
0% implemented
|
0% implemented
|
||||||
- [ ] associate_phone_number_with_user
|
- [ ] associate_phone_number_with_user
|
||||||
- [ ] associate_phone_numbers_with_voice_connector
|
- [ ] associate_phone_numbers_with_voice_connector
|
||||||
|
- [ ] associate_phone_numbers_with_voice_connector_group
|
||||||
- [ ] batch_delete_phone_number
|
- [ ] batch_delete_phone_number
|
||||||
- [ ] batch_suspend_user
|
- [ ] batch_suspend_user
|
||||||
- [ ] batch_unsuspend_user
|
- [ ] batch_unsuspend_user
|
||||||
@ -709,15 +710,19 @@
|
|||||||
- [ ] create_bot
|
- [ ] create_bot
|
||||||
- [ ] create_phone_number_order
|
- [ ] create_phone_number_order
|
||||||
- [ ] create_voice_connector
|
- [ ] create_voice_connector
|
||||||
|
- [ ] create_voice_connector_group
|
||||||
- [ ] delete_account
|
- [ ] delete_account
|
||||||
- [ ] delete_events_configuration
|
- [ ] delete_events_configuration
|
||||||
- [ ] delete_phone_number
|
- [ ] delete_phone_number
|
||||||
- [ ] delete_voice_connector
|
- [ ] delete_voice_connector
|
||||||
|
- [ ] delete_voice_connector_group
|
||||||
- [ ] delete_voice_connector_origination
|
- [ ] delete_voice_connector_origination
|
||||||
|
- [ ] delete_voice_connector_streaming_configuration
|
||||||
- [ ] delete_voice_connector_termination
|
- [ ] delete_voice_connector_termination
|
||||||
- [ ] delete_voice_connector_termination_credentials
|
- [ ] delete_voice_connector_termination_credentials
|
||||||
- [ ] disassociate_phone_number_from_user
|
- [ ] disassociate_phone_number_from_user
|
||||||
- [ ] disassociate_phone_numbers_from_voice_connector
|
- [ ] disassociate_phone_numbers_from_voice_connector
|
||||||
|
- [ ] disassociate_phone_numbers_from_voice_connector_group
|
||||||
- [ ] get_account
|
- [ ] get_account
|
||||||
- [ ] get_account_settings
|
- [ ] get_account_settings
|
||||||
- [ ] get_bot
|
- [ ] get_bot
|
||||||
@ -725,10 +730,14 @@
|
|||||||
- [ ] get_global_settings
|
- [ ] get_global_settings
|
||||||
- [ ] get_phone_number
|
- [ ] get_phone_number
|
||||||
- [ ] get_phone_number_order
|
- [ ] get_phone_number_order
|
||||||
|
- [ ] get_phone_number_settings
|
||||||
- [ ] get_user
|
- [ ] get_user
|
||||||
- [ ] get_user_settings
|
- [ ] get_user_settings
|
||||||
- [ ] get_voice_connector
|
- [ ] get_voice_connector
|
||||||
|
- [ ] get_voice_connector_group
|
||||||
|
- [ ] get_voice_connector_logging_configuration
|
||||||
- [ ] get_voice_connector_origination
|
- [ ] get_voice_connector_origination
|
||||||
|
- [ ] get_voice_connector_streaming_configuration
|
||||||
- [ ] get_voice_connector_termination
|
- [ ] get_voice_connector_termination
|
||||||
- [ ] get_voice_connector_termination_health
|
- [ ] get_voice_connector_termination_health
|
||||||
- [ ] invite_users
|
- [ ] invite_users
|
||||||
@ -737,11 +746,14 @@
|
|||||||
- [ ] list_phone_number_orders
|
- [ ] list_phone_number_orders
|
||||||
- [ ] list_phone_numbers
|
- [ ] list_phone_numbers
|
||||||
- [ ] list_users
|
- [ ] list_users
|
||||||
|
- [ ] list_voice_connector_groups
|
||||||
- [ ] list_voice_connector_termination_credentials
|
- [ ] list_voice_connector_termination_credentials
|
||||||
- [ ] list_voice_connectors
|
- [ ] list_voice_connectors
|
||||||
- [ ] logout_user
|
- [ ] logout_user
|
||||||
- [ ] put_events_configuration
|
- [ ] put_events_configuration
|
||||||
|
- [ ] put_voice_connector_logging_configuration
|
||||||
- [ ] put_voice_connector_origination
|
- [ ] put_voice_connector_origination
|
||||||
|
- [ ] put_voice_connector_streaming_configuration
|
||||||
- [ ] put_voice_connector_termination
|
- [ ] put_voice_connector_termination
|
||||||
- [ ] put_voice_connector_termination_credentials
|
- [ ] put_voice_connector_termination_credentials
|
||||||
- [ ] regenerate_security_token
|
- [ ] regenerate_security_token
|
||||||
@ -753,9 +765,11 @@
|
|||||||
- [ ] update_bot
|
- [ ] update_bot
|
||||||
- [ ] update_global_settings
|
- [ ] update_global_settings
|
||||||
- [ ] update_phone_number
|
- [ ] update_phone_number
|
||||||
|
- [ ] update_phone_number_settings
|
||||||
- [ ] update_user
|
- [ ] update_user
|
||||||
- [ ] update_user_settings
|
- [ ] update_user_settings
|
||||||
- [ ] update_voice_connector
|
- [ ] update_voice_connector
|
||||||
|
- [ ] update_voice_connector_group
|
||||||
|
|
||||||
## cloud9
|
## cloud9
|
||||||
0% implemented
|
0% implemented
|
||||||
@ -1525,6 +1539,10 @@
|
|||||||
- [ ] get_current_metric_data
|
- [ ] get_current_metric_data
|
||||||
- [ ] get_federation_token
|
- [ ] get_federation_token
|
||||||
- [ ] get_metric_data
|
- [ ] get_metric_data
|
||||||
|
- [ ] list_contact_flows
|
||||||
|
- [ ] list_hours_of_operations
|
||||||
|
- [ ] list_phone_numbers
|
||||||
|
- [ ] list_queues
|
||||||
- [ ] list_routing_profiles
|
- [ ] list_routing_profiles
|
||||||
- [ ] list_security_profiles
|
- [ ] list_security_profiles
|
||||||
- [ ] list_user_hierarchy_groups
|
- [ ] list_user_hierarchy_groups
|
||||||
@ -3244,7 +3262,7 @@
|
|||||||
- [ ] describe_events
|
- [ ] describe_events
|
||||||
|
|
||||||
## iam
|
## iam
|
||||||
61% implemented
|
60% implemented
|
||||||
- [ ] add_client_id_to_open_id_connect_provider
|
- [ ] add_client_id_to_open_id_connect_provider
|
||||||
- [X] add_role_to_instance_profile
|
- [X] add_role_to_instance_profile
|
||||||
- [X] add_user_to_group
|
- [X] add_user_to_group
|
||||||
@ -6029,8 +6047,8 @@
|
|||||||
- [ ] update_job
|
- [ ] update_job
|
||||||
|
|
||||||
## sns
|
## sns
|
||||||
57% implemented
|
63% implemented
|
||||||
- [ ] add_permission
|
- [X] add_permission
|
||||||
- [ ] check_if_phone_number_is_opted_out
|
- [ ] check_if_phone_number_is_opted_out
|
||||||
- [ ] confirm_subscription
|
- [ ] confirm_subscription
|
||||||
- [X] create_platform_application
|
- [X] create_platform_application
|
||||||
@ -6053,7 +6071,7 @@
|
|||||||
- [X] list_topics
|
- [X] list_topics
|
||||||
- [ ] opt_in_phone_number
|
- [ ] opt_in_phone_number
|
||||||
- [X] publish
|
- [X] publish
|
||||||
- [ ] remove_permission
|
- [X] remove_permission
|
||||||
- [X] set_endpoint_attributes
|
- [X] set_endpoint_attributes
|
||||||
- [ ] set_platform_application_attributes
|
- [ ] set_platform_application_attributes
|
||||||
- [ ] set_sms_attributes
|
- [ ] set_sms_attributes
|
||||||
@ -6065,7 +6083,7 @@
|
|||||||
- [X] untag_resource
|
- [X] untag_resource
|
||||||
|
|
||||||
## sqs
|
## sqs
|
||||||
65% implemented
|
85% implemented
|
||||||
- [X] add_permission
|
- [X] add_permission
|
||||||
- [X] change_message_visibility
|
- [X] change_message_visibility
|
||||||
- [ ] change_message_visibility_batch
|
- [ ] change_message_visibility_batch
|
||||||
@ -6073,16 +6091,16 @@
|
|||||||
- [X] delete_message
|
- [X] delete_message
|
||||||
- [ ] delete_message_batch
|
- [ ] delete_message_batch
|
||||||
- [X] delete_queue
|
- [X] delete_queue
|
||||||
- [ ] get_queue_attributes
|
- [X] get_queue_attributes
|
||||||
- [ ] get_queue_url
|
- [X] get_queue_url
|
||||||
- [X] list_dead_letter_source_queues
|
- [X] list_dead_letter_source_queues
|
||||||
- [ ] list_queue_tags
|
- [X] list_queue_tags
|
||||||
- [X] list_queues
|
- [X] list_queues
|
||||||
- [X] purge_queue
|
- [X] purge_queue
|
||||||
- [ ] receive_message
|
- [ ] receive_message
|
||||||
- [X] remove_permission
|
- [X] remove_permission
|
||||||
- [X] send_message
|
- [X] send_message
|
||||||
- [ ] send_message_batch
|
- [X] send_message_batch
|
||||||
- [X] set_queue_attributes
|
- [X] set_queue_attributes
|
||||||
- [X] tag_queue
|
- [X] tag_queue
|
||||||
- [X] untag_queue
|
- [X] untag_queue
|
||||||
|
100
moto/__init__.py
100
moto/__init__.py
@ -1,59 +1,59 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import logging
|
# import logging
|
||||||
# logging.getLogger('boto').setLevel(logging.CRITICAL)
|
# logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||||
|
|
||||||
__title__ = 'moto'
|
__title__ = 'moto'
|
||||||
__version__ = '1.3.14.dev'
|
__version__ = '1.3.14.dev'
|
||||||
|
|
||||||
from .acm import mock_acm # flake8: noqa
|
from .acm import mock_acm # noqa
|
||||||
from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa
|
from .apigateway import mock_apigateway, mock_apigateway_deprecated # noqa
|
||||||
from .athena import mock_athena # flake8: noqa
|
from .athena import mock_athena # noqa
|
||||||
from .autoscaling import mock_autoscaling, mock_autoscaling_deprecated # flake8: noqa
|
from .autoscaling import mock_autoscaling, mock_autoscaling_deprecated # noqa
|
||||||
from .awslambda import mock_lambda, mock_lambda_deprecated # flake8: noqa
|
from .awslambda import mock_lambda, mock_lambda_deprecated # noqa
|
||||||
from .cloudformation import mock_cloudformation, mock_cloudformation_deprecated # flake8: noqa
|
from .cloudformation import mock_cloudformation, mock_cloudformation_deprecated # noqa
|
||||||
from .cloudwatch import mock_cloudwatch, mock_cloudwatch_deprecated # flake8: noqa
|
from .cloudwatch import mock_cloudwatch, mock_cloudwatch_deprecated # noqa
|
||||||
from .cognitoidentity import mock_cognitoidentity, mock_cognitoidentity_deprecated # flake8: noqa
|
from .cognitoidentity import mock_cognitoidentity, mock_cognitoidentity_deprecated # noqa
|
||||||
from .cognitoidp import mock_cognitoidp, mock_cognitoidp_deprecated # flake8: noqa
|
from .cognitoidp import mock_cognitoidp, mock_cognitoidp_deprecated # noqa
|
||||||
from .config import mock_config # flake8: noqa
|
from .config import mock_config # noqa
|
||||||
from .datapipeline import mock_datapipeline, mock_datapipeline_deprecated # flake8: noqa
|
from .datapipeline import mock_datapipeline, mock_datapipeline_deprecated # noqa
|
||||||
from .dynamodb import mock_dynamodb, mock_dynamodb_deprecated # flake8: noqa
|
from .dynamodb import mock_dynamodb, mock_dynamodb_deprecated # noqa
|
||||||
from .dynamodb2 import mock_dynamodb2, mock_dynamodb2_deprecated # flake8: noqa
|
from .dynamodb2 import mock_dynamodb2, mock_dynamodb2_deprecated # noqa
|
||||||
from .dynamodbstreams import mock_dynamodbstreams # flake8: noqa
|
from .dynamodbstreams import mock_dynamodbstreams # noqa
|
||||||
from .ec2 import mock_ec2, mock_ec2_deprecated # flake8: noqa
|
from .ec2 import mock_ec2, mock_ec2_deprecated # noqa
|
||||||
from .ecr import mock_ecr, mock_ecr_deprecated # flake8: noqa
|
from .ecr import mock_ecr, mock_ecr_deprecated # noqa
|
||||||
from .ecs import mock_ecs, mock_ecs_deprecated # flake8: noqa
|
from .ecs import mock_ecs, mock_ecs_deprecated # noqa
|
||||||
from .elb import mock_elb, mock_elb_deprecated # flake8: noqa
|
from .elb import mock_elb, mock_elb_deprecated # noqa
|
||||||
from .elbv2 import mock_elbv2 # flake8: noqa
|
from .elbv2 import mock_elbv2 # noqa
|
||||||
from .emr import mock_emr, mock_emr_deprecated # flake8: noqa
|
from .emr import mock_emr, mock_emr_deprecated # noqa
|
||||||
from .events import mock_events # flake8: noqa
|
from .events import mock_events # noqa
|
||||||
from .glacier import mock_glacier, mock_glacier_deprecated # flake8: noqa
|
from .glacier import mock_glacier, mock_glacier_deprecated # noqa
|
||||||
from .glue import mock_glue # flake8: noqa
|
from .glue import mock_glue # noqa
|
||||||
from .iam import mock_iam, mock_iam_deprecated # flake8: noqa
|
from .iam import mock_iam, mock_iam_deprecated # noqa
|
||||||
from .kinesis import mock_kinesis, mock_kinesis_deprecated # flake8: noqa
|
from .kinesis import mock_kinesis, mock_kinesis_deprecated # noqa
|
||||||
from .kms import mock_kms, mock_kms_deprecated # flake8: noqa
|
from .kms import mock_kms, mock_kms_deprecated # noqa
|
||||||
from .organizations import mock_organizations # flake8: noqa
|
from .organizations import mock_organizations # noqa
|
||||||
from .opsworks import mock_opsworks, mock_opsworks_deprecated # flake8: noqa
|
from .opsworks import mock_opsworks, mock_opsworks_deprecated # noqa
|
||||||
from .polly import mock_polly # flake8: noqa
|
from .polly import mock_polly # noqa
|
||||||
from .rds import mock_rds, mock_rds_deprecated # flake8: noqa
|
from .rds import mock_rds, mock_rds_deprecated # noqa
|
||||||
from .rds2 import mock_rds2, mock_rds2_deprecated # flake8: noqa
|
from .rds2 import mock_rds2, mock_rds2_deprecated # noqa
|
||||||
from .redshift import mock_redshift, mock_redshift_deprecated # flake8: noqa
|
from .redshift import mock_redshift, mock_redshift_deprecated # noqa
|
||||||
from .resourcegroups import mock_resourcegroups # flake8: noqa
|
from .resourcegroups import mock_resourcegroups # noqa
|
||||||
from .s3 import mock_s3, mock_s3_deprecated # flake8: noqa
|
from .s3 import mock_s3, mock_s3_deprecated # noqa
|
||||||
from .ses import mock_ses, mock_ses_deprecated # flake8: noqa
|
from .ses import mock_ses, mock_ses_deprecated # noqa
|
||||||
from .secretsmanager import mock_secretsmanager # flake8: noqa
|
from .secretsmanager import mock_secretsmanager # noqa
|
||||||
from .sns import mock_sns, mock_sns_deprecated # flake8: noqa
|
from .sns import mock_sns, mock_sns_deprecated # noqa
|
||||||
from .sqs import mock_sqs, mock_sqs_deprecated # flake8: noqa
|
from .sqs import mock_sqs, mock_sqs_deprecated # noqa
|
||||||
from .stepfunctions import mock_stepfunctions # flake8: noqa
|
from .stepfunctions import mock_stepfunctions # noqa
|
||||||
from .sts import mock_sts, mock_sts_deprecated # flake8: noqa
|
from .sts import mock_sts, mock_sts_deprecated # noqa
|
||||||
from .ssm import mock_ssm # flake8: noqa
|
from .ssm import mock_ssm # noqa
|
||||||
from .route53 import mock_route53, mock_route53_deprecated # flake8: noqa
|
from .route53 import mock_route53, mock_route53_deprecated # noqa
|
||||||
from .swf import mock_swf, mock_swf_deprecated # flake8: noqa
|
from .swf import mock_swf, mock_swf_deprecated # noqa
|
||||||
from .xray import mock_xray, mock_xray_client, XRaySegment # flake8: noqa
|
from .xray import mock_xray, mock_xray_client, XRaySegment # noqa
|
||||||
from .logs import mock_logs, mock_logs_deprecated # flake8: noqa
|
from .logs import mock_logs, mock_logs_deprecated # noqa
|
||||||
from .batch import mock_batch # flake8: noqa
|
from .batch import mock_batch # noqa
|
||||||
from .resourcegroupstaggingapi import mock_resourcegroupstaggingapi # flake8: noqa
|
from .resourcegroupstaggingapi import mock_resourcegroupstaggingapi # noqa
|
||||||
from .iot import mock_iot # flake8: noqa
|
from .iot import mock_iot # noqa
|
||||||
from .iotdata import mock_iotdata # flake8: noqa
|
from .iotdata import mock_iotdata # noqa
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -96,11 +96,11 @@ class CloudWatchResponse(BaseResponse):
|
|||||||
extended_statistics = self._get_param('ExtendedStatistics')
|
extended_statistics = self._get_param('ExtendedStatistics')
|
||||||
dimensions = self._get_param('Dimensions')
|
dimensions = self._get_param('Dimensions')
|
||||||
if unit or extended_statistics or dimensions:
|
if unit or extended_statistics or dimensions:
|
||||||
raise NotImplemented()
|
raise NotImplementedError()
|
||||||
|
|
||||||
# TODO: this should instead throw InvalidParameterCombination
|
# TODO: this should instead throw InvalidParameterCombination
|
||||||
if not statistics:
|
if not statistics:
|
||||||
raise NotImplemented("Must specify either Statistics or ExtendedStatistics")
|
raise NotImplementedError("Must specify either Statistics or ExtendedStatistics")
|
||||||
|
|
||||||
datapoints = self.cloudwatch_backend.get_metric_statistics(namespace, metric_name, start_time, end_time, period, statistics)
|
datapoints = self.cloudwatch_backend.get_metric_statistics(namespace, metric_name, start_time, end_time, period, statistics)
|
||||||
template = self.response_template(GET_METRIC_STATISTICS_TEMPLATE)
|
template = self.response_template(GET_METRIC_STATISTICS_TEMPLATE)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
try:
|
try:
|
||||||
from collections import OrderedDict # flake8: noqa
|
from collections import OrderedDict # noqa
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# python 2.6 or earlier, use backport
|
# python 2.6 or earlier, use backport
|
||||||
from ordereddict import OrderedDict # flake8: noqa
|
from ordereddict import OrderedDict # noqa
|
||||||
|
@ -744,6 +744,7 @@ class ConfigBackend(BaseBackend):
|
|||||||
|
|
||||||
def list_aggregate_discovered_resources(self, aggregator_name, resource_type, filters, limit, next_token):
|
def list_aggregate_discovered_resources(self, aggregator_name, resource_type, filters, limit, next_token):
|
||||||
"""This will query against the mocked AWS Config listing function that must exist for the resource backend.
|
"""This will query against the mocked AWS Config listing function that must exist for the resource backend.
|
||||||
|
|
||||||
As far a moto goes -- the only real difference between this function and the `list_discovered_resources` function is that
|
As far a moto goes -- the only real difference between this function and the `list_discovered_resources` function is that
|
||||||
this will require a Config Aggregator be set up a priori and can search based on resource regions.
|
this will require a Config Aggregator be set up a priori and can search based on resource regions.
|
||||||
|
|
||||||
@ -897,6 +898,9 @@ class ConfigBackend(BaseBackend):
|
|||||||
|
|
||||||
item['accountId'] = DEFAULT_ACCOUNT_ID
|
item['accountId'] = DEFAULT_ACCOUNT_ID
|
||||||
|
|
||||||
|
# The 'tags' field is not included in aggregate results for some reason...
|
||||||
|
item.pop('tags', None)
|
||||||
|
|
||||||
found.append(item)
|
found.append(item)
|
||||||
|
|
||||||
return {'BaseConfigurationItems': found, 'UnprocessedResourceIdentifiers': not_found}
|
return {'BaseConfigurationItems': found, 'UnprocessedResourceIdentifiers': not_found}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .models import BaseModel, BaseBackend, moto_api_backend # flake8: noqa
|
from .models import BaseModel, BaseBackend, moto_api_backend # noqa
|
||||||
from .responses import ActionAuthenticatorMixin
|
from .responses import ActionAuthenticatorMixin
|
||||||
|
|
||||||
moto_api_backends = {"global": moto_api_backend}
|
moto_api_backends = {"global": moto_api_backend}
|
||||||
|
@ -83,6 +83,8 @@ class DynamicDictLoader(DictLoader):
|
|||||||
|
|
||||||
|
|
||||||
class _TemplateEnvironmentMixin(object):
|
class _TemplateEnvironmentMixin(object):
|
||||||
|
LEFT_PATTERN = re.compile(r"[\s\n]+<")
|
||||||
|
RIGHT_PATTERN = re.compile(r">[\s\n]+")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(_TemplateEnvironmentMixin, self).__init__()
|
super(_TemplateEnvironmentMixin, self).__init__()
|
||||||
@ -101,7 +103,12 @@ class _TemplateEnvironmentMixin(object):
|
|||||||
def response_template(self, source):
|
def response_template(self, source):
|
||||||
template_id = id(source)
|
template_id = id(source)
|
||||||
if not self.contains_template(template_id):
|
if not self.contains_template(template_id):
|
||||||
self.loader.update({template_id: source})
|
collapsed = re.sub(
|
||||||
|
self.RIGHT_PATTERN,
|
||||||
|
">",
|
||||||
|
re.sub(self.LEFT_PATTERN, "<", source)
|
||||||
|
)
|
||||||
|
self.loader.update({template_id: collapsed})
|
||||||
self.environment = Environment(loader=self.loader, autoescape=self.should_autoescape, trim_blocks=True,
|
self.environment = Environment(loader=self.loader, autoescape=self.should_autoescape, trim_blocks=True,
|
||||||
lstrip_blocks=True)
|
lstrip_blocks=True)
|
||||||
return self.environment.get_template(template_id)
|
return self.environment.get_template(template_id)
|
||||||
@ -454,7 +461,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
|||||||
index = 1
|
index = 1
|
||||||
while True:
|
while True:
|
||||||
value_dict = self._get_multi_param_helper(prefix + str(index))
|
value_dict = self._get_multi_param_helper(prefix + str(index))
|
||||||
if not value_dict:
|
if not value_dict and value_dict != '':
|
||||||
break
|
break
|
||||||
|
|
||||||
values.append(value_dict)
|
values.append(value_dict)
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import re
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
@ -91,12 +89,12 @@ class Op(object):
|
|||||||
|
|
||||||
# TODO add tests for all of these
|
# TODO add tests for all of these
|
||||||
|
|
||||||
EQ_FUNCTION = lambda item_value, test_value: item_value == test_value # flake8: noqa
|
EQ_FUNCTION = lambda item_value, test_value: item_value == test_value # noqa
|
||||||
NE_FUNCTION = lambda item_value, test_value: item_value != test_value # flake8: noqa
|
NE_FUNCTION = lambda item_value, test_value: item_value != test_value # noqa
|
||||||
LE_FUNCTION = lambda item_value, test_value: item_value <= test_value # flake8: noqa
|
LE_FUNCTION = lambda item_value, test_value: item_value <= test_value # noqa
|
||||||
LT_FUNCTION = lambda item_value, test_value: item_value < test_value # flake8: noqa
|
LT_FUNCTION = lambda item_value, test_value: item_value < test_value # noqa
|
||||||
GE_FUNCTION = lambda item_value, test_value: item_value >= test_value # flake8: noqa
|
GE_FUNCTION = lambda item_value, test_value: item_value >= test_value # noqa
|
||||||
GT_FUNCTION = lambda item_value, test_value: item_value > test_value # flake8: noqa
|
GT_FUNCTION = lambda item_value, test_value: item_value > test_value # noqa
|
||||||
|
|
||||||
COMPARISON_FUNCS = {
|
COMPARISON_FUNCS = {
|
||||||
'EQ': EQ_FUNCTION,
|
'EQ': EQ_FUNCTION,
|
||||||
@ -221,7 +219,6 @@ class ConditionExpressionParser:
|
|||||||
# --------------
|
# --------------
|
||||||
LITERAL = 'LITERAL'
|
LITERAL = 'LITERAL'
|
||||||
|
|
||||||
|
|
||||||
class Nonterminal:
|
class Nonterminal:
|
||||||
"""Enum defining nonterminals for productions."""
|
"""Enum defining nonterminals for productions."""
|
||||||
|
|
||||||
@ -240,7 +237,6 @@ class ConditionExpressionParser:
|
|||||||
RIGHT_PAREN = 'RIGHT_PAREN'
|
RIGHT_PAREN = 'RIGHT_PAREN'
|
||||||
WHITESPACE = 'WHITESPACE'
|
WHITESPACE = 'WHITESPACE'
|
||||||
|
|
||||||
|
|
||||||
Node = namedtuple('Node', ['nonterminal', 'kind', 'text', 'value', 'children'])
|
Node = namedtuple('Node', ['nonterminal', 'kind', 'text', 'value', 'children'])
|
||||||
|
|
||||||
def _lex_condition_expression(self):
|
def _lex_condition_expression(self):
|
||||||
@ -290,7 +286,6 @@ class ConditionExpressionParser:
|
|||||||
raise ValueError("Cannot parse condition starting at: " +
|
raise ValueError("Cannot parse condition starting at: " +
|
||||||
remaining_expression)
|
remaining_expression)
|
||||||
|
|
||||||
value = match_text
|
|
||||||
node = self.Node(
|
node = self.Node(
|
||||||
nonterminal=nonterminal,
|
nonterminal=nonterminal,
|
||||||
kind=self.Kind.LITERAL,
|
kind=self.Kind.LITERAL,
|
||||||
@ -351,7 +346,6 @@ class ConditionExpressionParser:
|
|||||||
'size',
|
'size',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if name.lower() in reserved:
|
if name.lower() in reserved:
|
||||||
# e.g. AND
|
# e.g. AND
|
||||||
nonterminal = reserved[name.lower()]
|
nonterminal = reserved[name.lower()]
|
||||||
@ -755,7 +749,6 @@ class ConditionExpressionParser:
|
|||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
raise ValueError("Unknown operand: %r" % node)
|
raise ValueError("Unknown operand: %r" % node)
|
||||||
|
|
||||||
|
|
||||||
def _make_op_condition(self, node):
|
def _make_op_condition(self, node):
|
||||||
if node.kind == self.Kind.OR:
|
if node.kind == self.Kind.OR:
|
||||||
lhs, rhs = node.children
|
lhs, rhs = node.children
|
||||||
|
@ -2,7 +2,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
from copy import copy
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import random
|
from random import random
|
||||||
|
|
||||||
@ -27,11 +26,12 @@ class BaseObject(BaseModel):
|
|||||||
return ''.join(words)
|
return ''.join(words)
|
||||||
|
|
||||||
def gen_response_object(self):
|
def gen_response_object(self):
|
||||||
response_object = copy(self.__dict__)
|
response_object = dict()
|
||||||
for key, value in response_object.items():
|
for key, value in self.__dict__.items():
|
||||||
if '_' in key:
|
if '_' in key:
|
||||||
response_object[self.camelCase(key)] = value
|
response_object[self.camelCase(key)] = value
|
||||||
del response_object[key]
|
else:
|
||||||
|
response_object[key] = value
|
||||||
return response_object
|
return response_object
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -934,6 +934,10 @@ class FakeBucket(BaseModel):
|
|||||||
# This is a dobule-wrapped JSON for some reason...
|
# This is a dobule-wrapped JSON for some reason...
|
||||||
s_config = {'AccessControlList': json.dumps(json.dumps(self.acl.to_config_dict()))}
|
s_config = {'AccessControlList': json.dumps(json.dumps(self.acl.to_config_dict()))}
|
||||||
|
|
||||||
|
# Tagging is special:
|
||||||
|
if config_dict['tags']:
|
||||||
|
s_config['BucketTaggingConfiguration'] = json.dumps({'tagSets': [{'tags': config_dict['tags']}]})
|
||||||
|
|
||||||
# TODO implement Accelerate Configuration:
|
# TODO implement Accelerate Configuration:
|
||||||
s_config['BucketAccelerateConfiguration'] = {'status': None}
|
s_config['BucketAccelerateConfiguration'] = {'status': None}
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ class Topic(BaseModel):
|
|||||||
self.sns_backend = sns_backend
|
self.sns_backend = sns_backend
|
||||||
self.account_id = DEFAULT_ACCOUNT_ID
|
self.account_id = DEFAULT_ACCOUNT_ID
|
||||||
self.display_name = ""
|
self.display_name = ""
|
||||||
self.policy = json.dumps(DEFAULT_TOPIC_POLICY)
|
|
||||||
self.delivery_policy = ""
|
self.delivery_policy = ""
|
||||||
self.effective_delivery_policy = json.dumps(DEFAULT_EFFECTIVE_DELIVERY_POLICY)
|
self.effective_delivery_policy = json.dumps(DEFAULT_EFFECTIVE_DELIVERY_POLICY)
|
||||||
self.arn = make_arn_for_topic(
|
self.arn = make_arn_for_topic(
|
||||||
@ -44,6 +43,7 @@ class Topic(BaseModel):
|
|||||||
self.subscriptions_confimed = 0
|
self.subscriptions_confimed = 0
|
||||||
self.subscriptions_deleted = 0
|
self.subscriptions_deleted = 0
|
||||||
|
|
||||||
|
self._policy_json = self._create_default_topic_policy(sns_backend.region_name, self.account_id, name)
|
||||||
self._tags = {}
|
self._tags = {}
|
||||||
|
|
||||||
def publish(self, message, subject=None, message_attributes=None):
|
def publish(self, message, subject=None, message_attributes=None):
|
||||||
@ -64,6 +64,14 @@ class Topic(BaseModel):
|
|||||||
def physical_resource_id(self):
|
def physical_resource_id(self):
|
||||||
return self.arn
|
return self.arn
|
||||||
|
|
||||||
|
@property
|
||||||
|
def policy(self):
|
||||||
|
return json.dumps(self._policy_json)
|
||||||
|
|
||||||
|
@policy.setter
|
||||||
|
def policy(self, policy):
|
||||||
|
self._policy_json = json.loads(policy)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
sns_backend = sns_backends[region_name]
|
sns_backend = sns_backends[region_name]
|
||||||
@ -77,6 +85,37 @@ class Topic(BaseModel):
|
|||||||
'Endpoint'], subscription['Protocol'])
|
'Endpoint'], subscription['Protocol'])
|
||||||
return topic
|
return topic
|
||||||
|
|
||||||
|
def _create_default_topic_policy(self, region_name, account_id, name):
|
||||||
|
return {
|
||||||
|
"Version": "2008-10-17",
|
||||||
|
"Id": "__default_policy_ID",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Sid": "__default_statement_ID",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "*"
|
||||||
|
},
|
||||||
|
"Action": [
|
||||||
|
"SNS:GetTopicAttributes",
|
||||||
|
"SNS:SetTopicAttributes",
|
||||||
|
"SNS:AddPermission",
|
||||||
|
"SNS:RemovePermission",
|
||||||
|
"SNS:DeleteTopic",
|
||||||
|
"SNS:Subscribe",
|
||||||
|
"SNS:ListSubscriptionsByTopic",
|
||||||
|
"SNS:Publish",
|
||||||
|
"SNS:Receive",
|
||||||
|
],
|
||||||
|
"Resource": make_arn_for_topic(
|
||||||
|
self.account_id, name, region_name),
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"AWS:SourceOwner": str(account_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Subscription(BaseModel):
|
class Subscription(BaseModel):
|
||||||
|
|
||||||
@ -269,7 +308,6 @@ class SNSBackend(BaseBackend):
|
|||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.sms_attributes = {}
|
self.sms_attributes = {}
|
||||||
self.opt_out_numbers = ['+447420500600', '+447420505401', '+447632960543', '+447632960028', '+447700900149', '+447700900550', '+447700900545', '+447700900907']
|
self.opt_out_numbers = ['+447420500600', '+447420505401', '+447632960543', '+447632960028', '+447700900149', '+447700900550', '+447700900545', '+447700900907']
|
||||||
self.permissions = {}
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
region_name = self.region_name
|
region_name = self.region_name
|
||||||
@ -511,6 +549,43 @@ class SNSBackend(BaseBackend):
|
|||||||
|
|
||||||
raise SNSInvalidParameter("Invalid parameter: FilterPolicy: Match value must be String, number, true, false, or null")
|
raise SNSInvalidParameter("Invalid parameter: FilterPolicy: Match value must be String, number, true, false, or null")
|
||||||
|
|
||||||
|
def add_permission(self, topic_arn, label, aws_account_ids, action_names):
|
||||||
|
if topic_arn not in self.topics:
|
||||||
|
raise SNSNotFoundError('Topic does not exist')
|
||||||
|
|
||||||
|
policy = self.topics[topic_arn]._policy_json
|
||||||
|
statement = next((statement for statement in policy['Statement'] if statement['Sid'] == label), None)
|
||||||
|
|
||||||
|
if statement:
|
||||||
|
raise SNSInvalidParameter('Statement already exists')
|
||||||
|
|
||||||
|
if any(action_name not in VALID_POLICY_ACTIONS for action_name in action_names):
|
||||||
|
raise SNSInvalidParameter('Policy statement action out of service scope!')
|
||||||
|
|
||||||
|
principals = ['arn:aws:iam::{}:root'.format(account_id) for account_id in aws_account_ids]
|
||||||
|
actions = ['SNS:{}'.format(action_name) for action_name in action_names]
|
||||||
|
|
||||||
|
statement = {
|
||||||
|
'Sid': label,
|
||||||
|
'Effect': 'Allow',
|
||||||
|
'Principal': {
|
||||||
|
'AWS': principals[0] if len(principals) == 1 else principals
|
||||||
|
},
|
||||||
|
'Action': actions[0] if len(actions) == 1 else actions,
|
||||||
|
'Resource': topic_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
self.topics[topic_arn]._policy_json['Statement'].append(statement)
|
||||||
|
|
||||||
|
def remove_permission(self, topic_arn, label):
|
||||||
|
if topic_arn not in self.topics:
|
||||||
|
raise SNSNotFoundError('Topic does not exist')
|
||||||
|
|
||||||
|
statements = self.topics[topic_arn]._policy_json['Statement']
|
||||||
|
statements = [statement for statement in statements if statement['Sid'] != label]
|
||||||
|
|
||||||
|
self.topics[topic_arn]._policy_json['Statement'] = statements
|
||||||
|
|
||||||
def list_tags_for_resource(self, resource_arn):
|
def list_tags_for_resource(self, resource_arn):
|
||||||
if resource_arn not in self.topics:
|
if resource_arn not in self.topics:
|
||||||
raise ResourceNotFoundError
|
raise ResourceNotFoundError
|
||||||
@ -542,35 +617,6 @@ for region in Session().get_available_regions('sns'):
|
|||||||
sns_backends[region] = SNSBackend(region)
|
sns_backends[region] = SNSBackend(region)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_TOPIC_POLICY = {
|
|
||||||
"Version": "2008-10-17",
|
|
||||||
"Id": "us-east-1/698519295917/test__default_policy_ID",
|
|
||||||
"Statement": [{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Sid": "us-east-1/698519295917/test__default_statement_ID",
|
|
||||||
"Principal": {
|
|
||||||
"AWS": "*"
|
|
||||||
},
|
|
||||||
"Action": [
|
|
||||||
"SNS:GetTopicAttributes",
|
|
||||||
"SNS:SetTopicAttributes",
|
|
||||||
"SNS:AddPermission",
|
|
||||||
"SNS:RemovePermission",
|
|
||||||
"SNS:DeleteTopic",
|
|
||||||
"SNS:Subscribe",
|
|
||||||
"SNS:ListSubscriptionsByTopic",
|
|
||||||
"SNS:Publish",
|
|
||||||
"SNS:Receive",
|
|
||||||
],
|
|
||||||
"Resource": "arn:aws:sns:us-east-1:698519295917:test",
|
|
||||||
"Condition": {
|
|
||||||
"StringLike": {
|
|
||||||
"AWS:SourceArn": "arn:aws:*:*:698519295917:*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_EFFECTIVE_DELIVERY_POLICY = {
|
DEFAULT_EFFECTIVE_DELIVERY_POLICY = {
|
||||||
'http': {
|
'http': {
|
||||||
'disableSubscriptionOverrides': False,
|
'disableSubscriptionOverrides': False,
|
||||||
@ -585,3 +631,16 @@ DEFAULT_EFFECTIVE_DELIVERY_POLICY = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VALID_POLICY_ACTIONS = [
|
||||||
|
'GetTopicAttributes',
|
||||||
|
'SetTopicAttributes',
|
||||||
|
'AddPermission',
|
||||||
|
'RemovePermission',
|
||||||
|
'DeleteTopic',
|
||||||
|
'Subscribe',
|
||||||
|
'ListSubscriptionsByTopic',
|
||||||
|
'Publish',
|
||||||
|
'Receive'
|
||||||
|
]
|
||||||
|
@ -639,34 +639,21 @@ class SNSResponse(BaseResponse):
|
|||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def add_permission(self):
|
def add_permission(self):
|
||||||
arn = self._get_param('TopicArn')
|
topic_arn = self._get_param('TopicArn')
|
||||||
label = self._get_param('Label')
|
label = self._get_param('Label')
|
||||||
accounts = self._get_multi_param('AWSAccountId.member.')
|
aws_account_ids = self._get_multi_param('AWSAccountId.member.')
|
||||||
action = self._get_multi_param('ActionName.member.')
|
action_names = self._get_multi_param('ActionName.member.')
|
||||||
|
|
||||||
if arn not in self.backend.topics:
|
self.backend.add_permission(topic_arn, label, aws_account_ids, action_names)
|
||||||
error_response = self._error('NotFound', 'Topic does not exist')
|
|
||||||
return error_response, dict(status=404)
|
|
||||||
|
|
||||||
key = (arn, label)
|
|
||||||
self.backend.permissions[key] = {'accounts': accounts, 'action': action}
|
|
||||||
|
|
||||||
template = self.response_template(ADD_PERMISSION_TEMPLATE)
|
template = self.response_template(ADD_PERMISSION_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def remove_permission(self):
|
def remove_permission(self):
|
||||||
arn = self._get_param('TopicArn')
|
topic_arn = self._get_param('TopicArn')
|
||||||
label = self._get_param('Label')
|
label = self._get_param('Label')
|
||||||
|
|
||||||
if arn not in self.backend.topics:
|
self.backend.remove_permission(topic_arn, label)
|
||||||
error_response = self._error('NotFound', 'Topic does not exist')
|
|
||||||
return error_response, dict(status=404)
|
|
||||||
|
|
||||||
try:
|
|
||||||
key = (arn, label)
|
|
||||||
del self.backend.permissions[key]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
template = self.response_template(DEL_PERMISSION_TEMPLATE)
|
template = self.response_template(DEL_PERMISSION_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
@ -7,9 +7,14 @@ class MessageNotInflight(Exception):
|
|||||||
status_code = 400
|
status_code = 400
|
||||||
|
|
||||||
|
|
||||||
class ReceiptHandleIsInvalid(Exception):
|
class ReceiptHandleIsInvalid(RESTError):
|
||||||
description = "The receipt handle provided is not valid."
|
code = 400
|
||||||
status_code = 400
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ReceiptHandleIsInvalid, self).__init__(
|
||||||
|
'ReceiptHandleIsInvalid',
|
||||||
|
'The input receipt handle is invalid.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MessageAttributesInvalid(Exception):
|
class MessageAttributesInvalid(Exception):
|
||||||
@ -33,3 +38,66 @@ class QueueAlreadyExists(RESTError):
|
|||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super(QueueAlreadyExists, self).__init__(
|
super(QueueAlreadyExists, self).__init__(
|
||||||
"QueueAlreadyExists", message)
|
"QueueAlreadyExists", message)
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyBatchRequest(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(EmptyBatchRequest, self).__init__(
|
||||||
|
'EmptyBatchRequest',
|
||||||
|
'There should be at least one SendMessageBatchRequestEntry in the request.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidBatchEntryId(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(InvalidBatchEntryId, self).__init__(
|
||||||
|
'InvalidBatchEntryId',
|
||||||
|
'A batch entry id can only contain alphanumeric characters, '
|
||||||
|
'hyphens and underscores. It can be at most 80 letters long.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BatchRequestTooLong(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, length):
|
||||||
|
super(BatchRequestTooLong, self).__init__(
|
||||||
|
'BatchRequestTooLong',
|
||||||
|
'Batch requests cannot be longer than 262144 bytes. '
|
||||||
|
'You have sent {} bytes.'.format(length)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BatchEntryIdsNotDistinct(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, entry_id):
|
||||||
|
super(BatchEntryIdsNotDistinct, self).__init__(
|
||||||
|
'BatchEntryIdsNotDistinct',
|
||||||
|
'Id {} repeated.'.format(entry_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TooManyEntriesInBatchRequest(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, number):
|
||||||
|
super(TooManyEntriesInBatchRequest, self).__init__(
|
||||||
|
'TooManyEntriesInBatchRequest',
|
||||||
|
'Maximum number of entries per request are 10. '
|
||||||
|
'You have sent {}.'.format(number)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAttributeName(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, attribute_name):
|
||||||
|
super(InvalidAttributeName, self).__init__(
|
||||||
|
'InvalidAttributeName',
|
||||||
|
'Unknown Attribute {}.'.format(attribute_name)
|
||||||
|
)
|
||||||
|
@ -20,11 +20,18 @@ from .exceptions import (
|
|||||||
QueueDoesNotExist,
|
QueueDoesNotExist,
|
||||||
QueueAlreadyExists,
|
QueueAlreadyExists,
|
||||||
ReceiptHandleIsInvalid,
|
ReceiptHandleIsInvalid,
|
||||||
|
InvalidBatchEntryId,
|
||||||
|
BatchRequestTooLong,
|
||||||
|
BatchEntryIdsNotDistinct,
|
||||||
|
TooManyEntriesInBatchRequest,
|
||||||
|
InvalidAttributeName
|
||||||
)
|
)
|
||||||
|
|
||||||
DEFAULT_ACCOUNT_ID = 123456789012
|
DEFAULT_ACCOUNT_ID = 123456789012
|
||||||
DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU"
|
DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU"
|
||||||
|
|
||||||
|
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
|
||||||
|
|
||||||
TRANSPORT_TYPE_ENCODINGS = {'String': b'\x01', 'Binary': b'\x02', 'Number': b'\x01'}
|
TRANSPORT_TYPE_ENCODINGS = {'String': b'\x01', 'Binary': b'\x02', 'Number': b'\x01'}
|
||||||
|
|
||||||
|
|
||||||
@ -155,7 +162,7 @@ class Message(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Queue(BaseModel):
|
class Queue(BaseModel):
|
||||||
base_attributes = ['ApproximateNumberOfMessages',
|
BASE_ATTRIBUTES = ['ApproximateNumberOfMessages',
|
||||||
'ApproximateNumberOfMessagesDelayed',
|
'ApproximateNumberOfMessagesDelayed',
|
||||||
'ApproximateNumberOfMessagesNotVisible',
|
'ApproximateNumberOfMessagesNotVisible',
|
||||||
'CreatedTimestamp',
|
'CreatedTimestamp',
|
||||||
@ -166,9 +173,9 @@ class Queue(BaseModel):
|
|||||||
'QueueArn',
|
'QueueArn',
|
||||||
'ReceiveMessageWaitTimeSeconds',
|
'ReceiveMessageWaitTimeSeconds',
|
||||||
'VisibilityTimeout']
|
'VisibilityTimeout']
|
||||||
fifo_attributes = ['FifoQueue',
|
FIFO_ATTRIBUTES = ['FifoQueue',
|
||||||
'ContentBasedDeduplication']
|
'ContentBasedDeduplication']
|
||||||
kms_attributes = ['KmsDataKeyReusePeriodSeconds',
|
KMS_ATTRIBUTES = ['KmsDataKeyReusePeriodSeconds',
|
||||||
'KmsMasterKeyId']
|
'KmsMasterKeyId']
|
||||||
ALLOWED_PERMISSIONS = ('*', 'ChangeMessageVisibility', 'DeleteMessage',
|
ALLOWED_PERMISSIONS = ('*', 'ChangeMessageVisibility', 'DeleteMessage',
|
||||||
'GetQueueAttributes', 'GetQueueUrl',
|
'GetQueueAttributes', 'GetQueueUrl',
|
||||||
@ -185,7 +192,8 @@ class Queue(BaseModel):
|
|||||||
|
|
||||||
now = unix_time()
|
now = unix_time()
|
||||||
self.created_timestamp = now
|
self.created_timestamp = now
|
||||||
self.queue_arn = 'arn:aws:sqs:{0}:123456789012:{1}'.format(self.region,
|
self.queue_arn = 'arn:aws:sqs:{0}:{1}:{2}'.format(self.region,
|
||||||
|
DEFAULT_ACCOUNT_ID,
|
||||||
self.name)
|
self.name)
|
||||||
self.dead_letter_queue = None
|
self.dead_letter_queue = None
|
||||||
|
|
||||||
@ -330,17 +338,17 @@ class Queue(BaseModel):
|
|||||||
def attributes(self):
|
def attributes(self):
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
for attribute in self.base_attributes:
|
for attribute in self.BASE_ATTRIBUTES:
|
||||||
attr = getattr(self, camelcase_to_underscores(attribute))
|
attr = getattr(self, camelcase_to_underscores(attribute))
|
||||||
result[attribute] = attr
|
result[attribute] = attr
|
||||||
|
|
||||||
if self.fifo_queue:
|
if self.fifo_queue:
|
||||||
for attribute in self.fifo_attributes:
|
for attribute in self.FIFO_ATTRIBUTES:
|
||||||
attr = getattr(self, camelcase_to_underscores(attribute))
|
attr = getattr(self, camelcase_to_underscores(attribute))
|
||||||
result[attribute] = attr
|
result[attribute] = attr
|
||||||
|
|
||||||
if self.kms_master_key_id:
|
if self.kms_master_key_id:
|
||||||
for attribute in self.kms_attributes:
|
for attribute in self.KMS_ATTRIBUTES:
|
||||||
attr = getattr(self, camelcase_to_underscores(attribute))
|
attr = getattr(self, camelcase_to_underscores(attribute))
|
||||||
result[attribute] = attr
|
result[attribute] = attr
|
||||||
|
|
||||||
@ -460,6 +468,9 @@ class SQSBackend(BaseBackend):
|
|||||||
|
|
||||||
return queue
|
return queue
|
||||||
|
|
||||||
|
def get_queue_url(self, queue_name):
|
||||||
|
return self.get_queue(queue_name)
|
||||||
|
|
||||||
def list_queues(self, queue_name_prefix):
|
def list_queues(self, queue_name_prefix):
|
||||||
re_str = '.*'
|
re_str = '.*'
|
||||||
if queue_name_prefix:
|
if queue_name_prefix:
|
||||||
@ -482,6 +493,28 @@ class SQSBackend(BaseBackend):
|
|||||||
return self.queues.pop(queue_name)
|
return self.queues.pop(queue_name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_queue_attributes(self, queue_name, attribute_names):
|
||||||
|
queue = self.get_queue(queue_name)
|
||||||
|
|
||||||
|
if not len(attribute_names):
|
||||||
|
attribute_names.append('All')
|
||||||
|
|
||||||
|
valid_names = ['All'] + queue.BASE_ATTRIBUTES + queue.FIFO_ATTRIBUTES + queue.KMS_ATTRIBUTES
|
||||||
|
invalid_name = next((name for name in attribute_names if name not in valid_names), None)
|
||||||
|
|
||||||
|
if invalid_name or invalid_name == '':
|
||||||
|
raise InvalidAttributeName(invalid_name)
|
||||||
|
|
||||||
|
attributes = {}
|
||||||
|
|
||||||
|
if 'All' in attribute_names:
|
||||||
|
attributes = queue.attributes
|
||||||
|
else:
|
||||||
|
for name in (name for name in attribute_names if name in queue.attributes):
|
||||||
|
attributes[name] = queue.attributes.get(name)
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
|
||||||
def set_queue_attributes(self, queue_name, attributes):
|
def set_queue_attributes(self, queue_name, attributes):
|
||||||
queue = self.get_queue(queue_name)
|
queue = self.get_queue(queue_name)
|
||||||
queue._set_attributes(attributes)
|
queue._set_attributes(attributes)
|
||||||
@ -516,6 +549,49 @@ class SQSBackend(BaseBackend):
|
|||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
def send_message_batch(self, queue_name, entries):
|
||||||
|
self.get_queue(queue_name)
|
||||||
|
|
||||||
|
if any(not re.match(r'^[\w-]{1,80}$', entry['Id']) for entry in entries.values()):
|
||||||
|
raise InvalidBatchEntryId()
|
||||||
|
|
||||||
|
body_length = next(
|
||||||
|
(len(entry['MessageBody']) for entry in entries.values() if len(entry['MessageBody']) > MAXIMUM_MESSAGE_LENGTH),
|
||||||
|
False
|
||||||
|
)
|
||||||
|
if body_length:
|
||||||
|
raise BatchRequestTooLong(body_length)
|
||||||
|
|
||||||
|
duplicate_id = self._get_first_duplicate_id([entry['Id'] for entry in entries.values()])
|
||||||
|
if duplicate_id:
|
||||||
|
raise BatchEntryIdsNotDistinct(duplicate_id)
|
||||||
|
|
||||||
|
if len(entries) > 10:
|
||||||
|
raise TooManyEntriesInBatchRequest(len(entries))
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
for index, entry in entries.items():
|
||||||
|
# Loop through looking for messages
|
||||||
|
message = self.send_message(
|
||||||
|
queue_name,
|
||||||
|
entry['MessageBody'],
|
||||||
|
message_attributes=entry['MessageAttributes'],
|
||||||
|
delay_seconds=entry['DelaySeconds']
|
||||||
|
)
|
||||||
|
message.user_id = entry['Id']
|
||||||
|
|
||||||
|
messages.append(message)
|
||||||
|
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def _get_first_duplicate_id(self, ids):
|
||||||
|
unique_ids = set()
|
||||||
|
for id in ids:
|
||||||
|
if id in unique_ids:
|
||||||
|
return id
|
||||||
|
unique_ids.add(id)
|
||||||
|
return None
|
||||||
|
|
||||||
def receive_messages(self, queue_name, count, wait_seconds_timeout, visibility_timeout):
|
def receive_messages(self, queue_name, count, wait_seconds_timeout, visibility_timeout):
|
||||||
"""
|
"""
|
||||||
Attempt to retrieve visible messages from a queue.
|
Attempt to retrieve visible messages from a queue.
|
||||||
@ -593,6 +669,10 @@ class SQSBackend(BaseBackend):
|
|||||||
|
|
||||||
def delete_message(self, queue_name, receipt_handle):
|
def delete_message(self, queue_name, receipt_handle):
|
||||||
queue = self.get_queue(queue_name)
|
queue = self.get_queue(queue_name)
|
||||||
|
|
||||||
|
if not any(message.receipt_handle == receipt_handle for message in queue._messages):
|
||||||
|
raise ReceiptHandleIsInvalid()
|
||||||
|
|
||||||
new_messages = []
|
new_messages = []
|
||||||
for message in queue._messages:
|
for message in queue._messages:
|
||||||
# Only delete message if it is not visible and the reciept_handle
|
# Only delete message if it is not visible and the reciept_handle
|
||||||
@ -677,6 +757,9 @@ class SQSBackend(BaseBackend):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def list_queue_tags(self, queue_name):
|
||||||
|
return self.get_queue(queue_name)
|
||||||
|
|
||||||
|
|
||||||
sqs_backends = {}
|
sqs_backends = {}
|
||||||
for region in boto.sqs.regions():
|
for region in boto.sqs.regions():
|
||||||
|
@ -11,6 +11,8 @@ from .exceptions import (
|
|||||||
MessageAttributesInvalid,
|
MessageAttributesInvalid,
|
||||||
MessageNotInflight,
|
MessageNotInflight,
|
||||||
ReceiptHandleIsInvalid,
|
ReceiptHandleIsInvalid,
|
||||||
|
EmptyBatchRequest,
|
||||||
|
InvalidAttributeName
|
||||||
)
|
)
|
||||||
|
|
||||||
MAXIMUM_VISIBILTY_TIMEOUT = 43200
|
MAXIMUM_VISIBILTY_TIMEOUT = 43200
|
||||||
@ -89,13 +91,10 @@ class SQSResponse(BaseResponse):
|
|||||||
request_url = urlparse(self.uri)
|
request_url = urlparse(self.uri)
|
||||||
queue_name = self._get_param("QueueName")
|
queue_name = self._get_param("QueueName")
|
||||||
|
|
||||||
queue = self.sqs_backend.get_queue(queue_name)
|
queue = self.sqs_backend.get_queue_url(queue_name)
|
||||||
|
|
||||||
if queue:
|
|
||||||
template = self.response_template(GET_QUEUE_URL_RESPONSE)
|
template = self.response_template(GET_QUEUE_URL_RESPONSE)
|
||||||
return template.render(queue=queue, request_url=request_url)
|
return template.render(queue_url=queue.url(request_url))
|
||||||
else:
|
|
||||||
return "", dict(status=404)
|
|
||||||
|
|
||||||
def list_queues(self):
|
def list_queues(self):
|
||||||
request_url = urlparse(self.uri)
|
request_url = urlparse(self.uri)
|
||||||
@ -119,7 +118,7 @@ class SQSResponse(BaseResponse):
|
|||||||
receipt_handle=receipt_handle,
|
receipt_handle=receipt_handle,
|
||||||
visibility_timeout=visibility_timeout
|
visibility_timeout=visibility_timeout
|
||||||
)
|
)
|
||||||
except (ReceiptHandleIsInvalid, MessageNotInflight) as e:
|
except MessageNotInflight as e:
|
||||||
return "Invalid request: {0}".format(e.description), dict(status=e.status_code)
|
return "Invalid request: {0}".format(e.description), dict(status=e.status_code)
|
||||||
|
|
||||||
template = self.response_template(CHANGE_MESSAGE_VISIBILITY_RESPONSE)
|
template = self.response_template(CHANGE_MESSAGE_VISIBILITY_RESPONSE)
|
||||||
@ -171,10 +170,15 @@ class SQSResponse(BaseResponse):
|
|||||||
def get_queue_attributes(self):
|
def get_queue_attributes(self):
|
||||||
queue_name = self._get_queue_name()
|
queue_name = self._get_queue_name()
|
||||||
|
|
||||||
queue = self.sqs_backend.get_queue(queue_name)
|
if self.querystring.get('AttributeNames'):
|
||||||
|
raise InvalidAttributeName('')
|
||||||
|
|
||||||
|
attribute_names = self._get_multi_param('AttributeName')
|
||||||
|
|
||||||
|
attributes = self.sqs_backend.get_queue_attributes(queue_name, attribute_names)
|
||||||
|
|
||||||
template = self.response_template(GET_QUEUE_ATTRIBUTES_RESPONSE)
|
template = self.response_template(GET_QUEUE_ATTRIBUTES_RESPONSE)
|
||||||
return template.render(queue=queue)
|
return template.render(attributes=attributes)
|
||||||
|
|
||||||
def set_queue_attributes(self):
|
def set_queue_attributes(self):
|
||||||
# TODO validate self.get_param('QueueUrl')
|
# TODO validate self.get_param('QueueUrl')
|
||||||
@ -237,72 +241,31 @@ class SQSResponse(BaseResponse):
|
|||||||
self.sqs_backend.get_queue(queue_name)
|
self.sqs_backend.get_queue(queue_name)
|
||||||
|
|
||||||
if self.querystring.get('Entries'):
|
if self.querystring.get('Entries'):
|
||||||
return self._error('AWS.SimpleQueueService.EmptyBatchRequest',
|
raise EmptyBatchRequest()
|
||||||
'There should be at least one SendMessageBatchRequestEntry in the request.')
|
|
||||||
|
|
||||||
entries = {}
|
entries = {}
|
||||||
for key, value in self.querystring.items():
|
for key, value in self.querystring.items():
|
||||||
match = re.match(r'^SendMessageBatchRequestEntry\.(\d+)\.Id', key)
|
match = re.match(r'^SendMessageBatchRequestEntry\.(\d+)\.Id', key)
|
||||||
if match:
|
if match:
|
||||||
entries[match.group(1)] = {
|
index = match.group(1)
|
||||||
'Id': value[0],
|
|
||||||
'MessageBody': self.querystring.get(
|
|
||||||
'SendMessageBatchRequestEntry.{}.MessageBody'.format(match.group(1)))[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if any(not re.match(r'^[\w-]{1,80}$', entry['Id']) for entry in entries.values()):
|
|
||||||
return self._error('AWS.SimpleQueueService.InvalidBatchEntryId',
|
|
||||||
'A batch entry id can only contain alphanumeric characters, '
|
|
||||||
'hyphens and underscores. It can be at most 80 letters long.')
|
|
||||||
|
|
||||||
body_length = next(
|
|
||||||
(len(entry['MessageBody']) for entry in entries.values() if len(entry['MessageBody']) > MAXIMUM_MESSAGE_LENGTH),
|
|
||||||
False
|
|
||||||
)
|
|
||||||
if body_length:
|
|
||||||
return self._error('AWS.SimpleQueueService.BatchRequestTooLong',
|
|
||||||
'Batch requests cannot be longer than 262144 bytes. '
|
|
||||||
'You have sent {} bytes.'.format(body_length))
|
|
||||||
|
|
||||||
duplicate_id = self._get_first_duplicate_id([entry['Id'] for entry in entries.values()])
|
|
||||||
if duplicate_id:
|
|
||||||
return self._error('AWS.SimpleQueueService.BatchEntryIdsNotDistinct',
|
|
||||||
'Id {} repeated.'.format(duplicate_id))
|
|
||||||
|
|
||||||
if len(entries) > 10:
|
|
||||||
return self._error('AWS.SimpleQueueService.TooManyEntriesInBatchRequest',
|
|
||||||
'Maximum number of entries per request are 10. '
|
|
||||||
'You have sent 11.')
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
for index, entry in entries.items():
|
|
||||||
# Loop through looking for messages
|
|
||||||
delay_key = 'SendMessageBatchRequestEntry.{0}.DelaySeconds'.format(
|
|
||||||
index)
|
|
||||||
delay_seconds = self.querystring.get(delay_key, [None])[0]
|
|
||||||
message = self.sqs_backend.send_message(
|
|
||||||
queue_name, entry['MessageBody'], delay_seconds=delay_seconds)
|
|
||||||
message.user_id = entry['Id']
|
|
||||||
|
|
||||||
message_attributes = parse_message_attributes(
|
message_attributes = parse_message_attributes(
|
||||||
self.querystring, base='SendMessageBatchRequestEntry.{0}.'.format(index))
|
self.querystring, base='SendMessageBatchRequestEntry.{}.'.format(index))
|
||||||
if type(message_attributes) == tuple:
|
|
||||||
return message_attributes[0], message_attributes[1]
|
|
||||||
message.message_attributes = message_attributes
|
|
||||||
|
|
||||||
messages.append(message)
|
entries[index] = {
|
||||||
|
'Id': value[0],
|
||||||
|
'MessageBody': self.querystring.get(
|
||||||
|
'SendMessageBatchRequestEntry.{}.MessageBody'.format(index))[0],
|
||||||
|
'DelaySeconds': self.querystring.get(
|
||||||
|
'SendMessageBatchRequestEntry.{}.DelaySeconds'.format(index), [None])[0],
|
||||||
|
'MessageAttributes': message_attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = self.sqs_backend.send_message_batch(queue_name, entries)
|
||||||
|
|
||||||
template = self.response_template(SEND_MESSAGE_BATCH_RESPONSE)
|
template = self.response_template(SEND_MESSAGE_BATCH_RESPONSE)
|
||||||
return template.render(messages=messages)
|
return template.render(messages=messages)
|
||||||
|
|
||||||
def _get_first_duplicate_id(self, ids):
|
|
||||||
unique_ids = set()
|
|
||||||
for id in ids:
|
|
||||||
if id in unique_ids:
|
|
||||||
return id
|
|
||||||
unique_ids.add(id)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def delete_message(self):
|
def delete_message(self):
|
||||||
queue_name = self._get_queue_name()
|
queue_name = self._get_queue_name()
|
||||||
receipt_handle = self.querystring.get("ReceiptHandle")[0]
|
receipt_handle = self.querystring.get("ReceiptHandle")[0]
|
||||||
@ -441,7 +404,7 @@ class SQSResponse(BaseResponse):
|
|||||||
def list_queue_tags(self):
|
def list_queue_tags(self):
|
||||||
queue_name = self._get_queue_name()
|
queue_name = self._get_queue_name()
|
||||||
|
|
||||||
queue = self.sqs_backend.get_queue(queue_name)
|
queue = self.sqs_backend.list_queue_tags(queue_name)
|
||||||
|
|
||||||
template = self.response_template(LIST_QUEUE_TAGS_RESPONSE)
|
template = self.response_template(LIST_QUEUE_TAGS_RESPONSE)
|
||||||
return template.render(tags=queue.tags)
|
return template.render(tags=queue.tags)
|
||||||
@ -458,7 +421,7 @@ CREATE_QUEUE_RESPONSE = """<CreateQueueResponse>
|
|||||||
|
|
||||||
GET_QUEUE_URL_RESPONSE = """<GetQueueUrlResponse>
|
GET_QUEUE_URL_RESPONSE = """<GetQueueUrlResponse>
|
||||||
<GetQueueUrlResult>
|
<GetQueueUrlResult>
|
||||||
<QueueUrl>{{ queue.url(request_url) }}</QueueUrl>
|
<QueueUrl>{{ queue_url }}</QueueUrl>
|
||||||
</GetQueueUrlResult>
|
</GetQueueUrlResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId></RequestId>
|
<RequestId></RequestId>
|
||||||
@ -484,7 +447,7 @@ DELETE_QUEUE_RESPONSE = """<DeleteQueueResponse>
|
|||||||
|
|
||||||
GET_QUEUE_ATTRIBUTES_RESPONSE = """<GetQueueAttributesResponse>
|
GET_QUEUE_ATTRIBUTES_RESPONSE = """<GetQueueAttributesResponse>
|
||||||
<GetQueueAttributesResult>
|
<GetQueueAttributesResult>
|
||||||
{% for key, value in queue.attributes.items() %}
|
{% for key, value in attributes.items() %}
|
||||||
<Attribute>
|
<Attribute>
|
||||||
<Name>{{ key }}</Name>
|
<Name>{{ key }}</Name>
|
||||||
<Value>{{ value }}</Value>
|
<Value>{{ value }}</Value>
|
||||||
|
@ -12,15 +12,15 @@ from ..exceptions import (
|
|||||||
SWFTypeDeprecatedFault,
|
SWFTypeDeprecatedFault,
|
||||||
SWFValidationException,
|
SWFValidationException,
|
||||||
)
|
)
|
||||||
from .activity_task import ActivityTask # flake8: noqa
|
from .activity_task import ActivityTask # noqa
|
||||||
from .activity_type import ActivityType # flake8: noqa
|
from .activity_type import ActivityType # noqa
|
||||||
from .decision_task import DecisionTask # flake8: noqa
|
from .decision_task import DecisionTask # noqa
|
||||||
from .domain import Domain # flake8: noqa
|
from .domain import Domain # noqa
|
||||||
from .generic_type import GenericType # flake8: noqa
|
from .generic_type import GenericType # noqa
|
||||||
from .history_event import HistoryEvent # flake8: noqa
|
from .history_event import HistoryEvent # noqa
|
||||||
from .timeout import Timeout # flake8: noqa
|
from .timeout import Timeout # noqa
|
||||||
from .workflow_type import WorkflowType # flake8: noqa
|
from .workflow_type import WorkflowType # noqa
|
||||||
from .workflow_execution import WorkflowExecution # flake8: noqa
|
from .workflow_execution import WorkflowExecution # noqa
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
KNOWN_SWF_TYPES = {
|
KNOWN_SWF_TYPES = {
|
||||||
|
@ -3,7 +3,7 @@ mock
|
|||||||
nose
|
nose
|
||||||
sure==1.4.11
|
sure==1.4.11
|
||||||
coverage
|
coverage
|
||||||
flake8==3.5.0
|
flake8==3.7.8
|
||||||
freezegun
|
freezegun
|
||||||
flask
|
flask
|
||||||
boto>=2.45.0
|
boto>=2.45.0
|
||||||
|
@ -119,7 +119,7 @@ def append_mock_to_init_py(service):
|
|||||||
filtered_lines = [_ for _ in lines if re.match('^from.*mock.*$', _)]
|
filtered_lines = [_ for _ in lines if re.match('^from.*mock.*$', _)]
|
||||||
last_import_line_index = lines.index(filtered_lines[-1])
|
last_import_line_index = lines.index(filtered_lines[-1])
|
||||||
|
|
||||||
new_line = 'from .{} import mock_{} # flake8: noqa'.format(get_escaped_service(service), get_escaped_service(service))
|
new_line = 'from .{} import mock_{} # noqa'.format(get_escaped_service(service), get_escaped_service(service))
|
||||||
lines.insert(last_import_line_index + 1, new_line)
|
lines.insert(last_import_line_index + 1, new_line)
|
||||||
|
|
||||||
body = '\n'.join(lines) + '\n'
|
body = '\n'.join(lines) + '\n'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
@ -1314,10 +1315,12 @@ def test_batch_get_aggregate_resource_config():
|
|||||||
s3_client = boto3.client('s3', region_name='us-west-2')
|
s3_client = boto3.client('s3', region_name='us-west-2')
|
||||||
for x in range(0, 10):
|
for x in range(0, 10):
|
||||||
s3_client.create_bucket(Bucket='bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'us-west-2'})
|
s3_client.create_bucket(Bucket='bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'us-west-2'})
|
||||||
|
s3_client.put_bucket_tagging(Bucket='bucket{}'.format(x), Tagging={'TagSet': [{'Key': 'Some', 'Value': 'Tag'}]})
|
||||||
|
|
||||||
s3_client_eu = boto3.client('s3', region_name='eu-west-1')
|
s3_client_eu = boto3.client('s3', region_name='eu-west-1')
|
||||||
for x in range(10, 12):
|
for x in range(10, 12):
|
||||||
s3_client_eu.create_bucket(Bucket='eu-bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})
|
s3_client_eu.create_bucket(Bucket='eu-bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})
|
||||||
|
s3_client.put_bucket_tagging(Bucket='eu-bucket{}'.format(x), Tagging={'TagSet': [{'Key': 'Some', 'Value': 'Tag'}]})
|
||||||
|
|
||||||
# Now try with resources that exist and ones that don't:
|
# Now try with resources that exist and ones that don't:
|
||||||
identifiers = [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'us-west-2', 'ResourceType': 'AWS::S3::Bucket',
|
identifiers = [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'us-west-2', 'ResourceType': 'AWS::S3::Bucket',
|
||||||
@ -1339,6 +1342,11 @@ def test_batch_get_aggregate_resource_config():
|
|||||||
|
|
||||||
assert not missing_buckets
|
assert not missing_buckets
|
||||||
|
|
||||||
|
# Verify that 'tags' is not in the result set:
|
||||||
|
for b in result['BaseConfigurationItems']:
|
||||||
|
assert not b.get('tags')
|
||||||
|
assert json.loads(b['supplementaryConfiguration']['BucketTaggingConfiguration']) == {'tagSets': [{'tags': {'Some': 'Tag'}}]}
|
||||||
|
|
||||||
# Verify that if the resource name and ID are correct that things are good:
|
# Verify that if the resource name and ID are correct that things are good:
|
||||||
identifiers = [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'us-west-2', 'ResourceType': 'AWS::S3::Bucket',
|
identifiers = [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'us-west-2', 'ResourceType': 'AWS::S3::Bucket',
|
||||||
'ResourceId': 'bucket1', 'ResourceName': 'bucket1'}]
|
'ResourceId': 'bucket1', 'ResourceName': 'bucket1'}]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
# Ensure 'assert_raises' context manager support for Python 2.6
|
# Ensure 'assert_raises' context manager support for Python 2.6
|
||||||
import tests.backport_assert_raises # flake8: noqa
|
import tests.backport_assert_raises # noqa
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
from moto.ec2.exceptions import EC2ClientError
|
from moto.ec2.exceptions import EC2ClientError
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
|
@ -3718,6 +3718,8 @@ def test_s3_config_dict():
|
|||||||
assert bucket1_result['awsRegion'] == 'us-west-2'
|
assert bucket1_result['awsRegion'] == 'us-west-2'
|
||||||
assert bucket1_result['resourceName'] == bucket1_result['resourceId'] == 'bucket1'
|
assert bucket1_result['resourceName'] == bucket1_result['resourceId'] == 'bucket1'
|
||||||
assert bucket1_result['tags'] == {'someTag': 'someValue', 'someOtherTag': 'someOtherValue'}
|
assert bucket1_result['tags'] == {'someTag': 'someValue', 'someOtherTag': 'someOtherValue'}
|
||||||
|
assert json.loads(bucket1_result['supplementaryConfiguration']['BucketTaggingConfiguration']) == \
|
||||||
|
{'tagSets': [{'tags': bucket1_result['tags']}]}
|
||||||
assert isinstance(bucket1_result['configuration'], str)
|
assert isinstance(bucket1_result['configuration'], str)
|
||||||
exist_list = ['AccessControlList', 'BucketAccelerateConfiguration', 'BucketLoggingConfiguration', 'BucketPolicy',
|
exist_list = ['AccessControlList', 'BucketAccelerateConfiguration', 'BucketLoggingConfiguration', 'BucketPolicy',
|
||||||
'IsRequesterPaysEnabled', 'BucketNotificationConfiguration']
|
'IsRequesterPaysEnabled', 'BucketNotificationConfiguration']
|
||||||
@ -3748,5 +3750,8 @@ def test_s3_config_dict():
|
|||||||
assert not s3_config_query.get_config_resource('bucket1', resource_name='eu-bucket-1')
|
assert not s3_config_query.get_config_resource('bucket1', resource_name='eu-bucket-1')
|
||||||
|
|
||||||
# Verify that no bucket policy returns the proper value:
|
# Verify that no bucket policy returns the proper value:
|
||||||
assert json.loads(s3_config_query.get_config_resource('logbucket')['supplementaryConfiguration']['BucketPolicy']) == \
|
logging_bucket = s3_config_query.get_config_resource('logbucket')
|
||||||
|
assert json.loads(logging_bucket['supplementaryConfiguration']['BucketPolicy']) == \
|
||||||
{'policyText': None}
|
{'policyText': None}
|
||||||
|
assert not logging_bucket['tags']
|
||||||
|
assert not logging_bucket['supplementaryConfiguration'].get('BucketTaggingConfiguration')
|
||||||
|
@ -7,7 +7,7 @@ import sure # noqa
|
|||||||
|
|
||||||
from boto.exception import BotoServerError
|
from boto.exception import BotoServerError
|
||||||
from moto import mock_sns_deprecated
|
from moto import mock_sns_deprecated
|
||||||
from moto.sns.models import DEFAULT_TOPIC_POLICY, DEFAULT_EFFECTIVE_DELIVERY_POLICY, DEFAULT_PAGE_SIZE
|
from moto.sns.models import DEFAULT_EFFECTIVE_DELIVERY_POLICY, DEFAULT_PAGE_SIZE
|
||||||
|
|
||||||
|
|
||||||
@mock_sns_deprecated
|
@mock_sns_deprecated
|
||||||
@ -76,7 +76,34 @@ def test_topic_attributes():
|
|||||||
.format(conn.region.name)
|
.format(conn.region.name)
|
||||||
)
|
)
|
||||||
attributes["Owner"].should.equal(123456789012)
|
attributes["Owner"].should.equal(123456789012)
|
||||||
json.loads(attributes["Policy"]).should.equal(DEFAULT_TOPIC_POLICY)
|
json.loads(attributes["Policy"]).should.equal({
|
||||||
|
"Version": "2008-10-17",
|
||||||
|
"Id": "__default_policy_ID",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Sid": "__default_statement_ID",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "*"
|
||||||
|
},
|
||||||
|
"Action": [
|
||||||
|
"SNS:GetTopicAttributes",
|
||||||
|
"SNS:SetTopicAttributes",
|
||||||
|
"SNS:AddPermission",
|
||||||
|
"SNS:RemovePermission",
|
||||||
|
"SNS:DeleteTopic",
|
||||||
|
"SNS:Subscribe",
|
||||||
|
"SNS:ListSubscriptionsByTopic",
|
||||||
|
"SNS:Publish",
|
||||||
|
"SNS:Receive",
|
||||||
|
],
|
||||||
|
"Resource": "arn:aws:sns:us-east-1:123456789012:some-topic",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"AWS:SourceOwner": "123456789012"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
})
|
||||||
attributes["DisplayName"].should.equal("")
|
attributes["DisplayName"].should.equal("")
|
||||||
attributes["SubscriptionsPending"].should.equal(0)
|
attributes["SubscriptionsPending"].should.equal(0)
|
||||||
attributes["SubscriptionsConfirmed"].should.equal(0)
|
attributes["SubscriptionsConfirmed"].should.equal(0)
|
||||||
@ -89,11 +116,11 @@ def test_topic_attributes():
|
|||||||
# i.e. unicode on Python 2 -- u"foobar"
|
# i.e. unicode on Python 2 -- u"foobar"
|
||||||
# and bytes on Python 3 -- b"foobar"
|
# and bytes on Python 3 -- b"foobar"
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
policy = {b"foo": b"bar"}
|
policy = json.dumps({b"foo": b"bar"})
|
||||||
displayname = b"My display name"
|
displayname = b"My display name"
|
||||||
delivery = {b"http": {b"defaultHealthyRetryPolicy": {b"numRetries": 5}}}
|
delivery = {b"http": {b"defaultHealthyRetryPolicy": {b"numRetries": 5}}}
|
||||||
else:
|
else:
|
||||||
policy = {u"foo": u"bar"}
|
policy = json.dumps({u"foo": u"bar"})
|
||||||
displayname = u"My display name"
|
displayname = u"My display name"
|
||||||
delivery = {u"http": {u"defaultHealthyRetryPolicy": {u"numRetries": 5}}}
|
delivery = {u"http": {u"defaultHealthyRetryPolicy": {u"numRetries": 5}}}
|
||||||
conn.set_topic_attributes(topic_arn, "Policy", policy)
|
conn.set_topic_attributes(topic_arn, "Policy", policy)
|
||||||
@ -102,7 +129,7 @@ def test_topic_attributes():
|
|||||||
|
|
||||||
attributes = conn.get_topic_attributes(topic_arn)['GetTopicAttributesResponse'][
|
attributes = conn.get_topic_attributes(topic_arn)['GetTopicAttributesResponse'][
|
||||||
'GetTopicAttributesResult']['Attributes']
|
'GetTopicAttributesResult']['Attributes']
|
||||||
attributes["Policy"].should.equal("{'foo': 'bar'}")
|
attributes["Policy"].should.equal('{"foo": "bar"}')
|
||||||
attributes["DisplayName"].should.equal("My display name")
|
attributes["DisplayName"].should.equal("My display name")
|
||||||
attributes["DeliveryPolicy"].should.equal(
|
attributes["DeliveryPolicy"].should.equal(
|
||||||
"{'http': {'defaultHealthyRetryPolicy': {'numRetries': 5}}}")
|
"{'http': {'defaultHealthyRetryPolicy': {'numRetries': 5}}}")
|
||||||
|
@ -7,7 +7,7 @@ import sure # noqa
|
|||||||
|
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from moto import mock_sns
|
from moto import mock_sns
|
||||||
from moto.sns.models import DEFAULT_TOPIC_POLICY, DEFAULT_EFFECTIVE_DELIVERY_POLICY, DEFAULT_PAGE_SIZE
|
from moto.sns.models import DEFAULT_EFFECTIVE_DELIVERY_POLICY, DEFAULT_PAGE_SIZE
|
||||||
|
|
||||||
|
|
||||||
@mock_sns
|
@mock_sns
|
||||||
@ -156,7 +156,34 @@ def test_topic_attributes():
|
|||||||
.format(conn._client_config.region_name)
|
.format(conn._client_config.region_name)
|
||||||
)
|
)
|
||||||
attributes["Owner"].should.equal('123456789012')
|
attributes["Owner"].should.equal('123456789012')
|
||||||
json.loads(attributes["Policy"]).should.equal(DEFAULT_TOPIC_POLICY)
|
json.loads(attributes["Policy"]).should.equal({
|
||||||
|
"Version": "2008-10-17",
|
||||||
|
"Id": "__default_policy_ID",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Sid": "__default_statement_ID",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": "*"
|
||||||
|
},
|
||||||
|
"Action": [
|
||||||
|
"SNS:GetTopicAttributes",
|
||||||
|
"SNS:SetTopicAttributes",
|
||||||
|
"SNS:AddPermission",
|
||||||
|
"SNS:RemovePermission",
|
||||||
|
"SNS:DeleteTopic",
|
||||||
|
"SNS:Subscribe",
|
||||||
|
"SNS:ListSubscriptionsByTopic",
|
||||||
|
"SNS:Publish",
|
||||||
|
"SNS:Receive",
|
||||||
|
],
|
||||||
|
"Resource": "arn:aws:sns:us-east-1:123456789012:some-topic",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"AWS:SourceOwner": "123456789012"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
})
|
||||||
attributes["DisplayName"].should.equal("")
|
attributes["DisplayName"].should.equal("")
|
||||||
attributes["SubscriptionsPending"].should.equal('0')
|
attributes["SubscriptionsPending"].should.equal('0')
|
||||||
attributes["SubscriptionsConfirmed"].should.equal('0')
|
attributes["SubscriptionsConfirmed"].should.equal('0')
|
||||||
@ -217,18 +244,190 @@ def test_topic_paging():
|
|||||||
|
|
||||||
@mock_sns
|
@mock_sns
|
||||||
def test_add_remove_permissions():
|
def test_add_remove_permissions():
|
||||||
conn = boto3.client('sns', region_name='us-east-1')
|
client = boto3.client('sns', region_name='us-east-1')
|
||||||
response = conn.create_topic(Name='testpermissions')
|
topic_arn = client.create_topic(Name='test-permissions')['TopicArn']
|
||||||
|
|
||||||
conn.add_permission(
|
client.add_permission(
|
||||||
TopicArn=response['TopicArn'],
|
TopicArn=topic_arn,
|
||||||
Label='Test1234',
|
Label='test',
|
||||||
|
AWSAccountId=['999999999999'],
|
||||||
|
ActionName=['Publish']
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_topic_attributes(TopicArn=topic_arn)
|
||||||
|
json.loads(response['Attributes']['Policy']).should.equal({
|
||||||
|
'Version': '2008-10-17',
|
||||||
|
'Id': '__default_policy_ID',
|
||||||
|
'Statement': [
|
||||||
|
{
|
||||||
|
'Effect': 'Allow',
|
||||||
|
'Sid': '__default_statement_ID',
|
||||||
|
'Principal': {
|
||||||
|
'AWS': '*'
|
||||||
|
},
|
||||||
|
'Action': [
|
||||||
|
'SNS:GetTopicAttributes',
|
||||||
|
'SNS:SetTopicAttributes',
|
||||||
|
'SNS:AddPermission',
|
||||||
|
'SNS:RemovePermission',
|
||||||
|
'SNS:DeleteTopic',
|
||||||
|
'SNS:Subscribe',
|
||||||
|
'SNS:ListSubscriptionsByTopic',
|
||||||
|
'SNS:Publish',
|
||||||
|
'SNS:Receive',
|
||||||
|
],
|
||||||
|
'Resource': 'arn:aws:sns:us-east-1:123456789012:test-permissions',
|
||||||
|
'Condition': {
|
||||||
|
'StringEquals': {
|
||||||
|
'AWS:SourceOwner': '123456789012'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Sid': 'test',
|
||||||
|
'Effect': 'Allow',
|
||||||
|
'Principal': {
|
||||||
|
'AWS': 'arn:aws:iam::999999999999:root'
|
||||||
|
},
|
||||||
|
'Action': 'SNS:Publish',
|
||||||
|
'Resource': 'arn:aws:sns:us-east-1:123456789012:test-permissions'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
client.remove_permission(
|
||||||
|
TopicArn=topic_arn,
|
||||||
|
Label='test'
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_topic_attributes(TopicArn=topic_arn)
|
||||||
|
json.loads(response['Attributes']['Policy']).should.equal({
|
||||||
|
'Version': '2008-10-17',
|
||||||
|
'Id': '__default_policy_ID',
|
||||||
|
'Statement': [
|
||||||
|
{
|
||||||
|
'Effect': 'Allow',
|
||||||
|
'Sid': '__default_statement_ID',
|
||||||
|
'Principal': {
|
||||||
|
'AWS': '*'
|
||||||
|
},
|
||||||
|
'Action': [
|
||||||
|
'SNS:GetTopicAttributes',
|
||||||
|
'SNS:SetTopicAttributes',
|
||||||
|
'SNS:AddPermission',
|
||||||
|
'SNS:RemovePermission',
|
||||||
|
'SNS:DeleteTopic',
|
||||||
|
'SNS:Subscribe',
|
||||||
|
'SNS:ListSubscriptionsByTopic',
|
||||||
|
'SNS:Publish',
|
||||||
|
'SNS:Receive',
|
||||||
|
],
|
||||||
|
'Resource': 'arn:aws:sns:us-east-1:123456789012:test-permissions',
|
||||||
|
'Condition': {
|
||||||
|
'StringEquals': {
|
||||||
|
'AWS:SourceOwner': '123456789012'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
client.add_permission(
|
||||||
|
TopicArn=topic_arn,
|
||||||
|
Label='test',
|
||||||
|
AWSAccountId=[
|
||||||
|
'888888888888',
|
||||||
|
'999999999999'
|
||||||
|
],
|
||||||
|
ActionName=[
|
||||||
|
'Publish',
|
||||||
|
'Subscribe'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_topic_attributes(TopicArn=topic_arn)
|
||||||
|
json.loads(response['Attributes']['Policy'])['Statement'][1].should.equal({
|
||||||
|
'Sid': 'test',
|
||||||
|
'Effect': 'Allow',
|
||||||
|
'Principal': {
|
||||||
|
'AWS': [
|
||||||
|
'arn:aws:iam::888888888888:root',
|
||||||
|
'arn:aws:iam::999999999999:root'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'Action': [
|
||||||
|
'SNS:Publish',
|
||||||
|
'SNS:Subscribe'
|
||||||
|
],
|
||||||
|
'Resource': 'arn:aws:sns:us-east-1:123456789012:test-permissions'
|
||||||
|
})
|
||||||
|
|
||||||
|
# deleting non existing permission should be successful
|
||||||
|
client.remove_permission(
|
||||||
|
TopicArn=topic_arn,
|
||||||
|
Label='non-existing'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sns
|
||||||
|
def test_add_permission_errors():
|
||||||
|
client = boto3.client('sns', region_name='us-east-1')
|
||||||
|
topic_arn = client.create_topic(Name='test-permissions')['TopicArn']
|
||||||
|
client.add_permission(
|
||||||
|
TopicArn=topic_arn,
|
||||||
|
Label='test',
|
||||||
|
AWSAccountId=['999999999999'],
|
||||||
|
ActionName=['Publish']
|
||||||
|
)
|
||||||
|
|
||||||
|
client.add_permission.when.called_with(
|
||||||
|
TopicArn=topic_arn,
|
||||||
|
Label='test',
|
||||||
AWSAccountId=['999999999999'],
|
AWSAccountId=['999999999999'],
|
||||||
ActionName=['AddPermission']
|
ActionName=['AddPermission']
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Statement already exists'
|
||||||
)
|
)
|
||||||
conn.remove_permission(
|
|
||||||
TopicArn=response['TopicArn'],
|
client.add_permission.when.called_with(
|
||||||
Label='Test1234'
|
TopicArn=topic_arn + '-not-existing',
|
||||||
|
Label='test-2',
|
||||||
|
AWSAccountId=['999999999999'],
|
||||||
|
ActionName=['AddPermission']
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Topic does not exist'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.add_permission.when.called_with(
|
||||||
|
TopicArn=topic_arn,
|
||||||
|
Label='test-2',
|
||||||
|
AWSAccountId=['999999999999'],
|
||||||
|
ActionName=['NotExistingAction']
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Policy statement action out of service scope!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sns
|
||||||
|
def test_remove_permission_errors():
|
||||||
|
client = boto3.client('sns', region_name='us-east-1')
|
||||||
|
topic_arn = client.create_topic(Name='test-permissions')['TopicArn']
|
||||||
|
client.add_permission(
|
||||||
|
TopicArn=topic_arn,
|
||||||
|
Label='test',
|
||||||
|
AWSAccountId=['999999999999'],
|
||||||
|
ActionName=['Publish']
|
||||||
|
)
|
||||||
|
|
||||||
|
client.remove_permission.when.called_with(
|
||||||
|
TopicArn=topic_arn + '-not-existing',
|
||||||
|
Label='test',
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Topic does not exist'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import os
|
|||||||
import boto
|
import boto
|
||||||
import boto3
|
import boto3
|
||||||
import botocore.exceptions
|
import botocore.exceptions
|
||||||
|
import six
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from boto.exception import SQSError
|
from boto.exception import SQSError
|
||||||
from boto.sqs.message import RawMessage, Message
|
from boto.sqs.message import RawMessage, Message
|
||||||
@ -156,6 +157,28 @@ def test_create_queue_with_tags():
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sqs
|
||||||
|
def test_get_queue_url():
|
||||||
|
client = boto3.client('sqs', region_name='us-east-1')
|
||||||
|
client.create_queue(QueueName='test-queue')
|
||||||
|
|
||||||
|
response = client.get_queue_url(QueueName='test-queue')
|
||||||
|
|
||||||
|
response.should.have.key('QueueUrl').which.should.contain('test-queue')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sqs
|
||||||
|
def test_get_queue_url_errors():
|
||||||
|
client = boto3.client('sqs', region_name='us-east-1')
|
||||||
|
|
||||||
|
client.get_queue_url.when.called_with(
|
||||||
|
QueueName='non-existing-queue'
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The specified queue does not exist for this wsdl version.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_sqs
|
@mock_sqs
|
||||||
def test_get_nonexistent_queue():
|
def test_get_nonexistent_queue():
|
||||||
sqs = boto3.resource('sqs', region_name='us-east-1')
|
sqs = boto3.resource('sqs', region_name='us-east-1')
|
||||||
@ -341,6 +364,98 @@ def test_delete_queue():
|
|||||||
queue.delete()
|
queue.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sqs
|
||||||
|
def test_get_queue_attributes():
|
||||||
|
client = boto3.client('sqs', region_name='us-east-1')
|
||||||
|
response = client.create_queue(QueueName='test-queue')
|
||||||
|
queue_url = response['QueueUrl']
|
||||||
|
|
||||||
|
response = client.get_queue_attributes(QueueUrl=queue_url)
|
||||||
|
|
||||||
|
response['Attributes']['ApproximateNumberOfMessages'].should.equal('0')
|
||||||
|
response['Attributes']['ApproximateNumberOfMessagesDelayed'].should.equal('0')
|
||||||
|
response['Attributes']['ApproximateNumberOfMessagesNotVisible'].should.equal('0')
|
||||||
|
response['Attributes']['CreatedTimestamp'].should.be.a(six.string_types)
|
||||||
|
response['Attributes']['DelaySeconds'].should.equal('0')
|
||||||
|
response['Attributes']['LastModifiedTimestamp'].should.be.a(six.string_types)
|
||||||
|
response['Attributes']['MaximumMessageSize'].should.equal('65536')
|
||||||
|
response['Attributes']['MessageRetentionPeriod'].should.equal('345600')
|
||||||
|
response['Attributes']['QueueArn'].should.equal('arn:aws:sqs:us-east-1:123456789012:test-queue')
|
||||||
|
response['Attributes']['ReceiveMessageWaitTimeSeconds'].should.equal('0')
|
||||||
|
response['Attributes']['VisibilityTimeout'].should.equal('30')
|
||||||
|
|
||||||
|
response = client.get_queue_attributes(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
AttributeNames=[
|
||||||
|
'ApproximateNumberOfMessages',
|
||||||
|
'MaximumMessageSize',
|
||||||
|
'QueueArn',
|
||||||
|
'VisibilityTimeout'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
response['Attributes'].should.equal({
|
||||||
|
'ApproximateNumberOfMessages': '0',
|
||||||
|
'MaximumMessageSize': '65536',
|
||||||
|
'QueueArn': 'arn:aws:sqs:us-east-1:123456789012:test-queue',
|
||||||
|
'VisibilityTimeout': '30'
|
||||||
|
})
|
||||||
|
|
||||||
|
# should not return any attributes, if it was not set before
|
||||||
|
response = client.get_queue_attributes(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
AttributeNames=[
|
||||||
|
'KmsMasterKeyId'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
response.should_not.have.key('Attributes')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sqs
|
||||||
|
def test_get_queue_attributes_errors():
|
||||||
|
client = boto3.client('sqs', region_name='us-east-1')
|
||||||
|
response = client.create_queue(QueueName='test-queue')
|
||||||
|
queue_url = response['QueueUrl']
|
||||||
|
|
||||||
|
client.get_queue_attributes.when.called_with(
|
||||||
|
QueueUrl=queue_url + '-non-existing'
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The specified queue does not exist for this wsdl version.'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_queue_attributes.when.called_with(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
AttributeNames=[
|
||||||
|
'QueueArn',
|
||||||
|
'not-existing',
|
||||||
|
'VisibilityTimeout'
|
||||||
|
]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Unknown Attribute not-existing.'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_queue_attributes.when.called_with(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
AttributeNames=[
|
||||||
|
''
|
||||||
|
]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Unknown Attribute .'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_queue_attributes.when.called_with(
|
||||||
|
QueueUrl = queue_url,
|
||||||
|
AttributeNames = []
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Unknown Attribute .'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_sqs
|
@mock_sqs
|
||||||
def test_set_queue_attribute():
|
def test_set_queue_attribute():
|
||||||
sqs = boto3.resource('sqs', region_name='us-east-1')
|
sqs = boto3.resource('sqs', region_name='us-east-1')
|
||||||
@ -882,11 +997,101 @@ def test_delete_message_after_visibility_timeout():
|
|||||||
assert new_queue.count() == 0
|
assert new_queue.count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
@mock_sqs
|
||||||
|
def test_delete_message_errors():
|
||||||
|
client = boto3.client('sqs', region_name='us-east-1')
|
||||||
|
response = client.create_queue(QueueName='test-queue')
|
||||||
|
queue_url = response['QueueUrl']
|
||||||
|
client.send_message(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
MessageBody='body'
|
||||||
|
)
|
||||||
|
response = client.receive_message(
|
||||||
|
QueueUrl=queue_url
|
||||||
|
)
|
||||||
|
receipt_handle = response['Messages'][0]['ReceiptHandle']
|
||||||
|
|
||||||
|
client.delete_message.when.called_with(
|
||||||
|
QueueUrl=queue_url + '-not-existing',
|
||||||
|
ReceiptHandle=receipt_handle
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The specified queue does not exist for this wsdl version.'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.delete_message.when.called_with(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
ReceiptHandle='not-existing'
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The input receipt handle is invalid.'
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock_sqs
|
||||||
|
def test_send_message_batch():
|
||||||
|
client = boto3.client('sqs', region_name='us-east-1')
|
||||||
|
response = client.create_queue(QueueName='test-queue')
|
||||||
|
queue_url = response['QueueUrl']
|
||||||
|
|
||||||
|
response = client.send_message_batch(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
Entries=[
|
||||||
|
{
|
||||||
|
'Id': 'id_1',
|
||||||
|
'MessageBody': 'body_1',
|
||||||
|
'DelaySeconds': 0,
|
||||||
|
'MessageAttributes': {
|
||||||
|
'attribute_name_1': {
|
||||||
|
'StringValue': 'attribute_value_1',
|
||||||
|
'DataType': 'String'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Id': 'id_2',
|
||||||
|
'MessageBody': 'body_2',
|
||||||
|
'DelaySeconds': 0,
|
||||||
|
'MessageAttributes': {
|
||||||
|
'attribute_name_2': {
|
||||||
|
'StringValue': '123',
|
||||||
|
'DataType': 'Number'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
sorted([entry['Id'] for entry in response['Successful']]).should.equal([
|
||||||
|
'id_1',
|
||||||
|
'id_2'
|
||||||
|
])
|
||||||
|
|
||||||
|
response = client.receive_message(
|
||||||
|
QueueUrl=queue_url,
|
||||||
|
MaxNumberOfMessages=10
|
||||||
|
)
|
||||||
|
|
||||||
|
response['Messages'][0]['Body'].should.equal('body_1')
|
||||||
|
response['Messages'][0]['MessageAttributes'].should.equal({
|
||||||
|
'attribute_name_1': {
|
||||||
|
'StringValue': 'attribute_value_1',
|
||||||
|
'DataType': 'String'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
response['Messages'][1]['Body'].should.equal('body_2')
|
||||||
|
response['Messages'][1]['MessageAttributes'].should.equal({
|
||||||
|
'attribute_name_2': {
|
||||||
|
'StringValue': '123',
|
||||||
|
'DataType': 'Number'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@mock_sqs
|
@mock_sqs
|
||||||
def test_send_message_batch_errors():
|
def test_send_message_batch_errors():
|
||||||
client = boto3.client('sqs', region_name='us-east-1')
|
client = boto3.client('sqs', region_name='us-east-1')
|
||||||
|
|
||||||
response = client.create_queue(QueueName='test-queue-with-tags')
|
response = client.create_queue(QueueName='test-queue')
|
||||||
queue_url = response['QueueUrl']
|
queue_url = response['QueueUrl']
|
||||||
|
|
||||||
client.send_message_batch.when.called_with(
|
client.send_message_batch.when.called_with(
|
||||||
|
Loading…
Reference in New Issue
Block a user