From 696b809b5ac251c41281684d783ea5583898d0eb Mon Sep 17 00:00:00 2001 From: rafcio19 Date: Tue, 4 Oct 2022 14:18:14 +0100 Subject: [PATCH] Improve DDB transaction validation (#5521) --- moto/dynamodb/exceptions.py | 9 +++++ moto/dynamodb/models/__init__.py | 7 ++++ .../exceptions/test_dynamodb_exceptions.py | 39 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py index 973a4721f..9f572764f 100644 --- a/moto/dynamodb/exceptions.py +++ b/moto/dynamodb/exceptions.py @@ -323,3 +323,12 @@ class InvalidConversion(JsonRESTError): def __init__(self): er = "SerializationException" super().__init__(er, "NUMBER_VALUE cannot be converted to String") + + +class TransactWriteSingleOpException(MockValidationException): + there_can_be_only_one = ( + "TransactItems can only contain one of Check, Put, Update or Delete" + ) + + def __init__(self): + super().__init__(self.there_can_be_only_one) diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py index c0f5f4354..830e833d8 100644 --- a/moto/dynamodb/models/__init__.py +++ b/moto/dynamodb/models/__init__.py @@ -32,6 +32,7 @@ from moto.dynamodb.exceptions import ( StreamAlreadyEnabledException, MockValidationException, InvalidConversion, + TransactWriteSingleOpException, ) from moto.dynamodb.models.utilities import bytesize from moto.dynamodb.models.dynamo_type import DynamoType @@ -1656,6 +1657,11 @@ class DynamoDBBackend(BaseBackend): errors = [] # [(Code, Message, Item), ..] for item in transact_items: + # check transact writes are not performing multiple operations + # in the same item + if len(list(item.keys())) > 1: + raise TransactWriteSingleOpException + try: if "ConditionCheck" in item: item = item["ConditionCheck"] @@ -1682,6 +1688,7 @@ class DynamoDBBackend(BaseBackend): item = item["Put"] attrs = item["Item"] table_name = item["TableName"] + check_unicity(table_name, item) condition_expression = item.get("ConditionExpression", None) expression_attribute_names = item.get( "ExpressionAttributeNames", None diff --git a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py index 047d569b5..ba1f6d293 100644 --- a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py +++ b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py @@ -761,3 +761,42 @@ def test_query_begins_with_without_brackets(): 'Invalid KeyConditionExpression: Syntax error; token: "sk"' ) err["Code"].should.equal("ValidationException") + + +@mock_dynamodb +def test_transact_write_items_multiple_operations_fail(): + + # Setup + table_schema = { + "KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}], + "AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}], + } + dynamodb = boto3.client("dynamodb", region_name="us-east-1") + table_name = "test-table" + dynamodb.create_table( + TableName=table_name, BillingMode="PAY_PER_REQUEST", **table_schema + ) + + # Execute + with pytest.raises(ClientError) as exc: + dynamodb.transact_write_items( + TransactItems=[ + { + "Put": { + "Item": {"id": {"S": "test"}}, + "TableName": table_name, + }, + "Delete": { + "Key": {"id": {"S": "test"}}, + "TableName": table_name, + }, + } + ] + ) + # Verify + err = exc.value.response["Error"] + assert err["Code"] == "ValidationException" + assert ( + err["Message"] + == "TransactItems can only contain one of Check, Put, Update or Delete" + )