DynamoDB: transact_write_items() should raise ValidationException when putting and deleting the same item (#7378)

This commit is contained in:
Khanh Le 2024-02-23 02:06:51 +07:00 committed by GitHub
parent cfbc275e02
commit 7c26f9f831
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 74 additions and 17 deletions

View File

@ -579,7 +579,11 @@ class DynamoDBBackend(BaseBackend):
item = item["Put"]
attrs = item["Item"]
table_name = item["TableName"]
check_unicity(table_name, item)
table = self.get_table(table_name)
key = {table.hash_key_attr: attrs[table.hash_key_attr]}
if table.range_key_attr is not None:
key[table.range_key_attr] = attrs[table.range_key_attr]
check_unicity(table_name, key)
condition_expression = item.get("ConditionExpression", None)
expression_attribute_names = item.get(
"ExpressionAttributeNames", None

View File

@ -46,6 +46,55 @@ def test_multiple_transactions_on_same_item():
)
@mock_aws
def test_transact_write_items__put_and_delete_on_same_item():
schema = {
"KeySchema": [
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"},
],
"AttributeDefinitions": [
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
],
}
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **schema
)
with pytest.raises(ClientError) as exc:
dynamodb.transact_write_items(
TransactItems=[
{
"Put": {
"TableName": "test-table",
"Item": {
"pk": {"S": "test-pk"},
"sk": {"S": "test-sk"},
"field": {"S": "test-field"},
},
}
},
{
"Delete": {
"TableName": "test-table",
"Key": {
"pk": {"S": "test-pk"},
"sk": {"S": "test-sk"},
},
}
},
]
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "Transaction request cannot include multiple operations on one item"
)
@mock_aws
def test_transact_write_items__too_many_transactions():
schema = {

View File

@ -3856,24 +3856,27 @@ def test_transact_write_items_conditioncheck_passes():
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item without email address
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
# Put an email address, after verifying it doesn't exist yet
# Insert an item with email address
dynamodb.put_item(
TableName="test-table",
Item={"id": {"S": "foo"}, "email_address": {"S": "foo@moto.com"}},
)
# Put a new item, after verifying the exising item also has email address
dynamodb.transact_write_items(
TransactItems=[
{
"ConditionCheck": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"ConditionExpression": "attribute_not_exists(#e)",
"ConditionExpression": "attribute_exists(#e)",
"ExpressionAttributeNames": {"#e": "email_address"},
}
},
{
"Put": {
"Item": {
"id": {"S": "foo"},
"email_address": {"S": "test@moto.com"},
"id": {"S": "bar"},
"email_address": {"S": "bar@moto.com"},
},
"TableName": "test-table",
}
@ -3882,8 +3885,9 @@ def test_transact_write_items_conditioncheck_passes():
)
# Assert all are present
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 1
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
assert len(items) == 2
assert items[0] == {"email_address": {"S": "foo@moto.com"}, "id": {"S": "foo"}}
assert items[1] == {"email_address": {"S": "bar@moto.com"}, "id": {"S": "bar"}}
@mock_aws
@ -3896,12 +3900,12 @@ def test_transact_write_items_conditioncheck_fails():
dynamodb.create_table(
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
)
# Insert an item with email address
# Insert an item without email address
dynamodb.put_item(
TableName="test-table",
Item={"id": {"S": "foo"}, "email_address": {"S": "test@moto.com"}},
Item={"id": {"S": "foo"}},
)
# Try to put an email address, but verify whether it exists
# Try putting a new item, after verifying the exising item also has email address
# ConditionCheck should fail
with pytest.raises(ClientError) as ex:
dynamodb.transact_write_items(
@ -3910,15 +3914,15 @@ def test_transact_write_items_conditioncheck_fails():
"ConditionCheck": {
"Key": {"id": {"S": "foo"}},
"TableName": "test-table",
"ConditionExpression": "attribute_not_exists(#e)",
"ConditionExpression": "attribute_exists(#e)",
"ExpressionAttributeNames": {"#e": "email_address"},
}
},
{
"Put": {
"Item": {
"id": {"S": "foo"},
"email_address": {"S": "update@moto.com"},
"id": {"S": "bar"},
"email_address": {"S": "bar@moto.com"},
},
"TableName": "test-table",
}
@ -3929,10 +3933,10 @@ def test_transact_write_items_conditioncheck_fails():
assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
# Assert the original email address is still present
# Assert the original item is still present
items = dynamodb.scan(TableName="test-table")["Items"]
assert len(items) == 1
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
assert items[0] == {"id": {"S": "foo"}}
@mock_aws