Techdebt: Replace sure with regular assertions in DynamoDB (#6517)

This commit is contained in:
Bert Blommers 2023-07-13 10:21:47 +00:00 committed by GitHub
parent 9323747ebb
commit c6b46a48f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1541 additions and 1634 deletions

View File

@ -1,7 +1,6 @@
import boto3
import botocore
import pytest
import sure # noqa # pylint: disable=unused-import
from boto3.dynamodb.conditions import Key
from botocore.exceptions import ClientError
from unittest import SkipTest
@ -51,10 +50,8 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
IndexName="GSI-K1",
)["Items"]
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Query condition missed key schema element: gsiK1SortKey"
)
assert err["Code"] == "ValidationException"
assert err["Message"] == "Query condition missed key schema element: gsiK1SortKey"
# check using wrong name for partition key throws exception
with pytest.raises(ClientError) as exc:
@ -64,9 +61,9 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
IndexName="GSI-K1",
)["Items"]
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Query condition missed key schema element: gsiK1PartitionKey"
assert err["Code"] == "ValidationException"
assert (
err["Message"] == "Query condition missed key schema element: gsiK1PartitionKey"
)
# verify same behaviour for begins_with
@ -77,10 +74,8 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
IndexName="GSI-K1",
)["Items"]
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Query condition missed key schema element: gsiK1SortKey"
)
assert err["Code"] == "ValidationException"
assert err["Message"] == "Query condition missed key schema element: gsiK1SortKey"
# verify same behaviour for between
with pytest.raises(ClientError) as exc:
@ -94,10 +89,8 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
IndexName="GSI-K1",
)["Items"]
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Query condition missed key schema element: gsiK1SortKey"
)
assert err["Code"] == "ValidationException"
assert err["Message"] == "Query condition missed key schema element: gsiK1SortKey"
@mock_dynamodb
@ -121,10 +114,8 @@ def test_query_table_with_wrong_key_attribute_names_throws_exception():
ExpressionAttributeValues={":pk": "pk"},
)["Items"]
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Query condition missed key schema element: partitionKey"
)
assert err["Code"] == "ValidationException"
assert err["Message"] == "Query condition missed key schema element: partitionKey"
@mock_dynamodb
@ -137,9 +128,10 @@ def test_empty_expressionattributenames():
with pytest.raises(ClientError) as exc:
table.get_item(Key={"id": "my_id"}, ExpressionAttributeNames={})
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"ExpressionAttributeNames can only be specified when using expressions"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "ExpressionAttributeNames can only be specified when using expressions"
)
@ -155,8 +147,8 @@ def test_empty_expressionattributenames_with_empty_projection():
Key={"id": "my_id"}, ProjectionExpression="", ExpressionAttributeNames={}
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal("ExpressionAttributeNames must not be empty")
assert err["Code"] == "ValidationException"
assert err["Message"] == "ExpressionAttributeNames must not be empty"
@mock_dynamodb
@ -171,8 +163,8 @@ def test_empty_expressionattributenames_with_projection():
Key={"id": "my_id"}, ProjectionExpression="id", ExpressionAttributeNames={}
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal("ExpressionAttributeNames must not be empty")
assert err["Code"] == "ValidationException"
assert err["Message"] == "ExpressionAttributeNames must not be empty"
@mock_dynamodb
@ -191,9 +183,10 @@ def test_update_item_range_key_set():
ExpressionAttributeValues={":one": 1, ":a": "lore ipsum"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
'Invalid UpdateExpression: The "ADD" section can only be used once in an update expression;'
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== 'Invalid UpdateExpression: The "ADD" section can only be used once in an update expression;'
)
@ -204,8 +197,8 @@ def test_batch_get_item_non_existing_table():
with pytest.raises(client.exceptions.ResourceNotFoundException) as exc:
client.batch_get_item(RequestItems={"my-table": {"Keys": [{"id": {"N": "0"}}]}})
err = exc.value.response["Error"]
assert err["Code"].should.equal("ResourceNotFoundException")
assert err["Message"].should.equal("Requested resource not found")
assert err["Code"] == "ResourceNotFoundException"
assert err["Message"] == "Requested resource not found"
@mock_dynamodb
@ -218,8 +211,8 @@ def test_batch_write_item_non_existing_table():
RequestItems={"my-table": [{"PutRequest": {"Item": {}}}]}
)
err = exc.value.response["Error"]
assert err["Code"].should.equal("ResourceNotFoundException")
assert err["Message"].should.equal("Requested resource not found")
assert err["Code"] == "ResourceNotFoundException"
assert err["Message"] == "Requested resource not found"
@mock_dynamodb
@ -238,9 +231,10 @@ def test_create_table_with_redundant_attributes():
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions"
)
with pytest.raises(ClientError) as exc:
@ -263,9 +257,10 @@ def test_create_table_with_redundant_attributes():
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Some AttributeDefinitions are not used. AttributeDefinitions: [created_at, id, user], keys used: [id, user]"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Some AttributeDefinitions are not used. AttributeDefinitions: [created_at, id, user], keys used: [id, user]"
)
@ -285,9 +280,10 @@ def test_create_table_with_missing_attributes():
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Invalid KeySchema: Some index key attribute have no definition"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "Invalid KeySchema: Some index key attribute have no definition"
)
with pytest.raises(ClientError) as exc:
@ -306,9 +302,10 @@ def test_create_table_with_missing_attributes():
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [id]"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [id]"
)
@ -326,9 +323,10 @@ def test_create_table_with_redundant_and_missing_attributes():
BillingMode="PAY_PER_REQUEST",
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [id], AttributeDefinitions: [created_at]"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [id], AttributeDefinitions: [created_at]"
)
with pytest.raises(ClientError) as exc:
@ -349,9 +347,10 @@ def test_create_table_with_redundant_and_missing_attributes():
BillingMode="PAY_PER_REQUEST",
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [created_at, id]"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [created_at, id]"
)
@ -381,9 +380,10 @@ def test_put_item_wrong_attribute_type():
with pytest.raises(ClientError) as exc:
dynamodb.put_item(TableName="test-table", Item=item)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Type mismatch for key id expected: S actual: N"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Type mismatch for key id expected: S actual: N"
)
item = {
@ -395,9 +395,10 @@ def test_put_item_wrong_attribute_type():
with pytest.raises(ClientError) as exc:
dynamodb.put_item(TableName="test-table", Item=item)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Type mismatch for key created_at expected: N actual: S"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Type mismatch for key created_at expected: N actual: S"
)
@ -425,10 +426,8 @@ def test_hash_key_cannot_use_begins_with_operations():
table = dynamodb.Table("test-table")
with pytest.raises(ClientError) as ex:
table.query(KeyConditionExpression=Key("key").begins_with("prefix-"))
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["Error"]["Message"].should.equal(
"Query key condition not supported"
)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["Error"]["Message"] == "Query key condition not supported"
# Test this again, but with manually supplying an operator
@ -450,8 +449,8 @@ def test_hash_key_can_only_use_equals_operations(operator):
ExpressionAttributeValues={":pk": "p"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal("Query key condition not supported")
assert err["Code"] == "ValidationException"
assert err["Message"] == "Query key condition not supported"
@mock_dynamodb
@ -467,9 +466,10 @@ def test_creating_table_with_0_local_indexes():
LocalSecondaryIndexes=[],
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: List of LocalSecondaryIndexes is empty"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: List of LocalSecondaryIndexes is empty"
)
@ -486,9 +486,10 @@ def test_creating_table_with_0_global_indexes():
GlobalSecondaryIndexes=[],
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: List of GlobalSecondaryIndexes is empty"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: List of GlobalSecondaryIndexes is empty"
)
@ -524,9 +525,10 @@ def test_multiple_transactions_on_same_item():
]
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Transaction request cannot include multiple operations on one item"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "Transaction request cannot include multiple operations on one item"
)
@ -558,10 +560,10 @@ def test_transact_write_items__too_many_transactions():
]
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.match(
"1 validation error detected at 'transactItems' failed to satisfy constraint: "
"Member must have length less than or equal to 100."
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected at 'transactItems' failed to satisfy constraint: Member must have length less than or equal to 100."
)
@ -576,8 +578,8 @@ def test_update_item_non_existent_table():
ExpressionAttributeValues={":Body": {"S": ""}},
)
err = exc.value.response["Error"]
assert err["Code"].should.equal("ResourceNotFoundException")
assert err["Message"].should.equal("Requested resource not found")
assert err["Code"] == "ResourceNotFoundException"
assert err["Message"] == "Requested resource not found"
@mock_dynamodb
@ -609,14 +611,15 @@ def test_update_item_with_duplicate_expressions(expression):
ExpressionAttributeValues={":example_column": "test"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"Invalid UpdateExpression: Two document paths overlap with each other; must remove or rewrite one of these paths; path one: [example_column], path two: [example_column]"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "Invalid UpdateExpression: Two document paths overlap with each other; must remove or rewrite one of these paths; path one: [example_column], path two: [example_column]"
)
# The item is not updated
item = table.get_item(Key={"pk": "example_id"})["Item"]
item.should.equal({"pk": "example_id", "example_column": "example"})
assert item == {"pk": "example_id", "example_column": "example"}
@mock_dynamodb
@ -635,8 +638,8 @@ def test_put_item_wrong_datatype():
with pytest.raises(ClientError) as exc:
client.put_item(TableName="test2", Item={"mykey": {"N": 123}})
err = exc.value.response["Error"]
err["Code"].should.equal("SerializationException")
err["Message"].should.equal("NUMBER_VALUE cannot be converted to String")
assert err["Code"] == "SerializationException"
assert err["Message"] == "NUMBER_VALUE cannot be converted to String"
# Same thing - but with a non-key, and nested
with pytest.raises(ClientError) as exc:
@ -645,8 +648,8 @@ def test_put_item_wrong_datatype():
Item={"mykey": {"N": "123"}, "nested": {"M": {"sth": {"N": 5}}}},
)
err = exc.value.response["Error"]
err["Code"].should.equal("SerializationException")
err["Message"].should.equal("NUMBER_VALUE cannot be converted to String")
assert err["Code"] == "SerializationException"
assert err["Message"] == "NUMBER_VALUE cannot be converted to String"
@mock_dynamodb
@ -663,9 +666,10 @@ def test_put_item_empty_set():
with pytest.raises(ClientError) as exc:
table.put_item(Item={"Key": "some-irrelevant_key", "attr2": {"SS": set([])}})
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: An number set may not be empty"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: An number set may not be empty"
)
@ -689,9 +693,10 @@ def test_update_expression_with_trailing_comma():
ExpressionAttributeValues={":val1": 3, ":val2": 4},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
'Invalid UpdateExpression: Syntax error; token: "<EOF>", near: ","'
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== 'Invalid UpdateExpression: Syntax error; token: "<EOF>", near: ","'
)
@ -717,20 +722,22 @@ def test_batch_put_item_with_empty_value():
with table.batch_writer() as batch:
batch.put_item(Item={"pk": "", "sk": "sth"})
err = exc.value.response["Error"]
err["Message"].should.equal(
"One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: pk"
assert (
err["Message"]
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: pk"
)
err["Code"].should.equal("ValidationException")
assert err["Code"] == "ValidationException"
# Empty SortKey throws an error
with pytest.raises(botocore.exceptions.ClientError) as exc:
with table.batch_writer() as batch:
batch.put_item(Item={"pk": "sth", "sk": ""})
err = exc.value.response["Error"]
err["Message"].should.equal(
"One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: sk"
assert (
err["Message"]
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: sk"
)
err["Code"].should.equal("ValidationException")
assert err["Code"] == "ValidationException"
# Empty regular parameter workst just fine though
with table.batch_writer() as batch:
@ -759,10 +766,8 @@ def test_query_begins_with_without_brackets():
ExpressionAttributeValues={":pk": {"S": "test1"}, ":sk": {"S": "test2"}},
)
err = exc.value.response["Error"]
err["Message"].should.equal(
'Invalid KeyConditionExpression: Syntax error; token: "sk"'
)
err["Code"].should.equal("ValidationException")
assert err["Message"] == 'Invalid KeyConditionExpression: Syntax error; token: "sk"'
assert err["Code"] == "ValidationException"
@mock_dynamodb
@ -834,9 +839,10 @@ def test_transact_write_items_with_empty_gsi_key():
with pytest.raises(ClientError) as exc:
client.transact_write_items(TransactItems=transact_items)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values are not valid. A value specified for a secondary index key is not supported. The AttributeValue for a key attribute cannot contain an empty string value. IndexName: gsi_index, IndexKey: unique_id"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values are not valid. A value specified for a secondary index key is not supported. The AttributeValue for a key attribute cannot contain an empty string value. IndexName: gsi_index, IndexKey: unique_id"
)
@ -869,14 +875,14 @@ def test_update_primary_key_with_sortkey():
ExpressionAttributeValues={":val1": "different"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
)
table.get_item(Key={"pk": "testchangepk", "sk": "else"})["Item"].should.equal(
{"pk": "testchangepk", "sk": "else"}
)
item = table.get_item(Key={"pk": "testchangepk", "sk": "else"})["Item"]
assert item == {"pk": "testchangepk", "sk": "else"}
@mock_dynamodb
@ -902,14 +908,14 @@ def test_update_primary_key():
ExpressionAttributeValues={":val1": "different"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
)
table.get_item(Key={"pk": "testchangepk"})["Item"].should.equal(
{"pk": "testchangepk"}
)
item = table.get_item(Key={"pk": "testchangepk"})["Item"]
assert item == {"pk": "testchangepk"}
@mock_dynamodb
@ -928,15 +934,15 @@ def test_put_item__string_as_integer_value():
with pytest.raises(ClientError) as exc:
client.put_item(TableName="without_sk", Item={"pk": {"S": 123}})
err = exc.value.response["Error"]
err["Code"].should.equal("SerializationException")
err["Message"].should.equal("NUMBER_VALUE cannot be converted to String")
assert err["Code"] == "SerializationException"
assert err["Message"] == "NUMBER_VALUE cannot be converted to String"
# A primary key cannot be of type S, but then point to a dictionary
with pytest.raises(ClientError) as exc:
client.put_item(TableName="without_sk", Item={"pk": {"S": {"S": "asdf"}}})
err = exc.value.response["Error"]
err["Code"].should.equal("SerializationException")
err["Message"].should.equal("Start of structure or map found where not expected")
assert err["Code"] == "SerializationException"
assert err["Message"] == "Start of structure or map found where not expected"
# Note that a normal attribute name can be an 'S', which follows the same pattern
# Nested 'S'-s like this are allowed for non-key attributes
@ -981,9 +987,10 @@ def test_gsi_key_cannot_be_empty():
},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: Type mismatch for Index Key hello Expected: S Actual: NULL IndexName: hello-index"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: Type mismatch for Index Key hello Expected: S Actual: NULL IndexName: hello-index"
)
@ -1013,9 +1020,10 @@ def test_list_append_errors_for_unknown_attribute_value():
ReturnValues="UPDATED_NEW",
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"The provided expression refers to an attribute that does not exist in the item"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "The provided expression refers to an attribute that does not exist in the item"
)
# append to unknown list via ExpressionAttributeNames
@ -1029,9 +1037,10 @@ def test_list_append_errors_for_unknown_attribute_value():
ReturnValues="UPDATED_NEW",
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"The provided expression refers to an attribute that does not exist in the item"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "The provided expression refers to an attribute that does not exist in the item"
)
# append to unknown list, even though end result is known
@ -1044,9 +1053,10 @@ def test_list_append_errors_for_unknown_attribute_value():
ReturnValues="UPDATED_NEW",
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"The provided expression refers to an attribute that does not exist in the item"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "The provided expression refers to an attribute that does not exist in the item"
)
# We can append to a known list, into an unknown/new list

View File

@ -1,5 +1,4 @@
import boto3
import sure # noqa # pylint: disable=unused-import
import pytest
from moto import mock_dynamodb
@ -40,11 +39,12 @@ def test_item_add_long_string_hash_key_exception():
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal(
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
)
@ -87,11 +87,12 @@ def test_item_add_long_string_nonascii_hash_key_exception():
},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal(
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
)
@ -135,10 +136,11 @@ def test_item_add_long_string_range_key_exception():
},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["Error"]["Message"].should.equal(
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
)
@ -204,10 +206,11 @@ def test_put_long_string_gsi_range_key_exception():
},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["Error"]["Message"].should.equal(
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
)
@ -243,11 +246,12 @@ def test_update_item_with_long_string_hash_key_exception():
ExpressionAttributeValues={":New": {"S": "hello"}},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal(
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
)
@ -288,11 +292,12 @@ def test_update_item_with_long_string_range_key_exception():
ExpressionAttributeValues={":New": {"S": "hello"}},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal(
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
)
@ -312,9 +317,9 @@ def test_item_add_empty_key_exception():
TableName=name,
Key={"forum_name": {"S": ""}},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["Error"]["Message"].should.equal(
"One or more parameter values are not valid. The AttributeValue for a key attribute "
"cannot contain an empty string value. Key: forum_name"
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert (
ex.value.response["Error"]["Message"]
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: forum_name"
)

View File

@ -15,9 +15,9 @@ class TestHashKey:
schema=self.schema,
expression_attribute_names=dict(),
)
desired_hash_key.should.equal(eav[":id"])
comparison.should.equal(None)
range_values.should.equal([])
assert desired_hash_key == eav[":id"]
assert comparison is None
assert range_values == []
def test_unknown_hash_key(self):
kce = "wrongName = :id"
@ -29,9 +29,7 @@ class TestHashKey:
schema=self.schema,
expression_attribute_names=dict(),
)
exc.value.message.should.equal(
"Query condition missed key schema element: job_id"
)
assert exc.value.message == "Query condition missed key schema element: job_id"
def test_unknown_hash_value(self):
# TODO: is this correct? I'd assume that this should throw an error instead
@ -44,9 +42,9 @@ class TestHashKey:
schema=self.schema,
expression_attribute_names=dict(),
)
desired_hash_key.should.equal({"S": ":unknown"})
comparison.should.equal(None)
range_values.should.equal([])
assert desired_hash_key == {"S": ":unknown"}
assert comparison is None
assert range_values == []
class TestHashAndRangeKey:
@ -65,9 +63,7 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
exc.value.message.should.equal(
"Query condition missed key schema element: job_id"
)
assert exc.value.message == "Query condition missed key schema element: job_id"
@pytest.mark.parametrize(
"expr",
@ -86,8 +82,8 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
exc.value.message.should.equal(
"Query condition missed key schema element: start_date"
assert (
exc.value.message == "Query condition missed key schema element: start_date"
)
@pytest.mark.parametrize(
@ -108,9 +104,9 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
desired_hash_key.should.equal("pk")
comparison.should.equal("BEGINS_WITH")
range_values.should.equal(["19"])
assert desired_hash_key == "pk"
assert comparison == "BEGINS_WITH"
assert range_values == ["19"]
@pytest.mark.parametrize("fn", ["Begins_with", "Begins_With", "BEGINS_WITH"])
def test_begin_with__wrong_case(self, fn):
@ -122,8 +118,9 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
exc.value.message.should.equal(
f"Invalid KeyConditionExpression: Invalid function name; function: {fn}"
assert (
exc.value.message
== f"Invalid KeyConditionExpression: Invalid function name; function: {fn}"
)
@pytest.mark.parametrize(
@ -142,9 +139,9 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
desired_hash_key.should.equal("pk")
comparison.should.equal("BETWEEN")
range_values.should.equal(["19", "21"])
assert desired_hash_key == "pk"
assert comparison == "BETWEEN"
assert range_values == ["19", "21"]
@pytest.mark.parametrize("operator", [" < ", " <=", "= ", ">", ">="])
def test_numeric_comparisons(self, operator):
@ -156,9 +153,9 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
desired_hash_key.should.equal("pk")
comparison.should.equal(operator.strip())
range_values.should.equal(["19"])
assert desired_hash_key == "pk"
assert comparison == operator.strip()
assert range_values == ["19"]
@pytest.mark.parametrize(
"expr",
@ -177,7 +174,7 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
desired_hash_key.should.equal("pk")
assert desired_hash_key == "pk"
@pytest.mark.parametrize(
"expr",
@ -195,7 +192,7 @@ class TestHashAndRangeKey:
schema=self.schema,
expression_attribute_names=dict(),
)
desired_hash_key.should.equal("pk")
assert desired_hash_key == "pk"
class TestNamesAndValues:
@ -211,6 +208,6 @@ class TestNamesAndValues:
schema=self.schema,
expression_attribute_names=ean,
)
desired_hash_key.should.equal(eav[":id"])
comparison.should.equal(None)
range_values.should.equal([])
assert desired_hash_key == eav[":id"]
assert comparison is None
assert range_values == []

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
import boto3
import sure # noqa # pylint: disable=unused-import
import pytest
from moto import mock_dynamodb
@ -64,11 +63,11 @@ def test_batch_items_throws_exception_when_requesting_100_items_for_single_table
}
}
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
assert ex.value.response["Error"]["Code"] == "ValidationException"
msg = ex.value.response["Error"]["Message"]
msg.should.contain("1 validation error detected: Value")
msg.should.contain(
"at 'requestItems.users.member.keys' failed to satisfy constraint: Member must have length less than or equal to 100"
assert (
msg
== "1 validation error detected: Value at 'requestItems.users.member.keys' failed to satisfy constraint: Member must have length less than or equal to 100"
)
@ -92,10 +91,9 @@ def test_batch_items_throws_exception_when_requesting_100_items_across_all_table
},
}
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["Error"]["Message"].should.equal(
"Too many items requested for the BatchGetItem call"
)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "Too many items requested for the BatchGetItem call"
@mock_dynamodb
@ -116,11 +114,13 @@ def test_batch_items_with_basic_projection_expression():
}
)["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])
assert len(returned_items) == 3
assert [item["username"]["S"] for item in returned_items] == [
"user1",
"user2",
"user3",
]
assert [item.get("foo") for item in returned_items] == [None, None, None]
# The projection expression should not remove data from storage
returned_items = dynamodb.batch_get_item(
@ -137,10 +137,12 @@ def test_batch_items_with_basic_projection_expression():
}
)["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"])
assert [item["username"]["S"] for item in returned_items] == [
"user1",
"user2",
"user3",
]
assert [item["foo"]["S"] for item in returned_items] == ["bar", "bar", "bar"]
@mock_dynamodb
@ -162,11 +164,13 @@ def test_batch_items_with_basic_projection_expression_and_attr_expression_names(
}
)["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])
assert len(returned_items) == 3
assert [item["username"]["S"] for item in returned_items] == [
"user1",
"user2",
"user3",
]
assert [item.get("foo") for item in returned_items] == [None, None, None]
@mock_dynamodb
@ -184,10 +188,9 @@ def test_batch_items_should_throw_exception_for_duplicate_request():
}
}
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["Error"]["Message"].should.equal(
"Provided list of item keys contains duplicates"
)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "Provided list of item keys contains duplicates"
@mock_dynamodb
@ -218,14 +221,14 @@ def test_batch_items_should_return_16mb_max():
}
)
resp["Responses"]["users"].should.have.length_of(55)
assert len(resp["Responses"]["users"]) == 55
unprocessed_keys = resp["UnprocessedKeys"]["users"]["Keys"]
# 75 requested, 55 returned --> 20 unprocessed
unprocessed_keys.should.have.length_of(20)
assert len(unprocessed_keys) == 20
# Keys 55-75 are unprocessed
unprocessed_keys.should.contain({"username": {"S": "largedata55"}})
unprocessed_keys.should.contain({"username": {"S": "largedata65"}})
assert {"username": {"S": "largedata55"}} in unprocessed_keys
assert {"username": {"S": "largedata65"}} in unprocessed_keys
# Keys 0-55 are processed in the regular response, so they shouldn't show up here
unprocessed_keys.shouldnt.contain({"username": {"S": "largedata45"}})
assert {"username": {"S": "largedata45"}} not in unprocessed_keys

View File

@ -1,6 +1,5 @@
import boto3
import json
import sure # noqa # pylint: disable=unused-import
from moto import mock_cloudformation, mock_dynamodb
@ -40,11 +39,11 @@ def test_delete_stack_dynamo_template_boto3():
StackName="test_stack", TemplateBody=json.dumps(template_create_table)
)
table_desc = dynamodb_client.list_tables()
len(table_desc.get("TableNames")).should.equal(1)
assert len(table_desc.get("TableNames")) == 1
conn.delete_stack(StackName="test_stack")
table_desc = dynamodb_client.list_tables()
len(table_desc.get("TableNames")).should.equal(0)
assert len(table_desc.get("TableNames")) == 0
conn.create_stack(
StackName="test_stack", TemplateBody=json.dumps(template_create_table)

View File

@ -3,7 +3,6 @@ import re
import boto3
import pytest
import sure # noqa # pylint: disable=unused-import
from moto import mock_dynamodb
@ -38,7 +37,7 @@ def test_condition_expression_with_dot_in_attr_name():
)
item = table.get_item(Key={"id": "key-0"})["Item"]
item.should.equal({"id": "key-0", "first": {}})
assert item == {"id": "key-0", "first": {}}
@mock_dynamodb
@ -232,8 +231,8 @@ def test_condition_expressions():
def _assert_conditional_check_failed_exception(exc):
err = exc.value.response["Error"]
err["Code"].should.equal("ConditionalCheckFailedException")
err["Message"].should.equal("The conditional request failed")
assert err["Code"] == "ConditionalCheckFailedException"
assert err["Message"] == "The conditional request failed"
@mock_dynamodb
@ -274,9 +273,7 @@ def update_numerical_con_expr(key, con_expr, res, table):
ExpressionAttributeValues={":zero": 0, ":one": 1},
ConditionExpression=con_expr,
)
table.get_item(Key={"partitionKey": key})["Item"]["myAttr"].should.equal(
Decimal(res)
)
assert table.get_item(Key={"partitionKey": key})["Item"]["myAttr"] == Decimal(res)
@mock_dynamodb
@ -404,7 +401,7 @@ def test_condition_expression_with_reserved_keyword_as_attr_name():
# table is unchanged
item = table.get_item(Key={"id": "key-0"})["Item"]
item.should.equal(record)
assert item == record
# using attribute names solves the issue
table.update_item(
@ -422,4 +419,4 @@ def test_condition_expression_with_reserved_keyword_as_attr_name():
)
item = table.get_item(Key={"id": "key-0"})["Item"]
item.should.equal({"id": "key-0", "first": {}})
assert item == {"id": "key-0", "first": {}}

View File

@ -1,6 +1,5 @@
import boto3
import pytest
import sure # noqa # pylint: disable=unused-import
from botocore.exceptions import ClientError
from moto import mock_dynamodb
@ -24,9 +23,10 @@ def test_error_on_wrong_value_for_consumed_capacity():
with pytest.raises(ClientError) as ex:
table.put_item(Item=item, ReturnConsumedCapacity="Garbage")
err = ex.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"1 validation error detected: Value 'Garbage' at 'returnConsumedCapacity' failed to satisfy constraint: Member must satisfy enum value set: [INDEXES, TOTAL, NONE]"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected: Value 'Garbage' at 'returnConsumedCapacity' failed to satisfy constraint: Member must satisfy enum value set: [INDEXES, TOTAL, NONE]"
)
@ -46,10 +46,10 @@ def test_consumed_capacity_get_unknown_item():
)
# Should still return ConsumedCapacity, even if it does not return an item
response.should.have.key("ConsumedCapacity")
response["ConsumedCapacity"].should.equal(
{"TableName": "test_table", "CapacityUnits": 0.5}
)
assert response["ConsumedCapacity"] == {
"TableName": "test_table",
"CapacityUnits": 0.5,
}
@mock_dynamodb
@ -135,15 +135,14 @@ def validate_response(
response, should_have_capacity, should_have_table, is_index=False, value=1.0
):
if should_have_capacity:
response.should.have.key("ConsumedCapacity")
response["ConsumedCapacity"]["TableName"].should.equal("jobs")
response["ConsumedCapacity"]["CapacityUnits"].should.equal(value)
capacity = response["ConsumedCapacity"]
assert capacity["TableName"] == "jobs"
assert capacity["CapacityUnits"] == value
if should_have_table:
response["ConsumedCapacity"]["Table"].should.equal({"CapacityUnits": value})
assert capacity["Table"] == {"CapacityUnits": value}
if is_index:
response["ConsumedCapacity"].should.have.key("LocalSecondaryIndexes")
response["ConsumedCapacity"]["LocalSecondaryIndexes"].should.equal(
{"job_name-index": {"CapacityUnits": value}}
)
assert capacity["LocalSecondaryIndexes"] == {
"job_name-index": {"CapacityUnits": value}
}
else:
response.shouldnt.have.key("ConsumedCapacity")
assert "ConsumedCapacity" not in response

View File

@ -1,6 +1,5 @@
import boto3
from botocore.exceptions import ClientError
import sure # noqa # pylint: disable=unused-import
from datetime import datetime
import pytest
@ -26,31 +25,29 @@ def test_create_table_standard():
)
actual = client.describe_table(TableName="messages")["Table"]
actual.should.have.key("AttributeDefinitions").equal(
[
assert actual["AttributeDefinitions"] == [
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
]
assert isinstance(actual["CreationDateTime"], datetime)
assert actual["GlobalSecondaryIndexes"] == []
assert actual["LocalSecondaryIndexes"] == []
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 5,
}
assert actual["TableSizeBytes"] == 0
assert actual["TableName"] == "messages"
assert actual["TableStatus"] == "ACTIVE"
assert (
actual["TableArn"] == f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
)
actual.should.have.key("CreationDateTime").be.a(datetime)
actual.should.have.key("GlobalSecondaryIndexes").equal([])
actual.should.have.key("LocalSecondaryIndexes").equal([])
actual.should.have.key("ProvisionedThroughput").equal(
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5}
)
actual.should.have.key("TableSizeBytes").equal(0)
actual.should.have.key("TableName").equal("messages")
actual.should.have.key("TableStatus").equal("ACTIVE")
actual.should.have.key("TableArn").equal(
f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
)
actual.should.have.key("KeySchema").equal(
[
assert actual["KeySchema"] == [
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
]
)
actual.should.have.key("ItemCount").equal(0)
assert actual["ItemCount"] == 0
@mock_dynamodb
@ -81,17 +78,14 @@ def test_create_table_with_local_index():
)
actual = client.describe_table(TableName="messages")["Table"]
actual.should.have.key("AttributeDefinitions").equal(
[
assert actual["AttributeDefinitions"] == [
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
{"AttributeName": "threads", "AttributeType": "S"},
]
)
actual.should.have.key("CreationDateTime").be.a(datetime)
actual.should.have.key("GlobalSecondaryIndexes").equal([])
actual.should.have.key("LocalSecondaryIndexes").equal(
[
assert isinstance(actual["CreationDateTime"], datetime)
assert actual["GlobalSecondaryIndexes"] == []
assert actual["LocalSecondaryIndexes"] == [
{
"IndexName": "threads_index",
"KeySchema": [
@ -101,23 +95,22 @@ def test_create_table_with_local_index():
"Projection": {"ProjectionType": "ALL"},
}
]
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 5,
}
assert actual["TableSizeBytes"] == 0
assert actual["TableName"] == "messages"
assert actual["TableStatus"] == "ACTIVE"
assert (
actual["TableArn"] == f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
)
actual.should.have.key("ProvisionedThroughput").equal(
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5}
)
actual.should.have.key("TableSizeBytes").equal(0)
actual.should.have.key("TableName").equal("messages")
actual.should.have.key("TableStatus").equal("ACTIVE")
actual.should.have.key("TableArn").equal(
f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
)
actual.should.have.key("KeySchema").equal(
[
assert actual["KeySchema"] == [
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
]
)
actual.should.have.key("ItemCount").equal(0)
assert actual["ItemCount"] == 0
@mock_dynamodb
@ -143,8 +136,7 @@ def test_create_table_with_gsi():
}
],
)
table["TableDescription"]["GlobalSecondaryIndexes"].should.equal(
[
assert table["TableDescription"]["GlobalSecondaryIndexes"] == [
{
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
"IndexName": "test_gsi",
@ -156,7 +148,6 @@ def test_create_table_with_gsi():
},
}
]
)
table = dynamodb.create_table(
TableName="users2",
@ -181,8 +172,7 @@ def test_create_table_with_gsi():
}
],
)
table["TableDescription"]["GlobalSecondaryIndexes"].should.equal(
[
assert table["TableDescription"]["GlobalSecondaryIndexes"] == [
{
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
"IndexName": "test_gsi",
@ -194,7 +184,6 @@ def test_create_table_with_gsi():
},
}
]
)
@mock_dynamodb
@ -212,16 +201,16 @@ def test_create_table_with_stream_specification():
},
)
resp["TableDescription"].should.have.key("StreamSpecification")
resp["TableDescription"]["StreamSpecification"].should.equal(
{"StreamEnabled": True, "StreamViewType": "NEW_AND_OLD_IMAGES"}
)
resp["TableDescription"].should.contain("LatestStreamLabel")
resp["TableDescription"].should.contain("LatestStreamArn")
assert resp["TableDescription"]["StreamSpecification"] == {
"StreamEnabled": True,
"StreamViewType": "NEW_AND_OLD_IMAGES",
}
assert "LatestStreamLabel" in resp["TableDescription"]
assert "LatestStreamArn" in resp["TableDescription"]
resp = conn.delete_table(TableName="test-streams")
resp["TableDescription"].should.contain("StreamSpecification")
assert "StreamSpecification" in resp["TableDescription"]
@mock_dynamodb
@ -239,7 +228,7 @@ def test_create_table_with_tags():
resp = client.list_tags_of_resource(
ResourceArn=resp["TableDescription"]["TableArn"]
)
resp.should.have.key("Tags").equals([{"Key": "tk", "Value": "tv"}])
assert resp["Tags"] == [{"Key": "tk", "Value": "tv"}]
@mock_dynamodb
@ -259,12 +248,12 @@ def test_create_table_pay_per_request():
)
actual = client.describe_table(TableName="test1")["Table"]
actual.should.have.key("BillingModeSummary").equals(
{"BillingMode": "PAY_PER_REQUEST"}
)
actual.should.have.key("ProvisionedThroughput").equals(
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 0, "WriteCapacityUnits": 0}
)
assert actual["BillingModeSummary"] == {"BillingMode": "PAY_PER_REQUEST"}
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 0,
"WriteCapacityUnits": 0,
}
@mock_dynamodb
@ -284,10 +273,12 @@ def test_create_table__provisioned_throughput():
)
actual = client.describe_table(TableName="test1")["Table"]
actual.should.have.key("BillingModeSummary").equals({"BillingMode": "PROVISIONED"})
actual.should.have.key("ProvisionedThroughput").equals(
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 2, "WriteCapacityUnits": 3}
)
assert actual["BillingModeSummary"] == {"BillingMode": "PROVISIONED"}
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 2,
"WriteCapacityUnits": 3,
}
@mock_dynamodb
@ -305,9 +296,10 @@ def test_create_table_without_specifying_throughput():
StreamSpecification={"StreamEnabled": False, "StreamViewType": "NEW_IMAGE"},
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED"
)
@ -330,9 +322,10 @@ def test_create_table_error_pay_per_request_with_provisioned_param():
BillingMode="PAY_PER_REQUEST",
)
err = exc.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.equal(
"ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST"
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST"
)
@ -354,7 +347,7 @@ def test_create_table_with_ssespecification__false():
)
actual = client.describe_table(TableName="test1")["Table"]
actual.shouldnt.have.key("SSEDescription")
assert "SSEDescription" not in actual
@mock_dynamodb
@ -375,12 +368,11 @@ def test_create_table_with_ssespecification__true():
)
actual = client.describe_table(TableName="test1")["Table"]
actual.should.have.key("SSEDescription")
actual["SSEDescription"].should.have.key("Status").equals("ENABLED")
actual["SSEDescription"].should.have.key("SSEType").equals("KMS")
actual["SSEDescription"].should.have.key("KMSMasterKeyArn").match(
"^arn:aws:kms"
) # Default KMS key for DynamoDB
assert "SSEDescription" in actual
assert actual["SSEDescription"]["Status"] == "ENABLED"
assert actual["SSEDescription"]["SSEType"] == "KMS"
# Default KMS key for DynamoDB
assert actual["SSEDescription"]["KMSMasterKeyArn"].startswith("arn:aws:kms")
@mock_dynamodb
@ -401,10 +393,10 @@ def test_create_table_with_ssespecification__custom_kms_key():
)
actual = client.describe_table(TableName="test1")["Table"]
actual.should.have.key("SSEDescription")
actual["SSEDescription"].should.have.key("Status").equals("ENABLED")
actual["SSEDescription"].should.have.key("SSEType").equals("KMS")
actual["SSEDescription"].should.have.key("KMSMasterKeyArn").equals("custom-kms-key")
assert "SSEDescription" in actual
assert actual["SSEDescription"]["Status"] == "ENABLED"
assert actual["SSEDescription"]["SSEType"] == "KMS"
assert actual["SSEDescription"]["KMSMasterKeyArn"] == "custom-kms-key"
@mock_dynamodb
@ -424,23 +416,19 @@ def test_create_table__specify_non_key_column():
)
actual = client.describe_table(TableName="tab")["Table"]
actual["KeySchema"].should.equal(
[
assert actual["KeySchema"] == [
{"AttributeName": "PK", "KeyType": "HASH"},
{"AttributeName": "SomeColumn", "KeyType": "N"},
]
)
if not settings.TEST_SERVER_MODE:
ddb = dynamodb_backends[ACCOUNT_ID]["us-east-2"]
ddb.tables["tab"].attr.should.contain(
{"AttributeName": "PK", "AttributeType": "S"}
)
ddb.tables["tab"].attr.should.contain(
{"AttributeName": "SomeColumn", "AttributeType": "N"}
)
assert {"AttributeName": "PK", "AttributeType": "S"} in ddb.tables["tab"].attr
assert {"AttributeName": "SomeColumn", "AttributeType": "N"} in ddb.tables[
"tab"
].attr
# It should recognize PK is the Hash Key
ddb.tables["tab"].hash_key_attr.should.equal("PK")
assert ddb.tables["tab"].hash_key_attr == "PK"
# It should recognize that SomeColumn is not a Range Key
ddb.tables["tab"].has_range_key.should.equal(False)
ddb.tables["tab"].range_key_names.should.equal([])
assert ddb.tables["tab"].has_range_key is False
assert ddb.tables["tab"].range_key_names == []

View File

@ -454,12 +454,12 @@ def test_normalize_with_one_action(table):
item=item,
table=table,
).validate()
validated_ast.children.should.have.length_of(1)
validated_ast.children[0].should.be.a(UpdateExpressionAddClause)
assert len(validated_ast.children) == 1
assert isinstance(validated_ast.children[0], UpdateExpressionAddClause)
validated_ast.normalize()
validated_ast.children.should.have.length_of(1)
validated_ast.children[0].should.be.a(UpdateExpressionAddAction)
assert len(validated_ast.children) == 1
assert isinstance(validated_ast.children[0], UpdateExpressionAddAction)
def test_normalize_with_multiple_actions__order_is_preserved(table):
@ -481,22 +481,22 @@ def test_normalize_with_multiple_actions__order_is_preserved(table):
item=item,
table=table,
).validate()
validated_ast.children.should.have.length_of(2)
assert len(validated_ast.children) == 2
# add clause first
validated_ast.children[0].should.be.a(UpdateExpressionAddClause)
assert isinstance(validated_ast.children[0], UpdateExpressionAddClause)
# rest of the expression next
validated_ast.children[1].should.be.a(UpdateExpression)
assert isinstance(validated_ast.children[1], UpdateExpression)
validated_ast.normalize()
validated_ast.children.should.have.length_of(5)
assert len(validated_ast.children) == 5
# add action first
validated_ast.children[0].should.be.a(UpdateExpressionAddAction)
assert isinstance(validated_ast.children[0], UpdateExpressionAddAction)
# Removal actions in reverse order
validated_ast.children[1].should.be.a(UpdateExpressionRemoveAction)
validated_ast.children[1]._get_value().should.equal(3)
validated_ast.children[2].should.be.a(UpdateExpressionRemoveAction)
validated_ast.children[2]._get_value().should.equal(2)
validated_ast.children[3].should.be.a(UpdateExpressionRemoveAction)
validated_ast.children[3]._get_value().should.equal(1)
assert isinstance(validated_ast.children[1], UpdateExpressionRemoveAction)
assert validated_ast.children[1]._get_value() == 3
assert isinstance(validated_ast.children[2], UpdateExpressionRemoveAction)
assert validated_ast.children[2]._get_value() == 2
assert isinstance(validated_ast.children[3], UpdateExpressionRemoveAction)
assert validated_ast.children[3]._get_value() == 1
# Set action last, as per insertion order
validated_ast.children[4].should.be.a(UpdateExpressionSetAction)
assert isinstance(validated_ast.children[4], UpdateExpressionSetAction)

View File

@ -1,3 +1,5 @@
import pytest
from moto.dynamodb.exceptions import (
InvalidTokenException,
InvalidExpressionAttributeNameKey,
@ -108,12 +110,10 @@ def test_expression_tokenizer_single_set_action_with_underscore_in_identifier():
def test_expression_tokenizer_leading_underscore_in_attribute_name_expression():
"""Leading underscore is not allowed for an attribute name"""
set_action = "SET attrName = _idid"
try:
with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "_"
assert te.near == "= _idid"
assert te.value.token == "_"
assert te.value.near == "= _idid"
def test_expression_tokenizer_leading_underscore_in_attribute_value_expression():
@ -188,11 +188,9 @@ def test_expression_tokenizer_single_set_action_attribute_name_invalid_key():
ExpressionAttributeNames contains invalid key: Syntax error; key: "#va#l2"
"""
set_action = "SET #va#l2 = 3"
try:
with pytest.raises(InvalidExpressionAttributeNameKey) as e:
ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly"
except InvalidExpressionAttributeNameKey as e:
assert e.key == "#va#l2"
assert e.value.key == "#va#l2"
def test_expression_tokenizer_single_set_action_attribute_name_invalid_key_double_hash():
@ -200,11 +198,9 @@ def test_expression_tokenizer_single_set_action_attribute_name_invalid_key_doubl
ExpressionAttributeNames contains invalid key: Syntax error; key: "#va#l"
"""
set_action = "SET #va#l = 3"
try:
with pytest.raises(InvalidExpressionAttributeNameKey) as e:
ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly"
except InvalidExpressionAttributeNameKey as e:
assert e.key == "#va#l"
assert e.value.key == "#va#l"
def test_expression_tokenizer_single_set_action_attribute_name_valid_key():
@ -245,39 +241,31 @@ def test_expression_tokenizer_single_set_action_attribute_name_leading_underscor
def test_expression_tokenizer_just_a_pipe():
set_action = "|"
try:
with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "|"
assert te.near == "|"
assert te.value.token == "|"
assert te.value.near == "|"
def test_expression_tokenizer_just_a_pipe_with_leading_white_spaces():
set_action = " |"
try:
with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "|"
assert te.near == " |"
assert te.value.token == "|"
assert te.value.near == " |"
def test_expression_tokenizer_just_a_pipe_for_set_expression():
set_action = "SET|"
try:
with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "|"
assert te.near == "SET|"
assert te.value.token == "|"
assert te.value.near == "SET|"
def test_expression_tokenizer_just_an_attribute_and_a_pipe_for_set_expression():
set_action = "SET a|"
try:
with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "|"
assert te.near == "a|"
assert te.value.token == "|"
assert te.value.near == "a|"

View File

@ -1,3 +1,5 @@
import pytest
from moto.dynamodb.exceptions import InvalidTokenException
from moto.dynamodb.parsing.expressions import UpdateExpressionParser
from moto.dynamodb.parsing.reserved_keywords import ReservedKeywords
@ -14,392 +16,314 @@ def test_get_reserved_keywords():
def test_update_expression_numeric_literal_in_expression():
set_action = "SET attrName = 3"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "3"
assert te.near == "= 3"
assert te.value.token == "3"
assert te.value.near == "= 3"
def test_expression_tokenizer_multi_number_numeric_literal_in_expression():
set_action = "SET attrName = 34"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "34"
assert te.near == "= 34"
assert te.value.token == "34"
assert te.value.near == "= 34"
def test_expression_tokenizer_numeric_literal_unclosed_square_bracket():
set_action = "SET MyStr[ 3"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "<EOF>"
assert te.near == "3"
assert te.value.token == "<EOF>"
assert te.value.near == "3"
def test_expression_tokenizer_wrong_closing_bracket_with_space():
set_action = "SET MyStr[3 )"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == ")"
assert te.near == "3 )"
assert te.value.token == ")"
assert te.value.near == "3 )"
def test_expression_tokenizer_wrong_closing_bracket():
set_action = "SET MyStr[3)"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == ")"
assert te.near == "3)"
assert te.value.token == ")"
assert te.value.near == "3)"
def test_expression_tokenizer_only_numeric_literal_for_set():
set_action = "SET 2"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "2"
assert te.near == "SET 2"
assert te.value.token == "2"
assert te.value.near == "SET 2"
def test_expression_tokenizer_only_numeric_literal():
set_action = "2"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "2"
assert te.near == "2"
assert te.value.token == "2"
assert te.value.near == "2"
def test_expression_tokenizer_set_closing_round_bracket():
set_action = "SET )"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == ")"
assert te.near == "SET )"
assert te.value.token == ")"
assert te.value.near == "SET )"
def test_expression_tokenizer_set_closing_followed_by_numeric_literal():
set_action = "SET ) 3"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == ")"
assert te.near == "SET ) 3"
assert te.value.token == ")"
assert te.value.near == "SET ) 3"
def test_expression_tokenizer_numeric_literal_unclosed_square_bracket_trailing_space():
set_action = "SET MyStr[ 3 "
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "<EOF>"
assert te.near == "3 "
assert te.value.token == "<EOF>"
assert te.value.near == "3 "
def test_expression_tokenizer_unbalanced_round_brackets_only_opening():
set_action = "SET MyStr = (:_val"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "<EOF>"
assert te.near == ":_val"
assert te.value.token == "<EOF>"
assert te.value.near == ":_val"
def test_expression_tokenizer_unbalanced_round_brackets_only_opening_trailing_space():
set_action = "SET MyStr = (:_val "
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "<EOF>"
assert te.near == ":_val "
assert te.value.token == "<EOF>"
assert te.value.near == ":_val "
def test_expression_tokenizer_unbalanced_square_brackets_only_opening():
set_action = "SET MyStr = [:_val"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "["
assert te.near == "= [:_val"
assert te.value.token == "["
assert te.value.near == "= [:_val"
def test_expression_tokenizer_unbalanced_square_brackets_only_opening_trailing_spaces():
set_action = "SET MyStr = [:_val "
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "["
assert te.near == "= [:_val"
assert te.value.token == "["
assert te.value.near == "= [:_val"
def test_expression_tokenizer_unbalanced_round_brackets_multiple_opening():
set_action = "SET MyStr = (:_val + (:val2"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "<EOF>"
assert te.near == ":val2"
assert te.value.token == "<EOF>"
assert te.value.near == ":val2"
def test_expression_tokenizer_unbalanced_round_brackets_only_closing():
set_action = "SET MyStr = ):_val"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == ")"
assert te.near == "= ):_val"
assert te.value.token == ")"
assert te.value.near == "= ):_val"
def test_expression_tokenizer_unbalanced_square_brackets_only_closing():
set_action = "SET MyStr = ]:_val"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "]"
assert te.near == "= ]:_val"
assert te.value.token == "]"
assert te.value.near == "= ]:_val"
def test_expression_tokenizer_unbalanced_round_brackets_only_closing_followed_by_other_parts():
set_action = "SET MyStr = ):_val + :val2"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == ")"
assert te.near == "= ):_val"
assert te.value.token == ")"
assert te.value.near == "= ):_val"
def test_update_expression_starts_with_keyword_reset_followed_by_identifier():
update_expression = "RESET NonExistent"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "RESET"
assert te.near == "RESET NonExistent"
assert te.value.token == "RESET"
assert te.value.near == "RESET NonExistent"
def test_update_expression_starts_with_keyword_reset_followed_by_identifier_and_value():
update_expression = "RESET NonExistent value"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "RESET"
assert te.near == "RESET NonExistent"
assert te.value.token == "RESET"
assert te.value.near == "RESET NonExistent"
def test_update_expression_starts_with_leading_spaces_and_keyword_reset_followed_by_identifier_and_value():
update_expression = " RESET NonExistent value"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "RESET"
assert te.near == " RESET NonExistent"
assert te.value.token == "RESET"
assert te.value.near == " RESET NonExistent"
def test_update_expression_with_only_keyword_reset():
update_expression = "RESET"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "RESET"
assert te.near == "RESET"
assert te.value.token == "RESET"
assert te.value.near == "RESET"
def test_update_nested_expression_with_selector_just_should_fail_parsing_at_numeric_literal_value():
update_expression = "SET a[0].b = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "5"
assert te.near == "= 5"
assert te.value.token == "5"
assert te.value.near == "= 5"
def test_update_nested_expression_with_selector_and_spaces_should_only_fail_parsing_at_numeric_literal_value():
update_expression = "SET a [ 2 ]. b = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "5"
assert te.near == "= 5"
assert te.value.token == "5"
assert te.value.near == "= 5"
def test_update_nested_expression_with_double_selector_and_spaces_should_only_fail_parsing_at_numeric_literal_value():
update_expression = "SET a [2][ 3 ]. b = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "5"
assert te.near == "= 5"
assert te.value.token == "5"
assert te.value.near == "= 5"
def test_update_nested_expression_should_only_fail_parsing_at_numeric_literal_value():
update_expression = "SET a . b = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "5"
assert te.near == "= 5"
assert te.value.token == "5"
assert te.value.near == "= 5"
def test_nested_selectors_in_update_expression_should_fail_at_nesting():
update_expression = "SET a [ [2] ]. b = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "["
assert te.near == "[ [2"
assert te.value.token == "["
assert te.value.near == "[ [2"
def test_update_expression_number_in_selector_cannot_be_splite():
update_expression = "SET a [2 1]. b = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "1"
assert te.near == "2 1]"
assert te.value.token == "1"
assert te.value.near == "2 1]"
def test_update_expression_cannot_have_successive_attributes():
update_expression = "SET #a a = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "a"
assert te.near == "#a a ="
assert te.value.token == "a"
assert te.value.near == "#a a ="
def test_update_expression_path_with_both_attribute_and_attribute_name_should_only_fail_at_numeric_value():
update_expression = "SET #a.a = 5"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(update_expression)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "5"
assert te.near == "= 5"
assert te.value.token == "5"
assert te.value.near == "= 5"
def test_expression_tokenizer_2_same_operators_back_to_back():
set_action = "SET MyStr = NoExist + + :_val "
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "+"
assert te.near == "+ + :_val"
assert te.value.token == "+"
assert te.value.near == "+ + :_val"
def test_expression_tokenizer_2_different_operators_back_to_back():
set_action = "SET MyStr = NoExist + - :_val "
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "-"
assert te.near == "+ - :_val"
assert te.value.token == "-"
assert te.value.near == "+ - :_val"
def test_update_expression_remove_does_not_allow_operations():
remove_action = "REMOVE NoExist + "
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(remove_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "+"
assert te.near == "NoExist + "
assert te.value.token == "+"
assert te.value.near == "NoExist + "
def test_update_expression_add_does_not_allow_attribute_after_path():
"""value here is not really a value since a value starts with a colon (:)"""
add_expr = "ADD attr val foobar"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(add_expr)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "val"
assert te.near == "attr val foobar"
assert te.value.token == "val"
assert te.value.near == "attr val foobar"
def test_update_expression_add_does_not_allow_attribute_foobar_after_value():
add_expr = "ADD attr :val foobar"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(add_expr)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "foobar"
assert te.near == ":val foobar"
assert te.value.token == "foobar"
assert te.value.near == ":val foobar"
def test_update_expression_delete_does_not_allow_attribute_after_path():
"""value here is not really a value since a value starts with a colon (:)"""
delete_expr = "DELETE attr val"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(delete_expr)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "val"
assert te.near == "attr val"
assert te.value.token == "val"
assert te.value.near == "attr val"
def test_update_expression_delete_does_not_allow_attribute_foobar_after_value():
delete_expr = "DELETE attr :val foobar"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(delete_expr)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "foobar"
assert te.near == ":val foobar"
assert te.value.token == "foobar"
assert te.value.near == ":val foobar"
def test_update_expression_parsing_is_not_keyword_aware():
"""path and VALUE are keywords. Yet a token error will be thrown for the numeric literal 1."""
delete_expr = "SET path = VALUE 1"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(delete_expr)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "1"
assert te.near == "VALUE 1"
assert te.value.token == "1"
assert te.value.near == "VALUE 1"
def test_expression_if_not_exists_is_not_valid_in_remove_statement():
set_action = "REMOVE if_not_exists(a,b)"
try:
with pytest.raises(InvalidTokenException) as te:
UpdateExpressionParser.make(set_action)
assert False, "Exception not raised correctly"
except InvalidTokenException as te:
assert te.token == "("
assert te.near == "if_not_exists(a"
assert te.value.token == "("
assert te.value.near == "if_not_exists(a"

View File

@ -5,7 +5,6 @@ from unittest import TestCase
class TestSelectStatements:
mock = mock_dynamodb()
@classmethod

View File

@ -3,7 +3,6 @@ from decimal import Decimal
import boto3
from boto3.dynamodb.conditions import Key
from botocore.exceptions import ClientError
import sure # noqa # pylint: disable=unused-import
import pytest
from moto import mock_dynamodb
@ -33,8 +32,8 @@ def test_get_item_without_range_key_boto3():
with pytest.raises(ClientError) as ex:
table.get_item(Key={"id": hash_key})
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["Error"]["Message"].should.equal("Validation Exception")
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert ex.value.response["Error"]["Message"] == "Validation Exception"
@mock_dynamodb
@ -59,23 +58,23 @@ def test_query_filter_boto3():
table.put_item(Item={"pk": "pk", "sk": f"sk-{i}"})
res = table.query(KeyConditionExpression=Key("pk").eq("pk"))
res["Items"].should.have.length_of(3)
assert len(res["Items"]) == 3
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").lt("sk-1"))
res["Items"].should.have.length_of(1)
res["Items"].should.equal([{"pk": "pk", "sk": "sk-0"}])
assert len(res["Items"]) == 1
assert res["Items"] == [{"pk": "pk", "sk": "sk-0"}]
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").lte("sk-1"))
res["Items"].should.have.length_of(2)
res["Items"].should.equal([{"pk": "pk", "sk": "sk-0"}, {"pk": "pk", "sk": "sk-1"}])
assert len(res["Items"]) == 2
assert res["Items"] == [{"pk": "pk", "sk": "sk-0"}, {"pk": "pk", "sk": "sk-1"}]
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").gt("sk-1"))
res["Items"].should.have.length_of(1)
res["Items"].should.equal([{"pk": "pk", "sk": "sk-2"}])
assert len(res["Items"]) == 1
assert res["Items"] == [{"pk": "pk", "sk": "sk-2"}]
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").gte("sk-1"))
res["Items"].should.have.length_of(2)
res["Items"].should.equal([{"pk": "pk", "sk": "sk-1"}, {"pk": "pk", "sk": "sk-2"}])
assert len(res["Items"]) == 2
assert res["Items"] == [{"pk": "pk", "sk": "sk-1"}, {"pk": "pk", "sk": "sk-2"}]
@mock_dynamodb
@ -108,7 +107,7 @@ def test_boto3_conditions():
)
expected = ["123", "456", "789"]
for index, item in enumerate(results["Items"]):
item["subject"].should.equal(expected[index])
assert item["subject"] == expected[index]
# Return all items again, but in reverse
results = table.query(
@ -116,7 +115,7 @@ def test_boto3_conditions():
ScanIndexForward=False,
)
for index, item in enumerate(reversed(results["Items"])):
item["subject"].should.equal(expected[index])
assert item["subject"] == expected[index]
# Filter the subjects to only return some of the results
results = table.query(
@ -124,32 +123,32 @@ def test_boto3_conditions():
& Key("subject").gt("234"),
ConsistentRead=True,
)
results["Count"].should.equal(2)
assert results["Count"] == 2
# Filter to return no results
results = table.query(
KeyConditionExpression=Key("forum_name").eq("the-key")
& Key("subject").gt("9999")
)
results["Count"].should.equal(0)
assert results["Count"] == 0
results = table.query(
KeyConditionExpression=Key("forum_name").eq("the-key")
& Key("subject").begins_with("12")
)
results["Count"].should.equal(1)
assert results["Count"] == 1
results = table.query(
KeyConditionExpression=Key("subject").begins_with("7")
& Key("forum_name").eq("the-key")
)
results["Count"].should.equal(1)
assert results["Count"] == 1
results = table.query(
KeyConditionExpression=Key("forum_name").eq("the-key")
& Key("subject").between("567", "890")
)
results["Count"].should.equal(1)
assert results["Count"] == 1
@mock_dynamodb
@ -199,7 +198,7 @@ def test_boto3_conditions_ignorecase():
":end": {"S": "200"},
},
)
results["Count"].should.equal(2)
assert results["Count"] == 2
with pytest.raises(ClientError) as ex:
dynamodb.query(
@ -210,20 +209,19 @@ def test_boto3_conditions_ignorecase():
":subject": {"S": "1"},
},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["Error"]["Message"].should.equal(
"Invalid KeyConditionExpression: Invalid function name; function: BegIns_WiTh"
assert ex.value.response["Error"]["Code"] == "ValidationException"
assert (
ex.value.response["Error"]["Message"]
== "Invalid KeyConditionExpression: Invalid function name; function: BegIns_WiTh"
)
@mock_dynamodb
def test_boto3_put_item_with_conditions():
import botocore
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create the DynamoDB table.
table = dynamodb.create_table(
dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
@ -244,15 +242,21 @@ def test_boto3_put_item_with_conditions():
ConditionExpression="attribute_not_exists(forum_name) AND attribute_not_exists(subject)",
)
table.put_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.put_item(
Item={"forum_name": "the-key", "subject": "123"},
ConditionExpression="attribute_not_exists(forum_name) AND attribute_not_exists(subject)",
).should.throw(botocore.exceptions.ClientError)
)
err = exc.value.response["Error"]
assert err["Code"] == "ConditionalCheckFailedException"
table.put_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.put_item(
Item={"forum_name": "bogus-key", "subject": "bogus", "test": "123"},
ConditionExpression="attribute_exists(forum_name) AND attribute_exists(subject)",
).should.throw(botocore.exceptions.ClientError)
)
err = exc.value.response["Error"]
assert err["Code"] == "ConditionalCheckFailedException"
def _create_table_with_range_key():
@ -316,15 +320,13 @@ def test_update_item_range_key_set():
(k, str(v) if isinstance(v, Decimal) else v)
for k, v in table.get_item(Key=item_key)["Item"].items()
)
dict(returned_item).should.equal(
{
assert returned_item == {
"username": "johndoe2",
"forum_name": "the-key",
"subject": "123",
"created": "4",
"mapfield": {"key": "value"},
}
)
@mock_dynamodb
@ -348,15 +350,13 @@ def test_update_item_does_not_exist_is_created():
(k, str(v) if isinstance(v, Decimal) else v)
for k, v in table.get_item(Key=item_key)["Item"].items()
)
dict(returned_item).should.equal(
{
assert returned_item == {
"username": "johndoe2",
"forum_name": "the-key",
"subject": "123",
"created": "4",
"mapfield": {"key": "value"},
}
)
@mock_dynamodb
@ -377,9 +377,11 @@ def test_update_item_add_value():
(k, str(v) if isinstance(v, Decimal) else v)
for k, v in table.get_item(Key=item_key)["Item"].items()
)
dict(returned_item).should.equal(
{"numeric_field": "1", "forum_name": "the-key", "subject": "123"}
)
assert returned_item == {
"numeric_field": "1",
"forum_name": "the-key",
"subject": "123",
}
@mock_dynamodb
@ -404,13 +406,11 @@ def test_update_item_add_value_string_set():
(k, str(v) if isinstance(v, Decimal) else v)
for k, v in table.get_item(Key=item_key)["Item"].items()
)
dict(returned_item).should.equal(
{
assert returned_item == {
"string_set": set(["str1", "str2", "str3"]),
"forum_name": "the-key",
"subject": "123",
}
)
@mock_dynamodb
@ -435,9 +435,11 @@ def test_update_item_delete_value_string_set():
(k, str(v) if isinstance(v, Decimal) else v)
for k, v in table.get_item(Key=item_key)["Item"].items()
)
dict(returned_item).should.equal(
{"string_set": set(["str1"]), "forum_name": "the-key", "subject": "123"}
)
assert returned_item == {
"string_set": set(["str1"]),
"forum_name": "the-key",
"subject": "123",
}
@mock_dynamodb
@ -454,9 +456,11 @@ def test_update_item_add_value_does_not_exist_is_created():
(k, str(v) if isinstance(v, Decimal) else v)
for k, v in table.get_item(Key=item_key)["Item"].items()
)
dict(returned_item).should.equal(
{"numeric_field": "2", "forum_name": "the-key", "subject": "123"}
)
assert returned_item == {
"numeric_field": "2",
"forum_name": "the-key",
"subject": "123",
}
@mock_dynamodb
@ -472,27 +476,30 @@ def test_update_item_with_expression():
UpdateExpression="SET field = :field_value",
ExpressionAttributeValues={":field_value": 2},
)
dict(table.get_item(Key=item_key)["Item"]).should.equal(
{"field": Decimal("2"), "forum_name": "the-key", "subject": "123"}
)
assert table.get_item(Key=item_key)["Item"] == {
"field": Decimal("2"),
"forum_name": "the-key",
"subject": "123",
}
table.update_item(
Key=item_key,
UpdateExpression="SET field = :field_value",
ExpressionAttributeValues={":field_value": 3},
)
dict(table.get_item(Key=item_key)["Item"]).should.equal(
{"field": Decimal("3"), "forum_name": "the-key", "subject": "123"}
)
assert table.get_item(Key=item_key)["Item"] == {
"field": Decimal("3"),
"forum_name": "the-key",
"subject": "123",
}
def assert_failure_due_to_key_not_in_schema(func, **kwargs):
with pytest.raises(ClientError) as ex:
func(**kwargs)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
ex.value.response["Error"]["Message"].should.equal(
"The provided key element does not match the schema"
)
err = ex.value.response["Error"]
assert err["Code"] == "ValidationException"
assert err["Message"] == "The provided key element does not match the schema"
@mock_dynamodb
@ -518,7 +525,7 @@ def test_update_item_add_with_expression():
ExpressionAttributeValues={":v": {"item4"}},
)
current_item["str_set"] = current_item["str_set"].union({"item4"})
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
assert table.get_item(Key=item_key)["Item"] == current_item
# Update item to add a string value to a non-existing set
table.update_item(
@ -527,7 +534,7 @@ def test_update_item_add_with_expression():
ExpressionAttributeValues={":v": {"item4"}},
)
current_item["non_existing_str_set"] = {"item4"}
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
assert table.get_item(Key=item_key)["Item"] == current_item
# Update item to add a num value to a num set
table.update_item(
@ -536,7 +543,7 @@ def test_update_item_add_with_expression():
ExpressionAttributeValues={":v": {6}},
)
current_item["num_set"] = current_item["num_set"].union({6})
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
assert table.get_item(Key=item_key)["Item"] == current_item
# Update item to add a value to a number value
table.update_item(
@ -545,35 +552,59 @@ def test_update_item_add_with_expression():
ExpressionAttributeValues={":v": 20},
)
current_item["num_val"] = current_item["num_val"] + 20
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
assert table.get_item(Key=item_key)["Item"] == current_item
# Attempt to add a number value to a string set, should raise Client Error
table.update_item.when.called_with(
# Attempt to add a number value to a string set
with pytest.raises(ClientError) as exc:
table.update_item(
Key=item_key,
UpdateExpression="ADD str_set :v",
ExpressionAttributeValues={":v": 20},
).should.have.raised(ClientError)
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
)
err = exc.value.response["Error"]
assert (
err["Message"]
== "An operand in the update expression has an incorrect data type"
)
# Attempt to add a number set to the string set, should raise a ClientError
table.update_item.when.called_with(
assert table.get_item(Key=item_key)["Item"] == current_item
# Attempt to add a number set to the string set
with pytest.raises(ClientError) as exc:
table.update_item(
Key=item_key,
UpdateExpression="ADD str_set :v",
ExpressionAttributeValues={":v": {20}},
).should.have.raised(ClientError)
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
)
err = exc.value.response["Error"]
assert (
err["Message"]
== "An operand in the update expression has an incorrect data type"
)
assert table.get_item(Key=item_key)["Item"] == current_item
# Attempt to update with a bad expression
table.update_item.when.called_with(
Key=item_key, UpdateExpression="ADD str_set bad_value"
).should.have.raised(ClientError)
with pytest.raises(ClientError) as exc:
table.update_item(Key=item_key, UpdateExpression="ADD str_set bad_value")
err = exc.value.response["Error"]
assert (
err["Message"]
== 'Invalid UpdateExpression: Syntax error; token: "bad_value", near: "str_set bad_value"'
)
# Attempt to add a string value instead of a string set
table.update_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.update_item(
Key=item_key,
UpdateExpression="ADD str_set :v",
ExpressionAttributeValues={":v": "new_string"},
).should.have.raised(ClientError)
)
err = exc.value.response["Error"]
assert (
err["Message"]
== "An operand in the update expression has an incorrect data type"
)
@mock_dynamodb
@ -599,10 +630,9 @@ def test_update_item_add_with_nested_sets():
current_item["nested"]["str_set"] = current_item["nested"]["str_set"].union(
{"item4"}
)
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
assert table.get_item(Key=item_key)["Item"] == current_item
# Update item to add a string value to a non-existing set
# Should raise
table.update_item(
Key=item_key,
UpdateExpression="ADD #ns.#ne :v",
@ -610,7 +640,7 @@ def test_update_item_add_with_nested_sets():
ExpressionAttributeValues={":v": {"new_item"}},
)
current_item["nested"]["non_existing_str_set"] = {"new_item"}
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
assert table.get_item(Key=item_key)["Item"] == current_item
@mock_dynamodb
@ -636,7 +666,7 @@ def test_update_item_delete_with_nested_sets():
current_item["nested"]["str_set"] = current_item["nested"]["str_set"].difference(
{"item3"}
)
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
assert table.get_item(Key=item_key)["Item"] == current_item
@mock_dynamodb
@ -662,7 +692,7 @@ def test_update_item_delete_with_expression():
ExpressionAttributeValues={":v": {"item2"}},
)
current_item["str_set"] = current_item["str_set"].difference({"item2"})
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
assert table.get_item(Key=item_key)["Item"] == current_item
# Update item to delete a num value from a num set
table.update_item(
@ -671,28 +701,46 @@ def test_update_item_delete_with_expression():
ExpressionAttributeValues={":v": {2}},
)
current_item["num_set"] = current_item["num_set"].difference({2})
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
assert table.get_item(Key=item_key)["Item"] == current_item
# Try to delete on a number, this should fail
table.update_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.update_item(
Key=item_key,
UpdateExpression="DELETE num_val :v",
ExpressionAttributeValues={":v": 20},
).should.have.raised(ClientError)
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
)
err = exc.value.response["Error"]
assert (
err["Message"]
== "Invalid UpdateExpression: Incorrect operand type for operator or function; operator or function: operator: DELETE, operand type: NUMBER"
)
assert table.get_item(Key=item_key)["Item"] == current_item
# Try to delete a string set from a number set
table.update_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.update_item(
Key=item_key,
UpdateExpression="DELETE num_set :v",
ExpressionAttributeValues={":v": {"del_str"}},
).should.have.raised(ClientError)
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
)
err = exc.value.response["Error"]
assert (
err["Message"]
== "An operand in the update expression has an incorrect data type"
)
assert table.get_item(Key=item_key)["Item"] == current_item
# Attempt to update with a bad expression
table.update_item.when.called_with(
Key=item_key, UpdateExpression="DELETE num_val badvalue"
).should.have.raised(ClientError)
with pytest.raises(ClientError) as exc:
table.update_item(Key=item_key, UpdateExpression="DELETE num_val badvalue")
err = exc.value.response["Error"]
assert (
err["Message"]
== 'Invalid UpdateExpression: Syntax error; token: "badvalue", near: "num_val badvalue"'
)
@mock_dynamodb
@ -748,7 +796,7 @@ def test_boto3_query_gsi_range_comparison():
)
expected = ["456", "789", "123"]
for index, item in enumerate(results["Items"]):
item["subject"].should.equal(expected[index])
assert item["subject"] == expected[index]
# Return all johndoe items again, but in reverse
results = table.query(
@ -757,7 +805,7 @@ def test_boto3_query_gsi_range_comparison():
IndexName="TestGSI",
)
for index, item in enumerate(reversed(results["Items"])):
item["subject"].should.equal(expected[index])
assert item["subject"] == expected[index]
# Filter the creation to only return some of the results
# And reverse order of hash + range key
@ -766,20 +814,20 @@ def test_boto3_query_gsi_range_comparison():
ConsistentRead=True,
IndexName="TestGSI",
)
results["Count"].should.equal(2)
assert results["Count"] == 2
# Filter to return no results
results = table.query(
KeyConditionExpression=Key("username").eq("janedoe") & Key("created").gt(9),
IndexName="TestGSI",
)
results["Count"].should.equal(0)
assert results["Count"] == 0
results = table.query(
KeyConditionExpression=Key("username").eq("janedoe") & Key("created").eq(5),
IndexName="TestGSI",
)
results["Count"].should.equal(1)
assert results["Count"] == 1
# Test range key sorting
results = table.query(
@ -788,7 +836,7 @@ def test_boto3_query_gsi_range_comparison():
)
expected = [Decimal("1"), Decimal("2"), Decimal("3")]
for index, item in enumerate(results["Items"]):
item["created"].should.equal(expected[index])
assert item["created"] == expected[index]
@mock_dynamodb
@ -810,8 +858,8 @@ def test_boto3_update_table_throughput():
)
table = dynamodb.Table("users")
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
table.update(
ProvisionedThroughput={"ReadCapacityUnits": 10, "WriteCapacityUnits": 11}
@ -819,8 +867,8 @@ def test_boto3_update_table_throughput():
table = dynamodb.Table("users")
table.provisioned_throughput["ReadCapacityUnits"].should.equal(10)
table.provisioned_throughput["WriteCapacityUnits"].should.equal(11)
assert table.provisioned_throughput["ReadCapacityUnits"] == 10
assert table.provisioned_throughput["WriteCapacityUnits"] == 11
@mock_dynamodb
@ -859,11 +907,11 @@ def test_boto3_update_table_gsi_throughput():
table = dynamodb.Table("users")
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
gsi_throughput["ReadCapacityUnits"].should.equal(3)
gsi_throughput["WriteCapacityUnits"].should.equal(4)
assert gsi_throughput["ReadCapacityUnits"] == 3
assert gsi_throughput["WriteCapacityUnits"] == 4
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
table.update(
GlobalSecondaryIndexUpdates=[
@ -882,12 +930,12 @@ def test_boto3_update_table_gsi_throughput():
table = dynamodb.Table("users")
# Primary throughput has not changed
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
gsi_throughput["ReadCapacityUnits"].should.equal(10)
gsi_throughput["WriteCapacityUnits"].should.equal(11)
assert gsi_throughput["ReadCapacityUnits"] == 10
assert gsi_throughput["WriteCapacityUnits"] == 11
@mock_dynamodb
@ -909,8 +957,8 @@ def test_update_table_gsi_create():
)
table = dynamodb.Table("users")
table.global_secondary_indexes.should.have.length_of(0)
table.attribute_definitions.should.have.length_of(2)
assert len(table.global_secondary_indexes) == 0
assert len(table.attribute_definitions) == 2
table.update(
AttributeDefinitions=[
@ -939,12 +987,12 @@ def test_update_table_gsi_create():
table = dynamodb.Table("users")
table.reload()
table.global_secondary_indexes.should.have.length_of(1)
table.attribute_definitions.should.have.length_of(4)
assert len(table.global_secondary_indexes) == 1
assert len(table.attribute_definitions) == 4
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
assert gsi_throughput["ReadCapacityUnits"].should.equal(3)
assert gsi_throughput["WriteCapacityUnits"].should.equal(4)
assert gsi_throughput["ReadCapacityUnits"] == 3
assert gsi_throughput["WriteCapacityUnits"] == 4
# Check update works
table.update(
@ -963,13 +1011,13 @@ def test_update_table_gsi_create():
table = dynamodb.Table("users")
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
assert gsi_throughput["ReadCapacityUnits"].should.equal(10)
assert gsi_throughput["WriteCapacityUnits"].should.equal(11)
assert gsi_throughput["ReadCapacityUnits"] == 10
assert gsi_throughput["WriteCapacityUnits"] == 11
table.update(GlobalSecondaryIndexUpdates=[{"Delete": {"IndexName": "TestGSI"}}])
table = dynamodb.Table("users")
table.global_secondary_indexes.should.have.length_of(0)
assert len(table.global_secondary_indexes) == 0
@mock_dynamodb
@ -1006,12 +1054,12 @@ def test_update_table_gsi_throughput():
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 6},
)
table = dynamodb.Table("users")
table.global_secondary_indexes.should.have.length_of(1)
assert len(table.global_secondary_indexes) == 1
table.update(GlobalSecondaryIndexUpdates=[{"Delete": {"IndexName": "TestGSI"}}])
table = dynamodb.Table("users")
table.global_secondary_indexes.should.have.length_of(0)
assert len(table.global_secondary_indexes) == 0
@mock_dynamodb
@ -1028,22 +1076,21 @@ def test_query_pagination():
)
page1 = table.query(KeyConditionExpression=Key("forum_name").eq("the-key"), Limit=6)
page1["Count"].should.equal(6)
page1["Items"].should.have.length_of(6)
page1.should.have.key("LastEvaluatedKey")
assert page1["Count"] == 6
assert len(page1["Items"]) == 6
page2 = table.query(
KeyConditionExpression=Key("forum_name").eq("the-key"),
Limit=6,
ExclusiveStartKey=page1["LastEvaluatedKey"],
)
page2["Count"].should.equal(4)
page2["Items"].should.have.length_of(4)
page2.should_not.have.key("LastEvaluatedKey")
assert page2["Count"] == 4
assert len(page2["Items"]) == 4
assert "LastEvaluatedKey" not in page2
results = page1["Items"] + page2["Items"]
subjects = set([int(r["subject"]) for r in results])
subjects.should.equal(set(range(10)))
assert subjects == set(range(10))
@mock_dynamodb
@ -1188,7 +1235,6 @@ def test_update_item_throws_exception_when_updating_hash_or_range_key(
ExpressionAttributeValues={":New": {"S": "2"}},
)
err = ex.value.response["Error"]
err["Code"].should.equal("ValidationException")
err["Message"].should.match(
r"One or more parameter values were invalid: Cannot update attribute (r|h). This attribute is part of the key"
)
assert err["Code"] == "ValidationException"
assert "Cannot update attribute" in err["Message"]
assert "This attribute is part of the key" in err["Message"]

View File

@ -1,12 +1,10 @@
import boto3
from boto3.dynamodb.conditions import Key
import sure # noqa # pylint: disable=unused-import
import pytest
from boto3.dynamodb.conditions import Key
from datetime import datetime
from botocore.exceptions import ClientError
from moto import mock_dynamodb
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
import botocore
@mock_dynamodb
@ -35,15 +33,12 @@ def test_create_table():
actual = client.describe_table(TableName="messages")["Table"]
actual.should.have.key("AttributeDefinitions").equal(
[
assert actual["AttributeDefinitions"] == [
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "gsi_col", "AttributeType": "S"},
]
)
actual.should.have.key("CreationDateTime").be.a(datetime)
actual.should.have.key("GlobalSecondaryIndexes").equal(
[
assert isinstance(actual["CreationDateTime"], datetime)
assert actual["GlobalSecondaryIndexes"] == [
{
"IndexName": "test_gsi",
"KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}],
@ -55,21 +50,20 @@ def test_create_table():
},
}
]
assert actual["LocalSecondaryIndexes"] == []
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
}
assert actual["TableSizeBytes"] == 0
assert actual["TableName"] == "messages"
assert actual["TableStatus"] == "ACTIVE"
assert (
actual["TableArn"] == f"arn:aws:dynamodb:us-east-2:{ACCOUNT_ID}:table/messages"
)
actual.should.have.key("LocalSecondaryIndexes").equal([])
actual.should.have.key("ProvisionedThroughput").equal(
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 1}
)
actual.should.have.key("TableSizeBytes").equal(0)
actual.should.have.key("TableName").equal("messages")
actual.should.have.key("TableStatus").equal("ACTIVE")
actual.should.have.key("TableArn").equal(
f"arn:aws:dynamodb:us-east-2:{ACCOUNT_ID}:table/messages"
)
actual.should.have.key("KeySchema").equal(
[{"AttributeName": "id", "KeyType": "HASH"}]
)
actual.should.have.key("ItemCount").equal(0)
assert actual["KeySchema"] == [{"AttributeName": "id", "KeyType": "HASH"}]
assert actual["ItemCount"] == 0
@mock_dynamodb
@ -81,17 +75,17 @@ def test_delete_table():
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.list_tables()["TableNames"].should.have.length_of(1)
assert len(conn.list_tables()["TableNames"]) == 1
conn.delete_table(TableName="messages")
conn.list_tables()["TableNames"].should.have.length_of(0)
assert conn.list_tables()["TableNames"] == []
with pytest.raises(ClientError) as ex:
conn.delete_table(TableName="messages")
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
@mock_dynamodb
@ -112,11 +106,13 @@ def test_item_add_and_describe_and_update():
table.put_item(Item=data)
returned_item = table.get_item(Key={"id": "LOLCat Forum"})
returned_item.shouldnt.have.key("ConsumedCapacity")
assert "ConsumedCapacity" not in returned_item
dict(returned_item["Item"]).should.equal(
{"id": "LOLCat Forum", "Body": "http://url_to_lolcat.gif", "SentBy": "User A"}
)
assert returned_item["Item"] == {
"id": "LOLCat Forum",
"Body": "http://url_to_lolcat.gif",
"SentBy": "User A",
}
table.update_item(
Key={"id": "LOLCat Forum"},
@ -125,9 +121,11 @@ def test_item_add_and_describe_and_update():
)
returned_item = table.get_item(Key={"id": "LOLCat Forum"})
returned_item["Item"].should.equal(
{"id": "LOLCat Forum", "Body": "http://url_to_lolcat.gif", "SentBy": "User B"}
)
assert returned_item["Item"] == {
"id": "LOLCat Forum",
"Body": "http://url_to_lolcat.gif",
"SentBy": "User B",
}
@mock_dynamodb
@ -144,9 +142,9 @@ def test_item_put_without_table():
},
)
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
@mock_dynamodb
@ -156,9 +154,9 @@ def test_get_item_with_undeclared_table():
with pytest.raises(ClientError) as ex:
conn.get_item(TableName="messages", Key={"forum_name": {"S": "LOLCat Forum"}})
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
@mock_dynamodb
@ -179,11 +177,11 @@ def test_delete_item():
}
table.put_item(Item=item_data)
table.item_count.should.equal(1)
assert table.item_count == 1
table.delete_item(Key={"id": "LOLCat Forum"})
table.item_count.should.equal(0)
assert table.item_count == 0
table.delete_item(Key={"id": "LOLCat Forum"})
@ -209,9 +207,9 @@ def test_scan_with_undeclared_table():
with pytest.raises(ClientError) as ex:
conn.scan(TableName="messages")
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
@mock_dynamodb
@ -224,7 +222,7 @@ def test_get_key_schema():
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table.key_schema.should.equal([{"AttributeName": "id", "KeyType": "HASH"}])
assert table.key_schema == [{"AttributeName": "id", "KeyType": "HASH"}]
@mock_dynamodb
@ -259,7 +257,7 @@ def test_update_item_double_nested_remove():
"username": {"S": "steve"},
"Meta": {"M": {"Name": {"M": {"Last": {"S": "Urkel"}}}}},
}
dict(returned_item["Item"]).should.equal(expected_item)
assert returned_item["Item"] == expected_item
@mock_dynamodb
@ -283,7 +281,7 @@ def test_update_item_set():
)
returned_item = table.get_item(Key=key_map)["Item"]
dict(returned_item).should.equal({"username": "steve", "foo": "bar", "blah": "baz"})
assert returned_item == {"username": "steve", "foo": "bar", "blah": "baz"}
@mock_dynamodb
@ -296,7 +294,7 @@ def test_create_table__using_resource():
AttributeDefinitions=[{"AttributeName": "username", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table.name.should.equal("users")
assert table.name == "users"
def _create_user_table():
@ -319,9 +317,8 @@ def test_conditions():
table.put_item(Item={"username": "janedoe"})
response = table.query(KeyConditionExpression=Key("username").eq("johndoe"))
response["Count"].should.equal(1)
response["Items"].should.have.length_of(1)
response["Items"][0].should.equal({"username": "johndoe"})
assert response["Count"] == 1
assert response["Items"] == [{"username": "johndoe"}]
@mock_dynamodb
@ -333,7 +330,7 @@ def test_put_item_conditions_pass():
Expected={"foo": {"ComparisonOperator": "EQ", "AttributeValueList": ["bar"]}},
)
final_item = table.get_item(Key={"username": "johndoe"})
assert dict(final_item)["Item"]["foo"].should.equal("baz")
assert final_item["Item"]["foo"] == "baz"
@mock_dynamodb
@ -345,7 +342,7 @@ def test_put_item_conditions_pass_because_expect_not_exists_by_compare_to_null()
Expected={"whatever": {"ComparisonOperator": "NULL"}},
)
final_item = table.get_item(Key={"username": "johndoe"})
assert dict(final_item)["Item"]["foo"].should.equal("baz")
assert final_item["Item"]["foo"] == "baz"
@mock_dynamodb
@ -357,53 +354,67 @@ def test_put_item_conditions_pass_because_expect_exists_by_compare_to_not_null()
Expected={"foo": {"ComparisonOperator": "NOT_NULL"}},
)
final_item = table.get_item(Key={"username": "johndoe"})
assert dict(final_item)["Item"]["foo"].should.equal("baz")
assert final_item["Item"]["foo"] == "baz"
@mock_dynamodb
def test_put_item_conditions_fail():
table = _create_user_table()
table.put_item(Item={"username": "johndoe", "foo": "bar"})
table.put_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.put_item(
Item={"username": "johndoe", "foo": "baz"},
Expected={"foo": {"ComparisonOperator": "NE", "AttributeValueList": ["bar"]}},
).should.throw(botocore.client.ClientError)
Expected={
"foo": {"ComparisonOperator": "NE", "AttributeValueList": ["bar"]}
},
)
err = exc.value.response["Error"]
assert err["Code"] == "ConditionalCheckFailedException"
@mock_dynamodb
def test_update_item_conditions_fail():
table = _create_user_table()
table.put_item(Item={"username": "johndoe", "foo": "baz"})
table.update_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.update_item(
Key={"username": "johndoe"},
UpdateExpression="SET foo=:bar",
Expected={"foo": {"Value": "bar"}},
ExpressionAttributeValues={":bar": "bar"},
).should.throw(botocore.client.ClientError)
)
err = exc.value.response["Error"]
assert err["Code"] == "ConditionalCheckFailedException"
@mock_dynamodb
def test_update_item_conditions_fail_because_expect_not_exists():
table = _create_user_table()
table.put_item(Item={"username": "johndoe", "foo": "baz"})
table.update_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.update_item(
Key={"username": "johndoe"},
UpdateExpression="SET foo=:bar",
Expected={"foo": {"Exists": False}},
ExpressionAttributeValues={":bar": "bar"},
).should.throw(botocore.client.ClientError)
)
err = exc.value.response["Error"]
assert err["Code"] == "ConditionalCheckFailedException"
@mock_dynamodb
def test_update_item_conditions_fail_because_expect_not_exists_by_compare_to_null():
table = _create_user_table()
table.put_item(Item={"username": "johndoe", "foo": "baz"})
table.update_item.when.called_with(
with pytest.raises(ClientError) as exc:
table.update_item(
Key={"username": "johndoe"},
UpdateExpression="SET foo=:bar",
Expected={"foo": {"ComparisonOperator": "NULL"}},
ExpressionAttributeValues={":bar": "bar"},
).should.throw(botocore.client.ClientError)
)
err = exc.value.response["Error"]
assert err["Code"] == "ConditionalCheckFailedException"
@mock_dynamodb
@ -417,7 +428,7 @@ def test_update_item_conditions_pass():
ExpressionAttributeValues={":baz": "baz"},
)
returned_item = table.get_item(Key={"username": "johndoe"})
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
assert returned_item["Item"]["foo"] == "baz"
@mock_dynamodb
@ -431,7 +442,7 @@ def test_update_item_conditions_pass_because_expect_not_exists():
ExpressionAttributeValues={":baz": "baz"},
)
returned_item = table.get_item(Key={"username": "johndoe"})
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
assert returned_item["Item"]["foo"] == "baz"
@mock_dynamodb
@ -445,7 +456,7 @@ def test_update_item_conditions_pass_because_expect_not_exists_by_compare_to_nul
ExpressionAttributeValues={":baz": "baz"},
)
returned_item = table.get_item(Key={"username": "johndoe"})
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
assert returned_item["Item"]["foo"] == "baz"
@mock_dynamodb
@ -459,7 +470,7 @@ def test_update_item_conditions_pass_because_expect_exists_by_compare_to_not_nul
ExpressionAttributeValues={":baz": "baz"},
)
returned_item = table.get_item(Key={"username": "johndoe"})
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
assert returned_item["Item"]["foo"] == "baz"
@mock_dynamodb
@ -496,7 +507,7 @@ def test_update_settype_item_with_conditions():
},
)
returned_item = table.get_item(Key={"username": "johndoe"})
assert dict(returned_item)["Item"]["foo"].should.equal(set(["baz"]))
assert returned_item["Item"]["foo"] == set(["baz"])
@mock_dynamodb
@ -508,18 +519,17 @@ def test_scan_pagination():
table.put_item(Item={"username": u})
page1 = table.scan(Limit=6)
page1["Count"].should.equal(6)
page1["Items"].should.have.length_of(6)
page1.should.have.key("LastEvaluatedKey")
assert page1["Count"] == 6
assert len(page1["Items"]) == 6
page2 = table.scan(Limit=6, ExclusiveStartKey=page1["LastEvaluatedKey"])
page2["Count"].should.equal(4)
page2["Items"].should.have.length_of(4)
page2.should_not.have.key("LastEvaluatedKey")
assert page2["Count"] == 4
assert len(page2["Items"]) == 4
assert "LastEvaluatedKey" not in page2
results = page1["Items"] + page2["Items"]
usernames = set([r["username"] for r in results])
usernames.should.equal(set(expected_usernames))
assert usernames == set(expected_usernames)
@mock_dynamodb

View File

@ -1,5 +1,4 @@
import boto3
import sure # noqa # pylint: disable=unused-import
from moto import mock_dynamodb
@ -27,6 +26,7 @@ def test_update_different_map_elements_in_single_request():
ExpressionAttributeValues={":h": "H", ":w": "W"},
ReturnValues="ALL_NEW",
)
updated["Attributes"].should.equal(
{"id": "example_id", "d": {"hello": "H", "world": "W"}}
)
assert updated["Attributes"] == {
"id": "example_id",
"d": {"hello": "H", "world": "W"},
}

View File

@ -1,5 +1,4 @@
import boto3
import sure # noqa # pylint: disable=unused-import
from moto import mock_dynamodb
@ -27,10 +26,11 @@ def test_update_table__billing_mode():
)
actual = client.describe_table(TableName="test")["Table"]
actual.should.have.key("BillingModeSummary").equals({"BillingMode": "PROVISIONED"})
actual.should.have.key("ProvisionedThroughput").equals(
{"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}
)
assert actual["BillingModeSummary"] == {"BillingMode": "PROVISIONED"}
assert actual["ProvisionedThroughput"] == {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1,
}
@mock_dynamodb
@ -42,15 +42,15 @@ def test_update_table_throughput():
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
table.provisioned_throughput["WriteCapacityUnits"].should.equal(5)
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
assert table.provisioned_throughput["WriteCapacityUnits"] == 5
table.update(
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 6}
)
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
@mock_dynamodb

View File

@ -1,5 +1,4 @@
import json
import sure # noqa # pylint: disable=unused-import
from moto import mock_dynamodb
import moto.server as server
@ -13,22 +12,20 @@ def test_table_list():
backend = server.create_backend_app("dynamodb")
test_client = backend.test_client()
res = test_client.get("/")
res.status_code.should.equal(404)
assert res.status_code == 404
headers = {"X-Amz-Target": "TestTable.ListTables"}
res = test_client.get("/", headers=headers)
res.data.should.contain(b"TableNames")
res.headers.should.have.key("X-Amz-Crc32")
assert b"TableNames" in res.data
assert "X-Amz-Crc32" in res.headers
headers = {"X-Amz-Target": "DynamoDB_20120810.DescribeTable"}
res = test_client.post(
"/", headers=headers, data=json.dumps({"TableName": "test-table2"})
)
res.headers.should.have.key("X-Amzn-ErrorType").equals("ResourceNotFoundException")
assert res.headers["X-Amzn-ErrorType"] == "ResourceNotFoundException"
body = json.loads(res.data.decode("utf-8"))
body.should.equal(
{
assert body == {
"__type": "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",
"message": "Requested resource not found: Table: test-table2 not found",
}
)