Merge branch 'master' into bugfix/1823
This commit is contained in:
commit
009a97db85
32 changed files with 2269 additions and 228 deletions
|
|
@ -12,7 +12,7 @@ import zipfile
|
|||
import sure # noqa
|
||||
|
||||
from freezegun import freeze_time
|
||||
from moto import mock_lambda, mock_s3, mock_ec2, mock_sns, mock_logs, settings, mock_sqs
|
||||
from moto import mock_dynamodb2, mock_lambda, mock_s3, mock_ec2, mock_sns, mock_logs, settings, mock_sqs
|
||||
from nose.tools import assert_raises
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
|
|
@ -1027,6 +1027,54 @@ def test_invoke_function_from_sqs():
|
|||
assert False, "Test Failed"
|
||||
|
||||
|
||||
@mock_logs
|
||||
@mock_lambda
|
||||
@mock_dynamodb2
|
||||
def test_invoke_function_from_dynamodb():
|
||||
logs_conn = boto3.client("logs")
|
||||
dynamodb = boto3.client('dynamodb')
|
||||
table_name = 'table_with_stream'
|
||||
table = dynamodb.create_table(TableName=table_name,
|
||||
KeySchema=[{'AttributeName':'id','KeyType':'HASH'}],
|
||||
AttributeDefinitions=[{'AttributeName':'id','AttributeType':'S'}],
|
||||
StreamSpecification={'StreamEnabled': True,
|
||||
'StreamViewType': 'NEW_AND_OLD_IMAGES'})
|
||||
|
||||
conn = boto3.client('lambda')
|
||||
func = conn.create_function(FunctionName='testFunction', Runtime='python2.7',
|
||||
Role='test-iam-role',
|
||||
Handler='lambda_function.lambda_handler',
|
||||
Code={'ZipFile': get_test_zip_file3()},
|
||||
Description='test lambda function executed after a DynamoDB table is updated',
|
||||
Timeout=3, MemorySize=128, Publish=True)
|
||||
|
||||
response = conn.create_event_source_mapping(
|
||||
EventSourceArn=table['TableDescription']['LatestStreamArn'],
|
||||
FunctionName=func['FunctionArn']
|
||||
)
|
||||
|
||||
assert response['EventSourceArn'] == table['TableDescription']['LatestStreamArn']
|
||||
assert response['State'] == 'Enabled'
|
||||
|
||||
dynamodb.put_item(TableName=table_name, Item={'id': { 'S': 'item 1' }})
|
||||
start = time.time()
|
||||
while (time.time() - start) < 30:
|
||||
result = logs_conn.describe_log_streams(logGroupName='/aws/lambda/testFunction')
|
||||
log_streams = result.get('logStreams')
|
||||
if not log_streams:
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
assert len(log_streams) == 1
|
||||
result = logs_conn.get_log_events(logGroupName='/aws/lambda/testFunction', logStreamName=log_streams[0]['logStreamName'])
|
||||
for event in result.get('events'):
|
||||
if event['message'] == 'get_test_zip_file3 success':
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
assert False, "Test Failed"
|
||||
|
||||
|
||||
@mock_logs
|
||||
@mock_lambda
|
||||
@mock_sqs
|
||||
|
|
@ -1245,3 +1293,175 @@ def test_delete_event_source_mapping():
|
|||
assert response['State'] == 'Deleting'
|
||||
conn.get_event_source_mapping.when.called_with(UUID=response['UUID'])\
|
||||
.should.throw(botocore.client.ClientError)
|
||||
|
||||
|
||||
@mock_lambda
|
||||
@mock_s3
|
||||
def test_update_configuration():
|
||||
s3_conn = boto3.client('s3', 'us-west-2')
|
||||
s3_conn.create_bucket(Bucket='test-bucket')
|
||||
|
||||
zip_content = get_test_zip_file2()
|
||||
s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
|
||||
conn = boto3.client('lambda', 'us-west-2')
|
||||
|
||||
fxn = conn.create_function(
|
||||
FunctionName='testFunction',
|
||||
Runtime='python2.7',
|
||||
Role='test-iam-role',
|
||||
Handler='lambda_function.lambda_handler',
|
||||
Code={
|
||||
'S3Bucket': 'test-bucket',
|
||||
'S3Key': 'test.zip',
|
||||
},
|
||||
Description='test lambda function',
|
||||
Timeout=3,
|
||||
MemorySize=128,
|
||||
Publish=True,
|
||||
)
|
||||
|
||||
assert fxn['Description'] == 'test lambda function'
|
||||
assert fxn['Handler'] == 'lambda_function.lambda_handler'
|
||||
assert fxn['MemorySize'] == 128
|
||||
assert fxn['Runtime'] == 'python2.7'
|
||||
assert fxn['Timeout'] == 3
|
||||
|
||||
updated_config = conn.update_function_configuration(
|
||||
FunctionName='testFunction',
|
||||
Description='updated test lambda function',
|
||||
Handler='lambda_function.new_lambda_handler',
|
||||
Runtime='python3.6',
|
||||
Timeout=7
|
||||
)
|
||||
|
||||
assert updated_config['ResponseMetadata']['HTTPStatusCode'] == 200
|
||||
assert updated_config['Description'] == 'updated test lambda function'
|
||||
assert updated_config['Handler'] == 'lambda_function.new_lambda_handler'
|
||||
assert updated_config['MemorySize'] == 128
|
||||
assert updated_config['Runtime'] == 'python3.6'
|
||||
assert updated_config['Timeout'] == 7
|
||||
|
||||
|
||||
@mock_lambda
|
||||
def test_update_function_zip():
|
||||
conn = boto3.client('lambda', 'us-west-2')
|
||||
|
||||
zip_content_one = get_test_zip_file1()
|
||||
|
||||
fxn = conn.create_function(
|
||||
FunctionName='testFunctionZip',
|
||||
Runtime='python2.7',
|
||||
Role='test-iam-role',
|
||||
Handler='lambda_function.lambda_handler',
|
||||
Code={
|
||||
'ZipFile': zip_content_one,
|
||||
},
|
||||
Description='test lambda function',
|
||||
Timeout=3,
|
||||
MemorySize=128,
|
||||
Publish=True,
|
||||
)
|
||||
|
||||
zip_content_two = get_test_zip_file2()
|
||||
|
||||
fxn_updated = conn.update_function_code(
|
||||
FunctionName='testFunctionZip',
|
||||
ZipFile=zip_content_two,
|
||||
Publish=True
|
||||
)
|
||||
|
||||
response = conn.get_function(
|
||||
FunctionName='testFunctionZip',
|
||||
Qualifier='2'
|
||||
)
|
||||
response['Configuration'].pop('LastModified')
|
||||
|
||||
response['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
assert len(response['Code']) == 2
|
||||
assert response['Code']['RepositoryType'] == 'S3'
|
||||
assert response['Code']['Location'].startswith('s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com'.format(_lambda_region))
|
||||
response['Configuration'].should.equal(
|
||||
{
|
||||
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(),
|
||||
"CodeSize": len(zip_content_two),
|
||||
"Description": "test lambda function",
|
||||
"FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunctionZip:2'.format(_lambda_region),
|
||||
"FunctionName": "testFunctionZip",
|
||||
"Handler": "lambda_function.lambda_handler",
|
||||
"MemorySize": 128,
|
||||
"Role": "test-iam-role",
|
||||
"Runtime": "python2.7",
|
||||
"Timeout": 3,
|
||||
"Version": '2',
|
||||
"VpcConfig": {
|
||||
"SecurityGroupIds": [],
|
||||
"SubnetIds": [],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@mock_lambda
|
||||
@mock_s3
|
||||
def test_update_function_s3():
|
||||
s3_conn = boto3.client('s3', 'us-west-2')
|
||||
s3_conn.create_bucket(Bucket='test-bucket')
|
||||
|
||||
zip_content = get_test_zip_file1()
|
||||
s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
|
||||
|
||||
conn = boto3.client('lambda', 'us-west-2')
|
||||
|
||||
fxn = conn.create_function(
|
||||
FunctionName='testFunctionS3',
|
||||
Runtime='python2.7',
|
||||
Role='test-iam-role',
|
||||
Handler='lambda_function.lambda_handler',
|
||||
Code={
|
||||
'S3Bucket': 'test-bucket',
|
||||
'S3Key': 'test.zip',
|
||||
},
|
||||
Description='test lambda function',
|
||||
Timeout=3,
|
||||
MemorySize=128,
|
||||
Publish=True,
|
||||
)
|
||||
|
||||
zip_content_two = get_test_zip_file2()
|
||||
s3_conn.put_object(Bucket='test-bucket', Key='test2.zip', Body=zip_content_two)
|
||||
|
||||
fxn_updated = conn.update_function_code(
|
||||
FunctionName='testFunctionS3',
|
||||
S3Bucket='test-bucket',
|
||||
S3Key='test2.zip',
|
||||
Publish=True
|
||||
)
|
||||
|
||||
response = conn.get_function(
|
||||
FunctionName='testFunctionS3',
|
||||
Qualifier='2'
|
||||
)
|
||||
response['Configuration'].pop('LastModified')
|
||||
|
||||
response['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
assert len(response['Code']) == 2
|
||||
assert response['Code']['RepositoryType'] == 'S3'
|
||||
assert response['Code']['Location'].startswith('s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com'.format(_lambda_region))
|
||||
response['Configuration'].should.equal(
|
||||
{
|
||||
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(),
|
||||
"CodeSize": len(zip_content_two),
|
||||
"Description": "test lambda function",
|
||||
"FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunctionS3:2'.format(_lambda_region),
|
||||
"FunctionName": "testFunctionS3",
|
||||
"Handler": "lambda_function.lambda_handler",
|
||||
"MemorySize": 128,
|
||||
"Role": "test-iam-role",
|
||||
"Runtime": "python2.7",
|
||||
"Timeout": 3,
|
||||
"Version": '2',
|
||||
"VpcConfig": {
|
||||
"SecurityGroupIds": [],
|
||||
"SubnetIds": [],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1184,3 +1184,159 @@ def test_list_aggregate_discovered_resource():
|
|||
with assert_raises(ClientError) as ce:
|
||||
client.list_aggregate_discovered_resources(ConfigurationAggregatorName='testing', ResourceType='AWS::S3::Bucket', Limit=101)
|
||||
assert '101' in ce.exception.response['Error']['Message']
|
||||
|
||||
|
||||
@mock_config
|
||||
@mock_s3
|
||||
def test_get_resource_config_history():
|
||||
"""NOTE: We are only really testing the Config part. For each individual service, please add tests
|
||||
for that individual service's "get_config_resource" function.
|
||||
"""
|
||||
client = boto3.client('config', region_name='us-west-2')
|
||||
|
||||
# With an invalid resource type:
|
||||
with assert_raises(ClientError) as ce:
|
||||
client.get_resource_config_history(resourceType='NOT::A::RESOURCE', resourceId='notcreatedyet')
|
||||
assert ce.exception.response['Error'] == {'Message': 'Resource notcreatedyet of resourceType:NOT::A::RESOURCE is unknown or has '
|
||||
'not been discovered', 'Code': 'ResourceNotDiscoveredException'}
|
||||
|
||||
# With nothing created yet:
|
||||
with assert_raises(ClientError) as ce:
|
||||
client.get_resource_config_history(resourceType='AWS::S3::Bucket', resourceId='notcreatedyet')
|
||||
assert ce.exception.response['Error'] == {'Message': 'Resource notcreatedyet of resourceType:AWS::S3::Bucket is unknown or has '
|
||||
'not been discovered', 'Code': 'ResourceNotDiscoveredException'}
|
||||
|
||||
# Create an S3 bucket:
|
||||
s3_client = boto3.client('s3', region_name='us-west-2')
|
||||
for x in range(0, 10):
|
||||
s3_client.create_bucket(Bucket='bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'us-west-2'})
|
||||
|
||||
# Now try:
|
||||
result = client.get_resource_config_history(resourceType='AWS::S3::Bucket', resourceId='bucket1')['configurationItems']
|
||||
assert len(result) == 1
|
||||
assert result[0]['resourceName'] == result[0]['resourceId'] == 'bucket1'
|
||||
assert result[0]['arn'] == 'arn:aws:s3:::bucket1'
|
||||
|
||||
|
||||
@mock_config
|
||||
@mock_s3
|
||||
def test_batch_get_resource_config():
|
||||
"""NOTE: We are only really testing the Config part. For each individual service, please add tests
|
||||
for that individual service's "get_config_resource" function.
|
||||
"""
|
||||
client = boto3.client('config', region_name='us-west-2')
|
||||
|
||||
# With more than 100 resourceKeys:
|
||||
with assert_raises(ClientError) as ce:
|
||||
client.batch_get_resource_config(resourceKeys=[{'resourceType': 'AWS::S3::Bucket', 'resourceId': 'someBucket'}] * 101)
|
||||
assert 'Member must have length less than or equal to 100' in ce.exception.response['Error']['Message']
|
||||
|
||||
# With invalid resource types and resources that don't exist:
|
||||
result = client.batch_get_resource_config(resourceKeys=[
|
||||
{'resourceType': 'NOT::A::RESOURCE', 'resourceId': 'NotAThing'}, {'resourceType': 'AWS::S3::Bucket', 'resourceId': 'NotAThing'},
|
||||
])
|
||||
|
||||
assert not result['baseConfigurationItems']
|
||||
assert not result['unprocessedResourceKeys']
|
||||
|
||||
# Create some S3 buckets:
|
||||
s3_client = boto3.client('s3', region_name='us-west-2')
|
||||
for x in range(0, 10):
|
||||
s3_client.create_bucket(Bucket='bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'us-west-2'})
|
||||
|
||||
# Get them all:
|
||||
keys = [{'resourceType': 'AWS::S3::Bucket', 'resourceId': 'bucket{}'.format(x)} for x in range(0, 10)]
|
||||
result = client.batch_get_resource_config(resourceKeys=keys)
|
||||
assert len(result['baseConfigurationItems']) == 10
|
||||
buckets_missing = ['bucket{}'.format(x) for x in range(0, 10)]
|
||||
for r in result['baseConfigurationItems']:
|
||||
buckets_missing.remove(r['resourceName'])
|
||||
|
||||
assert not buckets_missing
|
||||
|
||||
|
||||
@mock_config
|
||||
@mock_s3
|
||||
def test_batch_get_aggregate_resource_config():
|
||||
"""NOTE: We are only really testing the Config part. For each individual service, please add tests
|
||||
for that individual service's "get_config_resource" function.
|
||||
"""
|
||||
from moto.config.models import DEFAULT_ACCOUNT_ID
|
||||
client = boto3.client('config', region_name='us-west-2')
|
||||
|
||||
# Without an aggregator:
|
||||
bad_ri = {'SourceAccountId': '000000000000', 'SourceRegion': 'not-a-region', 'ResourceType': 'NOT::A::RESOURCE', 'ResourceId': 'nope'}
|
||||
with assert_raises(ClientError) as ce:
|
||||
client.batch_get_aggregate_resource_config(ConfigurationAggregatorName='lolno', ResourceIdentifiers=[bad_ri])
|
||||
assert 'The configuration aggregator does not exist' in ce.exception.response['Error']['Message']
|
||||
|
||||
# Create the aggregator:
|
||||
account_aggregation_source = {
|
||||
'AccountIds': [
|
||||
'012345678910',
|
||||
'111111111111',
|
||||
'222222222222'
|
||||
],
|
||||
'AllAwsRegions': True
|
||||
}
|
||||
client.put_configuration_aggregator(
|
||||
ConfigurationAggregatorName='testing',
|
||||
AccountAggregationSources=[account_aggregation_source]
|
||||
)
|
||||
|
||||
# With more than 100 items:
|
||||
with assert_raises(ClientError) as ce:
|
||||
client.batch_get_aggregate_resource_config(ConfigurationAggregatorName='testing', ResourceIdentifiers=[bad_ri] * 101)
|
||||
assert 'Member must have length less than or equal to 100' in ce.exception.response['Error']['Message']
|
||||
|
||||
# Create some S3 buckets:
|
||||
s3_client = boto3.client('s3', region_name='us-west-2')
|
||||
for x in range(0, 10):
|
||||
s3_client.create_bucket(Bucket='bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'us-west-2'})
|
||||
|
||||
s3_client_eu = boto3.client('s3', region_name='eu-west-1')
|
||||
for x in range(10, 12):
|
||||
s3_client_eu.create_bucket(Bucket='eu-bucket{}'.format(x), CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})
|
||||
|
||||
# Now try with resources that exist and ones that don't:
|
||||
identifiers = [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'us-west-2', 'ResourceType': 'AWS::S3::Bucket',
|
||||
'ResourceId': 'bucket{}'.format(x)} for x in range(0, 10)]
|
||||
identifiers += [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'eu-west-1', 'ResourceType': 'AWS::S3::Bucket',
|
||||
'ResourceId': 'eu-bucket{}'.format(x)} for x in range(10, 12)]
|
||||
identifiers += [bad_ri]
|
||||
|
||||
result = client.batch_get_aggregate_resource_config(ConfigurationAggregatorName='testing', ResourceIdentifiers=identifiers)
|
||||
assert len(result['UnprocessedResourceIdentifiers']) == 1
|
||||
assert result['UnprocessedResourceIdentifiers'][0] == bad_ri
|
||||
|
||||
# Verify all the buckets are there:
|
||||
assert len(result['BaseConfigurationItems']) == 12
|
||||
missing_buckets = ['bucket{}'.format(x) for x in range(0, 10)] + ['eu-bucket{}'.format(x) for x in range(10, 12)]
|
||||
|
||||
for r in result['BaseConfigurationItems']:
|
||||
missing_buckets.remove(r['resourceName'])
|
||||
|
||||
assert not missing_buckets
|
||||
|
||||
# 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',
|
||||
'ResourceId': 'bucket1', 'ResourceName': 'bucket1'}]
|
||||
result = client.batch_get_aggregate_resource_config(ConfigurationAggregatorName='testing', ResourceIdentifiers=identifiers)
|
||||
assert not result['UnprocessedResourceIdentifiers']
|
||||
assert len(result['BaseConfigurationItems']) == 1 and result['BaseConfigurationItems'][0]['resourceName'] == 'bucket1'
|
||||
|
||||
# Verify that if the resource name and ID mismatch that we don't get a result:
|
||||
identifiers = [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'us-west-2', 'ResourceType': 'AWS::S3::Bucket',
|
||||
'ResourceId': 'bucket1', 'ResourceName': 'bucket2'}]
|
||||
result = client.batch_get_aggregate_resource_config(ConfigurationAggregatorName='testing', ResourceIdentifiers=identifiers)
|
||||
assert not result['BaseConfigurationItems']
|
||||
assert len(result['UnprocessedResourceIdentifiers']) == 1
|
||||
assert len(result['UnprocessedResourceIdentifiers']) == 1 and result['UnprocessedResourceIdentifiers'][0]['ResourceName'] == 'bucket2'
|
||||
|
||||
# Verify that if the region is incorrect that we don't get a result:
|
||||
identifiers = [{'SourceAccountId': DEFAULT_ACCOUNT_ID, 'SourceRegion': 'eu-west-1', 'ResourceType': 'AWS::S3::Bucket',
|
||||
'ResourceId': 'bucket1'}]
|
||||
result = client.batch_get_aggregate_resource_config(ConfigurationAggregatorName='testing', ResourceIdentifiers=identifiers)
|
||||
assert not result['BaseConfigurationItems']
|
||||
assert len(result['UnprocessedResourceIdentifiers']) == 1
|
||||
assert len(result['UnprocessedResourceIdentifiers']) == 1 and result['UnprocessedResourceIdentifiers'][0]['SourceRegion'] == 'eu-west-1'
|
||||
|
|
|
|||
|
|
@ -369,7 +369,80 @@ def test_query_returns_consumed_capacity():
|
|||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions():
|
||||
def test_basic_projection_expression_using_get_item():
|
||||
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
|
||||
|
||||
# Create the DynamoDB table.
|
||||
table = dynamodb.create_table(
|
||||
TableName='users',
|
||||
KeySchema=[
|
||||
{
|
||||
'AttributeName': 'forum_name',
|
||||
'KeyType': 'HASH'
|
||||
},
|
||||
{
|
||||
'AttributeName': 'subject',
|
||||
'KeyType': 'RANGE'
|
||||
},
|
||||
],
|
||||
AttributeDefinitions=[
|
||||
{
|
||||
'AttributeName': 'forum_name',
|
||||
'AttributeType': 'S'
|
||||
},
|
||||
{
|
||||
'AttributeName': 'subject',
|
||||
'AttributeType': 'S'
|
||||
},
|
||||
],
|
||||
ProvisionedThroughput={
|
||||
'ReadCapacityUnits': 5,
|
||||
'WriteCapacityUnits': 5
|
||||
}
|
||||
)
|
||||
table = dynamodb.Table('users')
|
||||
|
||||
table.put_item(Item={
|
||||
'forum_name': 'the-key',
|
||||
'subject': '123',
|
||||
'body': 'some test message'
|
||||
})
|
||||
|
||||
table.put_item(Item={
|
||||
'forum_name': 'not-the-key',
|
||||
'subject': '123',
|
||||
'body': 'some other test message'
|
||||
})
|
||||
result = table.get_item(
|
||||
Key = {
|
||||
'forum_name': 'the-key',
|
||||
'subject': '123'
|
||||
},
|
||||
ProjectionExpression='body, subject'
|
||||
)
|
||||
|
||||
result['Item'].should.be.equal({
|
||||
'subject': '123',
|
||||
'body': 'some test message'
|
||||
})
|
||||
|
||||
# The projection expression should not remove data from storage
|
||||
result = table.get_item(
|
||||
Key = {
|
||||
'forum_name': 'the-key',
|
||||
'subject': '123'
|
||||
}
|
||||
)
|
||||
|
||||
result['Item'].should.be.equal({
|
||||
'forum_name': 'the-key',
|
||||
'subject': '123',
|
||||
'body': 'some test message'
|
||||
})
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_using_query():
|
||||
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
|
||||
|
||||
# Create the DynamoDB table.
|
||||
|
|
@ -452,6 +525,7 @@ def test_basic_projection_expressions():
|
|||
assert 'body' in results['Items'][1]
|
||||
assert 'forum_name' in results['Items'][1]
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_using_scan():
|
||||
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
|
||||
|
|
@ -538,7 +612,73 @@ def test_basic_projection_expressions_using_scan():
|
|||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_with_attr_expression_names():
|
||||
def test_basic_projection_expression_using_get_item_with_attr_expression_names():
|
||||
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
|
||||
|
||||
# Create the DynamoDB table.
|
||||
table = dynamodb.create_table(
|
||||
TableName='users',
|
||||
KeySchema=[
|
||||
{
|
||||
'AttributeName': 'forum_name',
|
||||
'KeyType': 'HASH'
|
||||
},
|
||||
{
|
||||
'AttributeName': 'subject',
|
||||
'KeyType': 'RANGE'
|
||||
},
|
||||
],
|
||||
AttributeDefinitions=[
|
||||
{
|
||||
'AttributeName': 'forum_name',
|
||||
'AttributeType': 'S'
|
||||
},
|
||||
{
|
||||
'AttributeName': 'subject',
|
||||
'AttributeType': 'S'
|
||||
},
|
||||
],
|
||||
ProvisionedThroughput={
|
||||
'ReadCapacityUnits': 5,
|
||||
'WriteCapacityUnits': 5
|
||||
}
|
||||
)
|
||||
table = dynamodb.Table('users')
|
||||
|
||||
table.put_item(Item={
|
||||
'forum_name': 'the-key',
|
||||
'subject': '123',
|
||||
'body': 'some test message',
|
||||
'attachment': 'something'
|
||||
})
|
||||
|
||||
table.put_item(Item={
|
||||
'forum_name': 'not-the-key',
|
||||
'subject': '123',
|
||||
'body': 'some other test message',
|
||||
'attachment': 'something'
|
||||
})
|
||||
result = table.get_item(
|
||||
Key={
|
||||
'forum_name': 'the-key',
|
||||
'subject': '123'
|
||||
},
|
||||
ProjectionExpression='#rl, #rt, subject',
|
||||
ExpressionAttributeNames={
|
||||
'#rl': 'body',
|
||||
'#rt': 'attachment'
|
||||
},
|
||||
)
|
||||
|
||||
result['Item'].should.be.equal({
|
||||
'subject': '123',
|
||||
'body': 'some test message',
|
||||
'attachment': 'something'
|
||||
})
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_using_query_with_attr_expression_names():
|
||||
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
|
||||
|
||||
# Create the DynamoDB table.
|
||||
|
|
@ -603,6 +743,7 @@ def test_basic_projection_expressions_with_attr_expression_names():
|
|||
assert 'attachment' in results['Items'][0]
|
||||
assert results['Items'][0]['attachment'] == 'something'
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_using_scan_with_attr_expression_names():
|
||||
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
|
||||
|
|
@ -2250,6 +2391,76 @@ def test_batch_items_returns_all():
|
|||
assert [item['username']['S'] for item in returned_items] == ['user1', 'user2', 'user3']
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_batch_items_with_basic_projection_expression():
|
||||
dynamodb = _create_user_table()
|
||||
returned_items = dynamodb.batch_get_item(RequestItems={
|
||||
'users': {
|
||||
'Keys': [{
|
||||
'username': {'S': 'user0'}
|
||||
}, {
|
||||
'username': {'S': 'user1'}
|
||||
}, {
|
||||
'username': {'S': 'user2'}
|
||||
}, {
|
||||
'username': {'S': 'user3'}
|
||||
}],
|
||||
'ConsistentRead': True,
|
||||
'ProjectionExpression': 'username'
|
||||
}
|
||||
})['Responses']['users']
|
||||
|
||||
returned_items.should.have.length_of(3)
|
||||
[item['username']['S'] for item in returned_items].should.be.equal(['user1', 'user2', 'user3'])
|
||||
[item.get('foo') for item in returned_items].should.be.equal([None, None, None])
|
||||
|
||||
# The projection expression should not remove data from storage
|
||||
returned_items = dynamodb.batch_get_item(RequestItems = {
|
||||
'users': {
|
||||
'Keys': [{
|
||||
'username': {'S': 'user0'}
|
||||
}, {
|
||||
'username': {'S': 'user1'}
|
||||
}, {
|
||||
'username': {'S': 'user2'}
|
||||
}, {
|
||||
'username': {'S': 'user3'}
|
||||
}],
|
||||
'ConsistentRead': True
|
||||
}
|
||||
})['Responses']['users']
|
||||
|
||||
[item['username']['S'] for item in returned_items].should.be.equal(['user1', 'user2', 'user3'])
|
||||
[item['foo']['S'] for item in returned_items].should.be.equal(['bar', 'bar', 'bar'])
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_batch_items_with_basic_projection_expression_and_attr_expression_names():
|
||||
dynamodb = _create_user_table()
|
||||
returned_items = dynamodb.batch_get_item(RequestItems={
|
||||
'users': {
|
||||
'Keys': [{
|
||||
'username': {'S': 'user0'}
|
||||
}, {
|
||||
'username': {'S': 'user1'}
|
||||
}, {
|
||||
'username': {'S': 'user2'}
|
||||
}, {
|
||||
'username': {'S': 'user3'}
|
||||
}],
|
||||
'ConsistentRead': True,
|
||||
'ProjectionExpression': '#rl',
|
||||
'ExpressionAttributeNames': {
|
||||
'#rl': 'username'
|
||||
},
|
||||
}
|
||||
})['Responses']['users']
|
||||
|
||||
returned_items.should.have.length_of(3)
|
||||
[item['username']['S'] for item in returned_items].should.be.equal(['user1', 'user2', 'user3'])
|
||||
[item.get('foo') for item in returned_items].should.be.equal([None, None, None])
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_batch_items_should_throw_exception_for_duplicate_request():
|
||||
client = _create_user_table()
|
||||
|
|
@ -2433,6 +2644,74 @@ def create_table_with_list(table_name):
|
|||
return client
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_sorted_query_with_numerical_sort_key():
|
||||
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
|
||||
dynamodb.create_table(TableName="CarCollection",
|
||||
KeySchema=[{ 'AttributeName': "CarModel", 'KeyType': 'HASH'},
|
||||
{'AttributeName': "CarPrice", 'KeyType': 'RANGE'}],
|
||||
AttributeDefinitions=[{'AttributeName': "CarModel", 'AttributeType': "S"},
|
||||
{'AttributeName': "CarPrice", 'AttributeType': "N"}],
|
||||
ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1})
|
||||
|
||||
def create_item(price):
|
||||
return {"CarModel": "M", "CarPrice": price}
|
||||
|
||||
table = dynamodb.Table('CarCollection')
|
||||
items = list(map(create_item, [2, 1, 10, 3]))
|
||||
for item in items:
|
||||
table.put_item(Item=item)
|
||||
|
||||
response = table.query(KeyConditionExpression=Key('CarModel').eq("M"))
|
||||
|
||||
response_items = response['Items']
|
||||
assert len(items) == len(response_items)
|
||||
assert all(isinstance(item["CarPrice"], Decimal) for item in response_items)
|
||||
response_prices = [item["CarPrice"] for item in response_items]
|
||||
expected_prices = [Decimal(item["CarPrice"]) for item in items]
|
||||
expected_prices.sort()
|
||||
assert expected_prices == response_prices, "result items are not sorted by numerical value"
|
||||
|
||||
|
||||
# https://github.com/spulec/moto/issues/1874
|
||||
@mock_dynamodb2
|
||||
def test_item_size_is_under_400KB():
|
||||
dynamodb = boto3.resource('dynamodb')
|
||||
client = boto3.client('dynamodb')
|
||||
|
||||
dynamodb.create_table(
|
||||
TableName='moto-test',
|
||||
KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
|
||||
AttributeDefinitions=[{'AttributeName': 'id', 'AttributeType': 'S'}],
|
||||
ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1}
|
||||
)
|
||||
table = dynamodb.Table('moto-test')
|
||||
|
||||
large_item = 'x' * 410 * 1000
|
||||
assert_failure_due_to_item_size(func=client.put_item,
|
||||
TableName='moto-test',
|
||||
Item={'id': {'S': 'foo'}, 'item': {'S': large_item}})
|
||||
assert_failure_due_to_item_size(func=table.put_item, Item = {'id': 'bar', 'item': large_item})
|
||||
assert_failure_due_to_item_size(func=client.update_item,
|
||||
TableName='moto-test',
|
||||
Key={'id': {'S': 'foo2'}},
|
||||
UpdateExpression='set item=:Item',
|
||||
ExpressionAttributeValues={':Item': {'S': large_item}})
|
||||
# Assert op fails when updating a nested item
|
||||
assert_failure_due_to_item_size(func=table.put_item,
|
||||
Item={'id': 'bar', 'itemlist': [{'item': large_item}]})
|
||||
assert_failure_due_to_item_size(func=client.put_item,
|
||||
TableName='moto-test',
|
||||
Item={'id': {'S': 'foo'}, 'itemlist': {'L': [{'M': {'item1': {'S': large_item}}}]}})
|
||||
|
||||
|
||||
def assert_failure_due_to_item_size(func, **kwargs):
|
||||
with assert_raises(ClientError) as ex:
|
||||
func(**kwargs)
|
||||
ex.exception.response['Error']['Code'].should.equal('ValidationException')
|
||||
ex.exception.response['Error']['Message'].should.equal('Item size has exceeded the maximum allowed size')
|
||||
|
||||
|
||||
def _create_user_table():
|
||||
client = boto3.client('dynamodb', region_name='us-east-1')
|
||||
client.create_table(
|
||||
|
|
|
|||
|
|
@ -889,6 +889,12 @@ def test_describe_container_instances():
|
|||
instance.keys().should.contain('runningTasksCount')
|
||||
instance.keys().should.contain('pendingTasksCount')
|
||||
|
||||
with assert_raises(ClientError) as e:
|
||||
ecs_client.describe_container_instances(
|
||||
cluster=test_cluster_name,
|
||||
containerInstances=[]
|
||||
)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
@mock_ecs
|
||||
|
|
|
|||
|
|
@ -9,7 +9,41 @@ import sure # noqa
|
|||
from moto import mock_kinesis
|
||||
|
||||
|
||||
def create_stream(client, stream_name):
|
||||
def create_s3_delivery_stream(client, stream_name):
|
||||
return client.create_delivery_stream(
|
||||
DeliveryStreamName=stream_name,
|
||||
DeliveryStreamType="DirectPut",
|
||||
ExtendedS3DestinationConfiguration={
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'BucketARN': 'arn:aws:s3:::kinesis-test',
|
||||
'Prefix': 'myFolder/',
|
||||
'CompressionFormat': 'UNCOMPRESSED',
|
||||
'DataFormatConversionConfiguration': {
|
||||
'Enabled': True,
|
||||
'InputFormatConfiguration': {
|
||||
'Deserializer': {
|
||||
'HiveJsonSerDe': {
|
||||
},
|
||||
},
|
||||
},
|
||||
'OutputFormatConfiguration': {
|
||||
'Serializer': {
|
||||
'ParquetSerDe': {
|
||||
'Compression': 'SNAPPY',
|
||||
},
|
||||
},
|
||||
},
|
||||
'SchemaConfiguration': {
|
||||
'DatabaseName': stream_name,
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'TableName': 'outputTable',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
def create_redshift_delivery_stream(client, stream_name):
|
||||
return client.create_delivery_stream(
|
||||
DeliveryStreamName=stream_name,
|
||||
RedshiftDestinationConfiguration={
|
||||
|
|
@ -36,10 +70,10 @@ def create_stream(client, stream_name):
|
|||
|
||||
|
||||
@mock_kinesis
|
||||
def test_create_stream():
|
||||
def test_create_redshift_delivery_stream():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
response = create_stream(client, 'stream1')
|
||||
response = create_redshift_delivery_stream(client, 'stream1')
|
||||
stream_arn = response['DeliveryStreamARN']
|
||||
|
||||
response = client.describe_delivery_stream(DeliveryStreamName='stream1')
|
||||
|
|
@ -82,6 +116,60 @@ def test_create_stream():
|
|||
})
|
||||
|
||||
|
||||
@mock_kinesis
|
||||
def test_create_s3_delivery_stream():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
response = create_s3_delivery_stream(client, 'stream1')
|
||||
stream_arn = response['DeliveryStreamARN']
|
||||
|
||||
response = client.describe_delivery_stream(DeliveryStreamName='stream1')
|
||||
stream_description = response['DeliveryStreamDescription']
|
||||
|
||||
# Sure and Freezegun don't play nicely together
|
||||
_ = stream_description.pop('CreateTimestamp')
|
||||
_ = stream_description.pop('LastUpdateTimestamp')
|
||||
|
||||
stream_description.should.equal({
|
||||
'DeliveryStreamName': 'stream1',
|
||||
'DeliveryStreamARN': stream_arn,
|
||||
'DeliveryStreamStatus': 'ACTIVE',
|
||||
'VersionId': 'string',
|
||||
'Destinations': [
|
||||
{
|
||||
'DestinationId': 'string',
|
||||
'ExtendedS3DestinationDescription': {
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'BucketARN': 'arn:aws:s3:::kinesis-test',
|
||||
'Prefix': 'myFolder/',
|
||||
'CompressionFormat': 'UNCOMPRESSED',
|
||||
'DataFormatConversionConfiguration': {
|
||||
'Enabled': True,
|
||||
'InputFormatConfiguration': {
|
||||
'Deserializer': {
|
||||
'HiveJsonSerDe': {
|
||||
},
|
||||
},
|
||||
},
|
||||
'OutputFormatConfiguration': {
|
||||
'Serializer': {
|
||||
'ParquetSerDe': {
|
||||
'Compression': 'SNAPPY',
|
||||
},
|
||||
},
|
||||
},
|
||||
'SchemaConfiguration': {
|
||||
'DatabaseName': 'stream1',
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'TableName': 'outputTable',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"HasMoreDestinations": False,
|
||||
})
|
||||
|
||||
@mock_kinesis
|
||||
def test_create_stream_without_redshift():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
|
@ -145,8 +233,8 @@ def test_deescribe_non_existant_stream():
|
|||
def test_list_and_delete_stream():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
create_stream(client, 'stream1')
|
||||
create_stream(client, 'stream2')
|
||||
create_redshift_delivery_stream(client, 'stream1')
|
||||
create_redshift_delivery_stream(client, 'stream2')
|
||||
|
||||
set(client.list_delivery_streams()['DeliveryStreamNames']).should.equal(
|
||||
set(['stream1', 'stream2']))
|
||||
|
|
@ -161,7 +249,7 @@ def test_list_and_delete_stream():
|
|||
def test_put_record():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
create_stream(client, 'stream1')
|
||||
create_redshift_delivery_stream(client, 'stream1')
|
||||
client.put_record(
|
||||
DeliveryStreamName='stream1',
|
||||
Record={
|
||||
|
|
@ -174,7 +262,7 @@ def test_put_record():
|
|||
def test_put_record_batch():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
create_stream(client, 'stream1')
|
||||
create_redshift_delivery_stream(client, 'stream1')
|
||||
client.put_record_batch(
|
||||
DeliveryStreamName='stream1',
|
||||
Records=[
|
||||
|
|
|
|||
|
|
@ -289,8 +289,8 @@ def test_multipart_etag_quotes_stripped():
|
|||
part2 = b'1'
|
||||
etag2 = multipart.upload_part_from_file(BytesIO(part2), 2).etag
|
||||
# Strip quotes from etags
|
||||
etag1 = etag1.replace('"','')
|
||||
etag2 = etag2.replace('"','')
|
||||
etag1 = etag1.replace('"', '')
|
||||
etag2 = etag2.replace('"', '')
|
||||
xml = "<Part><PartNumber>{0}</PartNumber><ETag>{1}</ETag></Part>"
|
||||
xml = xml.format(1, etag1) + xml.format(2, etag2)
|
||||
xml = "<CompleteMultipartUpload>{0}</CompleteMultipartUpload>".format(xml)
|
||||
|
|
@ -1592,7 +1592,8 @@ def test_boto3_copy_object_with_versioning():
|
|||
|
||||
response = client.create_multipart_upload(Bucket='blah', Key='test4')
|
||||
upload_id = response['UploadId']
|
||||
response = client.upload_part_copy(Bucket='blah', Key='test4', CopySource={'Bucket': 'blah', 'Key': 'test3', 'VersionId': obj3_version_new},
|
||||
response = client.upload_part_copy(Bucket='blah', Key='test4',
|
||||
CopySource={'Bucket': 'blah', 'Key': 'test3', 'VersionId': obj3_version_new},
|
||||
UploadId=upload_id, PartNumber=1)
|
||||
etag = response["CopyPartResult"]["ETag"]
|
||||
client.complete_multipart_upload(
|
||||
|
|
@ -2284,7 +2285,7 @@ def test_put_bucket_notification():
|
|||
assert not result.get("QueueConfigurations")
|
||||
assert result["LambdaFunctionConfigurations"][0]["Id"]
|
||||
assert result["LambdaFunctionConfigurations"][0]["LambdaFunctionArn"] == \
|
||||
"arn:aws:lambda:us-east-1:012345678910:function:lambda"
|
||||
"arn:aws:lambda:us-east-1:012345678910:function:lambda"
|
||||
assert result["LambdaFunctionConfigurations"][0]["Events"][0] == "s3:ObjectCreated:*"
|
||||
assert len(result["LambdaFunctionConfigurations"][0]["Events"]) == 1
|
||||
assert len(result["LambdaFunctionConfigurations"][0]["Filter"]["Key"]["FilterRules"]) == 1
|
||||
|
|
@ -2367,7 +2368,7 @@ def test_put_bucket_notification_errors():
|
|||
|
||||
assert err.exception.response["Error"]["Code"] == "InvalidArgument"
|
||||
assert err.exception.response["Error"]["Message"] == \
|
||||
"The notification destination service region is not valid for the bucket location constraint"
|
||||
"The notification destination service region is not valid for the bucket location constraint"
|
||||
|
||||
# Invalid event name:
|
||||
with assert_raises(ClientError) as err:
|
||||
|
|
@ -2949,7 +2950,7 @@ TEST_XML = """\
|
|||
def test_boto3_bucket_name_too_long():
|
||||
s3 = boto3.client('s3', region_name='us-east-1')
|
||||
with assert_raises(ClientError) as exc:
|
||||
s3.create_bucket(Bucket='x'*64)
|
||||
s3.create_bucket(Bucket='x' * 64)
|
||||
exc.exception.response['Error']['Code'].should.equal('InvalidBucketName')
|
||||
|
||||
|
||||
|
|
@ -2957,7 +2958,7 @@ def test_boto3_bucket_name_too_long():
|
|||
def test_boto3_bucket_name_too_short():
|
||||
s3 = boto3.client('s3', region_name='us-east-1')
|
||||
with assert_raises(ClientError) as exc:
|
||||
s3.create_bucket(Bucket='x'*2)
|
||||
s3.create_bucket(Bucket='x' * 2)
|
||||
exc.exception.response['Error']['Code'].should.equal('InvalidBucketName')
|
||||
|
||||
|
||||
|
|
@ -2979,7 +2980,7 @@ def test_can_enable_bucket_acceleration():
|
|||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Enabled'},
|
||||
)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp = s3.get_bucket_accelerate_configuration(Bucket=bucket_name)
|
||||
resp.should.have.key('Status')
|
||||
resp['Status'].should.equal('Enabled')
|
||||
|
|
@ -2998,7 +2999,7 @@ def test_can_suspend_bucket_acceleration():
|
|||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Suspended'},
|
||||
)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp = s3.get_bucket_accelerate_configuration(Bucket=bucket_name)
|
||||
resp.should.have.key('Status')
|
||||
resp['Status'].should.equal('Suspended')
|
||||
|
|
@ -3013,7 +3014,7 @@ def test_suspending_acceleration_on_not_configured_bucket_does_nothing():
|
|||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Suspended'},
|
||||
)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp = s3.get_bucket_accelerate_configuration(Bucket=bucket_name)
|
||||
resp.shouldnt.have.key('Status')
|
||||
|
||||
|
|
@ -3173,3 +3174,342 @@ def test_list_config_discovered_resources():
|
|||
s3_config_query.list_config_service_resources(None, None, 1, 'notabucket')
|
||||
|
||||
assert 'The nextToken provided is invalid' in inte.exception.message
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_s3_lifecycle_config_dict():
|
||||
from moto.s3.config import s3_config_query
|
||||
|
||||
# With 1 bucket in us-west-2:
|
||||
s3_config_query.backends['global'].create_bucket('bucket1', 'us-west-2')
|
||||
|
||||
# And a lifecycle policy
|
||||
lifecycle = [
|
||||
{
|
||||
'ID': 'rule1',
|
||||
'Status': 'Enabled',
|
||||
'Filter': {'Prefix': ''},
|
||||
'Expiration': {'Days': 1}
|
||||
},
|
||||
{
|
||||
'ID': 'rule2',
|
||||
'Status': 'Enabled',
|
||||
'Filter': {
|
||||
'And': {
|
||||
'Prefix': 'some/path',
|
||||
'Tag': [
|
||||
{'Key': 'TheKey', 'Value': 'TheValue'}
|
||||
]
|
||||
}
|
||||
},
|
||||
'Expiration': {'Days': 1}
|
||||
},
|
||||
{
|
||||
'ID': 'rule3',
|
||||
'Status': 'Enabled',
|
||||
'Filter': {},
|
||||
'Expiration': {'Days': 1}
|
||||
},
|
||||
{
|
||||
'ID': 'rule4',
|
||||
'Status': 'Enabled',
|
||||
'Filter': {'Prefix': ''},
|
||||
'AbortIncompleteMultipartUpload': {'DaysAfterInitiation': 1}
|
||||
}
|
||||
]
|
||||
s3_config_query.backends['global'].set_bucket_lifecycle('bucket1', lifecycle)
|
||||
|
||||
# Get the rules for this:
|
||||
lifecycles = [rule.to_config_dict() for rule in s3_config_query.backends['global'].buckets['bucket1'].rules]
|
||||
|
||||
# Verify the first:
|
||||
assert lifecycles[0] == {
|
||||
'id': 'rule1',
|
||||
'prefix': None,
|
||||
'status': 'Enabled',
|
||||
'expirationInDays': 1,
|
||||
'expiredObjectDeleteMarker': None,
|
||||
'noncurrentVersionExpirationInDays': -1,
|
||||
'expirationDate': None,
|
||||
'transitions': None,
|
||||
'noncurrentVersionTransitions': None,
|
||||
'abortIncompleteMultipartUpload': None,
|
||||
'filter': {
|
||||
'predicate': {
|
||||
'type': 'LifecyclePrefixPredicate',
|
||||
'prefix': ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Verify the second:
|
||||
assert lifecycles[1] == {
|
||||
'id': 'rule2',
|
||||
'prefix': None,
|
||||
'status': 'Enabled',
|
||||
'expirationInDays': 1,
|
||||
'expiredObjectDeleteMarker': None,
|
||||
'noncurrentVersionExpirationInDays': -1,
|
||||
'expirationDate': None,
|
||||
'transitions': None,
|
||||
'noncurrentVersionTransitions': None,
|
||||
'abortIncompleteMultipartUpload': None,
|
||||
'filter': {
|
||||
'predicate': {
|
||||
'type': 'LifecycleAndOperator',
|
||||
'operands': [
|
||||
{
|
||||
'type': 'LifecyclePrefixPredicate',
|
||||
'prefix': 'some/path'
|
||||
},
|
||||
{
|
||||
'type': 'LifecycleTagPredicate',
|
||||
'tag': {
|
||||
'key': 'TheKey',
|
||||
'value': 'TheValue'
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# And the third:
|
||||
assert lifecycles[2] == {
|
||||
'id': 'rule3',
|
||||
'prefix': None,
|
||||
'status': 'Enabled',
|
||||
'expirationInDays': 1,
|
||||
'expiredObjectDeleteMarker': None,
|
||||
'noncurrentVersionExpirationInDays': -1,
|
||||
'expirationDate': None,
|
||||
'transitions': None,
|
||||
'noncurrentVersionTransitions': None,
|
||||
'abortIncompleteMultipartUpload': None,
|
||||
'filter': {'predicate': None}
|
||||
}
|
||||
|
||||
# And the last:
|
||||
assert lifecycles[3] == {
|
||||
'id': 'rule4',
|
||||
'prefix': None,
|
||||
'status': 'Enabled',
|
||||
'expirationInDays': None,
|
||||
'expiredObjectDeleteMarker': None,
|
||||
'noncurrentVersionExpirationInDays': -1,
|
||||
'expirationDate': None,
|
||||
'transitions': None,
|
||||
'noncurrentVersionTransitions': None,
|
||||
'abortIncompleteMultipartUpload': {'daysAfterInitiation': 1},
|
||||
'filter': {
|
||||
'predicate': {
|
||||
'type': 'LifecyclePrefixPredicate',
|
||||
'prefix': ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_s3_notification_config_dict():
|
||||
from moto.s3.config import s3_config_query
|
||||
|
||||
# With 1 bucket in us-west-2:
|
||||
s3_config_query.backends['global'].create_bucket('bucket1', 'us-west-2')
|
||||
|
||||
# And some notifications:
|
||||
notifications = {
|
||||
'TopicConfiguration': [{
|
||||
'Id': 'Topic',
|
||||
"Topic": 'arn:aws:sns:us-west-2:012345678910:mytopic',
|
||||
"Event": [
|
||||
"s3:ReducedRedundancyLostObject",
|
||||
"s3:ObjectRestore:Completed"
|
||||
]
|
||||
}],
|
||||
'QueueConfiguration': [{
|
||||
'Id': 'Queue',
|
||||
'Queue': 'arn:aws:sqs:us-west-2:012345678910:myqueue',
|
||||
'Event': [
|
||||
"s3:ObjectRemoved:Delete"
|
||||
],
|
||||
'Filter': {
|
||||
'S3Key': {
|
||||
'FilterRule': [
|
||||
{
|
||||
'Name': 'prefix',
|
||||
'Value': 'stuff/here/'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}],
|
||||
'CloudFunctionConfiguration': [{
|
||||
'Id': 'Lambda',
|
||||
'CloudFunction': 'arn:aws:lambda:us-west-2:012345678910:function:mylambda',
|
||||
'Event': [
|
||||
"s3:ObjectCreated:Post",
|
||||
"s3:ObjectCreated:Copy",
|
||||
"s3:ObjectCreated:Put"
|
||||
],
|
||||
'Filter': {
|
||||
'S3Key': {
|
||||
'FilterRule': [
|
||||
{
|
||||
'Name': 'suffix',
|
||||
'Value': '.png'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
s3_config_query.backends['global'].put_bucket_notification_configuration('bucket1', notifications)
|
||||
|
||||
# Get the notifications for this:
|
||||
notifications = s3_config_query.backends['global'].buckets['bucket1'].notification_configuration.to_config_dict()
|
||||
|
||||
# Verify it all:
|
||||
assert notifications == {
|
||||
'configurations': {
|
||||
'Topic': {
|
||||
'events': ['s3:ReducedRedundancyLostObject', 's3:ObjectRestore:Completed'],
|
||||
'filter': None,
|
||||
'objectPrefixes': [],
|
||||
'topicARN': 'arn:aws:sns:us-west-2:012345678910:mytopic',
|
||||
'type': 'TopicConfiguration'
|
||||
},
|
||||
'Queue': {
|
||||
'events': ['s3:ObjectRemoved:Delete'],
|
||||
'filter': {
|
||||
's3KeyFilter': {
|
||||
'filterRules': [{
|
||||
'name': 'prefix',
|
||||
'value': 'stuff/here/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
'objectPrefixes': [],
|
||||
'queueARN': 'arn:aws:sqs:us-west-2:012345678910:myqueue',
|
||||
'type': 'QueueConfiguration'
|
||||
},
|
||||
'Lambda': {
|
||||
'events': ['s3:ObjectCreated:Post', 's3:ObjectCreated:Copy', 's3:ObjectCreated:Put'],
|
||||
'filter': {
|
||||
's3KeyFilter': {
|
||||
'filterRules': [{
|
||||
'name': 'suffix',
|
||||
'value': '.png'
|
||||
}]
|
||||
}
|
||||
},
|
||||
'objectPrefixes': [],
|
||||
'queueARN': 'arn:aws:lambda:us-west-2:012345678910:function:mylambda',
|
||||
'type': 'LambdaConfiguration'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_s3_acl_to_config_dict():
|
||||
from moto.s3.config import s3_config_query
|
||||
from moto.s3.models import FakeAcl, FakeGrant, FakeGrantee, OWNER
|
||||
|
||||
# With 1 bucket in us-west-2:
|
||||
s3_config_query.backends['global'].create_bucket('logbucket', 'us-west-2')
|
||||
|
||||
# Get the config dict with nothing other than the owner details:
|
||||
acls = s3_config_query.backends['global'].buckets['logbucket'].acl.to_config_dict()
|
||||
assert acls == {
|
||||
'grantSet': None,
|
||||
'owner': {'displayName': None, 'id': OWNER}
|
||||
}
|
||||
|
||||
# Add some Log Bucket ACLs:
|
||||
log_acls = FakeAcl([
|
||||
FakeGrant([FakeGrantee(uri="http://acs.amazonaws.com/groups/s3/LogDelivery")], "WRITE"),
|
||||
FakeGrant([FakeGrantee(uri="http://acs.amazonaws.com/groups/s3/LogDelivery")], "READ_ACP"),
|
||||
FakeGrant([FakeGrantee(id=OWNER)], "FULL_CONTROL")
|
||||
])
|
||||
s3_config_query.backends['global'].set_bucket_acl('logbucket', log_acls)
|
||||
|
||||
acls = s3_config_query.backends['global'].buckets['logbucket'].acl.to_config_dict()
|
||||
assert acls == {
|
||||
'grantSet': None,
|
||||
'grantList': [{'grantee': 'LogDelivery', 'permission': 'Write'}, {'grantee': 'LogDelivery', 'permission': 'ReadAcp'}],
|
||||
'owner': {'displayName': None, 'id': OWNER}
|
||||
}
|
||||
|
||||
# Give the owner less than full_control permissions:
|
||||
log_acls = FakeAcl([FakeGrant([FakeGrantee(id=OWNER)], "READ_ACP"), FakeGrant([FakeGrantee(id=OWNER)], "WRITE_ACP")])
|
||||
s3_config_query.backends['global'].set_bucket_acl('logbucket', log_acls)
|
||||
acls = s3_config_query.backends['global'].buckets['logbucket'].acl.to_config_dict()
|
||||
assert acls == {
|
||||
'grantSet': None,
|
||||
'grantList': [
|
||||
{'grantee': {'id': OWNER, 'displayName': None}, 'permission': 'ReadAcp'},
|
||||
{'grantee': {'id': OWNER, 'displayName': None}, 'permission': 'WriteAcp'}
|
||||
],
|
||||
'owner': {'displayName': None, 'id': OWNER}
|
||||
}
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_s3_config_dict():
|
||||
from moto.s3.config import s3_config_query
|
||||
from moto.s3.models import FakeAcl, FakeGrant, FakeGrantee, FakeTag, FakeTagging, FakeTagSet, OWNER
|
||||
|
||||
# Without any buckets:
|
||||
assert not s3_config_query.get_config_resource('some_bucket')
|
||||
|
||||
tags = FakeTagging(FakeTagSet([FakeTag('someTag', 'someValue'), FakeTag('someOtherTag', 'someOtherValue')]))
|
||||
|
||||
# With 1 bucket in us-west-2:
|
||||
s3_config_query.backends['global'].create_bucket('bucket1', 'us-west-2')
|
||||
s3_config_query.backends['global'].put_bucket_tagging('bucket1', tags)
|
||||
|
||||
# With a log bucket:
|
||||
s3_config_query.backends['global'].create_bucket('logbucket', 'us-west-2')
|
||||
log_acls = FakeAcl([
|
||||
FakeGrant([FakeGrantee(uri="http://acs.amazonaws.com/groups/s3/LogDelivery")], "WRITE"),
|
||||
FakeGrant([FakeGrantee(uri="http://acs.amazonaws.com/groups/s3/LogDelivery")], "READ_ACP"),
|
||||
FakeGrant([FakeGrantee(id=OWNER)], "FULL_CONTROL")
|
||||
])
|
||||
|
||||
s3_config_query.backends['global'].set_bucket_acl('logbucket', log_acls)
|
||||
s3_config_query.backends['global'].put_bucket_logging('bucket1', {'TargetBucket': 'logbucket', 'TargetPrefix': ''})
|
||||
|
||||
# Get the us-west-2 bucket and verify that it works properly:
|
||||
bucket1_result = s3_config_query.get_config_resource('bucket1')
|
||||
|
||||
# Just verify a few things:
|
||||
assert bucket1_result['arn'] == 'arn:aws:s3:::bucket1'
|
||||
assert bucket1_result['awsRegion'] == 'us-west-2'
|
||||
assert bucket1_result['resourceName'] == bucket1_result['resourceId'] == 'bucket1'
|
||||
assert bucket1_result['tags'] == {'someTag': 'someValue', 'someOtherTag': 'someOtherValue'}
|
||||
assert isinstance(bucket1_result['configuration'], str)
|
||||
exist_list = ['AccessControlList', 'BucketAccelerateConfiguration', 'BucketLoggingConfiguration', 'BucketPolicy',
|
||||
'IsRequesterPaysEnabled', 'BucketNotificationConfiguration']
|
||||
for exist in exist_list:
|
||||
assert isinstance(bucket1_result['supplementaryConfiguration'][exist], str)
|
||||
|
||||
# Verify the logging config:
|
||||
assert json.loads(bucket1_result['supplementaryConfiguration']['BucketLoggingConfiguration']) == \
|
||||
{'destinationBucketName': 'logbucket', 'logFilePrefix': ''}
|
||||
|
||||
# Verify the policy:
|
||||
assert json.loads(bucket1_result['supplementaryConfiguration']['BucketPolicy']) == {'policyText': None}
|
||||
|
||||
# Filter by correct region:
|
||||
assert bucket1_result == s3_config_query.get_config_resource('bucket1', resource_region='us-west-2')
|
||||
|
||||
# By incorrect region:
|
||||
assert not s3_config_query.get_config_resource('bucket1', resource_region='eu-west-1')
|
||||
|
||||
# With correct resource ID and name:
|
||||
assert bucket1_result == s3_config_query.get_config_resource('bucket1', resource_name='bucket1')
|
||||
|
||||
# With an incorrect resource name:
|
||||
assert not s3_config_query.get_config_resource('bucket1', resource_name='eu-bucket-1')
|
||||
|
|
|
|||
|
|
@ -44,6 +44,36 @@ def test_create_topic_with_attributes():
|
|||
attributes['DisplayName'].should.equal('test-topic')
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_create_topic_with_tags():
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
response = conn.create_topic(
|
||||
Name='some-topic-with-tags',
|
||||
Tags=[
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_1'
|
||||
},
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
]
|
||||
)
|
||||
topic_arn = response['TopicArn']
|
||||
|
||||
conn.list_tags_for_resource(ResourceArn=topic_arn)['Tags'].should.equal([
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_1'
|
||||
},
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_create_topic_should_be_indempodent():
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
|
|
@ -200,3 +230,204 @@ def test_add_remove_permissions():
|
|||
TopicArn=response['TopicArn'],
|
||||
Label='Test1234'
|
||||
)
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_tag_topic():
|
||||
conn = boto3.client('sns', region_name='us-east-1')
|
||||
response = conn.create_topic(
|
||||
Name = 'some-topic-with-tags'
|
||||
)
|
||||
topic_arn = response['TopicArn']
|
||||
|
||||
conn.tag_resource(
|
||||
ResourceArn=topic_arn,
|
||||
Tags=[
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_1'
|
||||
}
|
||||
]
|
||||
)
|
||||
conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_1'
|
||||
}
|
||||
])
|
||||
|
||||
conn.tag_resource(
|
||||
ResourceArn=topic_arn,
|
||||
Tags=[
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
]
|
||||
)
|
||||
conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_1'
|
||||
},
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
])
|
||||
|
||||
conn.tag_resource(
|
||||
ResourceArn = topic_arn,
|
||||
Tags = [
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_X'
|
||||
}
|
||||
]
|
||||
)
|
||||
conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_X'
|
||||
},
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_untag_topic():
|
||||
conn = boto3.client('sns', region_name = 'us-east-1')
|
||||
response = conn.create_topic(
|
||||
Name = 'some-topic-with-tags',
|
||||
Tags = [
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_1'
|
||||
},
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
]
|
||||
)
|
||||
topic_arn = response['TopicArn']
|
||||
|
||||
conn.untag_resource(
|
||||
ResourceArn = topic_arn,
|
||||
TagKeys = [
|
||||
'tag_key_1'
|
||||
]
|
||||
)
|
||||
conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
])
|
||||
|
||||
# removing a non existing tag should not raise any error
|
||||
conn.untag_resource(
|
||||
ResourceArn = topic_arn,
|
||||
TagKeys = [
|
||||
'not-existing-tag'
|
||||
]
|
||||
)
|
||||
conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([
|
||||
{
|
||||
'Key': 'tag_key_2',
|
||||
'Value': 'tag_value_2'
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_list_tags_for_resource_error():
|
||||
conn = boto3.client('sns', region_name = 'us-east-1')
|
||||
conn.create_topic(
|
||||
Name = 'some-topic-with-tags',
|
||||
Tags = [
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_X'
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
conn.list_tags_for_resource.when.called_with(
|
||||
ResourceArn = 'not-existing-topic'
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Resource does not exist'
|
||||
)
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_tag_resource_errors():
|
||||
conn = boto3.client('sns', region_name = 'us-east-1')
|
||||
response = conn.create_topic(
|
||||
Name = 'some-topic-with-tags',
|
||||
Tags = [
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_X'
|
||||
}
|
||||
]
|
||||
)
|
||||
topic_arn = response['TopicArn']
|
||||
|
||||
conn.tag_resource.when.called_with(
|
||||
ResourceArn = 'not-existing-topic',
|
||||
Tags = [
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_1'
|
||||
}
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Resource does not exist'
|
||||
)
|
||||
|
||||
too_many_tags = [{'Key': 'tag_key_{}'.format(i), 'Value': 'tag_value_{}'.format(i)} for i in range(51)]
|
||||
conn.tag_resource.when.called_with(
|
||||
ResourceArn = topic_arn,
|
||||
Tags = too_many_tags
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Could not complete request: tag quota of per resource exceeded'
|
||||
)
|
||||
|
||||
# when the request fails, the tags should not be updated
|
||||
conn.list_tags_for_resource(ResourceArn = topic_arn)['Tags'].should.equal([
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_X'
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_untag_resource_error():
|
||||
conn = boto3.client('sns', region_name = 'us-east-1')
|
||||
conn.create_topic(
|
||||
Name = 'some-topic-with-tags',
|
||||
Tags = [
|
||||
{
|
||||
'Key': 'tag_key_1',
|
||||
'Value': 'tag_value_X'
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
conn.untag_resource.when.called_with(
|
||||
ResourceArn = 'not-existing-topic',
|
||||
TagKeys = [
|
||||
'tag_key_1'
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Resource does not exist'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -140,6 +140,22 @@ def test_create_queue_kms():
|
|||
queue.attributes.get('KmsDataKeyReusePeriodSeconds').should.equal('600')
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_create_queue_with_tags():
|
||||
client = boto3.client('sqs', region_name='us-east-1')
|
||||
response = client.create_queue(
|
||||
QueueName = 'test-queue-with-tags',
|
||||
tags = {
|
||||
'tag_key_1': 'tag_value_1'
|
||||
}
|
||||
)
|
||||
queue_url = response['QueueUrl']
|
||||
|
||||
client.list_queue_tags(QueueUrl = queue_url)['Tags'].should.equal({
|
||||
'tag_key_1': 'tag_value_1'
|
||||
})
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_get_nonexistent_queue():
|
||||
sqs = boto3.resource('sqs', region_name='us-east-1')
|
||||
|
|
@ -959,6 +975,48 @@ def test_tags():
|
|||
resp['Tags'].should.contain('test1')
|
||||
resp['Tags'].should_not.contain('test2')
|
||||
|
||||
# removing a non existing tag should not raise any error
|
||||
client.untag_queue(
|
||||
QueueUrl=queue_url,
|
||||
TagKeys=[
|
||||
'not-existing-tag'
|
||||
]
|
||||
)
|
||||
client.list_queue_tags(QueueUrl=queue_url)['Tags'].should.equal({
|
||||
'test1': 'value1'
|
||||
})
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_untag_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_1'
|
||||
}
|
||||
)
|
||||
queue_url = response['QueueUrl']
|
||||
|
||||
client.untag_queue.when.called_with(
|
||||
QueueUrl=queue_url + '-not-existing',
|
||||
TagKeys=[
|
||||
'tag_key_1'
|
||||
]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
"The specified queue does not exist for this wsdl version."
|
||||
)
|
||||
|
||||
client.untag_queue.when.called_with(
|
||||
QueueUrl=queue_url,
|
||||
TagKeys=[]
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'Tag keys must be between 1 and 128 characters in length.'
|
||||
)
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_create_fifo_queue_with_dlq():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue