Techdebt: Replace sure with regular assertions in DynamoDB (#6517)
This commit is contained in:
parent
9323747ebb
commit
c6b46a48f4
@ -1,7 +1,6 @@
|
||||
import boto3
|
||||
import botocore
|
||||
import pytest
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
from boto3.dynamodb.conditions import Key
|
||||
from botocore.exceptions import ClientError
|
||||
from unittest import SkipTest
|
||||
@ -51,10 +50,8 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
|
||||
IndexName="GSI-K1",
|
||||
)["Items"]
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Query condition missed key schema element: gsiK1SortKey"
|
||||
)
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "Query condition missed key schema element: gsiK1SortKey"
|
||||
|
||||
# check using wrong name for partition key throws exception
|
||||
with pytest.raises(ClientError) as exc:
|
||||
@ -64,9 +61,9 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
|
||||
IndexName="GSI-K1",
|
||||
)["Items"]
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Query condition missed key schema element: gsiK1PartitionKey"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"] == "Query condition missed key schema element: gsiK1PartitionKey"
|
||||
)
|
||||
|
||||
# verify same behaviour for begins_with
|
||||
@ -77,10 +74,8 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
|
||||
IndexName="GSI-K1",
|
||||
)["Items"]
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Query condition missed key schema element: gsiK1SortKey"
|
||||
)
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "Query condition missed key schema element: gsiK1SortKey"
|
||||
|
||||
# verify same behaviour for between
|
||||
with pytest.raises(ClientError) as exc:
|
||||
@ -94,10 +89,8 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
|
||||
IndexName="GSI-K1",
|
||||
)["Items"]
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Query condition missed key schema element: gsiK1SortKey"
|
||||
)
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "Query condition missed key schema element: gsiK1SortKey"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -121,10 +114,8 @@ def test_query_table_with_wrong_key_attribute_names_throws_exception():
|
||||
ExpressionAttributeValues={":pk": "pk"},
|
||||
)["Items"]
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Query condition missed key schema element: partitionKey"
|
||||
)
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "Query condition missed key schema element: partitionKey"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -137,9 +128,10 @@ def test_empty_expressionattributenames():
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.get_item(Key={"id": "my_id"}, ExpressionAttributeNames={})
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"ExpressionAttributeNames can only be specified when using expressions"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "ExpressionAttributeNames can only be specified when using expressions"
|
||||
)
|
||||
|
||||
|
||||
@ -155,8 +147,8 @@ def test_empty_expressionattributenames_with_empty_projection():
|
||||
Key={"id": "my_id"}, ProjectionExpression="", ExpressionAttributeNames={}
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal("ExpressionAttributeNames must not be empty")
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "ExpressionAttributeNames must not be empty"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -171,8 +163,8 @@ def test_empty_expressionattributenames_with_projection():
|
||||
Key={"id": "my_id"}, ProjectionExpression="id", ExpressionAttributeNames={}
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal("ExpressionAttributeNames must not be empty")
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "ExpressionAttributeNames must not be empty"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -191,9 +183,10 @@ def test_update_item_range_key_set():
|
||||
ExpressionAttributeValues={":one": 1, ":a": "lore ipsum"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
'Invalid UpdateExpression: The "ADD" section can only be used once in an update expression;'
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== 'Invalid UpdateExpression: The "ADD" section can only be used once in an update expression;'
|
||||
)
|
||||
|
||||
|
||||
@ -204,8 +197,8 @@ def test_batch_get_item_non_existing_table():
|
||||
with pytest.raises(client.exceptions.ResourceNotFoundException) as exc:
|
||||
client.batch_get_item(RequestItems={"my-table": {"Keys": [{"id": {"N": "0"}}]}})
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"].should.equal("ResourceNotFoundException")
|
||||
assert err["Message"].should.equal("Requested resource not found")
|
||||
assert err["Code"] == "ResourceNotFoundException"
|
||||
assert err["Message"] == "Requested resource not found"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -218,8 +211,8 @@ def test_batch_write_item_non_existing_table():
|
||||
RequestItems={"my-table": [{"PutRequest": {"Item": {}}}]}
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"].should.equal("ResourceNotFoundException")
|
||||
assert err["Message"].should.equal("Requested resource not found")
|
||||
assert err["Code"] == "ResourceNotFoundException"
|
||||
assert err["Message"] == "Requested resource not found"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -238,9 +231,10 @@ def test_create_table_with_redundant_attributes():
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Number of attributes in KeySchema does not exactly match number of attributes defined in AttributeDefinitions"
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
@ -263,9 +257,10 @@ def test_create_table_with_redundant_attributes():
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Some AttributeDefinitions are not used. AttributeDefinitions: [created_at, id, user], keys used: [id, user]"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Some AttributeDefinitions are not used. AttributeDefinitions: [created_at, id, user], keys used: [id, user]"
|
||||
)
|
||||
|
||||
|
||||
@ -285,9 +280,10 @@ def test_create_table_with_missing_attributes():
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Invalid KeySchema: Some index key attribute have no definition"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "Invalid KeySchema: Some index key attribute have no definition"
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
@ -306,9 +302,10 @@ def test_create_table_with_missing_attributes():
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [id]"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [id]"
|
||||
)
|
||||
|
||||
|
||||
@ -326,9 +323,10 @@ def test_create_table_with_redundant_and_missing_attributes():
|
||||
BillingMode="PAY_PER_REQUEST",
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [id], AttributeDefinitions: [created_at]"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [id], AttributeDefinitions: [created_at]"
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
@ -349,9 +347,10 @@ def test_create_table_with_redundant_and_missing_attributes():
|
||||
BillingMode="PAY_PER_REQUEST",
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [created_at, id]"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. Keys: [user], AttributeDefinitions: [created_at, id]"
|
||||
)
|
||||
|
||||
|
||||
@ -381,9 +380,10 @@ def test_put_item_wrong_attribute_type():
|
||||
with pytest.raises(ClientError) as exc:
|
||||
dynamodb.put_item(TableName="test-table", Item=item)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Type mismatch for key id expected: S actual: N"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Type mismatch for key id expected: S actual: N"
|
||||
)
|
||||
|
||||
item = {
|
||||
@ -395,9 +395,10 @@ def test_put_item_wrong_attribute_type():
|
||||
with pytest.raises(ClientError) as exc:
|
||||
dynamodb.put_item(TableName="test-table", Item=item)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Type mismatch for key created_at expected: N actual: S"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Type mismatch for key created_at expected: N actual: S"
|
||||
)
|
||||
|
||||
|
||||
@ -425,10 +426,8 @@ def test_hash_key_cannot_use_begins_with_operations():
|
||||
table = dynamodb.Table("test-table")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
table.query(KeyConditionExpression=Key("key").begins_with("prefix-"))
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Query key condition not supported"
|
||||
)
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["Error"]["Message"] == "Query key condition not supported"
|
||||
|
||||
|
||||
# Test this again, but with manually supplying an operator
|
||||
@ -450,8 +449,8 @@ def test_hash_key_can_only_use_equals_operations(operator):
|
||||
ExpressionAttributeValues={":pk": "p"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal("Query key condition not supported")
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "Query key condition not supported"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -467,9 +466,10 @@ def test_creating_table_with_0_local_indexes():
|
||||
LocalSecondaryIndexes=[],
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: List of LocalSecondaryIndexes is empty"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: List of LocalSecondaryIndexes is empty"
|
||||
)
|
||||
|
||||
|
||||
@ -486,9 +486,10 @@ def test_creating_table_with_0_global_indexes():
|
||||
GlobalSecondaryIndexes=[],
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: List of GlobalSecondaryIndexes is empty"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: List of GlobalSecondaryIndexes is empty"
|
||||
)
|
||||
|
||||
|
||||
@ -524,9 +525,10 @@ def test_multiple_transactions_on_same_item():
|
||||
]
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Transaction request cannot include multiple operations on one item"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "Transaction request cannot include multiple operations on one item"
|
||||
)
|
||||
|
||||
|
||||
@ -558,10 +560,10 @@ def test_transact_write_items__too_many_transactions():
|
||||
]
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.match(
|
||||
"1 validation error detected at 'transactItems' failed to satisfy constraint: "
|
||||
"Member must have length less than or equal to 100."
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "1 validation error detected at 'transactItems' failed to satisfy constraint: Member must have length less than or equal to 100."
|
||||
)
|
||||
|
||||
|
||||
@ -576,8 +578,8 @@ def test_update_item_non_existent_table():
|
||||
ExpressionAttributeValues={":Body": {"S": ""}},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"].should.equal("ResourceNotFoundException")
|
||||
assert err["Message"].should.equal("Requested resource not found")
|
||||
assert err["Code"] == "ResourceNotFoundException"
|
||||
assert err["Message"] == "Requested resource not found"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -609,14 +611,15 @@ def test_update_item_with_duplicate_expressions(expression):
|
||||
ExpressionAttributeValues={":example_column": "test"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"Invalid UpdateExpression: Two document paths overlap with each other; must remove or rewrite one of these paths; path one: [example_column], path two: [example_column]"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "Invalid UpdateExpression: Two document paths overlap with each other; must remove or rewrite one of these paths; path one: [example_column], path two: [example_column]"
|
||||
)
|
||||
|
||||
# The item is not updated
|
||||
item = table.get_item(Key={"pk": "example_id"})["Item"]
|
||||
item.should.equal({"pk": "example_id", "example_column": "example"})
|
||||
assert item == {"pk": "example_id", "example_column": "example"}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -635,8 +638,8 @@ def test_put_item_wrong_datatype():
|
||||
with pytest.raises(ClientError) as exc:
|
||||
client.put_item(TableName="test2", Item={"mykey": {"N": 123}})
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("SerializationException")
|
||||
err["Message"].should.equal("NUMBER_VALUE cannot be converted to String")
|
||||
assert err["Code"] == "SerializationException"
|
||||
assert err["Message"] == "NUMBER_VALUE cannot be converted to String"
|
||||
|
||||
# Same thing - but with a non-key, and nested
|
||||
with pytest.raises(ClientError) as exc:
|
||||
@ -645,8 +648,8 @@ def test_put_item_wrong_datatype():
|
||||
Item={"mykey": {"N": "123"}, "nested": {"M": {"sth": {"N": 5}}}},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("SerializationException")
|
||||
err["Message"].should.equal("NUMBER_VALUE cannot be converted to String")
|
||||
assert err["Code"] == "SerializationException"
|
||||
assert err["Message"] == "NUMBER_VALUE cannot be converted to String"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -663,9 +666,10 @@ def test_put_item_empty_set():
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.put_item(Item={"Key": "some-irrelevant_key", "attr2": {"SS": set([])}})
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: An number set may not be empty"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: An number set may not be empty"
|
||||
)
|
||||
|
||||
|
||||
@ -689,9 +693,10 @@ def test_update_expression_with_trailing_comma():
|
||||
ExpressionAttributeValues={":val1": 3, ":val2": 4},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
'Invalid UpdateExpression: Syntax error; token: "<EOF>", near: ","'
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== 'Invalid UpdateExpression: Syntax error; token: "<EOF>", near: ","'
|
||||
)
|
||||
|
||||
|
||||
@ -717,20 +722,22 @@ def test_batch_put_item_with_empty_value():
|
||||
with table.batch_writer() as batch:
|
||||
batch.put_item(Item={"pk": "", "sk": "sth"})
|
||||
err = exc.value.response["Error"]
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: pk"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: pk"
|
||||
)
|
||||
err["Code"].should.equal("ValidationException")
|
||||
assert err["Code"] == "ValidationException"
|
||||
|
||||
# Empty SortKey throws an error
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
with table.batch_writer() as batch:
|
||||
batch.put_item(Item={"pk": "sth", "sk": ""})
|
||||
err = exc.value.response["Error"]
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: sk"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: sk"
|
||||
)
|
||||
err["Code"].should.equal("ValidationException")
|
||||
assert err["Code"] == "ValidationException"
|
||||
|
||||
# Empty regular parameter workst just fine though
|
||||
with table.batch_writer() as batch:
|
||||
@ -759,10 +766,8 @@ def test_query_begins_with_without_brackets():
|
||||
ExpressionAttributeValues={":pk": {"S": "test1"}, ":sk": {"S": "test2"}},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Message"].should.equal(
|
||||
'Invalid KeyConditionExpression: Syntax error; token: "sk"'
|
||||
)
|
||||
err["Code"].should.equal("ValidationException")
|
||||
assert err["Message"] == 'Invalid KeyConditionExpression: Syntax error; token: "sk"'
|
||||
assert err["Code"] == "ValidationException"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -834,9 +839,10 @@ def test_transact_write_items_with_empty_gsi_key():
|
||||
with pytest.raises(ClientError) as exc:
|
||||
client.transact_write_items(TransactItems=transact_items)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values are not valid. A value specified for a secondary index key is not supported. The AttributeValue for a key attribute cannot contain an empty string value. IndexName: gsi_index, IndexKey: unique_id"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values are not valid. A value specified for a secondary index key is not supported. The AttributeValue for a key attribute cannot contain an empty string value. IndexName: gsi_index, IndexKey: unique_id"
|
||||
)
|
||||
|
||||
|
||||
@ -869,14 +875,14 @@ def test_update_primary_key_with_sortkey():
|
||||
ExpressionAttributeValues={":val1": "different"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
|
||||
)
|
||||
|
||||
table.get_item(Key={"pk": "testchangepk", "sk": "else"})["Item"].should.equal(
|
||||
{"pk": "testchangepk", "sk": "else"}
|
||||
)
|
||||
item = table.get_item(Key={"pk": "testchangepk", "sk": "else"})["Item"]
|
||||
assert item == {"pk": "testchangepk", "sk": "else"}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -902,14 +908,14 @@ def test_update_primary_key():
|
||||
ExpressionAttributeValues={":val1": "different"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Cannot update attribute pk. This attribute is part of the key"
|
||||
)
|
||||
|
||||
table.get_item(Key={"pk": "testchangepk"})["Item"].should.equal(
|
||||
{"pk": "testchangepk"}
|
||||
)
|
||||
item = table.get_item(Key={"pk": "testchangepk"})["Item"]
|
||||
assert item == {"pk": "testchangepk"}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -928,15 +934,15 @@ def test_put_item__string_as_integer_value():
|
||||
with pytest.raises(ClientError) as exc:
|
||||
client.put_item(TableName="without_sk", Item={"pk": {"S": 123}})
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("SerializationException")
|
||||
err["Message"].should.equal("NUMBER_VALUE cannot be converted to String")
|
||||
assert err["Code"] == "SerializationException"
|
||||
assert err["Message"] == "NUMBER_VALUE cannot be converted to String"
|
||||
|
||||
# A primary key cannot be of type S, but then point to a dictionary
|
||||
with pytest.raises(ClientError) as exc:
|
||||
client.put_item(TableName="without_sk", Item={"pk": {"S": {"S": "asdf"}}})
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("SerializationException")
|
||||
err["Message"].should.equal("Start of structure or map found where not expected")
|
||||
assert err["Code"] == "SerializationException"
|
||||
assert err["Message"] == "Start of structure or map found where not expected"
|
||||
|
||||
# Note that a normal attribute name can be an 'S', which follows the same pattern
|
||||
# Nested 'S'-s like this are allowed for non-key attributes
|
||||
@ -981,9 +987,10 @@ def test_gsi_key_cannot_be_empty():
|
||||
},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Type mismatch for Index Key hello Expected: S Actual: NULL IndexName: hello-index"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: Type mismatch for Index Key hello Expected: S Actual: NULL IndexName: hello-index"
|
||||
)
|
||||
|
||||
|
||||
@ -1013,9 +1020,10 @@ def test_list_append_errors_for_unknown_attribute_value():
|
||||
ReturnValues="UPDATED_NEW",
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"The provided expression refers to an attribute that does not exist in the item"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "The provided expression refers to an attribute that does not exist in the item"
|
||||
)
|
||||
|
||||
# append to unknown list via ExpressionAttributeNames
|
||||
@ -1029,9 +1037,10 @@ def test_list_append_errors_for_unknown_attribute_value():
|
||||
ReturnValues="UPDATED_NEW",
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"The provided expression refers to an attribute that does not exist in the item"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "The provided expression refers to an attribute that does not exist in the item"
|
||||
)
|
||||
|
||||
# append to unknown list, even though end result is known
|
||||
@ -1044,9 +1053,10 @@ def test_list_append_errors_for_unknown_attribute_value():
|
||||
ReturnValues="UPDATED_NEW",
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"The provided expression refers to an attribute that does not exist in the item"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "The provided expression refers to an attribute that does not exist in the item"
|
||||
)
|
||||
|
||||
# We can append to a known list, into an unknown/new list
|
||||
|
@ -1,5 +1,4 @@
|
||||
import boto3
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
import pytest
|
||||
|
||||
from moto import mock_dynamodb
|
||||
@ -40,11 +39,12 @@ def test_item_add_long_string_hash_key_exception():
|
||||
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
|
||||
},
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
# deliberately no space between "of" and "2048"
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
|
||||
)
|
||||
|
||||
|
||||
@ -87,11 +87,12 @@ def test_item_add_long_string_nonascii_hash_key_exception():
|
||||
},
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
# deliberately no space between "of" and "2048"
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
|
||||
)
|
||||
|
||||
|
||||
@ -135,10 +136,11 @@ def test_item_add_long_string_range_key_exception():
|
||||
},
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
|
||||
)
|
||||
|
||||
|
||||
@ -204,10 +206,11 @@ def test_put_long_string_gsi_range_key_exception():
|
||||
},
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
|
||||
)
|
||||
|
||||
|
||||
@ -243,11 +246,12 @@ def test_update_item_with_long_string_hash_key_exception():
|
||||
ExpressionAttributeValues={":New": {"S": "hello"}},
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
# deliberately no space between "of" and "2048"
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes"
|
||||
)
|
||||
|
||||
|
||||
@ -288,11 +292,12 @@ def test_update_item_with_long_string_range_key_exception():
|
||||
ExpressionAttributeValues={":New": {"S": "hello"}},
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
# deliberately no space between "of" and "2048"
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes"
|
||||
)
|
||||
|
||||
|
||||
@ -312,9 +317,9 @@ def test_item_add_empty_key_exception():
|
||||
TableName=name,
|
||||
Key={"forum_name": {"S": ""}},
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"One or more parameter values are not valid. The AttributeValue for a key attribute "
|
||||
"cannot contain an empty string value. Key: forum_name"
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: forum_name"
|
||||
)
|
||||
|
@ -15,9 +15,9 @@ class TestHashKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
desired_hash_key.should.equal(eav[":id"])
|
||||
comparison.should.equal(None)
|
||||
range_values.should.equal([])
|
||||
assert desired_hash_key == eav[":id"]
|
||||
assert comparison is None
|
||||
assert range_values == []
|
||||
|
||||
def test_unknown_hash_key(self):
|
||||
kce = "wrongName = :id"
|
||||
@ -29,9 +29,7 @@ class TestHashKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
exc.value.message.should.equal(
|
||||
"Query condition missed key schema element: job_id"
|
||||
)
|
||||
assert exc.value.message == "Query condition missed key schema element: job_id"
|
||||
|
||||
def test_unknown_hash_value(self):
|
||||
# TODO: is this correct? I'd assume that this should throw an error instead
|
||||
@ -44,9 +42,9 @@ class TestHashKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
desired_hash_key.should.equal({"S": ":unknown"})
|
||||
comparison.should.equal(None)
|
||||
range_values.should.equal([])
|
||||
assert desired_hash_key == {"S": ":unknown"}
|
||||
assert comparison is None
|
||||
assert range_values == []
|
||||
|
||||
|
||||
class TestHashAndRangeKey:
|
||||
@ -65,9 +63,7 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
exc.value.message.should.equal(
|
||||
"Query condition missed key schema element: job_id"
|
||||
)
|
||||
assert exc.value.message == "Query condition missed key schema element: job_id"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"expr",
|
||||
@ -86,8 +82,8 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
exc.value.message.should.equal(
|
||||
"Query condition missed key schema element: start_date"
|
||||
assert (
|
||||
exc.value.message == "Query condition missed key schema element: start_date"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -108,9 +104,9 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
desired_hash_key.should.equal("pk")
|
||||
comparison.should.equal("BEGINS_WITH")
|
||||
range_values.should.equal(["19"])
|
||||
assert desired_hash_key == "pk"
|
||||
assert comparison == "BEGINS_WITH"
|
||||
assert range_values == ["19"]
|
||||
|
||||
@pytest.mark.parametrize("fn", ["Begins_with", "Begins_With", "BEGINS_WITH"])
|
||||
def test_begin_with__wrong_case(self, fn):
|
||||
@ -122,8 +118,9 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
exc.value.message.should.equal(
|
||||
f"Invalid KeyConditionExpression: Invalid function name; function: {fn}"
|
||||
assert (
|
||||
exc.value.message
|
||||
== f"Invalid KeyConditionExpression: Invalid function name; function: {fn}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -142,9 +139,9 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
desired_hash_key.should.equal("pk")
|
||||
comparison.should.equal("BETWEEN")
|
||||
range_values.should.equal(["19", "21"])
|
||||
assert desired_hash_key == "pk"
|
||||
assert comparison == "BETWEEN"
|
||||
assert range_values == ["19", "21"]
|
||||
|
||||
@pytest.mark.parametrize("operator", [" < ", " <=", "= ", ">", ">="])
|
||||
def test_numeric_comparisons(self, operator):
|
||||
@ -156,9 +153,9 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
desired_hash_key.should.equal("pk")
|
||||
comparison.should.equal(operator.strip())
|
||||
range_values.should.equal(["19"])
|
||||
assert desired_hash_key == "pk"
|
||||
assert comparison == operator.strip()
|
||||
assert range_values == ["19"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"expr",
|
||||
@ -177,7 +174,7 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
desired_hash_key.should.equal("pk")
|
||||
assert desired_hash_key == "pk"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"expr",
|
||||
@ -195,7 +192,7 @@ class TestHashAndRangeKey:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=dict(),
|
||||
)
|
||||
desired_hash_key.should.equal("pk")
|
||||
assert desired_hash_key == "pk"
|
||||
|
||||
|
||||
class TestNamesAndValues:
|
||||
@ -211,6 +208,6 @@ class TestNamesAndValues:
|
||||
schema=self.schema,
|
||||
expression_attribute_names=ean,
|
||||
)
|
||||
desired_hash_key.should.equal(eav[":id"])
|
||||
comparison.should.equal(None)
|
||||
range_values.should.equal([])
|
||||
assert desired_hash_key == eav[":id"]
|
||||
assert comparison is None
|
||||
assert range_values == []
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
||||
import boto3
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
import pytest
|
||||
|
||||
from moto import mock_dynamodb
|
||||
@ -64,11 +63,11 @@ def test_batch_items_throws_exception_when_requesting_100_items_for_single_table
|
||||
}
|
||||
}
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
msg = ex.value.response["Error"]["Message"]
|
||||
msg.should.contain("1 validation error detected: Value")
|
||||
msg.should.contain(
|
||||
"at 'requestItems.users.member.keys' failed to satisfy constraint: Member must have length less than or equal to 100"
|
||||
assert (
|
||||
msg
|
||||
== "1 validation error detected: Value at 'requestItems.users.member.keys' failed to satisfy constraint: Member must have length less than or equal to 100"
|
||||
)
|
||||
|
||||
|
||||
@ -92,10 +91,9 @@ def test_batch_items_throws_exception_when_requesting_100_items_across_all_table
|
||||
},
|
||||
}
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Too many items requested for the BatchGetItem call"
|
||||
)
|
||||
err = ex.value.response["Error"]
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "Too many items requested for the BatchGetItem call"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -116,11 +114,13 @@ def test_batch_items_with_basic_projection_expression():
|
||||
}
|
||||
)["Responses"]["users"]
|
||||
|
||||
returned_items.should.have.length_of(3)
|
||||
[item["username"]["S"] for item in returned_items].should.be.equal(
|
||||
["user1", "user2", "user3"]
|
||||
)
|
||||
[item.get("foo") for item in returned_items].should.be.equal([None, None, None])
|
||||
assert len(returned_items) == 3
|
||||
assert [item["username"]["S"] for item in returned_items] == [
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
]
|
||||
assert [item.get("foo") for item in returned_items] == [None, None, None]
|
||||
|
||||
# The projection expression should not remove data from storage
|
||||
returned_items = dynamodb.batch_get_item(
|
||||
@ -137,10 +137,12 @@ def test_batch_items_with_basic_projection_expression():
|
||||
}
|
||||
)["Responses"]["users"]
|
||||
|
||||
[item["username"]["S"] for item in returned_items].should.be.equal(
|
||||
["user1", "user2", "user3"]
|
||||
)
|
||||
[item["foo"]["S"] for item in returned_items].should.be.equal(["bar", "bar", "bar"])
|
||||
assert [item["username"]["S"] for item in returned_items] == [
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
]
|
||||
assert [item["foo"]["S"] for item in returned_items] == ["bar", "bar", "bar"]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -162,11 +164,13 @@ def test_batch_items_with_basic_projection_expression_and_attr_expression_names(
|
||||
}
|
||||
)["Responses"]["users"]
|
||||
|
||||
returned_items.should.have.length_of(3)
|
||||
[item["username"]["S"] for item in returned_items].should.be.equal(
|
||||
["user1", "user2", "user3"]
|
||||
)
|
||||
[item.get("foo") for item in returned_items].should.be.equal([None, None, None])
|
||||
assert len(returned_items) == 3
|
||||
assert [item["username"]["S"] for item in returned_items] == [
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
]
|
||||
assert [item.get("foo") for item in returned_items] == [None, None, None]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -184,10 +188,9 @@ def test_batch_items_should_throw_exception_for_duplicate_request():
|
||||
}
|
||||
}
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Provided list of item keys contains duplicates"
|
||||
)
|
||||
err = ex.value.response["Error"]
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "Provided list of item keys contains duplicates"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -218,14 +221,14 @@ def test_batch_items_should_return_16mb_max():
|
||||
}
|
||||
)
|
||||
|
||||
resp["Responses"]["users"].should.have.length_of(55)
|
||||
assert len(resp["Responses"]["users"]) == 55
|
||||
unprocessed_keys = resp["UnprocessedKeys"]["users"]["Keys"]
|
||||
# 75 requested, 55 returned --> 20 unprocessed
|
||||
unprocessed_keys.should.have.length_of(20)
|
||||
assert len(unprocessed_keys) == 20
|
||||
|
||||
# Keys 55-75 are unprocessed
|
||||
unprocessed_keys.should.contain({"username": {"S": "largedata55"}})
|
||||
unprocessed_keys.should.contain({"username": {"S": "largedata65"}})
|
||||
assert {"username": {"S": "largedata55"}} in unprocessed_keys
|
||||
assert {"username": {"S": "largedata65"}} in unprocessed_keys
|
||||
|
||||
# Keys 0-55 are processed in the regular response, so they shouldn't show up here
|
||||
unprocessed_keys.shouldnt.contain({"username": {"S": "largedata45"}})
|
||||
assert {"username": {"S": "largedata45"}} not in unprocessed_keys
|
||||
|
@ -1,6 +1,5 @@
|
||||
import boto3
|
||||
import json
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
|
||||
from moto import mock_cloudformation, mock_dynamodb
|
||||
|
||||
@ -40,11 +39,11 @@ def test_delete_stack_dynamo_template_boto3():
|
||||
StackName="test_stack", TemplateBody=json.dumps(template_create_table)
|
||||
)
|
||||
table_desc = dynamodb_client.list_tables()
|
||||
len(table_desc.get("TableNames")).should.equal(1)
|
||||
assert len(table_desc.get("TableNames")) == 1
|
||||
|
||||
conn.delete_stack(StackName="test_stack")
|
||||
table_desc = dynamodb_client.list_tables()
|
||||
len(table_desc.get("TableNames")).should.equal(0)
|
||||
assert len(table_desc.get("TableNames")) == 0
|
||||
|
||||
conn.create_stack(
|
||||
StackName="test_stack", TemplateBody=json.dumps(template_create_table)
|
||||
|
@ -3,7 +3,6 @@ import re
|
||||
|
||||
import boto3
|
||||
import pytest
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
from moto import mock_dynamodb
|
||||
|
||||
|
||||
@ -38,7 +37,7 @@ def test_condition_expression_with_dot_in_attr_name():
|
||||
)
|
||||
|
||||
item = table.get_item(Key={"id": "key-0"})["Item"]
|
||||
item.should.equal({"id": "key-0", "first": {}})
|
||||
assert item == {"id": "key-0", "first": {}}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -232,8 +231,8 @@ def test_condition_expressions():
|
||||
|
||||
def _assert_conditional_check_failed_exception(exc):
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ConditionalCheckFailedException")
|
||||
err["Message"].should.equal("The conditional request failed")
|
||||
assert err["Code"] == "ConditionalCheckFailedException"
|
||||
assert err["Message"] == "The conditional request failed"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -274,9 +273,7 @@ def update_numerical_con_expr(key, con_expr, res, table):
|
||||
ExpressionAttributeValues={":zero": 0, ":one": 1},
|
||||
ConditionExpression=con_expr,
|
||||
)
|
||||
table.get_item(Key={"partitionKey": key})["Item"]["myAttr"].should.equal(
|
||||
Decimal(res)
|
||||
)
|
||||
assert table.get_item(Key={"partitionKey": key})["Item"]["myAttr"] == Decimal(res)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -404,7 +401,7 @@ def test_condition_expression_with_reserved_keyword_as_attr_name():
|
||||
|
||||
# table is unchanged
|
||||
item = table.get_item(Key={"id": "key-0"})["Item"]
|
||||
item.should.equal(record)
|
||||
assert item == record
|
||||
|
||||
# using attribute names solves the issue
|
||||
table.update_item(
|
||||
@ -422,4 +419,4 @@ def test_condition_expression_with_reserved_keyword_as_attr_name():
|
||||
)
|
||||
|
||||
item = table.get_item(Key={"id": "key-0"})["Item"]
|
||||
item.should.equal({"id": "key-0", "first": {}})
|
||||
assert item == {"id": "key-0", "first": {}}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import boto3
|
||||
import pytest
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
from moto import mock_dynamodb
|
||||
@ -24,9 +23,10 @@ def test_error_on_wrong_value_for_consumed_capacity():
|
||||
with pytest.raises(ClientError) as ex:
|
||||
table.put_item(Item=item, ReturnConsumedCapacity="Garbage")
|
||||
err = ex.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"1 validation error detected: Value 'Garbage' at 'returnConsumedCapacity' failed to satisfy constraint: Member must satisfy enum value set: [INDEXES, TOTAL, NONE]"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "1 validation error detected: Value 'Garbage' at 'returnConsumedCapacity' failed to satisfy constraint: Member must satisfy enum value set: [INDEXES, TOTAL, NONE]"
|
||||
)
|
||||
|
||||
|
||||
@ -46,10 +46,10 @@ def test_consumed_capacity_get_unknown_item():
|
||||
)
|
||||
|
||||
# Should still return ConsumedCapacity, even if it does not return an item
|
||||
response.should.have.key("ConsumedCapacity")
|
||||
response["ConsumedCapacity"].should.equal(
|
||||
{"TableName": "test_table", "CapacityUnits": 0.5}
|
||||
)
|
||||
assert response["ConsumedCapacity"] == {
|
||||
"TableName": "test_table",
|
||||
"CapacityUnits": 0.5,
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -135,15 +135,14 @@ def validate_response(
|
||||
response, should_have_capacity, should_have_table, is_index=False, value=1.0
|
||||
):
|
||||
if should_have_capacity:
|
||||
response.should.have.key("ConsumedCapacity")
|
||||
response["ConsumedCapacity"]["TableName"].should.equal("jobs")
|
||||
response["ConsumedCapacity"]["CapacityUnits"].should.equal(value)
|
||||
capacity = response["ConsumedCapacity"]
|
||||
assert capacity["TableName"] == "jobs"
|
||||
assert capacity["CapacityUnits"] == value
|
||||
if should_have_table:
|
||||
response["ConsumedCapacity"]["Table"].should.equal({"CapacityUnits": value})
|
||||
assert capacity["Table"] == {"CapacityUnits": value}
|
||||
if is_index:
|
||||
response["ConsumedCapacity"].should.have.key("LocalSecondaryIndexes")
|
||||
response["ConsumedCapacity"]["LocalSecondaryIndexes"].should.equal(
|
||||
{"job_name-index": {"CapacityUnits": value}}
|
||||
)
|
||||
assert capacity["LocalSecondaryIndexes"] == {
|
||||
"job_name-index": {"CapacityUnits": value}
|
||||
}
|
||||
else:
|
||||
response.shouldnt.have.key("ConsumedCapacity")
|
||||
assert "ConsumedCapacity" not in response
|
||||
|
@ -1,6 +1,5 @@
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
from datetime import datetime
|
||||
import pytest
|
||||
|
||||
@ -26,31 +25,29 @@ def test_create_table_standard():
|
||||
)
|
||||
actual = client.describe_table(TableName="messages")["Table"]
|
||||
|
||||
actual.should.have.key("AttributeDefinitions").equal(
|
||||
[
|
||||
assert actual["AttributeDefinitions"] == [
|
||||
{"AttributeName": "id", "AttributeType": "S"},
|
||||
{"AttributeName": "subject", "AttributeType": "S"},
|
||||
]
|
||||
assert isinstance(actual["CreationDateTime"], datetime)
|
||||
assert actual["GlobalSecondaryIndexes"] == []
|
||||
assert actual["LocalSecondaryIndexes"] == []
|
||||
assert actual["ProvisionedThroughput"] == {
|
||||
"NumberOfDecreasesToday": 0,
|
||||
"ReadCapacityUnits": 1,
|
||||
"WriteCapacityUnits": 5,
|
||||
}
|
||||
assert actual["TableSizeBytes"] == 0
|
||||
assert actual["TableName"] == "messages"
|
||||
assert actual["TableStatus"] == "ACTIVE"
|
||||
assert (
|
||||
actual["TableArn"] == f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
|
||||
)
|
||||
actual.should.have.key("CreationDateTime").be.a(datetime)
|
||||
actual.should.have.key("GlobalSecondaryIndexes").equal([])
|
||||
actual.should.have.key("LocalSecondaryIndexes").equal([])
|
||||
actual.should.have.key("ProvisionedThroughput").equal(
|
||||
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5}
|
||||
)
|
||||
actual.should.have.key("TableSizeBytes").equal(0)
|
||||
actual.should.have.key("TableName").equal("messages")
|
||||
actual.should.have.key("TableStatus").equal("ACTIVE")
|
||||
actual.should.have.key("TableArn").equal(
|
||||
f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
|
||||
)
|
||||
actual.should.have.key("KeySchema").equal(
|
||||
[
|
||||
assert actual["KeySchema"] == [
|
||||
{"AttributeName": "id", "KeyType": "HASH"},
|
||||
{"AttributeName": "subject", "KeyType": "RANGE"},
|
||||
]
|
||||
)
|
||||
actual.should.have.key("ItemCount").equal(0)
|
||||
assert actual["ItemCount"] == 0
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -81,17 +78,14 @@ def test_create_table_with_local_index():
|
||||
)
|
||||
actual = client.describe_table(TableName="messages")["Table"]
|
||||
|
||||
actual.should.have.key("AttributeDefinitions").equal(
|
||||
[
|
||||
assert actual["AttributeDefinitions"] == [
|
||||
{"AttributeName": "id", "AttributeType": "S"},
|
||||
{"AttributeName": "subject", "AttributeType": "S"},
|
||||
{"AttributeName": "threads", "AttributeType": "S"},
|
||||
]
|
||||
)
|
||||
actual.should.have.key("CreationDateTime").be.a(datetime)
|
||||
actual.should.have.key("GlobalSecondaryIndexes").equal([])
|
||||
actual.should.have.key("LocalSecondaryIndexes").equal(
|
||||
[
|
||||
assert isinstance(actual["CreationDateTime"], datetime)
|
||||
assert actual["GlobalSecondaryIndexes"] == []
|
||||
assert actual["LocalSecondaryIndexes"] == [
|
||||
{
|
||||
"IndexName": "threads_index",
|
||||
"KeySchema": [
|
||||
@ -101,23 +95,22 @@ def test_create_table_with_local_index():
|
||||
"Projection": {"ProjectionType": "ALL"},
|
||||
}
|
||||
]
|
||||
assert actual["ProvisionedThroughput"] == {
|
||||
"NumberOfDecreasesToday": 0,
|
||||
"ReadCapacityUnits": 1,
|
||||
"WriteCapacityUnits": 5,
|
||||
}
|
||||
assert actual["TableSizeBytes"] == 0
|
||||
assert actual["TableName"] == "messages"
|
||||
assert actual["TableStatus"] == "ACTIVE"
|
||||
assert (
|
||||
actual["TableArn"] == f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
|
||||
)
|
||||
actual.should.have.key("ProvisionedThroughput").equal(
|
||||
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5}
|
||||
)
|
||||
actual.should.have.key("TableSizeBytes").equal(0)
|
||||
actual.should.have.key("TableName").equal("messages")
|
||||
actual.should.have.key("TableStatus").equal("ACTIVE")
|
||||
actual.should.have.key("TableArn").equal(
|
||||
f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
|
||||
)
|
||||
actual.should.have.key("KeySchema").equal(
|
||||
[
|
||||
assert actual["KeySchema"] == [
|
||||
{"AttributeName": "id", "KeyType": "HASH"},
|
||||
{"AttributeName": "subject", "KeyType": "RANGE"},
|
||||
]
|
||||
)
|
||||
actual.should.have.key("ItemCount").equal(0)
|
||||
assert actual["ItemCount"] == 0
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -143,8 +136,7 @@ def test_create_table_with_gsi():
|
||||
}
|
||||
],
|
||||
)
|
||||
table["TableDescription"]["GlobalSecondaryIndexes"].should.equal(
|
||||
[
|
||||
assert table["TableDescription"]["GlobalSecondaryIndexes"] == [
|
||||
{
|
||||
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
|
||||
"IndexName": "test_gsi",
|
||||
@ -156,7 +148,6 @@ def test_create_table_with_gsi():
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
table = dynamodb.create_table(
|
||||
TableName="users2",
|
||||
@ -181,8 +172,7 @@ def test_create_table_with_gsi():
|
||||
}
|
||||
],
|
||||
)
|
||||
table["TableDescription"]["GlobalSecondaryIndexes"].should.equal(
|
||||
[
|
||||
assert table["TableDescription"]["GlobalSecondaryIndexes"] == [
|
||||
{
|
||||
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
|
||||
"IndexName": "test_gsi",
|
||||
@ -194,7 +184,6 @@ def test_create_table_with_gsi():
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -212,16 +201,16 @@ def test_create_table_with_stream_specification():
|
||||
},
|
||||
)
|
||||
|
||||
resp["TableDescription"].should.have.key("StreamSpecification")
|
||||
resp["TableDescription"]["StreamSpecification"].should.equal(
|
||||
{"StreamEnabled": True, "StreamViewType": "NEW_AND_OLD_IMAGES"}
|
||||
)
|
||||
resp["TableDescription"].should.contain("LatestStreamLabel")
|
||||
resp["TableDescription"].should.contain("LatestStreamArn")
|
||||
assert resp["TableDescription"]["StreamSpecification"] == {
|
||||
"StreamEnabled": True,
|
||||
"StreamViewType": "NEW_AND_OLD_IMAGES",
|
||||
}
|
||||
assert "LatestStreamLabel" in resp["TableDescription"]
|
||||
assert "LatestStreamArn" in resp["TableDescription"]
|
||||
|
||||
resp = conn.delete_table(TableName="test-streams")
|
||||
|
||||
resp["TableDescription"].should.contain("StreamSpecification")
|
||||
assert "StreamSpecification" in resp["TableDescription"]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -239,7 +228,7 @@ def test_create_table_with_tags():
|
||||
resp = client.list_tags_of_resource(
|
||||
ResourceArn=resp["TableDescription"]["TableArn"]
|
||||
)
|
||||
resp.should.have.key("Tags").equals([{"Key": "tk", "Value": "tv"}])
|
||||
assert resp["Tags"] == [{"Key": "tk", "Value": "tv"}]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -259,12 +248,12 @@ def test_create_table_pay_per_request():
|
||||
)
|
||||
|
||||
actual = client.describe_table(TableName="test1")["Table"]
|
||||
actual.should.have.key("BillingModeSummary").equals(
|
||||
{"BillingMode": "PAY_PER_REQUEST"}
|
||||
)
|
||||
actual.should.have.key("ProvisionedThroughput").equals(
|
||||
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 0, "WriteCapacityUnits": 0}
|
||||
)
|
||||
assert actual["BillingModeSummary"] == {"BillingMode": "PAY_PER_REQUEST"}
|
||||
assert actual["ProvisionedThroughput"] == {
|
||||
"NumberOfDecreasesToday": 0,
|
||||
"ReadCapacityUnits": 0,
|
||||
"WriteCapacityUnits": 0,
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -284,10 +273,12 @@ def test_create_table__provisioned_throughput():
|
||||
)
|
||||
|
||||
actual = client.describe_table(TableName="test1")["Table"]
|
||||
actual.should.have.key("BillingModeSummary").equals({"BillingMode": "PROVISIONED"})
|
||||
actual.should.have.key("ProvisionedThroughput").equals(
|
||||
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 2, "WriteCapacityUnits": 3}
|
||||
)
|
||||
assert actual["BillingModeSummary"] == {"BillingMode": "PROVISIONED"}
|
||||
assert actual["ProvisionedThroughput"] == {
|
||||
"NumberOfDecreasesToday": 0,
|
||||
"ReadCapacityUnits": 2,
|
||||
"WriteCapacityUnits": 3,
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -305,9 +296,10 @@ def test_create_table_without_specifying_throughput():
|
||||
StreamSpecification={"StreamEnabled": False, "StreamViewType": "NEW_IMAGE"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED"
|
||||
)
|
||||
|
||||
|
||||
@ -330,9 +322,10 @@ def test_create_table_error_pay_per_request_with_provisioned_param():
|
||||
BillingMode="PAY_PER_REQUEST",
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.equal(
|
||||
"ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST"
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert (
|
||||
err["Message"]
|
||||
== "ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST"
|
||||
)
|
||||
|
||||
|
||||
@ -354,7 +347,7 @@ def test_create_table_with_ssespecification__false():
|
||||
)
|
||||
|
||||
actual = client.describe_table(TableName="test1")["Table"]
|
||||
actual.shouldnt.have.key("SSEDescription")
|
||||
assert "SSEDescription" not in actual
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -375,12 +368,11 @@ def test_create_table_with_ssespecification__true():
|
||||
)
|
||||
|
||||
actual = client.describe_table(TableName="test1")["Table"]
|
||||
actual.should.have.key("SSEDescription")
|
||||
actual["SSEDescription"].should.have.key("Status").equals("ENABLED")
|
||||
actual["SSEDescription"].should.have.key("SSEType").equals("KMS")
|
||||
actual["SSEDescription"].should.have.key("KMSMasterKeyArn").match(
|
||||
"^arn:aws:kms"
|
||||
) # Default KMS key for DynamoDB
|
||||
assert "SSEDescription" in actual
|
||||
assert actual["SSEDescription"]["Status"] == "ENABLED"
|
||||
assert actual["SSEDescription"]["SSEType"] == "KMS"
|
||||
# Default KMS key for DynamoDB
|
||||
assert actual["SSEDescription"]["KMSMasterKeyArn"].startswith("arn:aws:kms")
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -401,10 +393,10 @@ def test_create_table_with_ssespecification__custom_kms_key():
|
||||
)
|
||||
|
||||
actual = client.describe_table(TableName="test1")["Table"]
|
||||
actual.should.have.key("SSEDescription")
|
||||
actual["SSEDescription"].should.have.key("Status").equals("ENABLED")
|
||||
actual["SSEDescription"].should.have.key("SSEType").equals("KMS")
|
||||
actual["SSEDescription"].should.have.key("KMSMasterKeyArn").equals("custom-kms-key")
|
||||
assert "SSEDescription" in actual
|
||||
assert actual["SSEDescription"]["Status"] == "ENABLED"
|
||||
assert actual["SSEDescription"]["SSEType"] == "KMS"
|
||||
assert actual["SSEDescription"]["KMSMasterKeyArn"] == "custom-kms-key"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -424,23 +416,19 @@ def test_create_table__specify_non_key_column():
|
||||
)
|
||||
|
||||
actual = client.describe_table(TableName="tab")["Table"]
|
||||
actual["KeySchema"].should.equal(
|
||||
[
|
||||
assert actual["KeySchema"] == [
|
||||
{"AttributeName": "PK", "KeyType": "HASH"},
|
||||
{"AttributeName": "SomeColumn", "KeyType": "N"},
|
||||
]
|
||||
)
|
||||
|
||||
if not settings.TEST_SERVER_MODE:
|
||||
ddb = dynamodb_backends[ACCOUNT_ID]["us-east-2"]
|
||||
ddb.tables["tab"].attr.should.contain(
|
||||
{"AttributeName": "PK", "AttributeType": "S"}
|
||||
)
|
||||
ddb.tables["tab"].attr.should.contain(
|
||||
{"AttributeName": "SomeColumn", "AttributeType": "N"}
|
||||
)
|
||||
assert {"AttributeName": "PK", "AttributeType": "S"} in ddb.tables["tab"].attr
|
||||
assert {"AttributeName": "SomeColumn", "AttributeType": "N"} in ddb.tables[
|
||||
"tab"
|
||||
].attr
|
||||
# It should recognize PK is the Hash Key
|
||||
ddb.tables["tab"].hash_key_attr.should.equal("PK")
|
||||
assert ddb.tables["tab"].hash_key_attr == "PK"
|
||||
# It should recognize that SomeColumn is not a Range Key
|
||||
ddb.tables["tab"].has_range_key.should.equal(False)
|
||||
ddb.tables["tab"].range_key_names.should.equal([])
|
||||
assert ddb.tables["tab"].has_range_key is False
|
||||
assert ddb.tables["tab"].range_key_names == []
|
||||
|
@ -454,12 +454,12 @@ def test_normalize_with_one_action(table):
|
||||
item=item,
|
||||
table=table,
|
||||
).validate()
|
||||
validated_ast.children.should.have.length_of(1)
|
||||
validated_ast.children[0].should.be.a(UpdateExpressionAddClause)
|
||||
assert len(validated_ast.children) == 1
|
||||
assert isinstance(validated_ast.children[0], UpdateExpressionAddClause)
|
||||
|
||||
validated_ast.normalize()
|
||||
validated_ast.children.should.have.length_of(1)
|
||||
validated_ast.children[0].should.be.a(UpdateExpressionAddAction)
|
||||
assert len(validated_ast.children) == 1
|
||||
assert isinstance(validated_ast.children[0], UpdateExpressionAddAction)
|
||||
|
||||
|
||||
def test_normalize_with_multiple_actions__order_is_preserved(table):
|
||||
@ -481,22 +481,22 @@ def test_normalize_with_multiple_actions__order_is_preserved(table):
|
||||
item=item,
|
||||
table=table,
|
||||
).validate()
|
||||
validated_ast.children.should.have.length_of(2)
|
||||
assert len(validated_ast.children) == 2
|
||||
# add clause first
|
||||
validated_ast.children[0].should.be.a(UpdateExpressionAddClause)
|
||||
assert isinstance(validated_ast.children[0], UpdateExpressionAddClause)
|
||||
# rest of the expression next
|
||||
validated_ast.children[1].should.be.a(UpdateExpression)
|
||||
assert isinstance(validated_ast.children[1], UpdateExpression)
|
||||
|
||||
validated_ast.normalize()
|
||||
validated_ast.children.should.have.length_of(5)
|
||||
assert len(validated_ast.children) == 5
|
||||
# add action first
|
||||
validated_ast.children[0].should.be.a(UpdateExpressionAddAction)
|
||||
assert isinstance(validated_ast.children[0], UpdateExpressionAddAction)
|
||||
# Removal actions in reverse order
|
||||
validated_ast.children[1].should.be.a(UpdateExpressionRemoveAction)
|
||||
validated_ast.children[1]._get_value().should.equal(3)
|
||||
validated_ast.children[2].should.be.a(UpdateExpressionRemoveAction)
|
||||
validated_ast.children[2]._get_value().should.equal(2)
|
||||
validated_ast.children[3].should.be.a(UpdateExpressionRemoveAction)
|
||||
validated_ast.children[3]._get_value().should.equal(1)
|
||||
assert isinstance(validated_ast.children[1], UpdateExpressionRemoveAction)
|
||||
assert validated_ast.children[1]._get_value() == 3
|
||||
assert isinstance(validated_ast.children[2], UpdateExpressionRemoveAction)
|
||||
assert validated_ast.children[2]._get_value() == 2
|
||||
assert isinstance(validated_ast.children[3], UpdateExpressionRemoveAction)
|
||||
assert validated_ast.children[3]._get_value() == 1
|
||||
# Set action last, as per insertion order
|
||||
validated_ast.children[4].should.be.a(UpdateExpressionSetAction)
|
||||
assert isinstance(validated_ast.children[4], UpdateExpressionSetAction)
|
||||
|
@ -1,3 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from moto.dynamodb.exceptions import (
|
||||
InvalidTokenException,
|
||||
InvalidExpressionAttributeNameKey,
|
||||
@ -108,12 +110,10 @@ def test_expression_tokenizer_single_set_action_with_underscore_in_identifier():
|
||||
def test_expression_tokenizer_leading_underscore_in_attribute_name_expression():
|
||||
"""Leading underscore is not allowed for an attribute name"""
|
||||
set_action = "SET attrName = _idid"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
ExpressionTokenizer.make_list(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "_"
|
||||
assert te.near == "= _idid"
|
||||
assert te.value.token == "_"
|
||||
assert te.value.near == "= _idid"
|
||||
|
||||
|
||||
def test_expression_tokenizer_leading_underscore_in_attribute_value_expression():
|
||||
@ -188,11 +188,9 @@ def test_expression_tokenizer_single_set_action_attribute_name_invalid_key():
|
||||
ExpressionAttributeNames contains invalid key: Syntax error; key: "#va#l2"
|
||||
"""
|
||||
set_action = "SET #va#l2 = 3"
|
||||
try:
|
||||
with pytest.raises(InvalidExpressionAttributeNameKey) as e:
|
||||
ExpressionTokenizer.make_list(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidExpressionAttributeNameKey as e:
|
||||
assert e.key == "#va#l2"
|
||||
assert e.value.key == "#va#l2"
|
||||
|
||||
|
||||
def test_expression_tokenizer_single_set_action_attribute_name_invalid_key_double_hash():
|
||||
@ -200,11 +198,9 @@ def test_expression_tokenizer_single_set_action_attribute_name_invalid_key_doubl
|
||||
ExpressionAttributeNames contains invalid key: Syntax error; key: "#va#l"
|
||||
"""
|
||||
set_action = "SET #va#l = 3"
|
||||
try:
|
||||
with pytest.raises(InvalidExpressionAttributeNameKey) as e:
|
||||
ExpressionTokenizer.make_list(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidExpressionAttributeNameKey as e:
|
||||
assert e.key == "#va#l"
|
||||
assert e.value.key == "#va#l"
|
||||
|
||||
|
||||
def test_expression_tokenizer_single_set_action_attribute_name_valid_key():
|
||||
@ -245,39 +241,31 @@ def test_expression_tokenizer_single_set_action_attribute_name_leading_underscor
|
||||
|
||||
def test_expression_tokenizer_just_a_pipe():
|
||||
set_action = "|"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
ExpressionTokenizer.make_list(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "|"
|
||||
assert te.near == "|"
|
||||
assert te.value.token == "|"
|
||||
assert te.value.near == "|"
|
||||
|
||||
|
||||
def test_expression_tokenizer_just_a_pipe_with_leading_white_spaces():
|
||||
set_action = " |"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
ExpressionTokenizer.make_list(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "|"
|
||||
assert te.near == " |"
|
||||
assert te.value.token == "|"
|
||||
assert te.value.near == " |"
|
||||
|
||||
|
||||
def test_expression_tokenizer_just_a_pipe_for_set_expression():
|
||||
set_action = "SET|"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
ExpressionTokenizer.make_list(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "|"
|
||||
assert te.near == "SET|"
|
||||
assert te.value.token == "|"
|
||||
assert te.value.near == "SET|"
|
||||
|
||||
|
||||
def test_expression_tokenizer_just_an_attribute_and_a_pipe_for_set_expression():
|
||||
set_action = "SET a|"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
ExpressionTokenizer.make_list(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "|"
|
||||
assert te.near == "a|"
|
||||
assert te.value.token == "|"
|
||||
assert te.value.near == "a|"
|
||||
|
@ -1,3 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from moto.dynamodb.exceptions import InvalidTokenException
|
||||
from moto.dynamodb.parsing.expressions import UpdateExpressionParser
|
||||
from moto.dynamodb.parsing.reserved_keywords import ReservedKeywords
|
||||
@ -14,392 +16,314 @@ def test_get_reserved_keywords():
|
||||
|
||||
def test_update_expression_numeric_literal_in_expression():
|
||||
set_action = "SET attrName = 3"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "3"
|
||||
assert te.near == "= 3"
|
||||
assert te.value.token == "3"
|
||||
assert te.value.near == "= 3"
|
||||
|
||||
|
||||
def test_expression_tokenizer_multi_number_numeric_literal_in_expression():
|
||||
set_action = "SET attrName = 34"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "34"
|
||||
assert te.near == "= 34"
|
||||
assert te.value.token == "34"
|
||||
assert te.value.near == "= 34"
|
||||
|
||||
|
||||
def test_expression_tokenizer_numeric_literal_unclosed_square_bracket():
|
||||
set_action = "SET MyStr[ 3"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "<EOF>"
|
||||
assert te.near == "3"
|
||||
assert te.value.token == "<EOF>"
|
||||
assert te.value.near == "3"
|
||||
|
||||
|
||||
def test_expression_tokenizer_wrong_closing_bracket_with_space():
|
||||
set_action = "SET MyStr[3 )"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == ")"
|
||||
assert te.near == "3 )"
|
||||
assert te.value.token == ")"
|
||||
assert te.value.near == "3 )"
|
||||
|
||||
|
||||
def test_expression_tokenizer_wrong_closing_bracket():
|
||||
set_action = "SET MyStr[3)"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == ")"
|
||||
assert te.near == "3)"
|
||||
assert te.value.token == ")"
|
||||
assert te.value.near == "3)"
|
||||
|
||||
|
||||
def test_expression_tokenizer_only_numeric_literal_for_set():
|
||||
set_action = "SET 2"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "2"
|
||||
assert te.near == "SET 2"
|
||||
assert te.value.token == "2"
|
||||
assert te.value.near == "SET 2"
|
||||
|
||||
|
||||
def test_expression_tokenizer_only_numeric_literal():
|
||||
set_action = "2"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "2"
|
||||
assert te.near == "2"
|
||||
assert te.value.token == "2"
|
||||
assert te.value.near == "2"
|
||||
|
||||
|
||||
def test_expression_tokenizer_set_closing_round_bracket():
|
||||
set_action = "SET )"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == ")"
|
||||
assert te.near == "SET )"
|
||||
assert te.value.token == ")"
|
||||
assert te.value.near == "SET )"
|
||||
|
||||
|
||||
def test_expression_tokenizer_set_closing_followed_by_numeric_literal():
|
||||
set_action = "SET ) 3"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == ")"
|
||||
assert te.near == "SET ) 3"
|
||||
assert te.value.token == ")"
|
||||
assert te.value.near == "SET ) 3"
|
||||
|
||||
|
||||
def test_expression_tokenizer_numeric_literal_unclosed_square_bracket_trailing_space():
|
||||
set_action = "SET MyStr[ 3 "
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "<EOF>"
|
||||
assert te.near == "3 "
|
||||
assert te.value.token == "<EOF>"
|
||||
assert te.value.near == "3 "
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_round_brackets_only_opening():
|
||||
set_action = "SET MyStr = (:_val"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "<EOF>"
|
||||
assert te.near == ":_val"
|
||||
assert te.value.token == "<EOF>"
|
||||
assert te.value.near == ":_val"
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_round_brackets_only_opening_trailing_space():
|
||||
set_action = "SET MyStr = (:_val "
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "<EOF>"
|
||||
assert te.near == ":_val "
|
||||
assert te.value.token == "<EOF>"
|
||||
assert te.value.near == ":_val "
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_square_brackets_only_opening():
|
||||
set_action = "SET MyStr = [:_val"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "["
|
||||
assert te.near == "= [:_val"
|
||||
assert te.value.token == "["
|
||||
assert te.value.near == "= [:_val"
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_square_brackets_only_opening_trailing_spaces():
|
||||
set_action = "SET MyStr = [:_val "
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "["
|
||||
assert te.near == "= [:_val"
|
||||
assert te.value.token == "["
|
||||
assert te.value.near == "= [:_val"
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_round_brackets_multiple_opening():
|
||||
set_action = "SET MyStr = (:_val + (:val2"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "<EOF>"
|
||||
assert te.near == ":val2"
|
||||
assert te.value.token == "<EOF>"
|
||||
assert te.value.near == ":val2"
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_round_brackets_only_closing():
|
||||
set_action = "SET MyStr = ):_val"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == ")"
|
||||
assert te.near == "= ):_val"
|
||||
assert te.value.token == ")"
|
||||
assert te.value.near == "= ):_val"
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_square_brackets_only_closing():
|
||||
set_action = "SET MyStr = ]:_val"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "]"
|
||||
assert te.near == "= ]:_val"
|
||||
assert te.value.token == "]"
|
||||
assert te.value.near == "= ]:_val"
|
||||
|
||||
|
||||
def test_expression_tokenizer_unbalanced_round_brackets_only_closing_followed_by_other_parts():
|
||||
set_action = "SET MyStr = ):_val + :val2"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == ")"
|
||||
assert te.near == "= ):_val"
|
||||
assert te.value.token == ")"
|
||||
assert te.value.near == "= ):_val"
|
||||
|
||||
|
||||
def test_update_expression_starts_with_keyword_reset_followed_by_identifier():
|
||||
update_expression = "RESET NonExistent"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "RESET"
|
||||
assert te.near == "RESET NonExistent"
|
||||
assert te.value.token == "RESET"
|
||||
assert te.value.near == "RESET NonExistent"
|
||||
|
||||
|
||||
def test_update_expression_starts_with_keyword_reset_followed_by_identifier_and_value():
|
||||
update_expression = "RESET NonExistent value"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "RESET"
|
||||
assert te.near == "RESET NonExistent"
|
||||
assert te.value.token == "RESET"
|
||||
assert te.value.near == "RESET NonExistent"
|
||||
|
||||
|
||||
def test_update_expression_starts_with_leading_spaces_and_keyword_reset_followed_by_identifier_and_value():
|
||||
update_expression = " RESET NonExistent value"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "RESET"
|
||||
assert te.near == " RESET NonExistent"
|
||||
assert te.value.token == "RESET"
|
||||
assert te.value.near == " RESET NonExistent"
|
||||
|
||||
|
||||
def test_update_expression_with_only_keyword_reset():
|
||||
update_expression = "RESET"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "RESET"
|
||||
assert te.near == "RESET"
|
||||
assert te.value.token == "RESET"
|
||||
assert te.value.near == "RESET"
|
||||
|
||||
|
||||
def test_update_nested_expression_with_selector_just_should_fail_parsing_at_numeric_literal_value():
|
||||
update_expression = "SET a[0].b = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "5"
|
||||
assert te.near == "= 5"
|
||||
assert te.value.token == "5"
|
||||
assert te.value.near == "= 5"
|
||||
|
||||
|
||||
def test_update_nested_expression_with_selector_and_spaces_should_only_fail_parsing_at_numeric_literal_value():
|
||||
update_expression = "SET a [ 2 ]. b = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "5"
|
||||
assert te.near == "= 5"
|
||||
assert te.value.token == "5"
|
||||
assert te.value.near == "= 5"
|
||||
|
||||
|
||||
def test_update_nested_expression_with_double_selector_and_spaces_should_only_fail_parsing_at_numeric_literal_value():
|
||||
update_expression = "SET a [2][ 3 ]. b = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "5"
|
||||
assert te.near == "= 5"
|
||||
assert te.value.token == "5"
|
||||
assert te.value.near == "= 5"
|
||||
|
||||
|
||||
def test_update_nested_expression_should_only_fail_parsing_at_numeric_literal_value():
|
||||
update_expression = "SET a . b = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "5"
|
||||
assert te.near == "= 5"
|
||||
assert te.value.token == "5"
|
||||
assert te.value.near == "= 5"
|
||||
|
||||
|
||||
def test_nested_selectors_in_update_expression_should_fail_at_nesting():
|
||||
update_expression = "SET a [ [2] ]. b = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "["
|
||||
assert te.near == "[ [2"
|
||||
assert te.value.token == "["
|
||||
assert te.value.near == "[ [2"
|
||||
|
||||
|
||||
def test_update_expression_number_in_selector_cannot_be_splite():
|
||||
update_expression = "SET a [2 1]. b = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "1"
|
||||
assert te.near == "2 1]"
|
||||
assert te.value.token == "1"
|
||||
assert te.value.near == "2 1]"
|
||||
|
||||
|
||||
def test_update_expression_cannot_have_successive_attributes():
|
||||
update_expression = "SET #a a = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "a"
|
||||
assert te.near == "#a a ="
|
||||
assert te.value.token == "a"
|
||||
assert te.value.near == "#a a ="
|
||||
|
||||
|
||||
def test_update_expression_path_with_both_attribute_and_attribute_name_should_only_fail_at_numeric_value():
|
||||
update_expression = "SET #a.a = 5"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "5"
|
||||
assert te.near == "= 5"
|
||||
assert te.value.token == "5"
|
||||
assert te.value.near == "= 5"
|
||||
|
||||
|
||||
def test_expression_tokenizer_2_same_operators_back_to_back():
|
||||
set_action = "SET MyStr = NoExist + + :_val "
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "+"
|
||||
assert te.near == "+ + :_val"
|
||||
assert te.value.token == "+"
|
||||
assert te.value.near == "+ + :_val"
|
||||
|
||||
|
||||
def test_expression_tokenizer_2_different_operators_back_to_back():
|
||||
set_action = "SET MyStr = NoExist + - :_val "
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "-"
|
||||
assert te.near == "+ - :_val"
|
||||
assert te.value.token == "-"
|
||||
assert te.value.near == "+ - :_val"
|
||||
|
||||
|
||||
def test_update_expression_remove_does_not_allow_operations():
|
||||
remove_action = "REMOVE NoExist + "
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(remove_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "+"
|
||||
assert te.near == "NoExist + "
|
||||
assert te.value.token == "+"
|
||||
assert te.value.near == "NoExist + "
|
||||
|
||||
|
||||
def test_update_expression_add_does_not_allow_attribute_after_path():
|
||||
"""value here is not really a value since a value starts with a colon (:)"""
|
||||
add_expr = "ADD attr val foobar"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(add_expr)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "val"
|
||||
assert te.near == "attr val foobar"
|
||||
assert te.value.token == "val"
|
||||
assert te.value.near == "attr val foobar"
|
||||
|
||||
|
||||
def test_update_expression_add_does_not_allow_attribute_foobar_after_value():
|
||||
add_expr = "ADD attr :val foobar"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(add_expr)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "foobar"
|
||||
assert te.near == ":val foobar"
|
||||
assert te.value.token == "foobar"
|
||||
assert te.value.near == ":val foobar"
|
||||
|
||||
|
||||
def test_update_expression_delete_does_not_allow_attribute_after_path():
|
||||
"""value here is not really a value since a value starts with a colon (:)"""
|
||||
delete_expr = "DELETE attr val"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(delete_expr)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "val"
|
||||
assert te.near == "attr val"
|
||||
assert te.value.token == "val"
|
||||
assert te.value.near == "attr val"
|
||||
|
||||
|
||||
def test_update_expression_delete_does_not_allow_attribute_foobar_after_value():
|
||||
delete_expr = "DELETE attr :val foobar"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(delete_expr)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "foobar"
|
||||
assert te.near == ":val foobar"
|
||||
assert te.value.token == "foobar"
|
||||
assert te.value.near == ":val foobar"
|
||||
|
||||
|
||||
def test_update_expression_parsing_is_not_keyword_aware():
|
||||
"""path and VALUE are keywords. Yet a token error will be thrown for the numeric literal 1."""
|
||||
delete_expr = "SET path = VALUE 1"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(delete_expr)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "1"
|
||||
assert te.near == "VALUE 1"
|
||||
assert te.value.token == "1"
|
||||
assert te.value.near == "VALUE 1"
|
||||
|
||||
|
||||
def test_expression_if_not_exists_is_not_valid_in_remove_statement():
|
||||
set_action = "REMOVE if_not_exists(a,b)"
|
||||
try:
|
||||
with pytest.raises(InvalidTokenException) as te:
|
||||
UpdateExpressionParser.make(set_action)
|
||||
assert False, "Exception not raised correctly"
|
||||
except InvalidTokenException as te:
|
||||
assert te.token == "("
|
||||
assert te.near == "if_not_exists(a"
|
||||
assert te.value.token == "("
|
||||
assert te.value.near == "if_not_exists(a"
|
||||
|
@ -5,7 +5,6 @@ from unittest import TestCase
|
||||
|
||||
|
||||
class TestSelectStatements:
|
||||
|
||||
mock = mock_dynamodb()
|
||||
|
||||
@classmethod
|
||||
|
@ -3,7 +3,6 @@ from decimal import Decimal
|
||||
import boto3
|
||||
from boto3.dynamodb.conditions import Key
|
||||
from botocore.exceptions import ClientError
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
import pytest
|
||||
|
||||
from moto import mock_dynamodb
|
||||
@ -33,8 +32,8 @@ def test_get_item_without_range_key_boto3():
|
||||
with pytest.raises(ClientError) as ex:
|
||||
table.get_item(Key={"id": hash_key})
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["Error"]["Message"].should.equal("Validation Exception")
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert ex.value.response["Error"]["Message"] == "Validation Exception"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -59,23 +58,23 @@ def test_query_filter_boto3():
|
||||
table.put_item(Item={"pk": "pk", "sk": f"sk-{i}"})
|
||||
|
||||
res = table.query(KeyConditionExpression=Key("pk").eq("pk"))
|
||||
res["Items"].should.have.length_of(3)
|
||||
assert len(res["Items"]) == 3
|
||||
|
||||
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").lt("sk-1"))
|
||||
res["Items"].should.have.length_of(1)
|
||||
res["Items"].should.equal([{"pk": "pk", "sk": "sk-0"}])
|
||||
assert len(res["Items"]) == 1
|
||||
assert res["Items"] == [{"pk": "pk", "sk": "sk-0"}]
|
||||
|
||||
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").lte("sk-1"))
|
||||
res["Items"].should.have.length_of(2)
|
||||
res["Items"].should.equal([{"pk": "pk", "sk": "sk-0"}, {"pk": "pk", "sk": "sk-1"}])
|
||||
assert len(res["Items"]) == 2
|
||||
assert res["Items"] == [{"pk": "pk", "sk": "sk-0"}, {"pk": "pk", "sk": "sk-1"}]
|
||||
|
||||
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").gt("sk-1"))
|
||||
res["Items"].should.have.length_of(1)
|
||||
res["Items"].should.equal([{"pk": "pk", "sk": "sk-2"}])
|
||||
assert len(res["Items"]) == 1
|
||||
assert res["Items"] == [{"pk": "pk", "sk": "sk-2"}]
|
||||
|
||||
res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").gte("sk-1"))
|
||||
res["Items"].should.have.length_of(2)
|
||||
res["Items"].should.equal([{"pk": "pk", "sk": "sk-1"}, {"pk": "pk", "sk": "sk-2"}])
|
||||
assert len(res["Items"]) == 2
|
||||
assert res["Items"] == [{"pk": "pk", "sk": "sk-1"}, {"pk": "pk", "sk": "sk-2"}]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -108,7 +107,7 @@ def test_boto3_conditions():
|
||||
)
|
||||
expected = ["123", "456", "789"]
|
||||
for index, item in enumerate(results["Items"]):
|
||||
item["subject"].should.equal(expected[index])
|
||||
assert item["subject"] == expected[index]
|
||||
|
||||
# Return all items again, but in reverse
|
||||
results = table.query(
|
||||
@ -116,7 +115,7 @@ def test_boto3_conditions():
|
||||
ScanIndexForward=False,
|
||||
)
|
||||
for index, item in enumerate(reversed(results["Items"])):
|
||||
item["subject"].should.equal(expected[index])
|
||||
assert item["subject"] == expected[index]
|
||||
|
||||
# Filter the subjects to only return some of the results
|
||||
results = table.query(
|
||||
@ -124,32 +123,32 @@ def test_boto3_conditions():
|
||||
& Key("subject").gt("234"),
|
||||
ConsistentRead=True,
|
||||
)
|
||||
results["Count"].should.equal(2)
|
||||
assert results["Count"] == 2
|
||||
|
||||
# Filter to return no results
|
||||
results = table.query(
|
||||
KeyConditionExpression=Key("forum_name").eq("the-key")
|
||||
& Key("subject").gt("9999")
|
||||
)
|
||||
results["Count"].should.equal(0)
|
||||
assert results["Count"] == 0
|
||||
|
||||
results = table.query(
|
||||
KeyConditionExpression=Key("forum_name").eq("the-key")
|
||||
& Key("subject").begins_with("12")
|
||||
)
|
||||
results["Count"].should.equal(1)
|
||||
assert results["Count"] == 1
|
||||
|
||||
results = table.query(
|
||||
KeyConditionExpression=Key("subject").begins_with("7")
|
||||
& Key("forum_name").eq("the-key")
|
||||
)
|
||||
results["Count"].should.equal(1)
|
||||
assert results["Count"] == 1
|
||||
|
||||
results = table.query(
|
||||
KeyConditionExpression=Key("forum_name").eq("the-key")
|
||||
& Key("subject").between("567", "890")
|
||||
)
|
||||
results["Count"].should.equal(1)
|
||||
assert results["Count"] == 1
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -199,7 +198,7 @@ def test_boto3_conditions_ignorecase():
|
||||
":end": {"S": "200"},
|
||||
},
|
||||
)
|
||||
results["Count"].should.equal(2)
|
||||
assert results["Count"] == 2
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
dynamodb.query(
|
||||
@ -210,20 +209,19 @@ def test_boto3_conditions_ignorecase():
|
||||
":subject": {"S": "1"},
|
||||
},
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Invalid KeyConditionExpression: Invalid function name; function: BegIns_WiTh"
|
||||
assert ex.value.response["Error"]["Code"] == "ValidationException"
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "Invalid KeyConditionExpression: Invalid function name; function: BegIns_WiTh"
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
def test_boto3_put_item_with_conditions():
|
||||
import botocore
|
||||
|
||||
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
|
||||
|
||||
# Create the DynamoDB table.
|
||||
table = dynamodb.create_table(
|
||||
dynamodb.create_table(
|
||||
TableName="users",
|
||||
KeySchema=[
|
||||
{"AttributeName": "forum_name", "KeyType": "HASH"},
|
||||
@ -244,15 +242,21 @@ def test_boto3_put_item_with_conditions():
|
||||
ConditionExpression="attribute_not_exists(forum_name) AND attribute_not_exists(subject)",
|
||||
)
|
||||
|
||||
table.put_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.put_item(
|
||||
Item={"forum_name": "the-key", "subject": "123"},
|
||||
ConditionExpression="attribute_not_exists(forum_name) AND attribute_not_exists(subject)",
|
||||
).should.throw(botocore.exceptions.ClientError)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ConditionalCheckFailedException"
|
||||
|
||||
table.put_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.put_item(
|
||||
Item={"forum_name": "bogus-key", "subject": "bogus", "test": "123"},
|
||||
ConditionExpression="attribute_exists(forum_name) AND attribute_exists(subject)",
|
||||
).should.throw(botocore.exceptions.ClientError)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ConditionalCheckFailedException"
|
||||
|
||||
|
||||
def _create_table_with_range_key():
|
||||
@ -316,15 +320,13 @@ def test_update_item_range_key_set():
|
||||
(k, str(v) if isinstance(v, Decimal) else v)
|
||||
for k, v in table.get_item(Key=item_key)["Item"].items()
|
||||
)
|
||||
dict(returned_item).should.equal(
|
||||
{
|
||||
assert returned_item == {
|
||||
"username": "johndoe2",
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
"created": "4",
|
||||
"mapfield": {"key": "value"},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -348,15 +350,13 @@ def test_update_item_does_not_exist_is_created():
|
||||
(k, str(v) if isinstance(v, Decimal) else v)
|
||||
for k, v in table.get_item(Key=item_key)["Item"].items()
|
||||
)
|
||||
dict(returned_item).should.equal(
|
||||
{
|
||||
assert returned_item == {
|
||||
"username": "johndoe2",
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
"created": "4",
|
||||
"mapfield": {"key": "value"},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -377,9 +377,11 @@ def test_update_item_add_value():
|
||||
(k, str(v) if isinstance(v, Decimal) else v)
|
||||
for k, v in table.get_item(Key=item_key)["Item"].items()
|
||||
)
|
||||
dict(returned_item).should.equal(
|
||||
{"numeric_field": "1", "forum_name": "the-key", "subject": "123"}
|
||||
)
|
||||
assert returned_item == {
|
||||
"numeric_field": "1",
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -404,13 +406,11 @@ def test_update_item_add_value_string_set():
|
||||
(k, str(v) if isinstance(v, Decimal) else v)
|
||||
for k, v in table.get_item(Key=item_key)["Item"].items()
|
||||
)
|
||||
dict(returned_item).should.equal(
|
||||
{
|
||||
assert returned_item == {
|
||||
"string_set": set(["str1", "str2", "str3"]),
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -435,9 +435,11 @@ def test_update_item_delete_value_string_set():
|
||||
(k, str(v) if isinstance(v, Decimal) else v)
|
||||
for k, v in table.get_item(Key=item_key)["Item"].items()
|
||||
)
|
||||
dict(returned_item).should.equal(
|
||||
{"string_set": set(["str1"]), "forum_name": "the-key", "subject": "123"}
|
||||
)
|
||||
assert returned_item == {
|
||||
"string_set": set(["str1"]),
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -454,9 +456,11 @@ def test_update_item_add_value_does_not_exist_is_created():
|
||||
(k, str(v) if isinstance(v, Decimal) else v)
|
||||
for k, v in table.get_item(Key=item_key)["Item"].items()
|
||||
)
|
||||
dict(returned_item).should.equal(
|
||||
{"numeric_field": "2", "forum_name": "the-key", "subject": "123"}
|
||||
)
|
||||
assert returned_item == {
|
||||
"numeric_field": "2",
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -472,27 +476,30 @@ def test_update_item_with_expression():
|
||||
UpdateExpression="SET field = :field_value",
|
||||
ExpressionAttributeValues={":field_value": 2},
|
||||
)
|
||||
dict(table.get_item(Key=item_key)["Item"]).should.equal(
|
||||
{"field": Decimal("2"), "forum_name": "the-key", "subject": "123"}
|
||||
)
|
||||
assert table.get_item(Key=item_key)["Item"] == {
|
||||
"field": Decimal("2"),
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
}
|
||||
|
||||
table.update_item(
|
||||
Key=item_key,
|
||||
UpdateExpression="SET field = :field_value",
|
||||
ExpressionAttributeValues={":field_value": 3},
|
||||
)
|
||||
dict(table.get_item(Key=item_key)["Item"]).should.equal(
|
||||
{"field": Decimal("3"), "forum_name": "the-key", "subject": "123"}
|
||||
)
|
||||
assert table.get_item(Key=item_key)["Item"] == {
|
||||
"field": Decimal("3"),
|
||||
"forum_name": "the-key",
|
||||
"subject": "123",
|
||||
}
|
||||
|
||||
|
||||
def assert_failure_due_to_key_not_in_schema(func, **kwargs):
|
||||
with pytest.raises(ClientError) as ex:
|
||||
func(**kwargs)
|
||||
ex.value.response["Error"]["Code"].should.equal("ValidationException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"The provided key element does not match the schema"
|
||||
)
|
||||
err = ex.value.response["Error"]
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert err["Message"] == "The provided key element does not match the schema"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -518,7 +525,7 @@ def test_update_item_add_with_expression():
|
||||
ExpressionAttributeValues={":v": {"item4"}},
|
||||
)
|
||||
current_item["str_set"] = current_item["str_set"].union({"item4"})
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Update item to add a string value to a non-existing set
|
||||
table.update_item(
|
||||
@ -527,7 +534,7 @@ def test_update_item_add_with_expression():
|
||||
ExpressionAttributeValues={":v": {"item4"}},
|
||||
)
|
||||
current_item["non_existing_str_set"] = {"item4"}
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Update item to add a num value to a num set
|
||||
table.update_item(
|
||||
@ -536,7 +543,7 @@ def test_update_item_add_with_expression():
|
||||
ExpressionAttributeValues={":v": {6}},
|
||||
)
|
||||
current_item["num_set"] = current_item["num_set"].union({6})
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Update item to add a value to a number value
|
||||
table.update_item(
|
||||
@ -545,35 +552,59 @@ def test_update_item_add_with_expression():
|
||||
ExpressionAttributeValues={":v": 20},
|
||||
)
|
||||
current_item["num_val"] = current_item["num_val"] + 20
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Attempt to add a number value to a string set, should raise Client Error
|
||||
table.update_item.when.called_with(
|
||||
# Attempt to add a number value to a string set
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key=item_key,
|
||||
UpdateExpression="ADD str_set :v",
|
||||
ExpressionAttributeValues={":v": 20},
|
||||
).should.have.raised(ClientError)
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert (
|
||||
err["Message"]
|
||||
== "An operand in the update expression has an incorrect data type"
|
||||
)
|
||||
|
||||
# Attempt to add a number set to the string set, should raise a ClientError
|
||||
table.update_item.when.called_with(
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Attempt to add a number set to the string set
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key=item_key,
|
||||
UpdateExpression="ADD str_set :v",
|
||||
ExpressionAttributeValues={":v": {20}},
|
||||
).should.have.raised(ClientError)
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert (
|
||||
err["Message"]
|
||||
== "An operand in the update expression has an incorrect data type"
|
||||
)
|
||||
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Attempt to update with a bad expression
|
||||
table.update_item.when.called_with(
|
||||
Key=item_key, UpdateExpression="ADD str_set bad_value"
|
||||
).should.have.raised(ClientError)
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(Key=item_key, UpdateExpression="ADD str_set bad_value")
|
||||
err = exc.value.response["Error"]
|
||||
assert (
|
||||
err["Message"]
|
||||
== 'Invalid UpdateExpression: Syntax error; token: "bad_value", near: "str_set bad_value"'
|
||||
)
|
||||
|
||||
# Attempt to add a string value instead of a string set
|
||||
table.update_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key=item_key,
|
||||
UpdateExpression="ADD str_set :v",
|
||||
ExpressionAttributeValues={":v": "new_string"},
|
||||
).should.have.raised(ClientError)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert (
|
||||
err["Message"]
|
||||
== "An operand in the update expression has an incorrect data type"
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -599,10 +630,9 @@ def test_update_item_add_with_nested_sets():
|
||||
current_item["nested"]["str_set"] = current_item["nested"]["str_set"].union(
|
||||
{"item4"}
|
||||
)
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Update item to add a string value to a non-existing set
|
||||
# Should raise
|
||||
table.update_item(
|
||||
Key=item_key,
|
||||
UpdateExpression="ADD #ns.#ne :v",
|
||||
@ -610,7 +640,7 @@ def test_update_item_add_with_nested_sets():
|
||||
ExpressionAttributeValues={":v": {"new_item"}},
|
||||
)
|
||||
current_item["nested"]["non_existing_str_set"] = {"new_item"}
|
||||
assert dict(table.get_item(Key=item_key)["Item"]) == current_item
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -636,7 +666,7 @@ def test_update_item_delete_with_nested_sets():
|
||||
current_item["nested"]["str_set"] = current_item["nested"]["str_set"].difference(
|
||||
{"item3"}
|
||||
)
|
||||
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -662,7 +692,7 @@ def test_update_item_delete_with_expression():
|
||||
ExpressionAttributeValues={":v": {"item2"}},
|
||||
)
|
||||
current_item["str_set"] = current_item["str_set"].difference({"item2"})
|
||||
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Update item to delete a num value from a num set
|
||||
table.update_item(
|
||||
@ -671,28 +701,46 @@ def test_update_item_delete_with_expression():
|
||||
ExpressionAttributeValues={":v": {2}},
|
||||
)
|
||||
current_item["num_set"] = current_item["num_set"].difference({2})
|
||||
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Try to delete on a number, this should fail
|
||||
table.update_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key=item_key,
|
||||
UpdateExpression="DELETE num_val :v",
|
||||
ExpressionAttributeValues={":v": 20},
|
||||
).should.have.raised(ClientError)
|
||||
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert (
|
||||
err["Message"]
|
||||
== "Invalid UpdateExpression: Incorrect operand type for operator or function; operator or function: operator: DELETE, operand type: NUMBER"
|
||||
)
|
||||
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Try to delete a string set from a number set
|
||||
table.update_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key=item_key,
|
||||
UpdateExpression="DELETE num_set :v",
|
||||
ExpressionAttributeValues={":v": {"del_str"}},
|
||||
).should.have.raised(ClientError)
|
||||
dict(table.get_item(Key=item_key)["Item"]).should.equal(current_item)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert (
|
||||
err["Message"]
|
||||
== "An operand in the update expression has an incorrect data type"
|
||||
)
|
||||
|
||||
assert table.get_item(Key=item_key)["Item"] == current_item
|
||||
|
||||
# Attempt to update with a bad expression
|
||||
table.update_item.when.called_with(
|
||||
Key=item_key, UpdateExpression="DELETE num_val badvalue"
|
||||
).should.have.raised(ClientError)
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(Key=item_key, UpdateExpression="DELETE num_val badvalue")
|
||||
err = exc.value.response["Error"]
|
||||
assert (
|
||||
err["Message"]
|
||||
== 'Invalid UpdateExpression: Syntax error; token: "badvalue", near: "num_val badvalue"'
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -748,7 +796,7 @@ def test_boto3_query_gsi_range_comparison():
|
||||
)
|
||||
expected = ["456", "789", "123"]
|
||||
for index, item in enumerate(results["Items"]):
|
||||
item["subject"].should.equal(expected[index])
|
||||
assert item["subject"] == expected[index]
|
||||
|
||||
# Return all johndoe items again, but in reverse
|
||||
results = table.query(
|
||||
@ -757,7 +805,7 @@ def test_boto3_query_gsi_range_comparison():
|
||||
IndexName="TestGSI",
|
||||
)
|
||||
for index, item in enumerate(reversed(results["Items"])):
|
||||
item["subject"].should.equal(expected[index])
|
||||
assert item["subject"] == expected[index]
|
||||
|
||||
# Filter the creation to only return some of the results
|
||||
# And reverse order of hash + range key
|
||||
@ -766,20 +814,20 @@ def test_boto3_query_gsi_range_comparison():
|
||||
ConsistentRead=True,
|
||||
IndexName="TestGSI",
|
||||
)
|
||||
results["Count"].should.equal(2)
|
||||
assert results["Count"] == 2
|
||||
|
||||
# Filter to return no results
|
||||
results = table.query(
|
||||
KeyConditionExpression=Key("username").eq("janedoe") & Key("created").gt(9),
|
||||
IndexName="TestGSI",
|
||||
)
|
||||
results["Count"].should.equal(0)
|
||||
assert results["Count"] == 0
|
||||
|
||||
results = table.query(
|
||||
KeyConditionExpression=Key("username").eq("janedoe") & Key("created").eq(5),
|
||||
IndexName="TestGSI",
|
||||
)
|
||||
results["Count"].should.equal(1)
|
||||
assert results["Count"] == 1
|
||||
|
||||
# Test range key sorting
|
||||
results = table.query(
|
||||
@ -788,7 +836,7 @@ def test_boto3_query_gsi_range_comparison():
|
||||
)
|
||||
expected = [Decimal("1"), Decimal("2"), Decimal("3")]
|
||||
for index, item in enumerate(results["Items"]):
|
||||
item["created"].should.equal(expected[index])
|
||||
assert item["created"] == expected[index]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -810,8 +858,8 @@ def test_boto3_update_table_throughput():
|
||||
)
|
||||
table = dynamodb.Table("users")
|
||||
|
||||
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
|
||||
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
|
||||
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
|
||||
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
|
||||
|
||||
table.update(
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 10, "WriteCapacityUnits": 11}
|
||||
@ -819,8 +867,8 @@ def test_boto3_update_table_throughput():
|
||||
|
||||
table = dynamodb.Table("users")
|
||||
|
||||
table.provisioned_throughput["ReadCapacityUnits"].should.equal(10)
|
||||
table.provisioned_throughput["WriteCapacityUnits"].should.equal(11)
|
||||
assert table.provisioned_throughput["ReadCapacityUnits"] == 10
|
||||
assert table.provisioned_throughput["WriteCapacityUnits"] == 11
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -859,11 +907,11 @@ def test_boto3_update_table_gsi_throughput():
|
||||
table = dynamodb.Table("users")
|
||||
|
||||
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
|
||||
gsi_throughput["ReadCapacityUnits"].should.equal(3)
|
||||
gsi_throughput["WriteCapacityUnits"].should.equal(4)
|
||||
assert gsi_throughput["ReadCapacityUnits"] == 3
|
||||
assert gsi_throughput["WriteCapacityUnits"] == 4
|
||||
|
||||
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
|
||||
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
|
||||
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
|
||||
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
|
||||
|
||||
table.update(
|
||||
GlobalSecondaryIndexUpdates=[
|
||||
@ -882,12 +930,12 @@ def test_boto3_update_table_gsi_throughput():
|
||||
table = dynamodb.Table("users")
|
||||
|
||||
# Primary throughput has not changed
|
||||
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
|
||||
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
|
||||
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
|
||||
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
|
||||
|
||||
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
|
||||
gsi_throughput["ReadCapacityUnits"].should.equal(10)
|
||||
gsi_throughput["WriteCapacityUnits"].should.equal(11)
|
||||
assert gsi_throughput["ReadCapacityUnits"] == 10
|
||||
assert gsi_throughput["WriteCapacityUnits"] == 11
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -909,8 +957,8 @@ def test_update_table_gsi_create():
|
||||
)
|
||||
table = dynamodb.Table("users")
|
||||
|
||||
table.global_secondary_indexes.should.have.length_of(0)
|
||||
table.attribute_definitions.should.have.length_of(2)
|
||||
assert len(table.global_secondary_indexes) == 0
|
||||
assert len(table.attribute_definitions) == 2
|
||||
|
||||
table.update(
|
||||
AttributeDefinitions=[
|
||||
@ -939,12 +987,12 @@ def test_update_table_gsi_create():
|
||||
|
||||
table = dynamodb.Table("users")
|
||||
table.reload()
|
||||
table.global_secondary_indexes.should.have.length_of(1)
|
||||
table.attribute_definitions.should.have.length_of(4)
|
||||
assert len(table.global_secondary_indexes) == 1
|
||||
assert len(table.attribute_definitions) == 4
|
||||
|
||||
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
|
||||
assert gsi_throughput["ReadCapacityUnits"].should.equal(3)
|
||||
assert gsi_throughput["WriteCapacityUnits"].should.equal(4)
|
||||
assert gsi_throughput["ReadCapacityUnits"] == 3
|
||||
assert gsi_throughput["WriteCapacityUnits"] == 4
|
||||
|
||||
# Check update works
|
||||
table.update(
|
||||
@ -963,13 +1011,13 @@ def test_update_table_gsi_create():
|
||||
table = dynamodb.Table("users")
|
||||
|
||||
gsi_throughput = table.global_secondary_indexes[0]["ProvisionedThroughput"]
|
||||
assert gsi_throughput["ReadCapacityUnits"].should.equal(10)
|
||||
assert gsi_throughput["WriteCapacityUnits"].should.equal(11)
|
||||
assert gsi_throughput["ReadCapacityUnits"] == 10
|
||||
assert gsi_throughput["WriteCapacityUnits"] == 11
|
||||
|
||||
table.update(GlobalSecondaryIndexUpdates=[{"Delete": {"IndexName": "TestGSI"}}])
|
||||
|
||||
table = dynamodb.Table("users")
|
||||
table.global_secondary_indexes.should.have.length_of(0)
|
||||
assert len(table.global_secondary_indexes) == 0
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -1006,12 +1054,12 @@ def test_update_table_gsi_throughput():
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 6},
|
||||
)
|
||||
table = dynamodb.Table("users")
|
||||
table.global_secondary_indexes.should.have.length_of(1)
|
||||
assert len(table.global_secondary_indexes) == 1
|
||||
|
||||
table.update(GlobalSecondaryIndexUpdates=[{"Delete": {"IndexName": "TestGSI"}}])
|
||||
|
||||
table = dynamodb.Table("users")
|
||||
table.global_secondary_indexes.should.have.length_of(0)
|
||||
assert len(table.global_secondary_indexes) == 0
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -1028,22 +1076,21 @@ def test_query_pagination():
|
||||
)
|
||||
|
||||
page1 = table.query(KeyConditionExpression=Key("forum_name").eq("the-key"), Limit=6)
|
||||
page1["Count"].should.equal(6)
|
||||
page1["Items"].should.have.length_of(6)
|
||||
page1.should.have.key("LastEvaluatedKey")
|
||||
assert page1["Count"] == 6
|
||||
assert len(page1["Items"]) == 6
|
||||
|
||||
page2 = table.query(
|
||||
KeyConditionExpression=Key("forum_name").eq("the-key"),
|
||||
Limit=6,
|
||||
ExclusiveStartKey=page1["LastEvaluatedKey"],
|
||||
)
|
||||
page2["Count"].should.equal(4)
|
||||
page2["Items"].should.have.length_of(4)
|
||||
page2.should_not.have.key("LastEvaluatedKey")
|
||||
assert page2["Count"] == 4
|
||||
assert len(page2["Items"]) == 4
|
||||
assert "LastEvaluatedKey" not in page2
|
||||
|
||||
results = page1["Items"] + page2["Items"]
|
||||
subjects = set([int(r["subject"]) for r in results])
|
||||
subjects.should.equal(set(range(10)))
|
||||
assert subjects == set(range(10))
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -1188,7 +1235,6 @@ def test_update_item_throws_exception_when_updating_hash_or_range_key(
|
||||
ExpressionAttributeValues={":New": {"S": "2"}},
|
||||
)
|
||||
err = ex.value.response["Error"]
|
||||
err["Code"].should.equal("ValidationException")
|
||||
err["Message"].should.match(
|
||||
r"One or more parameter values were invalid: Cannot update attribute (r|h). This attribute is part of the key"
|
||||
)
|
||||
assert err["Code"] == "ValidationException"
|
||||
assert "Cannot update attribute" in err["Message"]
|
||||
assert "This attribute is part of the key" in err["Message"]
|
||||
|
@ -1,12 +1,10 @@
|
||||
import boto3
|
||||
from boto3.dynamodb.conditions import Key
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
import pytest
|
||||
from boto3.dynamodb.conditions import Key
|
||||
from datetime import datetime
|
||||
from botocore.exceptions import ClientError
|
||||
from moto import mock_dynamodb
|
||||
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
||||
import botocore
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -35,15 +33,12 @@ def test_create_table():
|
||||
|
||||
actual = client.describe_table(TableName="messages")["Table"]
|
||||
|
||||
actual.should.have.key("AttributeDefinitions").equal(
|
||||
[
|
||||
assert actual["AttributeDefinitions"] == [
|
||||
{"AttributeName": "id", "AttributeType": "S"},
|
||||
{"AttributeName": "gsi_col", "AttributeType": "S"},
|
||||
]
|
||||
)
|
||||
actual.should.have.key("CreationDateTime").be.a(datetime)
|
||||
actual.should.have.key("GlobalSecondaryIndexes").equal(
|
||||
[
|
||||
assert isinstance(actual["CreationDateTime"], datetime)
|
||||
assert actual["GlobalSecondaryIndexes"] == [
|
||||
{
|
||||
"IndexName": "test_gsi",
|
||||
"KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}],
|
||||
@ -55,21 +50,20 @@ def test_create_table():
|
||||
},
|
||||
}
|
||||
]
|
||||
assert actual["LocalSecondaryIndexes"] == []
|
||||
assert actual["ProvisionedThroughput"] == {
|
||||
"NumberOfDecreasesToday": 0,
|
||||
"ReadCapacityUnits": 1,
|
||||
"WriteCapacityUnits": 1,
|
||||
}
|
||||
assert actual["TableSizeBytes"] == 0
|
||||
assert actual["TableName"] == "messages"
|
||||
assert actual["TableStatus"] == "ACTIVE"
|
||||
assert (
|
||||
actual["TableArn"] == f"arn:aws:dynamodb:us-east-2:{ACCOUNT_ID}:table/messages"
|
||||
)
|
||||
actual.should.have.key("LocalSecondaryIndexes").equal([])
|
||||
actual.should.have.key("ProvisionedThroughput").equal(
|
||||
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 1}
|
||||
)
|
||||
actual.should.have.key("TableSizeBytes").equal(0)
|
||||
actual.should.have.key("TableName").equal("messages")
|
||||
actual.should.have.key("TableStatus").equal("ACTIVE")
|
||||
actual.should.have.key("TableArn").equal(
|
||||
f"arn:aws:dynamodb:us-east-2:{ACCOUNT_ID}:table/messages"
|
||||
)
|
||||
actual.should.have.key("KeySchema").equal(
|
||||
[{"AttributeName": "id", "KeyType": "HASH"}]
|
||||
)
|
||||
actual.should.have.key("ItemCount").equal(0)
|
||||
assert actual["KeySchema"] == [{"AttributeName": "id", "KeyType": "HASH"}]
|
||||
assert actual["ItemCount"] == 0
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -81,17 +75,17 @@ def test_delete_table():
|
||||
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||
)
|
||||
conn.list_tables()["TableNames"].should.have.length_of(1)
|
||||
assert len(conn.list_tables()["TableNames"]) == 1
|
||||
|
||||
conn.delete_table(TableName="messages")
|
||||
conn.list_tables()["TableNames"].should.have.length_of(0)
|
||||
assert conn.list_tables()["TableNames"] == []
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.delete_table(TableName="messages")
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
|
||||
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -112,11 +106,13 @@ def test_item_add_and_describe_and_update():
|
||||
|
||||
table.put_item(Item=data)
|
||||
returned_item = table.get_item(Key={"id": "LOLCat Forum"})
|
||||
returned_item.shouldnt.have.key("ConsumedCapacity")
|
||||
assert "ConsumedCapacity" not in returned_item
|
||||
|
||||
dict(returned_item["Item"]).should.equal(
|
||||
{"id": "LOLCat Forum", "Body": "http://url_to_lolcat.gif", "SentBy": "User A"}
|
||||
)
|
||||
assert returned_item["Item"] == {
|
||||
"id": "LOLCat Forum",
|
||||
"Body": "http://url_to_lolcat.gif",
|
||||
"SentBy": "User A",
|
||||
}
|
||||
|
||||
table.update_item(
|
||||
Key={"id": "LOLCat Forum"},
|
||||
@ -125,9 +121,11 @@ def test_item_add_and_describe_and_update():
|
||||
)
|
||||
|
||||
returned_item = table.get_item(Key={"id": "LOLCat Forum"})
|
||||
returned_item["Item"].should.equal(
|
||||
{"id": "LOLCat Forum", "Body": "http://url_to_lolcat.gif", "SentBy": "User B"}
|
||||
)
|
||||
assert returned_item["Item"] == {
|
||||
"id": "LOLCat Forum",
|
||||
"Body": "http://url_to_lolcat.gif",
|
||||
"SentBy": "User B",
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -144,9 +142,9 @@ def test_item_put_without_table():
|
||||
},
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
|
||||
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -156,9 +154,9 @@ def test_get_item_with_undeclared_table():
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.get_item(TableName="messages", Key={"forum_name": {"S": "LOLCat Forum"}})
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
|
||||
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -179,11 +177,11 @@ def test_delete_item():
|
||||
}
|
||||
table.put_item(Item=item_data)
|
||||
|
||||
table.item_count.should.equal(1)
|
||||
assert table.item_count == 1
|
||||
|
||||
table.delete_item(Key={"id": "LOLCat Forum"})
|
||||
|
||||
table.item_count.should.equal(0)
|
||||
assert table.item_count == 0
|
||||
|
||||
table.delete_item(Key={"id": "LOLCat Forum"})
|
||||
|
||||
@ -209,9 +207,9 @@ def test_scan_with_undeclared_table():
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.scan(TableName="messages")
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.value.response["Error"]["Message"].should.equal("Requested resource not found")
|
||||
assert ex.value.response["Error"]["Code"] == "ResourceNotFoundException"
|
||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
||||
assert ex.value.response["Error"]["Message"] == "Requested resource not found"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -224,7 +222,7 @@ def test_get_key_schema():
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||
)
|
||||
|
||||
table.key_schema.should.equal([{"AttributeName": "id", "KeyType": "HASH"}])
|
||||
assert table.key_schema == [{"AttributeName": "id", "KeyType": "HASH"}]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -259,7 +257,7 @@ def test_update_item_double_nested_remove():
|
||||
"username": {"S": "steve"},
|
||||
"Meta": {"M": {"Name": {"M": {"Last": {"S": "Urkel"}}}}},
|
||||
}
|
||||
dict(returned_item["Item"]).should.equal(expected_item)
|
||||
assert returned_item["Item"] == expected_item
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -283,7 +281,7 @@ def test_update_item_set():
|
||||
)
|
||||
|
||||
returned_item = table.get_item(Key=key_map)["Item"]
|
||||
dict(returned_item).should.equal({"username": "steve", "foo": "bar", "blah": "baz"})
|
||||
assert returned_item == {"username": "steve", "foo": "bar", "blah": "baz"}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -296,7 +294,7 @@ def test_create_table__using_resource():
|
||||
AttributeDefinitions=[{"AttributeName": "username", "AttributeType": "S"}],
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||
)
|
||||
table.name.should.equal("users")
|
||||
assert table.name == "users"
|
||||
|
||||
|
||||
def _create_user_table():
|
||||
@ -319,9 +317,8 @@ def test_conditions():
|
||||
table.put_item(Item={"username": "janedoe"})
|
||||
|
||||
response = table.query(KeyConditionExpression=Key("username").eq("johndoe"))
|
||||
response["Count"].should.equal(1)
|
||||
response["Items"].should.have.length_of(1)
|
||||
response["Items"][0].should.equal({"username": "johndoe"})
|
||||
assert response["Count"] == 1
|
||||
assert response["Items"] == [{"username": "johndoe"}]
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -333,7 +330,7 @@ def test_put_item_conditions_pass():
|
||||
Expected={"foo": {"ComparisonOperator": "EQ", "AttributeValueList": ["bar"]}},
|
||||
)
|
||||
final_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(final_item)["Item"]["foo"].should.equal("baz")
|
||||
assert final_item["Item"]["foo"] == "baz"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -345,7 +342,7 @@ def test_put_item_conditions_pass_because_expect_not_exists_by_compare_to_null()
|
||||
Expected={"whatever": {"ComparisonOperator": "NULL"}},
|
||||
)
|
||||
final_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(final_item)["Item"]["foo"].should.equal("baz")
|
||||
assert final_item["Item"]["foo"] == "baz"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -357,53 +354,67 @@ def test_put_item_conditions_pass_because_expect_exists_by_compare_to_not_null()
|
||||
Expected={"foo": {"ComparisonOperator": "NOT_NULL"}},
|
||||
)
|
||||
final_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(final_item)["Item"]["foo"].should.equal("baz")
|
||||
assert final_item["Item"]["foo"] == "baz"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
def test_put_item_conditions_fail():
|
||||
table = _create_user_table()
|
||||
table.put_item(Item={"username": "johndoe", "foo": "bar"})
|
||||
table.put_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.put_item(
|
||||
Item={"username": "johndoe", "foo": "baz"},
|
||||
Expected={"foo": {"ComparisonOperator": "NE", "AttributeValueList": ["bar"]}},
|
||||
).should.throw(botocore.client.ClientError)
|
||||
Expected={
|
||||
"foo": {"ComparisonOperator": "NE", "AttributeValueList": ["bar"]}
|
||||
},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ConditionalCheckFailedException"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
def test_update_item_conditions_fail():
|
||||
table = _create_user_table()
|
||||
table.put_item(Item={"username": "johndoe", "foo": "baz"})
|
||||
table.update_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key={"username": "johndoe"},
|
||||
UpdateExpression="SET foo=:bar",
|
||||
Expected={"foo": {"Value": "bar"}},
|
||||
ExpressionAttributeValues={":bar": "bar"},
|
||||
).should.throw(botocore.client.ClientError)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ConditionalCheckFailedException"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
def test_update_item_conditions_fail_because_expect_not_exists():
|
||||
table = _create_user_table()
|
||||
table.put_item(Item={"username": "johndoe", "foo": "baz"})
|
||||
table.update_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key={"username": "johndoe"},
|
||||
UpdateExpression="SET foo=:bar",
|
||||
Expected={"foo": {"Exists": False}},
|
||||
ExpressionAttributeValues={":bar": "bar"},
|
||||
).should.throw(botocore.client.ClientError)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ConditionalCheckFailedException"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
def test_update_item_conditions_fail_because_expect_not_exists_by_compare_to_null():
|
||||
table = _create_user_table()
|
||||
table.put_item(Item={"username": "johndoe", "foo": "baz"})
|
||||
table.update_item.when.called_with(
|
||||
with pytest.raises(ClientError) as exc:
|
||||
table.update_item(
|
||||
Key={"username": "johndoe"},
|
||||
UpdateExpression="SET foo=:bar",
|
||||
Expected={"foo": {"ComparisonOperator": "NULL"}},
|
||||
ExpressionAttributeValues={":bar": "bar"},
|
||||
).should.throw(botocore.client.ClientError)
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ConditionalCheckFailedException"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -417,7 +428,7 @@ def test_update_item_conditions_pass():
|
||||
ExpressionAttributeValues={":baz": "baz"},
|
||||
)
|
||||
returned_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
|
||||
assert returned_item["Item"]["foo"] == "baz"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -431,7 +442,7 @@ def test_update_item_conditions_pass_because_expect_not_exists():
|
||||
ExpressionAttributeValues={":baz": "baz"},
|
||||
)
|
||||
returned_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
|
||||
assert returned_item["Item"]["foo"] == "baz"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -445,7 +456,7 @@ def test_update_item_conditions_pass_because_expect_not_exists_by_compare_to_nul
|
||||
ExpressionAttributeValues={":baz": "baz"},
|
||||
)
|
||||
returned_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
|
||||
assert returned_item["Item"]["foo"] == "baz"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -459,7 +470,7 @@ def test_update_item_conditions_pass_because_expect_exists_by_compare_to_not_nul
|
||||
ExpressionAttributeValues={":baz": "baz"},
|
||||
)
|
||||
returned_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(returned_item)["Item"]["foo"].should.equal("baz")
|
||||
assert returned_item["Item"]["foo"] == "baz"
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -496,7 +507,7 @@ def test_update_settype_item_with_conditions():
|
||||
},
|
||||
)
|
||||
returned_item = table.get_item(Key={"username": "johndoe"})
|
||||
assert dict(returned_item)["Item"]["foo"].should.equal(set(["baz"]))
|
||||
assert returned_item["Item"]["foo"] == set(["baz"])
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -508,18 +519,17 @@ def test_scan_pagination():
|
||||
table.put_item(Item={"username": u})
|
||||
|
||||
page1 = table.scan(Limit=6)
|
||||
page1["Count"].should.equal(6)
|
||||
page1["Items"].should.have.length_of(6)
|
||||
page1.should.have.key("LastEvaluatedKey")
|
||||
assert page1["Count"] == 6
|
||||
assert len(page1["Items"]) == 6
|
||||
|
||||
page2 = table.scan(Limit=6, ExclusiveStartKey=page1["LastEvaluatedKey"])
|
||||
page2["Count"].should.equal(4)
|
||||
page2["Items"].should.have.length_of(4)
|
||||
page2.should_not.have.key("LastEvaluatedKey")
|
||||
assert page2["Count"] == 4
|
||||
assert len(page2["Items"]) == 4
|
||||
assert "LastEvaluatedKey" not in page2
|
||||
|
||||
results = page1["Items"] + page2["Items"]
|
||||
usernames = set([r["username"] for r in results])
|
||||
usernames.should.equal(set(expected_usernames))
|
||||
assert usernames == set(expected_usernames)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
|
@ -1,5 +1,4 @@
|
||||
import boto3
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
from moto import mock_dynamodb
|
||||
|
||||
|
||||
@ -27,6 +26,7 @@ def test_update_different_map_elements_in_single_request():
|
||||
ExpressionAttributeValues={":h": "H", ":w": "W"},
|
||||
ReturnValues="ALL_NEW",
|
||||
)
|
||||
updated["Attributes"].should.equal(
|
||||
{"id": "example_id", "d": {"hello": "H", "world": "W"}}
|
||||
)
|
||||
assert updated["Attributes"] == {
|
||||
"id": "example_id",
|
||||
"d": {"hello": "H", "world": "W"},
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import boto3
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
|
||||
from moto import mock_dynamodb
|
||||
|
||||
@ -27,10 +26,11 @@ def test_update_table__billing_mode():
|
||||
)
|
||||
|
||||
actual = client.describe_table(TableName="test")["Table"]
|
||||
actual.should.have.key("BillingModeSummary").equals({"BillingMode": "PROVISIONED"})
|
||||
actual.should.have.key("ProvisionedThroughput").equals(
|
||||
{"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}
|
||||
)
|
||||
assert actual["BillingModeSummary"] == {"BillingMode": "PROVISIONED"}
|
||||
assert actual["ProvisionedThroughput"] == {
|
||||
"ReadCapacityUnits": 1,
|
||||
"WriteCapacityUnits": 1,
|
||||
}
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
@ -42,15 +42,15 @@ def test_update_table_throughput():
|
||||
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||
)
|
||||
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
|
||||
table.provisioned_throughput["WriteCapacityUnits"].should.equal(5)
|
||||
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
|
||||
assert table.provisioned_throughput["WriteCapacityUnits"] == 5
|
||||
|
||||
table.update(
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 6}
|
||||
)
|
||||
|
||||
table.provisioned_throughput["ReadCapacityUnits"].should.equal(5)
|
||||
table.provisioned_throughput["WriteCapacityUnits"].should.equal(6)
|
||||
assert table.provisioned_throughput["ReadCapacityUnits"] == 5
|
||||
assert table.provisioned_throughput["WriteCapacityUnits"] == 6
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
|
@ -1,5 +1,4 @@
|
||||
import json
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
from moto import mock_dynamodb
|
||||
import moto.server as server
|
||||
|
||||
@ -13,22 +12,20 @@ def test_table_list():
|
||||
backend = server.create_backend_app("dynamodb")
|
||||
test_client = backend.test_client()
|
||||
res = test_client.get("/")
|
||||
res.status_code.should.equal(404)
|
||||
assert res.status_code == 404
|
||||
|
||||
headers = {"X-Amz-Target": "TestTable.ListTables"}
|
||||
res = test_client.get("/", headers=headers)
|
||||
res.data.should.contain(b"TableNames")
|
||||
res.headers.should.have.key("X-Amz-Crc32")
|
||||
assert b"TableNames" in res.data
|
||||
assert "X-Amz-Crc32" in res.headers
|
||||
|
||||
headers = {"X-Amz-Target": "DynamoDB_20120810.DescribeTable"}
|
||||
res = test_client.post(
|
||||
"/", headers=headers, data=json.dumps({"TableName": "test-table2"})
|
||||
)
|
||||
res.headers.should.have.key("X-Amzn-ErrorType").equals("ResourceNotFoundException")
|
||||
assert res.headers["X-Amzn-ErrorType"] == "ResourceNotFoundException"
|
||||
body = json.loads(res.data.decode("utf-8"))
|
||||
body.should.equal(
|
||||
{
|
||||
assert body == {
|
||||
"__type": "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",
|
||||
"message": "Requested resource not found: Table: test-table2 not found",
|
||||
}
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user