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

View File

@ -1,5 +1,4 @@
import boto3 import boto3
import sure # noqa # pylint: disable=unused-import
import pytest import pytest
from moto import mock_dynamodb 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"}, "ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
}, },
) )
ex.value.response["Error"]["Code"].should.equal("ValidationException") assert ex.value.response["Error"]["Code"] == "ValidationException"
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048" # deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal( assert (
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes" 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") assert ex.value.response["Error"]["Code"] == "ValidationException"
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048" # deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal( assert (
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes" 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") assert ex.value.response["Error"]["Code"] == "ValidationException"
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
ex.value.response["Error"]["Message"].should.equal( assert (
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes" 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") assert ex.value.response["Error"]["Code"] == "ValidationException"
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
ex.value.response["Error"]["Message"].should.equal( assert (
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes" 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"}}, ExpressionAttributeValues={":New": {"S": "hello"}},
) )
ex.value.response["Error"]["Code"].should.equal("ValidationException") assert ex.value.response["Error"]["Code"] == "ValidationException"
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048" # deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal( assert (
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes" 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"}}, ExpressionAttributeValues={":New": {"S": "hello"}},
) )
ex.value.response["Error"]["Code"].should.equal("ValidationException") assert ex.value.response["Error"]["Code"] == "ValidationException"
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# deliberately no space between "of" and "2048" # deliberately no space between "of" and "2048"
ex.value.response["Error"]["Message"].should.equal( assert (
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes" 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, TableName=name,
Key={"forum_name": {"S": ""}}, Key={"forum_name": {"S": ""}},
) )
ex.value.response["Error"]["Code"].should.equal("ValidationException") assert ex.value.response["Error"]["Code"] == "ValidationException"
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
ex.value.response["Error"]["Message"].should.equal( assert (
"One or more parameter values are not valid. The AttributeValue for a key attribute " ex.value.response["Error"]["Message"]
"cannot contain an empty string value. Key: forum_name" == "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, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
desired_hash_key.should.equal(eav[":id"]) assert desired_hash_key == eav[":id"]
comparison.should.equal(None) assert comparison is None
range_values.should.equal([]) assert range_values == []
def test_unknown_hash_key(self): def test_unknown_hash_key(self):
kce = "wrongName = :id" kce = "wrongName = :id"
@ -29,9 +29,7 @@ class TestHashKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
exc.value.message.should.equal( assert exc.value.message == "Query condition missed key schema element: job_id"
"Query condition missed key schema element: job_id"
)
def test_unknown_hash_value(self): def test_unknown_hash_value(self):
# TODO: is this correct? I'd assume that this should throw an error instead # TODO: is this correct? I'd assume that this should throw an error instead
@ -44,9 +42,9 @@ class TestHashKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
desired_hash_key.should.equal({"S": ":unknown"}) assert desired_hash_key == {"S": ":unknown"}
comparison.should.equal(None) assert comparison is None
range_values.should.equal([]) assert range_values == []
class TestHashAndRangeKey: class TestHashAndRangeKey:
@ -65,9 +63,7 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
exc.value.message.should.equal( assert exc.value.message == "Query condition missed key schema element: job_id"
"Query condition missed key schema element: job_id"
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"expr", "expr",
@ -86,8 +82,8 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
exc.value.message.should.equal( assert (
"Query condition missed key schema element: start_date" exc.value.message == "Query condition missed key schema element: start_date"
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -108,9 +104,9 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
desired_hash_key.should.equal("pk") assert desired_hash_key == "pk"
comparison.should.equal("BEGINS_WITH") assert comparison == "BEGINS_WITH"
range_values.should.equal(["19"]) assert range_values == ["19"]
@pytest.mark.parametrize("fn", ["Begins_with", "Begins_With", "BEGINS_WITH"]) @pytest.mark.parametrize("fn", ["Begins_with", "Begins_With", "BEGINS_WITH"])
def test_begin_with__wrong_case(self, fn): def test_begin_with__wrong_case(self, fn):
@ -122,8 +118,9 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
exc.value.message.should.equal( assert (
f"Invalid KeyConditionExpression: Invalid function name; function: {fn}" exc.value.message
== f"Invalid KeyConditionExpression: Invalid function name; function: {fn}"
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -142,9 +139,9 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
desired_hash_key.should.equal("pk") assert desired_hash_key == "pk"
comparison.should.equal("BETWEEN") assert comparison == "BETWEEN"
range_values.should.equal(["19", "21"]) assert range_values == ["19", "21"]
@pytest.mark.parametrize("operator", [" < ", " <=", "= ", ">", ">="]) @pytest.mark.parametrize("operator", [" < ", " <=", "= ", ">", ">="])
def test_numeric_comparisons(self, operator): def test_numeric_comparisons(self, operator):
@ -156,9 +153,9 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
desired_hash_key.should.equal("pk") assert desired_hash_key == "pk"
comparison.should.equal(operator.strip()) assert comparison == operator.strip()
range_values.should.equal(["19"]) assert range_values == ["19"]
@pytest.mark.parametrize( @pytest.mark.parametrize(
"expr", "expr",
@ -177,7 +174,7 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
desired_hash_key.should.equal("pk") assert desired_hash_key == "pk"
@pytest.mark.parametrize( @pytest.mark.parametrize(
"expr", "expr",
@ -195,7 +192,7 @@ class TestHashAndRangeKey:
schema=self.schema, schema=self.schema,
expression_attribute_names=dict(), expression_attribute_names=dict(),
) )
desired_hash_key.should.equal("pk") assert desired_hash_key == "pk"
class TestNamesAndValues: class TestNamesAndValues:
@ -211,6 +208,6 @@ class TestNamesAndValues:
schema=self.schema, schema=self.schema,
expression_attribute_names=ean, expression_attribute_names=ean,
) )
desired_hash_key.should.equal(eav[":id"]) assert desired_hash_key == eav[":id"]
comparison.should.equal(None) assert comparison is None
range_values.should.equal([]) assert range_values == []

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -3,7 +3,6 @@ import re
import boto3 import boto3
import pytest import pytest
import sure # noqa # pylint: disable=unused-import
from moto import mock_dynamodb 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 = table.get_item(Key={"id": "key-0"})["Item"]
item.should.equal({"id": "key-0", "first": {}}) assert item == {"id": "key-0", "first": {}}
@mock_dynamodb @mock_dynamodb
@ -232,8 +231,8 @@ def test_condition_expressions():
def _assert_conditional_check_failed_exception(exc): def _assert_conditional_check_failed_exception(exc):
err = exc.value.response["Error"] err = exc.value.response["Error"]
err["Code"].should.equal("ConditionalCheckFailedException") assert err["Code"] == "ConditionalCheckFailedException"
err["Message"].should.equal("The conditional request failed") assert err["Message"] == "The conditional request failed"
@mock_dynamodb @mock_dynamodb
@ -274,9 +273,7 @@ def update_numerical_con_expr(key, con_expr, res, table):
ExpressionAttributeValues={":zero": 0, ":one": 1}, ExpressionAttributeValues={":zero": 0, ":one": 1},
ConditionExpression=con_expr, ConditionExpression=con_expr,
) )
table.get_item(Key={"partitionKey": key})["Item"]["myAttr"].should.equal( assert table.get_item(Key={"partitionKey": key})["Item"]["myAttr"] == Decimal(res)
Decimal(res)
)
@mock_dynamodb @mock_dynamodb
@ -404,7 +401,7 @@ def test_condition_expression_with_reserved_keyword_as_attr_name():
# table is unchanged # table is unchanged
item = table.get_item(Key={"id": "key-0"})["Item"] item = table.get_item(Key={"id": "key-0"})["Item"]
item.should.equal(record) assert item == record
# using attribute names solves the issue # using attribute names solves the issue
table.update_item( 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 = 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 boto3
import pytest import pytest
import sure # noqa # pylint: disable=unused-import
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
from moto import mock_dynamodb from moto import mock_dynamodb
@ -24,9 +23,10 @@ def test_error_on_wrong_value_for_consumed_capacity():
with pytest.raises(ClientError) as ex: with pytest.raises(ClientError) as ex:
table.put_item(Item=item, ReturnConsumedCapacity="Garbage") table.put_item(Item=item, ReturnConsumedCapacity="Garbage")
err = ex.value.response["Error"] err = ex.value.response["Error"]
err["Code"].should.equal("ValidationException") assert err["Code"] == "ValidationException"
err["Message"].should.equal( assert (
"1 validation error detected: Value 'Garbage' at 'returnConsumedCapacity' failed to satisfy constraint: Member must satisfy enum value set: [INDEXES, TOTAL, NONE]" 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 # Should still return ConsumedCapacity, even if it does not return an item
response.should.have.key("ConsumedCapacity") assert response["ConsumedCapacity"] == {
response["ConsumedCapacity"].should.equal( "TableName": "test_table",
{"TableName": "test_table", "CapacityUnits": 0.5} "CapacityUnits": 0.5,
) }
@mock_dynamodb @mock_dynamodb
@ -135,15 +135,14 @@ def validate_response(
response, should_have_capacity, should_have_table, is_index=False, value=1.0 response, should_have_capacity, should_have_table, is_index=False, value=1.0
): ):
if should_have_capacity: if should_have_capacity:
response.should.have.key("ConsumedCapacity") capacity = response["ConsumedCapacity"]
response["ConsumedCapacity"]["TableName"].should.equal("jobs") assert capacity["TableName"] == "jobs"
response["ConsumedCapacity"]["CapacityUnits"].should.equal(value) assert capacity["CapacityUnits"] == value
if should_have_table: if should_have_table:
response["ConsumedCapacity"]["Table"].should.equal({"CapacityUnits": value}) assert capacity["Table"] == {"CapacityUnits": value}
if is_index: if is_index:
response["ConsumedCapacity"].should.have.key("LocalSecondaryIndexes") assert capacity["LocalSecondaryIndexes"] == {
response["ConsumedCapacity"]["LocalSecondaryIndexes"].should.equal( "job_name-index": {"CapacityUnits": value}
{"job_name-index": {"CapacityUnits": value}} }
)
else: else:
response.shouldnt.have.key("ConsumedCapacity") assert "ConsumedCapacity" not in response

View File

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

View File

@ -454,12 +454,12 @@ def test_normalize_with_one_action(table):
item=item, item=item,
table=table, table=table,
).validate() ).validate()
validated_ast.children.should.have.length_of(1) assert len(validated_ast.children) == 1
validated_ast.children[0].should.be.a(UpdateExpressionAddClause) assert isinstance(validated_ast.children[0], UpdateExpressionAddClause)
validated_ast.normalize() validated_ast.normalize()
validated_ast.children.should.have.length_of(1) assert len(validated_ast.children) == 1
validated_ast.children[0].should.be.a(UpdateExpressionAddAction) assert isinstance(validated_ast.children[0], UpdateExpressionAddAction)
def test_normalize_with_multiple_actions__order_is_preserved(table): 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, item=item,
table=table, table=table,
).validate() ).validate()
validated_ast.children.should.have.length_of(2) assert len(validated_ast.children) == 2
# add clause first # add clause first
validated_ast.children[0].should.be.a(UpdateExpressionAddClause) assert isinstance(validated_ast.children[0], UpdateExpressionAddClause)
# rest of the expression next # 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.normalize()
validated_ast.children.should.have.length_of(5) assert len(validated_ast.children) == 5
# add action first # add action first
validated_ast.children[0].should.be.a(UpdateExpressionAddAction) assert isinstance(validated_ast.children[0], UpdateExpressionAddAction)
# Removal actions in reverse order # Removal actions in reverse order
validated_ast.children[1].should.be.a(UpdateExpressionRemoveAction) assert isinstance(validated_ast.children[1], UpdateExpressionRemoveAction)
validated_ast.children[1]._get_value().should.equal(3) assert validated_ast.children[1]._get_value() == 3
validated_ast.children[2].should.be.a(UpdateExpressionRemoveAction) assert isinstance(validated_ast.children[2], UpdateExpressionRemoveAction)
validated_ast.children[2]._get_value().should.equal(2) assert validated_ast.children[2]._get_value() == 2
validated_ast.children[3].should.be.a(UpdateExpressionRemoveAction) assert isinstance(validated_ast.children[3], UpdateExpressionRemoveAction)
validated_ast.children[3]._get_value().should.equal(1) assert validated_ast.children[3]._get_value() == 1
# Set action last, as per insertion order # 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 ( from moto.dynamodb.exceptions import (
InvalidTokenException, InvalidTokenException,
InvalidExpressionAttributeNameKey, 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(): def test_expression_tokenizer_leading_underscore_in_attribute_name_expression():
"""Leading underscore is not allowed for an attribute name""" """Leading underscore is not allowed for an attribute name"""
set_action = "SET attrName = _idid" set_action = "SET attrName = _idid"
try: with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action) ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly" assert te.value.token == "_"
except InvalidTokenException as te: assert te.value.near == "= _idid"
assert te.token == "_"
assert te.near == "= _idid"
def test_expression_tokenizer_leading_underscore_in_attribute_value_expression(): 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" ExpressionAttributeNames contains invalid key: Syntax error; key: "#va#l2"
""" """
set_action = "SET #va#l2 = 3" set_action = "SET #va#l2 = 3"
try: with pytest.raises(InvalidExpressionAttributeNameKey) as e:
ExpressionTokenizer.make_list(set_action) ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly" assert e.value.key == "#va#l2"
except InvalidExpressionAttributeNameKey as e:
assert e.key == "#va#l2"
def test_expression_tokenizer_single_set_action_attribute_name_invalid_key_double_hash(): 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" ExpressionAttributeNames contains invalid key: Syntax error; key: "#va#l"
""" """
set_action = "SET #va#l = 3" set_action = "SET #va#l = 3"
try: with pytest.raises(InvalidExpressionAttributeNameKey) as e:
ExpressionTokenizer.make_list(set_action) ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly" assert e.value.key == "#va#l"
except InvalidExpressionAttributeNameKey as e:
assert e.key == "#va#l"
def test_expression_tokenizer_single_set_action_attribute_name_valid_key(): 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(): def test_expression_tokenizer_just_a_pipe():
set_action = "|" set_action = "|"
try: with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action) ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly" assert te.value.token == "|"
except InvalidTokenException as te: assert te.value.near == "|"
assert te.token == "|"
assert te.near == "|"
def test_expression_tokenizer_just_a_pipe_with_leading_white_spaces(): def test_expression_tokenizer_just_a_pipe_with_leading_white_spaces():
set_action = " |" set_action = " |"
try: with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action) ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly" assert te.value.token == "|"
except InvalidTokenException as te: assert te.value.near == " |"
assert te.token == "|"
assert te.near == " |"
def test_expression_tokenizer_just_a_pipe_for_set_expression(): def test_expression_tokenizer_just_a_pipe_for_set_expression():
set_action = "SET|" set_action = "SET|"
try: with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action) ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly" assert te.value.token == "|"
except InvalidTokenException as te: assert te.value.near == "SET|"
assert te.token == "|"
assert te.near == "SET|"
def test_expression_tokenizer_just_an_attribute_and_a_pipe_for_set_expression(): def test_expression_tokenizer_just_an_attribute_and_a_pipe_for_set_expression():
set_action = "SET a|" set_action = "SET a|"
try: with pytest.raises(InvalidTokenException) as te:
ExpressionTokenizer.make_list(set_action) ExpressionTokenizer.make_list(set_action)
assert False, "Exception not raised correctly" assert te.value.token == "|"
except InvalidTokenException as te: assert te.value.near == "a|"
assert te.token == "|"
assert te.near == "a|"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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