-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DynamoDB: transact_write_items() now validates empty keys on Update-q…
…ueries (#7089)
- Loading branch information
Showing
3 changed files
with
194 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -497,80 +497,6 @@ def test_creating_table_with_0_global_indexes(): | |
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_multiple_transactions_on_same_item(): | ||
schema = { | ||
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}], | ||
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}], | ||
} | ||
dynamodb = boto3.client("dynamodb", region_name="us-east-1") | ||
dynamodb.create_table( | ||
TableName="test-table", BillingMode="PAY_PER_REQUEST", **schema | ||
) | ||
# Insert an item | ||
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}}) | ||
|
||
def update_email_transact(email): | ||
return { | ||
"Update": { | ||
"Key": {"id": {"S": "foo"}}, | ||
"TableName": "test-table", | ||
"UpdateExpression": "SET #e = :v", | ||
"ExpressionAttributeNames": {"#e": "email_address"}, | ||
"ExpressionAttributeValues": {":v": {"S": email}}, | ||
} | ||
} | ||
|
||
with pytest.raises(ClientError) as exc: | ||
dynamodb.transact_write_items( | ||
TransactItems=[ | ||
update_email_transact("[email protected]"), | ||
update_email_transact("[email protected]"), | ||
] | ||
) | ||
err = exc.value.response["Error"] | ||
assert err["Code"] == "ValidationException" | ||
assert ( | ||
err["Message"] | ||
== "Transaction request cannot include multiple operations on one item" | ||
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_transact_write_items__too_many_transactions(): | ||
schema = { | ||
"KeySchema": [{"AttributeName": "pk", "KeyType": "HASH"}], | ||
"AttributeDefinitions": [{"AttributeName": "pk", "AttributeType": "S"}], | ||
} | ||
dynamodb = boto3.client("dynamodb", region_name="us-east-1") | ||
dynamodb.create_table( | ||
TableName="test-table", BillingMode="PAY_PER_REQUEST", **schema | ||
) | ||
|
||
def update_email_transact(email): | ||
return { | ||
"Put": { | ||
"TableName": "test-table", | ||
"Item": {"pk": {"S": ":v"}}, | ||
"ExpressionAttributeValues": {":v": {"S": email}}, | ||
} | ||
} | ||
|
||
update_email_transact("[email protected]") | ||
with pytest.raises(ClientError) as exc: | ||
dynamodb.transact_write_items( | ||
TransactItems=[ | ||
update_email_transact(f"test{idx}@moto.com") for idx in range(101) | ||
] | ||
) | ||
err = exc.value.response["Error"] | ||
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." | ||
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_update_item_non_existent_table(): | ||
client = boto3.client("dynamodb", region_name="us-west-2") | ||
|
@@ -816,82 +742,6 @@ def test_query_begins_with_without_brackets(): | |
assert err["Code"] == "ValidationException" | ||
|
||
|
||
@mock_dynamodb | ||
def test_transact_write_items_multiple_operations_fail(): | ||
# Setup | ||
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", **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" | ||
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_transact_write_items_with_empty_gsi_key(): | ||
client = boto3.client("dynamodb", "us-east-2") | ||
|
||
client.create_table( | ||
TableName="test_table", | ||
KeySchema=[{"AttributeName": "unique_code", "KeyType": "HASH"}], | ||
AttributeDefinitions=[ | ||
{"AttributeName": "unique_code", "AttributeType": "S"}, | ||
{"AttributeName": "unique_id", "AttributeType": "S"}, | ||
], | ||
GlobalSecondaryIndexes=[ | ||
{ | ||
"IndexName": "gsi_index", | ||
"KeySchema": [{"AttributeName": "unique_id", "KeyType": "HASH"}], | ||
"Projection": {"ProjectionType": "ALL"}, | ||
} | ||
], | ||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, | ||
) | ||
|
||
transact_items = [ | ||
{ | ||
"Put": { | ||
"Item": {"unique_code": {"S": "some code"}, "unique_id": {"S": ""}}, | ||
"TableName": "test_table", | ||
} | ||
} | ||
] | ||
|
||
with pytest.raises(ClientError) as exc: | ||
client.transact_write_items(TransactItems=transact_items) | ||
err = exc.value.response["Error"] | ||
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" | ||
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_update_primary_key_with_sortkey(): | ||
dynamodb = boto3.resource("dynamodb", region_name="us-east-1") | ||
|
186 changes: 186 additions & 0 deletions
186
tests/test_dynamodb/exceptions/test_dynamodb_transactions.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import boto3 | ||
import pytest | ||
from botocore.exceptions import ClientError | ||
|
||
from moto import mock_dynamodb | ||
|
||
from .. import dynamodb_aws_verified | ||
|
||
|
||
@mock_dynamodb | ||
def test_multiple_transactions_on_same_item(): | ||
schema = { | ||
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}], | ||
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}], | ||
} | ||
dynamodb = boto3.client("dynamodb", region_name="us-east-1") | ||
dynamodb.create_table( | ||
TableName="test-table", BillingMode="PAY_PER_REQUEST", **schema | ||
) | ||
# Insert an item | ||
dynamodb.put_item(TableName="test-table", Item={"id": {"S": "foo"}}) | ||
|
||
def update_email_transact(email): | ||
return { | ||
"Update": { | ||
"Key": {"id": {"S": "foo"}}, | ||
"TableName": "test-table", | ||
"UpdateExpression": "SET #e = :v", | ||
"ExpressionAttributeNames": {"#e": "email_address"}, | ||
"ExpressionAttributeValues": {":v": {"S": email}}, | ||
} | ||
} | ||
|
||
with pytest.raises(ClientError) as exc: | ||
dynamodb.transact_write_items( | ||
TransactItems=[ | ||
update_email_transact("[email protected]"), | ||
update_email_transact("[email protected]"), | ||
] | ||
) | ||
err = exc.value.response["Error"] | ||
assert err["Code"] == "ValidationException" | ||
assert ( | ||
err["Message"] | ||
== "Transaction request cannot include multiple operations on one item" | ||
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_transact_write_items__too_many_transactions(): | ||
schema = { | ||
"KeySchema": [{"AttributeName": "pk", "KeyType": "HASH"}], | ||
"AttributeDefinitions": [{"AttributeName": "pk", "AttributeType": "S"}], | ||
} | ||
dynamodb = boto3.client("dynamodb", region_name="us-east-1") | ||
dynamodb.create_table( | ||
TableName="test-table", BillingMode="PAY_PER_REQUEST", **schema | ||
) | ||
|
||
def update_email_transact(email): | ||
return { | ||
"Put": { | ||
"TableName": "test-table", | ||
"Item": {"pk": {"S": ":v"}}, | ||
"ExpressionAttributeValues": {":v": {"S": email}}, | ||
} | ||
} | ||
|
||
update_email_transact("[email protected]") | ||
with pytest.raises(ClientError) as exc: | ||
dynamodb.transact_write_items( | ||
TransactItems=[ | ||
update_email_transact(f"test{idx}@moto.com") for idx in range(101) | ||
] | ||
) | ||
err = exc.value.response["Error"] | ||
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." | ||
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_transact_write_items_multiple_operations_fail(): | ||
# Setup | ||
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", **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" | ||
) | ||
|
||
|
||
@mock_dynamodb | ||
def test_transact_write_items_with_empty_gsi_key(): | ||
client = boto3.client("dynamodb", "us-east-2") | ||
|
||
client.create_table( | ||
TableName="test_table", | ||
KeySchema=[{"AttributeName": "unique_code", "KeyType": "HASH"}], | ||
AttributeDefinitions=[ | ||
{"AttributeName": "unique_code", "AttributeType": "S"}, | ||
{"AttributeName": "unique_id", "AttributeType": "S"}, | ||
], | ||
GlobalSecondaryIndexes=[ | ||
{ | ||
"IndexName": "gsi_index", | ||
"KeySchema": [{"AttributeName": "unique_id", "KeyType": "HASH"}], | ||
"Projection": {"ProjectionType": "ALL"}, | ||
} | ||
], | ||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, | ||
) | ||
|
||
transact_items = [ | ||
{ | ||
"Put": { | ||
"Item": {"unique_code": {"S": "some code"}, "unique_id": {"S": ""}}, | ||
"TableName": "test_table", | ||
} | ||
} | ||
] | ||
|
||
with pytest.raises(ClientError) as exc: | ||
client.transact_write_items(TransactItems=transact_items) | ||
err = exc.value.response["Error"] | ||
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" | ||
) | ||
|
||
|
||
@pytest.mark.aws_verified | ||
@dynamodb_aws_verified() | ||
def test_transaction_with_empty_key(table_name=None): | ||
dynamodb = boto3.resource("dynamodb", region_name="us-east-1") | ||
client = boto3.client("dynamodb", region_name="us-east-1") | ||
table = dynamodb.Table(table_name) | ||
table.put_item(Item={"pk": "mark", "lock": {"acquired_at": 123}}) | ||
|
||
with pytest.raises(ClientError) as exc: | ||
client.transact_write_items( | ||
TransactItems=[ | ||
{ | ||
"Update": { | ||
"TableName": table_name, | ||
"Key": {"pk": {"S": ""}}, | ||
"UpdateExpression": "SET sth = :v", | ||
"ExpressionAttributeValues": {":v": {"N": "0"}}, | ||
} | ||
} | ||
] | ||
) | ||
err = exc.value.response["Error"] | ||
assert err["Code"] == "ValidationException" | ||
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" | ||
) |