commit
21d2fac468
@ -3163,7 +3163,7 @@
|
||||
- [ ] describe_events
|
||||
|
||||
## iam
|
||||
55% implemented
|
||||
57% implemented
|
||||
- [ ] add_client_id_to_open_id_connect_provider
|
||||
- [X] add_role_to_instance_profile
|
||||
- [X] add_user_to_group
|
||||
@ -3176,7 +3176,7 @@
|
||||
- [X] create_group
|
||||
- [X] create_instance_profile
|
||||
- [X] create_login_profile
|
||||
- [ ] create_open_id_connect_provider
|
||||
- [X] create_open_id_connect_provider
|
||||
- [X] create_policy
|
||||
- [X] create_policy_version
|
||||
- [X] create_role
|
||||
@ -3193,8 +3193,8 @@
|
||||
- [ ] delete_group_policy
|
||||
- [ ] delete_instance_profile
|
||||
- [X] delete_login_profile
|
||||
- [ ] delete_open_id_connect_provider
|
||||
- [ ] delete_policy
|
||||
- [X] delete_open_id_connect_provider
|
||||
- [X] delete_policy
|
||||
- [X] delete_policy_version
|
||||
- [X] delete_role
|
||||
- [ ] delete_role_permissions_boundary
|
||||
@ -3227,7 +3227,7 @@
|
||||
- [X] get_group_policy
|
||||
- [X] get_instance_profile
|
||||
- [X] get_login_profile
|
||||
- [ ] get_open_id_connect_provider
|
||||
- [X] get_open_id_connect_provider
|
||||
- [ ] get_organizations_access_report
|
||||
- [X] get_policy
|
||||
- [X] get_policy_version
|
||||
@ -3253,7 +3253,7 @@
|
||||
- [ ] list_instance_profiles
|
||||
- [ ] list_instance_profiles_for_role
|
||||
- [X] list_mfa_devices
|
||||
- [ ] list_open_id_connect_providers
|
||||
- [X] list_open_id_connect_providers
|
||||
- [X] list_policies
|
||||
- [ ] list_policies_granting_service_access
|
||||
- [X] list_policy_versions
|
||||
@ -5902,7 +5902,7 @@
|
||||
- [x] untag_resource
|
||||
|
||||
## sqs
|
||||
65% implemented
|
||||
75% implemented
|
||||
- [X] add_permission
|
||||
- [X] change_message_visibility
|
||||
- [ ] change_message_visibility_batch
|
||||
@ -5913,13 +5913,13 @@
|
||||
- [ ] get_queue_attributes
|
||||
- [ ] get_queue_url
|
||||
- [X] list_dead_letter_source_queues
|
||||
- [ ] list_queue_tags
|
||||
- [x] list_queue_tags
|
||||
- [X] list_queues
|
||||
- [X] purge_queue
|
||||
- [ ] receive_message
|
||||
- [X] remove_permission
|
||||
- [X] send_message
|
||||
- [ ] send_message_batch
|
||||
- [x] send_message_batch
|
||||
- [X] set_queue_attributes
|
||||
- [X] tag_queue
|
||||
- [X] untag_queue
|
||||
|
@ -457,6 +457,9 @@ class DynamoHandler(BaseResponse):
|
||||
range_comparison = None
|
||||
range_values = []
|
||||
|
||||
if '=' not in hash_key_expression:
|
||||
return self.error('com.amazonaws.dynamodb.v20111205#ValidationException',
|
||||
'Query key condition not supported')
|
||||
hash_key_value_alias = hash_key_expression.split("=")[1].strip()
|
||||
# Temporary fix until we get proper KeyConditionExpression function
|
||||
hash_key = value_alias_map.get(hash_key_value_alias, {'S': hash_key_value_alias})
|
||||
|
@ -50,7 +50,7 @@ EC2_PREFIX_TO_RESOURCE = dict((v, k) for (k, v) in EC2_RESOURCE_TO_PREFIX.items(
|
||||
|
||||
def random_resource_id(size=8):
|
||||
chars = list(range(10)) + ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
resource_id = ''.join(six.text_type(random.choice(chars)) for x in range(size))
|
||||
resource_id = ''.join(six.text_type(random.choice(chars)) for _ in range(size))
|
||||
return resource_id
|
||||
|
||||
|
||||
@ -460,8 +460,8 @@ def generic_filter(filters, objects):
|
||||
|
||||
|
||||
def simple_aws_filter_to_re(filter_string):
|
||||
tmp_filter = filter_string.replace('\?', '[?]')
|
||||
tmp_filter = tmp_filter.replace('\*', '[*]')
|
||||
tmp_filter = filter_string.replace(r'\?', '[?]')
|
||||
tmp_filter = tmp_filter.replace(r'\*', '[*]')
|
||||
tmp_filter = fnmatch.translate(tmp_filter)
|
||||
return tmp_filter
|
||||
|
||||
@ -491,7 +491,7 @@ def get_prefix(resource_id):
|
||||
'network-interface-attachment']
|
||||
if resource_id_prefix not in EC2_RESOURCE_TO_PREFIX.values():
|
||||
uuid4hex = re.compile(
|
||||
'[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z', re.I)
|
||||
r'[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z', re.I)
|
||||
if uuid4hex.match(resource_id) is not None:
|
||||
resource_id_prefix = EC2_RESOURCE_TO_PREFIX['reserved-instance']
|
||||
else:
|
||||
@ -510,7 +510,7 @@ def is_valid_resource_id(resource_id):
|
||||
|
||||
|
||||
def is_valid_cidr(cird):
|
||||
cidr_pattern = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$'
|
||||
cidr_pattern = r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$'
|
||||
cidr_pattern_re = re.compile(cidr_pattern)
|
||||
return cidr_pattern_re.match(cird) is not None
|
||||
|
||||
|
@ -93,3 +93,27 @@ class TooManyTags(RESTError):
|
||||
super(TooManyTags, self).__init__(
|
||||
'ValidationError', "1 validation error detected: Value '{}' at '{}' failed to satisfy "
|
||||
"constraint: Member must have length less than or equal to 50.".format(tags, param))
|
||||
|
||||
|
||||
class EntityAlreadyExists(RESTError):
|
||||
code = 409
|
||||
|
||||
def __init__(self):
|
||||
super(EntityAlreadyExists, self).__init__(
|
||||
'EntityAlreadyExists', "Unknown")
|
||||
|
||||
|
||||
class ValidationError(RESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
super(ValidationError, self).__init__(
|
||||
'ValidationError', message)
|
||||
|
||||
|
||||
class InvalidInput(RESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
super(InvalidInput, self).__init__(
|
||||
'InvalidInput', message)
|
||||
|
@ -7,6 +7,7 @@ import re
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
from moto.core.exceptions import RESTError
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
@ -14,8 +15,9 @@ from moto.core.utils import iso_8601_datetime_without_milliseconds, iso_8601_dat
|
||||
from moto.iam.policy_validation import IAMPolicyDocumentValidator
|
||||
|
||||
from .aws_managed_policies import aws_managed_policies_data
|
||||
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, IAMLimitExceededException, \
|
||||
MalformedCertificate, DuplicateTags, TagKeyTooBig, InvalidTagCharacters, TooManyTags, TagValueTooBig
|
||||
from .exceptions import (IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, IAMLimitExceededException,
|
||||
MalformedCertificate, DuplicateTags, TagKeyTooBig, InvalidTagCharacters, TooManyTags, TagValueTooBig,
|
||||
EntityAlreadyExists, ValidationError, InvalidInput)
|
||||
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
|
||||
|
||||
ACCOUNT_ID = 123456789012
|
||||
@ -93,6 +95,82 @@ class SAMLProvider(BaseModel):
|
||||
return "arn:aws:iam::{0}:saml-provider/{1}".format(ACCOUNT_ID, self.name)
|
||||
|
||||
|
||||
class OpenIDConnectProvider(BaseModel):
|
||||
def __init__(self, url, thumbprint_list, client_id_list=None):
|
||||
self._errors = []
|
||||
self._validate(url, thumbprint_list, client_id_list)
|
||||
|
||||
parsed_url = urlparse(url)
|
||||
self.url = parsed_url.netloc + parsed_url.path
|
||||
self.thumbprint_list = thumbprint_list
|
||||
self.client_id_list = client_id_list
|
||||
self.create_date = datetime.utcnow()
|
||||
|
||||
@property
|
||||
def arn(self):
|
||||
return 'arn:aws:iam::{0}:oidc-provider/{1}'.format(ACCOUNT_ID, self.url)
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_without_milliseconds(self.create_date)
|
||||
|
||||
def _validate(self, url, thumbprint_list, client_id_list):
|
||||
if any(len(client_id) > 255 for client_id in client_id_list):
|
||||
self._errors.append(self._format_error(
|
||||
key='clientIDList',
|
||||
value=client_id_list,
|
||||
constraint='Member must satisfy constraint: '
|
||||
'[Member must have length less than or equal to 255, '
|
||||
'Member must have length greater than or equal to 1]',
|
||||
))
|
||||
|
||||
if any(len(thumbprint) > 40 for thumbprint in thumbprint_list):
|
||||
self._errors.append(self._format_error(
|
||||
key='thumbprintList',
|
||||
value=thumbprint_list,
|
||||
constraint='Member must satisfy constraint: '
|
||||
'[Member must have length less than or equal to 40, '
|
||||
'Member must have length greater than or equal to 40]',
|
||||
))
|
||||
|
||||
if len(url) > 255:
|
||||
self._errors.append(self._format_error(
|
||||
key='url',
|
||||
value=url,
|
||||
constraint='Member must have length less than or equal to 255',
|
||||
))
|
||||
|
||||
self._raise_errors()
|
||||
|
||||
parsed_url = urlparse(url)
|
||||
if not parsed_url.scheme or not parsed_url.netloc:
|
||||
raise ValidationError('Invalid Open ID Connect Provider URL')
|
||||
|
||||
if len(thumbprint_list) > 5:
|
||||
raise InvalidInput('Thumbprint list must contain fewer than 5 entries.')
|
||||
|
||||
if len(client_id_list) > 100:
|
||||
raise IAMLimitExceededException('Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100')
|
||||
|
||||
def _format_error(self, key, value, constraint):
|
||||
return 'Value "{value}" at "{key}" failed to satisfy constraint: {constraint}'.format(
|
||||
constraint=constraint,
|
||||
key=key,
|
||||
value=value,
|
||||
)
|
||||
|
||||
def _raise_errors(self):
|
||||
if self._errors:
|
||||
count = len(self._errors)
|
||||
plural = "s" if len(self._errors) > 1 else ""
|
||||
errors = "; ".join(self._errors)
|
||||
self._errors = [] # reset collected errors
|
||||
|
||||
raise ValidationError('{count} validation error{plural} detected: {errors}'.format(
|
||||
count=count, plural=plural, errors=errors,
|
||||
))
|
||||
|
||||
|
||||
class PolicyVersion(object):
|
||||
|
||||
def __init__(self,
|
||||
@ -515,6 +593,7 @@ class IAMBackend(BaseBackend):
|
||||
self.managed_policies = self._init_managed_policies()
|
||||
self.account_aliases = []
|
||||
self.saml_providers = {}
|
||||
self.open_id_providers = {}
|
||||
self.policy_arn_regex = re.compile(
|
||||
r'^arn:aws:iam::[0-9]*:policy/.*$')
|
||||
super(IAMBackend, self).__init__()
|
||||
@ -1099,6 +1178,9 @@ class IAMBackend(BaseBackend):
|
||||
user = self.get_user(user_name)
|
||||
user.delete_policy(policy_name)
|
||||
|
||||
def delete_policy(self, policy_arn):
|
||||
del self.managed_policies[policy_arn]
|
||||
|
||||
def create_access_key(self, user_name=None):
|
||||
user = self.get_user(user_name)
|
||||
key = user.create_access_key()
|
||||
@ -1261,5 +1343,28 @@ class IAMBackend(BaseBackend):
|
||||
return user
|
||||
return None
|
||||
|
||||
def create_open_id_connect_provider(self, url, thumbprint_list, client_id_list):
|
||||
open_id_provider = OpenIDConnectProvider(url, thumbprint_list, client_id_list)
|
||||
|
||||
if open_id_provider.arn in self.open_id_providers:
|
||||
raise EntityAlreadyExists
|
||||
|
||||
self.open_id_providers[open_id_provider.arn] = open_id_provider
|
||||
return open_id_provider
|
||||
|
||||
def delete_open_id_connect_provider(self, arn):
|
||||
self.open_id_providers.pop(arn, None)
|
||||
|
||||
def get_open_id_connect_provider(self, arn):
|
||||
open_id_provider = self.open_id_providers.get(arn)
|
||||
|
||||
if not open_id_provider:
|
||||
raise IAMNotFoundException('OpenIDConnect Provider not found for arn {}'.format(arn))
|
||||
|
||||
return open_id_provider
|
||||
|
||||
def list_open_id_connect_providers(self):
|
||||
return list(self.open_id_providers.keys())
|
||||
|
||||
|
||||
iam_backend = IAMBackend()
|
||||
|
@ -604,6 +604,12 @@ class IamResponse(BaseResponse):
|
||||
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
|
||||
return template.render(name='DeleteUser')
|
||||
|
||||
def delete_policy(self):
|
||||
policy_arn = self._get_param('PolicyArn')
|
||||
iam_backend.delete_policy(policy_arn)
|
||||
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
|
||||
return template.render(name='DeletePolicy')
|
||||
|
||||
def delete_login_profile(self):
|
||||
user_name = self._get_param('UserName')
|
||||
iam_backend.delete_login_profile(user_name)
|
||||
@ -749,6 +755,38 @@ class IamResponse(BaseResponse):
|
||||
template = self.response_template(UNTAG_ROLE_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def create_open_id_connect_provider(self):
|
||||
open_id_provider_url = self._get_param('Url')
|
||||
thumbprint_list = self._get_multi_param('ThumbprintList.member')
|
||||
client_id_list = self._get_multi_param('ClientIDList.member')
|
||||
|
||||
open_id_provider = iam_backend.create_open_id_connect_provider(open_id_provider_url, thumbprint_list, client_id_list)
|
||||
|
||||
template = self.response_template(CREATE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE)
|
||||
return template.render(open_id_provider=open_id_provider)
|
||||
|
||||
def delete_open_id_connect_provider(self):
|
||||
open_id_provider_arn = self._get_param('OpenIDConnectProviderArn')
|
||||
|
||||
iam_backend.delete_open_id_connect_provider(open_id_provider_arn)
|
||||
|
||||
template = self.response_template(DELETE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def get_open_id_connect_provider(self):
|
||||
open_id_provider_arn = self._get_param('OpenIDConnectProviderArn')
|
||||
|
||||
open_id_provider = iam_backend.get_open_id_connect_provider(open_id_provider_arn)
|
||||
|
||||
template = self.response_template(GET_OPEN_ID_CONNECT_PROVIDER_TEMPLATE)
|
||||
return template.render(open_id_provider=open_id_provider)
|
||||
|
||||
def list_open_id_connect_providers(self):
|
||||
open_id_provider_arns = iam_backend.list_open_id_connect_providers()
|
||||
|
||||
template = self.response_template(LIST_OPEN_ID_CONNECT_PROVIDERS_TEMPLATE)
|
||||
return template.render(open_id_provider_arns=open_id_provider_arns)
|
||||
|
||||
|
||||
LIST_ENTITIES_FOR_POLICY_TEMPLATE = """<ListEntitiesForPolicyResponse>
|
||||
<ListEntitiesForPolicyResult>
|
||||
@ -1968,3 +2006,57 @@ UNTAG_ROLE_TEMPLATE = """<UntagRoleResponse xmlns="https://iam.amazonaws.com/doc
|
||||
<RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</UntagRoleResponse>"""
|
||||
|
||||
|
||||
CREATE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE = """<CreateOpenIDConnectProviderResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<CreateOpenIDConnectProviderResult>
|
||||
<OpenIDConnectProviderArn>{{ open_id_provider.arn }}</OpenIDConnectProviderArn>
|
||||
</CreateOpenIDConnectProviderResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>f248366a-4f64-11e4-aefa-bfd6aEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateOpenIDConnectProviderResponse>"""
|
||||
|
||||
|
||||
DELETE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE = """<DeleteOpenIDConnectProviderResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>b5e49e29-4f64-11e4-aefa-bfd6aEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DeleteOpenIDConnectProviderResponse>"""
|
||||
|
||||
|
||||
GET_OPEN_ID_CONNECT_PROVIDER_TEMPLATE = """<GetOpenIDConnectProviderResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<GetOpenIDConnectProviderResult>
|
||||
<ThumbprintList>
|
||||
{% for thumbprint in open_id_provider.thumbprint_list %}
|
||||
<member>{{ thumbprint }}</member>
|
||||
{% endfor %}
|
||||
</ThumbprintList>
|
||||
<CreateDate>{{ open_id_provider.created_iso_8601 }}</CreateDate>
|
||||
<ClientIDList>
|
||||
{% for client_id in open_id_provider.client_id_list %}
|
||||
<member>{{ client_id }}</member>
|
||||
{% endfor %}
|
||||
</ClientIDList>
|
||||
<Url>{{ open_id_provider.url }}</Url>
|
||||
</GetOpenIDConnectProviderResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>2c91531b-4f65-11e4-aefa-bfd6aEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</GetOpenIDConnectProviderResponse>"""
|
||||
|
||||
|
||||
LIST_OPEN_ID_CONNECT_PROVIDERS_TEMPLATE = """<ListOpenIDConnectProvidersResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<ListOpenIDConnectProvidersResult>
|
||||
<OpenIDConnectProviderList>
|
||||
{% for open_id_provider_arn in open_id_provider_arns %}
|
||||
<member>
|
||||
<Arn>{{ open_id_provider_arn }}</Arn>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</OpenIDConnectProviderList>
|
||||
</ListOpenIDConnectProvidersResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>de2c0228-4f63-11e4-aefa-bfd6aEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListOpenIDConnectProvidersResponse>"""
|
||||
|
@ -56,6 +56,8 @@ class Database(BaseModel):
|
||||
else:
|
||||
self.kms_key_id = kwargs.get("kms_key_id")
|
||||
self.storage_type = kwargs.get("storage_type")
|
||||
if self.storage_type is None:
|
||||
self.storage_type = Database.default_storage_type(iops=self.iops)
|
||||
self.master_username = kwargs.get('master_username')
|
||||
self.master_user_password = kwargs.get('master_user_password')
|
||||
self.auto_minor_version_upgrade = kwargs.get(
|
||||
@ -63,6 +65,8 @@ class Database(BaseModel):
|
||||
if self.auto_minor_version_upgrade is None:
|
||||
self.auto_minor_version_upgrade = True
|
||||
self.allocated_storage = kwargs.get('allocated_storage')
|
||||
if self.allocated_storage is None:
|
||||
self.allocated_storage = Database.default_allocated_storage(engine=self.engine, storage_type=self.storage_type)
|
||||
self.db_instance_identifier = kwargs.get('db_instance_identifier')
|
||||
self.source_db_identifier = kwargs.get("source_db_identifier")
|
||||
self.db_instance_class = kwargs.get('db_instance_class')
|
||||
@ -292,6 +296,78 @@ class Database(BaseModel):
|
||||
'sqlserver-web': 1433,
|
||||
}[engine]
|
||||
|
||||
@staticmethod
|
||||
def default_storage_type(iops):
|
||||
if iops is None:
|
||||
return 'gp2'
|
||||
else:
|
||||
return 'io1'
|
||||
|
||||
@staticmethod
|
||||
def default_allocated_storage(engine, storage_type):
|
||||
return {
|
||||
'aurora': {
|
||||
'gp2': 0,
|
||||
'io1': 0,
|
||||
'standard': 0,
|
||||
},
|
||||
'mysql': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 5,
|
||||
},
|
||||
'mariadb': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 5,
|
||||
},
|
||||
'postgres': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 5,
|
||||
},
|
||||
'oracle-ee': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 10,
|
||||
},
|
||||
'oracle-se2': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 10,
|
||||
},
|
||||
'oracle-se1': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 10,
|
||||
},
|
||||
'oracle-se': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 10,
|
||||
},
|
||||
'sqlserver-ee': {
|
||||
'gp2': 200,
|
||||
'io1': 200,
|
||||
'standard': 200,
|
||||
},
|
||||
'sqlserver-ex': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 20,
|
||||
},
|
||||
'sqlserver-se': {
|
||||
'gp2': 200,
|
||||
'io1': 200,
|
||||
'standard': 200,
|
||||
},
|
||||
'sqlserver-web': {
|
||||
'gp2': 20,
|
||||
'io1': 100,
|
||||
'standard': 20,
|
||||
},
|
||||
}[engine][storage_type]
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
@ -42,7 +42,7 @@ class RDS2Response(BaseResponse):
|
||||
"region": self.region,
|
||||
"security_groups": self._get_multi_param('DBSecurityGroups.DBSecurityGroupName'),
|
||||
"storage_encrypted": self._get_param("StorageEncrypted"),
|
||||
"storage_type": self._get_param("StorageType", 'standard'),
|
||||
"storage_type": self._get_param("StorageType", None),
|
||||
"vpc_security_group_ids": self._get_multi_param("VpcSecurityGroupIds.VpcSecurityGroupId"),
|
||||
"tags": list(),
|
||||
}
|
||||
|
@ -1107,7 +1107,7 @@ class S3Backend(BaseBackend):
|
||||
key = key_version
|
||||
break
|
||||
|
||||
if part_number and key.multipart:
|
||||
if part_number and key and key.multipart:
|
||||
key = key.multipart.parts[part_number]
|
||||
|
||||
if isinstance(key, FakeKey):
|
||||
@ -1115,8 +1115,8 @@ class S3Backend(BaseBackend):
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_key_tagging(self, bucket_name, key_name, tagging):
|
||||
key = self.get_key(bucket_name, key_name)
|
||||
def set_key_tagging(self, bucket_name, key_name, tagging, version_id=None):
|
||||
key = self.get_key(bucket_name, key_name, version_id)
|
||||
if key is None:
|
||||
raise MissingKey(key_name)
|
||||
key.set_tagging(tagging)
|
||||
|
@ -905,8 +905,12 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
||||
return 200, response_headers, ""
|
||||
|
||||
if 'tagging' in query:
|
||||
if 'versionId' in query:
|
||||
version_id = query['versionId'][0]
|
||||
else:
|
||||
version_id = None
|
||||
tagging = self._tagging_from_xml(body)
|
||||
self.backend.set_key_tagging(bucket_name, key_name, tagging)
|
||||
self.backend.set_key_tagging(bucket_name, key_name, tagging, version_id)
|
||||
return 200, response_headers, ""
|
||||
|
||||
if 'x-amz-copy-source' in request.headers:
|
||||
|
@ -7,11 +7,32 @@ class SecretsManagerClientError(JsonRESTError):
|
||||
|
||||
|
||||
class ResourceNotFoundException(SecretsManagerClientError):
|
||||
def __init__(self):
|
||||
def __init__(self, message):
|
||||
self.code = 404
|
||||
super(ResourceNotFoundException, self).__init__(
|
||||
"ResourceNotFoundException",
|
||||
"Secrets Manager can't find the specified secret"
|
||||
message,
|
||||
)
|
||||
|
||||
|
||||
# Using specialised exception due to the use of a non-ASCII character
|
||||
class SecretNotFoundException(SecretsManagerClientError):
|
||||
def __init__(self):
|
||||
self.code = 404
|
||||
super(SecretNotFoundException, self).__init__(
|
||||
"ResourceNotFoundException",
|
||||
message=u"Secrets Manager can\u2019t find the specified secret."
|
||||
)
|
||||
|
||||
|
||||
# Using specialised exception due to the use of a non-ASCII character
|
||||
class SecretHasNoValueException(SecretsManagerClientError):
|
||||
def __init__(self, version_stage):
|
||||
self.code = 404
|
||||
super(SecretHasNoValueException, self).__init__(
|
||||
"ResourceNotFoundException",
|
||||
message=u"Secrets Manager can\u2019t find the specified secret "
|
||||
u"value for staging label: {}".format(version_stage)
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import time
|
||||
@ -9,7 +10,8 @@ import boto3
|
||||
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
from .exceptions import (
|
||||
ResourceNotFoundException,
|
||||
SecretNotFoundException,
|
||||
SecretHasNoValueException,
|
||||
InvalidParameterException,
|
||||
ResourceExistsException,
|
||||
InvalidRequestException,
|
||||
@ -46,7 +48,7 @@ class SecretsManagerBackend(BaseBackend):
|
||||
def get_secret_value(self, secret_id, version_id, version_stage):
|
||||
|
||||
if not self._is_valid_identifier(secret_id):
|
||||
raise ResourceNotFoundException()
|
||||
raise SecretNotFoundException()
|
||||
|
||||
if not version_id and version_stage:
|
||||
# set version_id to match version_stage
|
||||
@ -56,7 +58,7 @@ class SecretsManagerBackend(BaseBackend):
|
||||
version_id = ver_id
|
||||
break
|
||||
if not version_id:
|
||||
raise ResourceNotFoundException()
|
||||
raise SecretNotFoundException()
|
||||
|
||||
# TODO check this part
|
||||
if 'deleted_date' in self.secrets[secret_id]:
|
||||
@ -84,6 +86,9 @@ class SecretsManagerBackend(BaseBackend):
|
||||
if 'secret_binary' in secret_version:
|
||||
response_data["SecretBinary"] = secret_version['secret_binary']
|
||||
|
||||
if 'secret_string' not in secret_version and 'secret_binary' not in secret_version:
|
||||
raise SecretHasNoValueException(version_stage or u"AWSCURRENT")
|
||||
|
||||
response = json.dumps(response_data)
|
||||
|
||||
return response
|
||||
@ -169,7 +174,7 @@ class SecretsManagerBackend(BaseBackend):
|
||||
|
||||
def describe_secret(self, secret_id):
|
||||
if not self._is_valid_identifier(secret_id):
|
||||
raise ResourceNotFoundException
|
||||
raise SecretNotFoundException()
|
||||
|
||||
secret = self.secrets[secret_id]
|
||||
|
||||
@ -198,7 +203,7 @@ class SecretsManagerBackend(BaseBackend):
|
||||
rotation_days = 'AutomaticallyAfterDays'
|
||||
|
||||
if not self._is_valid_identifier(secret_id):
|
||||
raise ResourceNotFoundException
|
||||
raise SecretNotFoundException()
|
||||
|
||||
if 'deleted_date' in self.secrets[secret_id]:
|
||||
raise InvalidRequestException(
|
||||
@ -340,7 +345,7 @@ class SecretsManagerBackend(BaseBackend):
|
||||
def delete_secret(self, secret_id, recovery_window_in_days, force_delete_without_recovery):
|
||||
|
||||
if not self._is_valid_identifier(secret_id):
|
||||
raise ResourceNotFoundException
|
||||
raise SecretNotFoundException()
|
||||
|
||||
if 'deleted_date' in self.secrets[secret_id]:
|
||||
raise InvalidRequestException(
|
||||
@ -370,7 +375,7 @@ class SecretsManagerBackend(BaseBackend):
|
||||
secret = self.secrets.get(secret_id, None)
|
||||
|
||||
if not secret:
|
||||
raise ResourceNotFoundException
|
||||
raise SecretNotFoundException()
|
||||
|
||||
arn = secret_arn(self.region, secret['secret_id'])
|
||||
name = secret['name']
|
||||
@ -380,7 +385,7 @@ class SecretsManagerBackend(BaseBackend):
|
||||
def restore_secret(self, secret_id):
|
||||
|
||||
if not self._is_valid_identifier(secret_id):
|
||||
raise ResourceNotFoundException
|
||||
raise SecretNotFoundException()
|
||||
|
||||
self.secrets[secret_id].pop('deleted_date', None)
|
||||
|
||||
|
@ -343,6 +343,14 @@ class SNSBackend(BaseBackend):
|
||||
return old_subscription
|
||||
topic = self.get_topic(topic_arn)
|
||||
subscription = Subscription(topic, endpoint, protocol)
|
||||
attributes = {
|
||||
'PendingConfirmation': 'false',
|
||||
'Endpoint': endpoint,
|
||||
'TopicArn': topic_arn,
|
||||
'Protocol': protocol,
|
||||
'SubscriptionArn': subscription.arn
|
||||
}
|
||||
subscription.attributes = attributes
|
||||
self.subscriptions[subscription.arn] = subscription
|
||||
return subscription
|
||||
|
||||
|
@ -19,9 +19,12 @@ class MessageAttributesInvalid(Exception):
|
||||
self.description = description
|
||||
|
||||
|
||||
class QueueDoesNotExist(Exception):
|
||||
status_code = 404
|
||||
description = "The specified queue does not exist for this wsdl version."
|
||||
class QueueDoesNotExist(RESTError):
|
||||
code = 404
|
||||
|
||||
def __init__(self):
|
||||
super(QueueDoesNotExist, self).__init__(
|
||||
"QueueDoesNotExist", "The specified queue does not exist for this wsdl version.")
|
||||
|
||||
|
||||
class QueueAlreadyExists(RESTError):
|
||||
|
@ -654,12 +654,21 @@ class SQSBackend(BaseBackend):
|
||||
|
||||
def tag_queue(self, queue_name, tags):
|
||||
queue = self.get_queue(queue_name)
|
||||
|
||||
if not len(tags):
|
||||
raise RESTError('MissingParameter',
|
||||
'The request must contain the parameter Tags.')
|
||||
|
||||
if len(tags) > 50:
|
||||
raise RESTError('InvalidParameterValue',
|
||||
'Too many tags added for queue {}.'.format(queue_name))
|
||||
|
||||
queue.tags.update(tags)
|
||||
|
||||
def untag_queue(self, queue_name, tag_keys):
|
||||
queue = self.get_queue(queue_name)
|
||||
|
||||
if len(tag_keys) == 0:
|
||||
if not len(tag_keys):
|
||||
raise RESTError('InvalidParameterValue', 'Tag keys must be between 1 and 128 characters in length.')
|
||||
|
||||
for key in tag_keys:
|
||||
|
@ -10,7 +10,6 @@ from .models import sqs_backends
|
||||
from .exceptions import (
|
||||
MessageAttributesInvalid,
|
||||
MessageNotInflight,
|
||||
QueueDoesNotExist,
|
||||
ReceiptHandleIsInvalid,
|
||||
)
|
||||
|
||||
@ -90,11 +89,7 @@ class SQSResponse(BaseResponse):
|
||||
request_url = urlparse(self.uri)
|
||||
queue_name = self._get_param("QueueName")
|
||||
|
||||
try:
|
||||
queue = self.sqs_backend.get_queue(queue_name)
|
||||
except QueueDoesNotExist as e:
|
||||
return self._error('AWS.SimpleQueueService.NonExistentQueue',
|
||||
e.description)
|
||||
queue = self.sqs_backend.get_queue(queue_name)
|
||||
|
||||
if queue:
|
||||
template = self.response_template(GET_QUEUE_URL_RESPONSE)
|
||||
@ -175,11 +170,8 @@ class SQSResponse(BaseResponse):
|
||||
|
||||
def get_queue_attributes(self):
|
||||
queue_name = self._get_queue_name()
|
||||
try:
|
||||
queue = self.sqs_backend.get_queue(queue_name)
|
||||
except QueueDoesNotExist as e:
|
||||
return self._error('AWS.SimpleQueueService.NonExistentQueue',
|
||||
e.description)
|
||||
|
||||
queue = self.sqs_backend.get_queue(queue_name)
|
||||
|
||||
template = self.response_template(GET_QUEUE_ATTRIBUTES_RESPONSE)
|
||||
return template.render(queue=queue)
|
||||
@ -242,25 +234,55 @@ class SQSResponse(BaseResponse):
|
||||
|
||||
queue_name = self._get_queue_name()
|
||||
|
||||
messages = []
|
||||
for index in range(1, 11):
|
||||
# Loop through looking for messages
|
||||
message_key = 'SendMessageBatchRequestEntry.{0}.MessageBody'.format(
|
||||
index)
|
||||
message_body = self.querystring.get(message_key)
|
||||
if not message_body:
|
||||
# Found all messages
|
||||
break
|
||||
self.sqs_backend.get_queue(queue_name)
|
||||
|
||||
message_user_id_key = 'SendMessageBatchRequestEntry.{0}.Id'.format(
|
||||
index)
|
||||
message_user_id = self.querystring.get(message_user_id_key)[0]
|
||||
if self.querystring.get('Entries'):
|
||||
return self._error('AWS.SimpleQueueService.EmptyBatchRequest',
|
||||
'There should be at least one SendMessageBatchRequestEntry in the request.')
|
||||
|
||||
entries = {}
|
||||
for key, value in self.querystring.items():
|
||||
match = re.match(r'^SendMessageBatchRequestEntry\.(\d+)\.Id', key)
|
||||
if match:
|
||||
entries[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, message_body[0], delay_seconds=delay_seconds)
|
||||
message.user_id = message_user_id
|
||||
queue_name, entry['MessageBody'], delay_seconds=delay_seconds)
|
||||
message.user_id = entry['Id']
|
||||
|
||||
message_attributes = parse_message_attributes(
|
||||
self.querystring, base='SendMessageBatchRequestEntry.{0}.'.format(index))
|
||||
@ -273,6 +295,14 @@ class SQSResponse(BaseResponse):
|
||||
template = self.response_template(SEND_MESSAGE_BATCH_RESPONSE)
|
||||
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):
|
||||
queue_name = self._get_queue_name()
|
||||
receipt_handle = self.querystring.get("ReceiptHandle")[0]
|
||||
@ -321,10 +351,7 @@ class SQSResponse(BaseResponse):
|
||||
def receive_message(self):
|
||||
queue_name = self._get_queue_name()
|
||||
|
||||
try:
|
||||
queue = self.sqs_backend.get_queue(queue_name)
|
||||
except QueueDoesNotExist as e:
|
||||
return self._error('QueueDoesNotExist', e.description)
|
||||
queue = self.sqs_backend.get_queue(queue_name)
|
||||
|
||||
try:
|
||||
message_count = int(self.querystring.get("MaxNumberOfMessages")[0])
|
||||
@ -406,11 +433,7 @@ class SQSResponse(BaseResponse):
|
||||
queue_name = self._get_queue_name()
|
||||
tag_keys = self._get_multi_param('TagKey')
|
||||
|
||||
try:
|
||||
self.sqs_backend.untag_queue(queue_name, tag_keys)
|
||||
except QueueDoesNotExist as e:
|
||||
return self._error('AWS.SimpleQueueService.NonExistentQueue',
|
||||
e.description)
|
||||
self.sqs_backend.untag_queue(queue_name, tag_keys)
|
||||
|
||||
template = self.response_template(UNTAG_QUEUE_RESPONSE)
|
||||
return template.render()
|
||||
|
@ -1134,7 +1134,7 @@ def test_invoke_function_from_sqs_exception():
|
||||
if 'I failed!' in event['message']:
|
||||
messages = queue.receive_messages(MaxNumberOfMessages=10)
|
||||
# Verify messages are still visible and unprocessed
|
||||
assert len(messages) is 3
|
||||
assert len(messages) == 3
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
|
@ -2705,6 +2705,31 @@ def test_item_size_is_under_400KB():
|
||||
Item={'id': {'S': 'foo'}, 'itemlist': {'L': [{'M': {'item1': {'S': large_item}}}]}})
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression
|
||||
def test_hash_key_cannot_use_begins_with_operations():
|
||||
dynamodb = boto3.resource('dynamodb')
|
||||
table = dynamodb.create_table(
|
||||
TableName='test-table',
|
||||
KeySchema=[{'AttributeName': 'key', 'KeyType': 'HASH'}],
|
||||
AttributeDefinitions=[{'AttributeName': 'key', 'AttributeType': 'S'}],
|
||||
ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1})
|
||||
|
||||
items = [{'key': 'prefix-$LATEST', 'value': '$LATEST'},
|
||||
{'key': 'prefix-DEV', 'value': 'DEV'},
|
||||
{'key': 'prefix-PROD', 'value': 'PROD'}]
|
||||
|
||||
with table.batch_writer() as batch:
|
||||
for item in items:
|
||||
batch.put_item(Item=item)
|
||||
|
||||
table = dynamodb.Table('test-table')
|
||||
with assert_raises(ClientError) as ex:
|
||||
table.query(KeyConditionExpression=Key('key').begins_with('prefix-'))
|
||||
ex.exception.response['Error']['Code'].should.equal('ValidationException')
|
||||
ex.exception.response['Error']['Message'].should.equal('Query key condition not supported')
|
||||
|
||||
|
||||
def assert_failure_due_to_item_size(func, **kwargs):
|
||||
with assert_raises(ClientError) as ex:
|
||||
func(**kwargs)
|
||||
|
@ -9,6 +9,8 @@ import sure # noqa
|
||||
import sys
|
||||
from boto.exception import BotoServerError
|
||||
from botocore.exceptions import ClientError
|
||||
from dateutil.tz import tzutc
|
||||
|
||||
from moto import mock_iam, mock_iam_deprecated
|
||||
from moto.iam.models import aws_managed_policies
|
||||
from nose.tools import assert_raises, assert_equals
|
||||
@ -338,6 +340,15 @@ def test_create_policy():
|
||||
response['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestCreatePolicy")
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_delete_policy():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
response = conn.create_policy(PolicyName="TestCreatePolicy", PolicyDocument=MOCK_POLICY)
|
||||
[pol['PolicyName'] for pol in conn.list_policies(Scope='Local')['Policies']].should.equal(['TestCreatePolicy'])
|
||||
conn.delete_policy(PolicyArn=response['Policy']['Arn'])
|
||||
assert conn.list_policies(Scope='Local')['Policies'].should.be.empty
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_create_policy_versions():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
@ -713,7 +724,7 @@ def test_mfa_devices():
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
def test_delete_user():
|
||||
def test_delete_user_deprecated():
|
||||
conn = boto.connect_iam()
|
||||
with assert_raises(BotoServerError):
|
||||
conn.delete_user('my-user')
|
||||
@ -721,6 +732,17 @@ def test_delete_user():
|
||||
conn.delete_user('my-user')
|
||||
|
||||
|
||||
@mock_iam()
|
||||
def test_delete_user():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
with assert_raises(ClientError):
|
||||
conn.delete_user(UserName='my-user')
|
||||
conn.create_user(UserName='my-user')
|
||||
[user['UserName'] for user in conn.list_users()['Users']].should.equal(['my-user'])
|
||||
conn.delete_user(UserName='my-user')
|
||||
assert conn.list_users()['Users'].should.be.empty
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
def test_generate_credential_report():
|
||||
conn = boto.connect_iam()
|
||||
@ -1545,3 +1567,251 @@ def test_create_role_with_permissions_boundary():
|
||||
|
||||
# Ensure the PermissionsBoundary is included in role listing as well
|
||||
conn.list_roles().get('Roles')[0].get('PermissionsBoundary').should.equal(expected)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_create_open_id_connect_provider():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='https://example.com',
|
||||
ThumbprintList=[] # even it is required to provide at least one thumbprint, AWS accepts an empty list
|
||||
)
|
||||
|
||||
response['OpenIDConnectProviderArn'].should.equal(
|
||||
'arn:aws:iam::123456789012:oidc-provider/example.com'
|
||||
)
|
||||
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='http://example.org',
|
||||
ThumbprintList=[
|
||||
'b' * 40
|
||||
],
|
||||
ClientIDList=[
|
||||
'b'
|
||||
]
|
||||
)
|
||||
|
||||
response['OpenIDConnectProviderArn'].should.equal(
|
||||
'arn:aws:iam::123456789012:oidc-provider/example.org'
|
||||
)
|
||||
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='http://example.org/oidc',
|
||||
ThumbprintList=[]
|
||||
)
|
||||
|
||||
response['OpenIDConnectProviderArn'].should.equal(
|
||||
'arn:aws:iam::123456789012:oidc-provider/example.org/oidc'
|
||||
)
|
||||
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='http://example.org/oidc-query?test=true',
|
||||
ThumbprintList=[]
|
||||
)
|
||||
|
||||
response['OpenIDConnectProviderArn'].should.equal(
|
||||
'arn:aws:iam::123456789012:oidc-provider/example.org/oidc-query'
|
||||
)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_create_open_id_connect_provider_errors():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
client.create_open_id_connect_provider(
|
||||
Url='https://example.com',
|
||||
ThumbprintList=[]
|
||||
)
|
||||
|
||||
client.create_open_id_connect_provider.when.called_with(
|
||||
Url='https://example.com',
|
||||
ThumbprintList=[]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Unknown'
|
||||
)
|
||||
|
||||
client.create_open_id_connect_provider.when.called_with(
|
||||
Url='example.org',
|
||||
ThumbprintList=[]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Invalid Open ID Connect Provider URL'
|
||||
)
|
||||
|
||||
client.create_open_id_connect_provider.when.called_with(
|
||||
Url='example',
|
||||
ThumbprintList=[]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Invalid Open ID Connect Provider URL'
|
||||
)
|
||||
|
||||
client.create_open_id_connect_provider.when.called_with(
|
||||
Url='http://example.org',
|
||||
ThumbprintList=[
|
||||
'a' * 40,
|
||||
'b' * 40,
|
||||
'c' * 40,
|
||||
'd' * 40,
|
||||
'e' * 40,
|
||||
'f' * 40,
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Thumbprint list must contain fewer than 5 entries.'
|
||||
)
|
||||
|
||||
too_many_client_ids = ['{}'.format(i) for i in range(101)]
|
||||
client.create_open_id_connect_provider.when.called_with(
|
||||
Url='http://example.org',
|
||||
ThumbprintList=[],
|
||||
ClientIDList=too_many_client_ids
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100'
|
||||
)
|
||||
|
||||
too_long_url = 'b' * 256
|
||||
too_long_thumbprint = 'b' * 41
|
||||
too_long_client_id = 'b' * 256
|
||||
client.create_open_id_connect_provider.when.called_with(
|
||||
Url=too_long_url,
|
||||
ThumbprintList=[
|
||||
too_long_thumbprint
|
||||
],
|
||||
ClientIDList=[
|
||||
too_long_client_id
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'3 validation errors detected: '
|
||||
'Value "{0}" at "clientIDList" failed to satisfy constraint: '
|
||||
'Member must satisfy constraint: '
|
||||
'[Member must have length less than or equal to 255, '
|
||||
'Member must have length greater than or equal to 1]; '
|
||||
'Value "{1}" at "thumbprintList" failed to satisfy constraint: '
|
||||
'Member must satisfy constraint: '
|
||||
'[Member must have length less than or equal to 40, '
|
||||
'Member must have length greater than or equal to 40]; '
|
||||
'Value "{2}" at "url" failed to satisfy constraint: '
|
||||
'Member must have length less than or equal to 255'.format([too_long_client_id], [too_long_thumbprint], too_long_url)
|
||||
)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_delete_open_id_connect_provider():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='https://example.com',
|
||||
ThumbprintList=[]
|
||||
)
|
||||
open_id_arn = response['OpenIDConnectProviderArn']
|
||||
|
||||
client.delete_open_id_connect_provider(
|
||||
OpenIDConnectProviderArn=open_id_arn
|
||||
)
|
||||
|
||||
client.get_open_id_connect_provider.when.called_with(
|
||||
OpenIDConnectProviderArn=open_id_arn
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'OpenIDConnect Provider not found for arn {}'.format(open_id_arn)
|
||||
)
|
||||
|
||||
# deleting a non existing provider should be successful
|
||||
client.delete_open_id_connect_provider(
|
||||
OpenIDConnectProviderArn=open_id_arn
|
||||
)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_get_open_id_connect_provider():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='https://example.com',
|
||||
ThumbprintList=[
|
||||
'b' * 40
|
||||
],
|
||||
ClientIDList=[
|
||||
'b'
|
||||
]
|
||||
)
|
||||
open_id_arn = response['OpenIDConnectProviderArn']
|
||||
|
||||
response = client.get_open_id_connect_provider(
|
||||
OpenIDConnectProviderArn=open_id_arn
|
||||
)
|
||||
|
||||
response['Url'].should.equal('example.com')
|
||||
response['ThumbprintList'].should.equal([
|
||||
'b' * 40
|
||||
])
|
||||
response['ClientIDList'].should.equal([
|
||||
'b'
|
||||
])
|
||||
response.should.have.key('CreateDate').should.be.a(datetime)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_get_open_id_connect_provider_errors():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='https://example.com',
|
||||
ThumbprintList=[
|
||||
'b' * 40
|
||||
],
|
||||
ClientIDList=[
|
||||
'b'
|
||||
]
|
||||
)
|
||||
open_id_arn = response['OpenIDConnectProviderArn']
|
||||
|
||||
client.get_open_id_connect_provider.when.called_with(
|
||||
OpenIDConnectProviderArn=open_id_arn + '-not-existing'
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'OpenIDConnect Provider not found for arn {}'.format(open_id_arn + '-not-existing')
|
||||
)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_list_open_id_connect_providers():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='https://example.com',
|
||||
ThumbprintList=[]
|
||||
)
|
||||
open_id_arn_1 = response['OpenIDConnectProviderArn']
|
||||
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='http://example.org',
|
||||
ThumbprintList=[
|
||||
'b' * 40
|
||||
],
|
||||
ClientIDList=[
|
||||
'b'
|
||||
]
|
||||
)
|
||||
open_id_arn_2 = response['OpenIDConnectProviderArn']
|
||||
|
||||
response = client.create_open_id_connect_provider(
|
||||
Url='http://example.org/oidc',
|
||||
ThumbprintList=[]
|
||||
)
|
||||
open_id_arn_3 = response['OpenIDConnectProviderArn']
|
||||
|
||||
response = client.list_open_id_connect_providers()
|
||||
|
||||
sorted(response['OpenIDConnectProviderList'], key=lambda i: i['Arn']).should.equal(
|
||||
[
|
||||
{
|
||||
'Arn': open_id_arn_1
|
||||
},
|
||||
{
|
||||
'Arn': open_id_arn_2
|
||||
},
|
||||
{
|
||||
'Arn': open_id_arn_3
|
||||
}
|
||||
]
|
||||
)
|
||||
|
@ -39,6 +39,20 @@ def test_create_database():
|
||||
db_instance['VpcSecurityGroups'][0]['VpcSecurityGroupId'].should.equal('sg-123456')
|
||||
|
||||
|
||||
@mock_rds2
|
||||
def test_create_database_no_allocated_storage():
|
||||
conn = boto3.client('rds', region_name='us-west-2')
|
||||
database = conn.create_db_instance(
|
||||
DBInstanceIdentifier='db-master-1',
|
||||
Engine='postgres',
|
||||
DBName='staging-postgres',
|
||||
DBInstanceClass='db.m1.small')
|
||||
db_instance = database['DBInstance']
|
||||
db_instance['Engine'].should.equal('postgres')
|
||||
db_instance['StorageType'].should.equal('gp2')
|
||||
db_instance['AllocatedStorage'].should.equal(20)
|
||||
|
||||
|
||||
@mock_rds2
|
||||
def test_create_database_non_existing_option_group():
|
||||
conn = boto3.client('rds', region_name='us-west-2')
|
||||
|
@ -1534,6 +1534,18 @@ def test_boto3_get_object():
|
||||
e.exception.response['Error']['Code'].should.equal('NoSuchKey')
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_boto3_get_missing_object_with_part_number():
|
||||
s3 = boto3.resource('s3', region_name='us-east-1')
|
||||
s3.create_bucket(Bucket="blah")
|
||||
|
||||
with assert_raises(ClientError) as e:
|
||||
s3.Object('blah', 'hello.txt').meta.client.head_object(
|
||||
Bucket='blah', Key='hello.txt', PartNumber=123)
|
||||
|
||||
e.exception.response['Error']['Code'].should.equal('404')
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_boto3_head_object_with_versioning():
|
||||
s3 = boto3.resource('s3', region_name='us-east-1')
|
||||
@ -2587,6 +2599,160 @@ def test_boto3_put_object_tagging():
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_boto3_put_object_tagging_on_earliest_version():
|
||||
s3 = boto3.client('s3', region_name='us-east-1')
|
||||
bucket_name = 'mybucket'
|
||||
key = 'key-with-tags'
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
s3_resource = boto3.resource('s3')
|
||||
bucket_versioning = s3_resource.BucketVersioning(bucket_name)
|
||||
bucket_versioning.enable()
|
||||
bucket_versioning.status.should.equal('Enabled')
|
||||
|
||||
with assert_raises(ClientError) as err:
|
||||
s3.put_object_tagging(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Tagging={'TagSet': [
|
||||
{'Key': 'item1', 'Value': 'foo'},
|
||||
{'Key': 'item2', 'Value': 'bar'},
|
||||
]}
|
||||
)
|
||||
|
||||
e = err.exception
|
||||
e.response['Error'].should.equal({
|
||||
'Code': 'NoSuchKey',
|
||||
'Message': 'The specified key does not exist.',
|
||||
'RequestID': '7a62c49f-347e-4fc4-9331-6e8eEXAMPLE',
|
||||
})
|
||||
|
||||
s3.put_object(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Body='test'
|
||||
)
|
||||
s3.put_object(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Body='test_updated'
|
||||
)
|
||||
|
||||
object_versions = list(s3_resource.Bucket(bucket_name).object_versions.all())
|
||||
first_object = object_versions[0]
|
||||
second_object = object_versions[1]
|
||||
|
||||
resp = s3.put_object_tagging(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Tagging={'TagSet': [
|
||||
{'Key': 'item1', 'Value': 'foo'},
|
||||
{'Key': 'item2', 'Value': 'bar'},
|
||||
]},
|
||||
VersionId=first_object.id
|
||||
)
|
||||
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
|
||||
# Older version has tags while the most recent does not
|
||||
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=first_object.id)
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
resp['TagSet'].should.equal(
|
||||
[
|
||||
{'Key': 'item1', 'Value': 'foo'},
|
||||
{'Key': 'item2', 'Value': 'bar'}
|
||||
]
|
||||
)
|
||||
|
||||
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=second_object.id)
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
resp['TagSet'].should.equal([])
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_boto3_put_object_tagging_on_both_version():
|
||||
s3 = boto3.client('s3', region_name='us-east-1')
|
||||
bucket_name = 'mybucket'
|
||||
key = 'key-with-tags'
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
s3_resource = boto3.resource('s3')
|
||||
bucket_versioning = s3_resource.BucketVersioning(bucket_name)
|
||||
bucket_versioning.enable()
|
||||
bucket_versioning.status.should.equal('Enabled')
|
||||
|
||||
with assert_raises(ClientError) as err:
|
||||
s3.put_object_tagging(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Tagging={'TagSet': [
|
||||
{'Key': 'item1', 'Value': 'foo'},
|
||||
{'Key': 'item2', 'Value': 'bar'},
|
||||
]}
|
||||
)
|
||||
|
||||
e = err.exception
|
||||
e.response['Error'].should.equal({
|
||||
'Code': 'NoSuchKey',
|
||||
'Message': 'The specified key does not exist.',
|
||||
'RequestID': '7a62c49f-347e-4fc4-9331-6e8eEXAMPLE',
|
||||
})
|
||||
|
||||
s3.put_object(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Body='test'
|
||||
)
|
||||
s3.put_object(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Body='test_updated'
|
||||
)
|
||||
|
||||
object_versions = list(s3_resource.Bucket(bucket_name).object_versions.all())
|
||||
first_object = object_versions[0]
|
||||
second_object = object_versions[1]
|
||||
|
||||
resp = s3.put_object_tagging(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Tagging={'TagSet': [
|
||||
{'Key': 'item1', 'Value': 'foo'},
|
||||
{'Key': 'item2', 'Value': 'bar'},
|
||||
]},
|
||||
VersionId=first_object.id
|
||||
)
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
|
||||
resp = s3.put_object_tagging(
|
||||
Bucket=bucket_name,
|
||||
Key=key,
|
||||
Tagging={'TagSet': [
|
||||
{'Key': 'item1', 'Value': 'baz'},
|
||||
{'Key': 'item2', 'Value': 'bin'},
|
||||
]},
|
||||
VersionId=second_object.id
|
||||
)
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
|
||||
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=first_object.id)
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
resp['TagSet'].should.equal(
|
||||
[
|
||||
{'Key': 'item1', 'Value': 'foo'},
|
||||
{'Key': 'item2', 'Value': 'bar'}
|
||||
]
|
||||
)
|
||||
|
||||
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=second_object.id)
|
||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
resp['TagSet'].should.equal(
|
||||
[
|
||||
{'Key': 'item1', 'Value': 'baz'},
|
||||
{'Key': 'item2', 'Value': 'bin'}
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_boto3_put_object_tagging_with_single_tag():
|
||||
s3 = boto3.client('s3', region_name='us-east-1')
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import boto3
|
||||
@ -8,7 +9,7 @@ import string
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
import sure # noqa
|
||||
from nose.tools import assert_raises
|
||||
from nose.tools import assert_raises, assert_equal
|
||||
from six import b
|
||||
|
||||
DEFAULT_SECRET_NAME = 'test-secret'
|
||||
@ -38,9 +39,14 @@ def test_get_secret_value_binary():
|
||||
def test_get_secret_that_does_not_exist():
|
||||
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with assert_raises(ClientError) as cm:
|
||||
result = conn.get_secret_value(SecretId='i-dont-exist')
|
||||
|
||||
assert_equal(
|
||||
u"Secrets Manager can\u2019t find the specified secret.",
|
||||
cm.exception.response['Error']['Message']
|
||||
)
|
||||
|
||||
|
||||
@mock_secretsmanager
|
||||
def test_get_secret_that_does_not_match():
|
||||
@ -48,9 +54,14 @@ def test_get_secret_that_does_not_match():
|
||||
create_secret = conn.create_secret(Name='java-util-test-password',
|
||||
SecretString="foosecret")
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with assert_raises(ClientError) as cm:
|
||||
result = conn.get_secret_value(SecretId='i-dont-match')
|
||||
|
||||
assert_equal(
|
||||
u"Secrets Manager can\u2019t find the specified secret.",
|
||||
cm.exception.response['Error']['Message']
|
||||
)
|
||||
|
||||
|
||||
@mock_secretsmanager
|
||||
def test_get_secret_value_that_is_marked_deleted():
|
||||
@ -65,6 +76,21 @@ def test_get_secret_value_that_is_marked_deleted():
|
||||
result = conn.get_secret_value(SecretId='test-secret')
|
||||
|
||||
|
||||
@mock_secretsmanager
|
||||
def test_get_secret_that_has_no_value():
|
||||
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||
|
||||
create_secret = conn.create_secret(Name="java-util-test-password")
|
||||
|
||||
with assert_raises(ClientError) as cm:
|
||||
result = conn.get_secret_value(SecretId='java-util-test-password')
|
||||
|
||||
assert_equal(
|
||||
u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT",
|
||||
cm.exception.response['Error']['Message']
|
||||
)
|
||||
|
||||
|
||||
@mock_secretsmanager
|
||||
def test_create_secret():
|
||||
conn = boto3.client('secretsmanager', region_name='us-east-1')
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
@ -49,7 +50,7 @@ def test_get_secret_that_does_not_exist():
|
||||
"X-Amz-Target": "secretsmanager.GetSecretValue"},
|
||||
)
|
||||
json_data = json.loads(get_secret.data.decode("utf-8"))
|
||||
assert json_data['message'] == "Secrets Manager can't find the specified secret"
|
||||
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
|
||||
assert json_data['__type'] == 'ResourceNotFoundException'
|
||||
|
||||
@mock_secretsmanager
|
||||
@ -70,7 +71,27 @@ def test_get_secret_that_does_not_match():
|
||||
"X-Amz-Target": "secretsmanager.GetSecretValue"},
|
||||
)
|
||||
json_data = json.loads(get_secret.data.decode("utf-8"))
|
||||
assert json_data['message'] == "Secrets Manager can't find the specified secret"
|
||||
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
|
||||
assert json_data['__type'] == 'ResourceNotFoundException'
|
||||
|
||||
@mock_secretsmanager
|
||||
def test_get_secret_that_has_no_value():
|
||||
backend = server.create_backend_app('secretsmanager')
|
||||
test_client = backend.test_client()
|
||||
|
||||
create_secret = test_client.post('/',
|
||||
data={"Name": DEFAULT_SECRET_NAME},
|
||||
headers={
|
||||
"X-Amz-Target": "secretsmanager.CreateSecret"},
|
||||
)
|
||||
get_secret = test_client.post('/',
|
||||
data={"SecretId": DEFAULT_SECRET_NAME},
|
||||
headers={
|
||||
"X-Amz-Target": "secretsmanager.GetSecretValue"},
|
||||
)
|
||||
|
||||
json_data = json.loads(get_secret.data.decode("utf-8"))
|
||||
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT"
|
||||
assert json_data['__type'] == 'ResourceNotFoundException'
|
||||
|
||||
@mock_secretsmanager
|
||||
@ -158,7 +179,7 @@ def test_describe_secret_that_does_not_exist():
|
||||
)
|
||||
|
||||
json_data = json.loads(describe_secret.data.decode("utf-8"))
|
||||
assert json_data['message'] == "Secrets Manager can't find the specified secret"
|
||||
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
|
||||
assert json_data['__type'] == 'ResourceNotFoundException'
|
||||
|
||||
@mock_secretsmanager
|
||||
@ -182,7 +203,7 @@ def test_describe_secret_that_does_not_match():
|
||||
)
|
||||
|
||||
json_data = json.loads(describe_secret.data.decode("utf-8"))
|
||||
assert json_data['message'] == "Secrets Manager can't find the specified secret"
|
||||
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
|
||||
assert json_data['__type'] == 'ResourceNotFoundException'
|
||||
|
||||
@mock_secretsmanager
|
||||
@ -283,7 +304,7 @@ def test_rotate_secret_that_does_not_exist():
|
||||
)
|
||||
|
||||
json_data = json.loads(rotate_secret.data.decode("utf-8"))
|
||||
assert json_data['message'] == "Secrets Manager can't find the specified secret"
|
||||
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
|
||||
assert json_data['__type'] == 'ResourceNotFoundException'
|
||||
|
||||
@mock_secretsmanager
|
||||
@ -307,7 +328,7 @@ def test_rotate_secret_that_does_not_match():
|
||||
)
|
||||
|
||||
json_data = json.loads(rotate_secret.data.decode("utf-8"))
|
||||
assert json_data['message'] == "Secrets Manager can't find the specified secret"
|
||||
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
|
||||
assert json_data['__type'] == 'ResourceNotFoundException'
|
||||
|
||||
@mock_secretsmanager
|
||||
|
@ -181,6 +181,35 @@ def test_subscription_paging():
|
||||
int(DEFAULT_PAGE_SIZE / 3))
|
||||
topic1_subscriptions.shouldnt.have("NextToken")
|
||||
|
||||
@mock_sns
|
||||
def test_subscribe_attributes():
|
||||
client = boto3.client('sns', region_name='us-east-1')
|
||||
client.create_topic(Name="some-topic")
|
||||
resp = client.create_topic(Name="some-topic")
|
||||
arn = resp['TopicArn']
|
||||
|
||||
resp = client.subscribe(
|
||||
TopicArn=arn,
|
||||
Protocol='http',
|
||||
Endpoint='http://test.com'
|
||||
)
|
||||
|
||||
attributes = client.get_subscription_attributes(
|
||||
SubscriptionArn=resp['SubscriptionArn']
|
||||
)
|
||||
|
||||
attributes.should.contain('Attributes')
|
||||
attributes['Attributes'].should.contain('PendingConfirmation')
|
||||
attributes['Attributes']['PendingConfirmation'].should.equal('false')
|
||||
attributes['Attributes'].should.contain('Endpoint')
|
||||
attributes['Attributes']['Endpoint'].should.equal('http://test.com')
|
||||
attributes['Attributes'].should.contain('TopicArn')
|
||||
attributes['Attributes']['TopicArn'].should.equal(arn)
|
||||
attributes['Attributes'].should.contain('Protocol')
|
||||
attributes['Attributes']['Protocol'].should.equal('http')
|
||||
attributes['Attributes'].should.contain('SubscriptionArn')
|
||||
attributes['Attributes']['SubscriptionArn'].should.equal(resp['SubscriptionArn'])
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_creating_subscription_with_attributes():
|
||||
|
@ -882,6 +882,127 @@ def test_delete_message_after_visibility_timeout():
|
||||
assert new_queue.count() == 0
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_send_message_batch_errors():
|
||||
client = boto3.client('sqs', region_name = 'us-east-1')
|
||||
|
||||
response = client.create_queue(QueueName='test-queue-with-tags')
|
||||
queue_url = response['QueueUrl']
|
||||
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url + '-not-existing',
|
||||
Entries=[
|
||||
{
|
||||
'Id': 'id_1',
|
||||
'MessageBody': 'body_1'
|
||||
}
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'The specified queue does not exist for this wsdl version.'
|
||||
)
|
||||
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Entries=[]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'There should be at least one SendMessageBatchRequestEntry in the request.'
|
||||
)
|
||||
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Entries=[
|
||||
{
|
||||
'Id': '',
|
||||
'MessageBody': 'body_1'
|
||||
}
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'A batch entry id can only contain alphanumeric characters, '
|
||||
'hyphens and underscores. It can be at most 80 letters long.'
|
||||
)
|
||||
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Entries=[
|
||||
{
|
||||
'Id': '.!@#$%^&*()+=',
|
||||
'MessageBody': 'body_1'
|
||||
}
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'A batch entry id can only contain alphanumeric characters, '
|
||||
'hyphens and underscores. It can be at most 80 letters long.'
|
||||
)
|
||||
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Entries=[
|
||||
{
|
||||
'Id': 'i' * 81,
|
||||
'MessageBody': 'body_1'
|
||||
}
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'A batch entry id can only contain alphanumeric characters, '
|
||||
'hyphens and underscores. It can be at most 80 letters long.'
|
||||
)
|
||||
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Entries=[
|
||||
{
|
||||
'Id': 'id_1',
|
||||
'MessageBody': 'b' * 262145
|
||||
}
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Batch requests cannot be longer than 262144 bytes. '
|
||||
'You have sent 262145 bytes.'
|
||||
)
|
||||
|
||||
# only the first duplicated Id is reported
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Entries=[
|
||||
{
|
||||
'Id': 'id_1',
|
||||
'MessageBody': 'body_1'
|
||||
},
|
||||
{
|
||||
'Id': 'id_2',
|
||||
'MessageBody': 'body_2'
|
||||
},
|
||||
{
|
||||
'Id': 'id_2',
|
||||
'MessageBody': 'body_2'
|
||||
},
|
||||
{
|
||||
'Id': 'id_1',
|
||||
'MessageBody': 'body_1'
|
||||
}
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Id id_2 repeated.'
|
||||
)
|
||||
|
||||
entries = [{'Id': 'id_{}'.format(i), 'MessageBody': 'body_{}'.format(i)} for i in range(11)]
|
||||
client.send_message_batch.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Entries=entries
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Maximum number of entries per request are 10. '
|
||||
'You have sent 11.'
|
||||
)
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_batch_change_message_visibility():
|
||||
if os.environ.get('TEST_SERVER_MODE', 'false').lower() == 'true':
|
||||
@ -987,6 +1108,73 @@ def test_tags():
|
||||
})
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_list_queue_tags_errors():
|
||||
client = boto3.client('sqs', region_name='us-east-1')
|
||||
|
||||
response = client.create_queue(
|
||||
QueueName='test-queue-with-tags',
|
||||
tags={
|
||||
'tag_key_1': 'tag_value_X'
|
||||
}
|
||||
)
|
||||
queue_url = response['QueueUrl']
|
||||
|
||||
client.list_queue_tags.when.called_with(
|
||||
QueueUrl=queue_url + '-not-existing',
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'The specified queue does not exist for this wsdl version.'
|
||||
)
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_tag_queue_errors():
|
||||
client = boto3.client('sqs', region_name='us-east-1')
|
||||
|
||||
response = client.create_queue(
|
||||
QueueName='test-queue-with-tags',
|
||||
tags={
|
||||
'tag_key_1': 'tag_value_X'
|
||||
}
|
||||
)
|
||||
queue_url = response['QueueUrl']
|
||||
|
||||
client.tag_queue.when.called_with(
|
||||
QueueUrl=queue_url + '-not-existing',
|
||||
Tags={
|
||||
'tag_key_1': 'tag_value_1'
|
||||
}
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'The specified queue does not exist for this wsdl version.'
|
||||
)
|
||||
|
||||
client.tag_queue.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Tags={}
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'The request must contain the parameter Tags.'
|
||||
)
|
||||
|
||||
too_many_tags = {'tag_key_{}'.format(i): 'tag_value_{}'.format(i) for i in range(51)}
|
||||
client.tag_queue.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
Tags=too_many_tags
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Too many tags added for queue test-queue-with-tags.'
|
||||
)
|
||||
|
||||
# when the request fails, the tags should not be updated
|
||||
client.list_queue_tags(QueueUrl=queue_url)['Tags'].should.equal(
|
||||
{
|
||||
'tag_key_1': 'tag_value_X'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_untag_queue_errors():
|
||||
client = boto3.client('sqs', region_name='us-east-1')
|
||||
@ -1006,7 +1194,7 @@ def test_untag_queue_errors():
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
"The specified queue does not exist for this wsdl version."
|
||||
'The specified queue does not exist for this wsdl version.'
|
||||
)
|
||||
|
||||
client.untag_queue.when.called_with(
|
||||
|
Loading…
Reference in New Issue
Block a user