DynamoDB: transact_write_items() should raise ValidationException when putting and deleting the same item (#7378)
This commit is contained in:
parent
cfbc275e02
commit
7c26f9f831
@ -579,7 +579,11 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
item = item["Put"]
|
item = item["Put"]
|
||||||
attrs = item["Item"]
|
attrs = item["Item"]
|
||||||
table_name = item["TableName"]
|
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)
|
condition_expression = item.get("ConditionExpression", None)
|
||||||
expression_attribute_names = item.get(
|
expression_attribute_names = item.get(
|
||||||
"ExpressionAttributeNames", None
|
"ExpressionAttributeNames", None
|
||||||
|
@ -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
|
@mock_aws
|
||||||
def test_transact_write_items__too_many_transactions():
|
def test_transact_write_items__too_many_transactions():
|
||||||
schema = {
|
schema = {
|
||||||
|
@ -3856,24 +3856,27 @@ def test_transact_write_items_conditioncheck_passes():
|
|||||||
dynamodb.create_table(
|
dynamodb.create_table(
|
||||||
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
|
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
|
||||||
)
|
)
|
||||||
# Insert an item without email address
|
# Insert an item with email address
|
||||||
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}})
|
dynamodb.put_item(
|
||||||
# Put an email address, after verifying it doesn't exist yet
|
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(
|
dynamodb.transact_write_items(
|
||||||
TransactItems=[
|
TransactItems=[
|
||||||
{
|
{
|
||||||
"ConditionCheck": {
|
"ConditionCheck": {
|
||||||
"Key": {"id": {"S": "foo"}},
|
"Key": {"id": {"S": "foo"}},
|
||||||
"TableName": "test-table",
|
"TableName": "test-table",
|
||||||
"ConditionExpression": "attribute_not_exists(#e)",
|
"ConditionExpression": "attribute_exists(#e)",
|
||||||
"ExpressionAttributeNames": {"#e": "email_address"},
|
"ExpressionAttributeNames": {"#e": "email_address"},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Put": {
|
"Put": {
|
||||||
"Item": {
|
"Item": {
|
||||||
"id": {"S": "foo"},
|
"id": {"S": "bar"},
|
||||||
"email_address": {"S": "test@moto.com"},
|
"email_address": {"S": "bar@moto.com"},
|
||||||
},
|
},
|
||||||
"TableName": "test-table",
|
"TableName": "test-table",
|
||||||
}
|
}
|
||||||
@ -3882,8 +3885,9 @@ def test_transact_write_items_conditioncheck_passes():
|
|||||||
)
|
)
|
||||||
# Assert all are present
|
# Assert all are present
|
||||||
items = dynamodb.scan(TableName="test-table")["Items"]
|
items = dynamodb.scan(TableName="test-table")["Items"]
|
||||||
assert len(items) == 1
|
assert len(items) == 2
|
||||||
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
|
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
|
@mock_aws
|
||||||
@ -3896,12 +3900,12 @@ def test_transact_write_items_conditioncheck_fails():
|
|||||||
dynamodb.create_table(
|
dynamodb.create_table(
|
||||||
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
|
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
|
||||||
)
|
)
|
||||||
# Insert an item with email address
|
# Insert an item without email address
|
||||||
dynamodb.put_item(
|
dynamodb.put_item(
|
||||||
TableName="test-table",
|
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
|
# ConditionCheck should fail
|
||||||
with pytest.raises(ClientError) as ex:
|
with pytest.raises(ClientError) as ex:
|
||||||
dynamodb.transact_write_items(
|
dynamodb.transact_write_items(
|
||||||
@ -3910,15 +3914,15 @@ def test_transact_write_items_conditioncheck_fails():
|
|||||||
"ConditionCheck": {
|
"ConditionCheck": {
|
||||||
"Key": {"id": {"S": "foo"}},
|
"Key": {"id": {"S": "foo"}},
|
||||||
"TableName": "test-table",
|
"TableName": "test-table",
|
||||||
"ConditionExpression": "attribute_not_exists(#e)",
|
"ConditionExpression": "attribute_exists(#e)",
|
||||||
"ExpressionAttributeNames": {"#e": "email_address"},
|
"ExpressionAttributeNames": {"#e": "email_address"},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Put": {
|
"Put": {
|
||||||
"Item": {
|
"Item": {
|
||||||
"id": {"S": "foo"},
|
"id": {"S": "bar"},
|
||||||
"email_address": {"S": "update@moto.com"},
|
"email_address": {"S": "bar@moto.com"},
|
||||||
},
|
},
|
||||||
"TableName": "test-table",
|
"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["Error"]["Code"] == "TransactionCanceledException"
|
||||||
assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
|
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"]
|
items = dynamodb.scan(TableName="test-table")["Items"]
|
||||||
assert len(items) == 1
|
assert len(items) == 1
|
||||||
assert items[0] == {"email_address": {"S": "test@moto.com"}, "id": {"S": "foo"}}
|
assert items[0] == {"id": {"S": "foo"}}
|
||||||
|
|
||||||
|
|
||||||
@mock_aws
|
@mock_aws
|
||||||
|
Loading…
Reference in New Issue
Block a user