Add support for empty strings in non-key dynamo attributes (#3467)

* Add support for empty strings in non-key attributes

https://github.com/spulec/moto/issues/3339

* Nose, not pytest

* Revert "Nose, not pytest"

This reverts commit 5a3cf6c887dd9fafa49096c82cfa3a3b7f91d224.

* PUT is default action
This commit is contained in:
Rich Unger 2020-11-17 01:12:39 -08:00 committed by GitHub
commit f045af7e0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 257 additions and 52 deletions

View file

@ -0,0 +1,13 @@
import pytest
from moto.dynamodb2.models import Table
@pytest.fixture
def table():
return Table(
"Forums",
schema=[
{"KeyType": "HASH", "AttributeName": "forum_name"},
{"KeyType": "RANGE", "AttributeName": "subject"},
],
)

View file

@ -186,7 +186,7 @@ def test_list_not_found_table_tags():
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_item_add_empty_string_exception():
def test_item_add_empty_string_in_key_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
@ -205,10 +205,10 @@ def test_item_add_empty_string_exception():
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"forum_name": {"S": ""},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": ""},
"SentBy": {"S": "someone@somewhere.edu"},
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
},
)
@ -222,7 +222,36 @@ def test_item_add_empty_string_exception():
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_update_item_with_empty_string_exception():
def test_item_add_empty_string_no_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": ""},
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
},
)
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_update_item_with_empty_string_in_key_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
@ -252,8 +281,8 @@ def test_update_item_with_empty_string_exception():
conn.update_item(
TableName=name,
Key={"forum_name": {"S": "LOLCat Forum"}},
UpdateExpression="set Body=:Body",
ExpressionAttributeValues={":Body": {"S": ""}},
UpdateExpression="set forum_name=:NewName",
ExpressionAttributeValues={":NewName": {"S": ""}},
)
ex.value.response["Error"]["Code"].should.equal("ValidationException")
@ -263,6 +292,42 @@ def test_update_item_with_empty_string_exception():
)
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_update_item_with_empty_string_no_exception():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "test"},
"ReceivedTime": {"S": "12/9/2011 11:36:03 PM"},
},
)
conn.update_item(
TableName=name,
Key={"forum_name": {"S": "LOLCat Forum"}},
UpdateExpression="set Body=:Body",
ExpressionAttributeValues={":Body": {"S": ""}},
)
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_query_invalid_table():

View file

@ -7,7 +7,7 @@ from moto.dynamodb2.parsing.expressions import UpdateExpressionParser
from moto.dynamodb2.parsing.validators import UpdateExpressionValidator
def test_execution_of_if_not_exists_not_existing_value():
def test_execution_of_if_not_exists_not_existing_value(table):
update_expression = "SET a = if_not_exists(b, a)"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -22,6 +22,7 @@ def test_execution_of_if_not_exists_not_existing_value():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -34,7 +35,9 @@ def test_execution_of_if_not_exists_not_existing_value():
assert expected_item == item
def test_execution_of_if_not_exists_with_existing_attribute_should_return_attribute():
def test_execution_of_if_not_exists_with_existing_attribute_should_return_attribute(
table,
):
update_expression = "SET a = if_not_exists(b, a)"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -49,6 +52,7 @@ def test_execution_of_if_not_exists_with_existing_attribute_should_return_attrib
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -61,7 +65,7 @@ def test_execution_of_if_not_exists_with_existing_attribute_should_return_attrib
assert expected_item == item
def test_execution_of_if_not_exists_with_existing_attribute_should_return_value():
def test_execution_of_if_not_exists_with_existing_attribute_should_return_value(table):
update_expression = "SET a = if_not_exists(b, :val)"
update_expression_values = {":val": {"N": "4"}}
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -77,6 +81,7 @@ def test_execution_of_if_not_exists_with_existing_attribute_should_return_value(
expression_attribute_names=None,
expression_attribute_values=update_expression_values,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -89,7 +94,9 @@ def test_execution_of_if_not_exists_with_existing_attribute_should_return_value(
assert expected_item == item
def test_execution_of_if_not_exists_with_non_existing_attribute_should_return_value():
def test_execution_of_if_not_exists_with_non_existing_attribute_should_return_value(
table,
):
update_expression = "SET a = if_not_exists(b, :val)"
update_expression_values = {":val": {"N": "4"}}
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -105,6 +112,7 @@ def test_execution_of_if_not_exists_with_non_existing_attribute_should_return_va
expression_attribute_names=None,
expression_attribute_values=update_expression_values,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -117,7 +125,7 @@ def test_execution_of_if_not_exists_with_non_existing_attribute_should_return_va
assert expected_item == item
def test_execution_of_sum_operation():
def test_execution_of_sum_operation(table):
update_expression = "SET a = a + b"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -132,6 +140,7 @@ def test_execution_of_sum_operation():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -144,7 +153,7 @@ def test_execution_of_sum_operation():
assert expected_item == item
def test_execution_of_remove():
def test_execution_of_remove(table):
update_expression = "Remove a"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -159,6 +168,7 @@ def test_execution_of_remove():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -171,7 +181,7 @@ def test_execution_of_remove():
assert expected_item == item
def test_execution_of_remove_in_map():
def test_execution_of_remove_in_map(table):
update_expression = "Remove itemmap.itemlist[1].foo11"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -198,6 +208,7 @@ def test_execution_of_remove_in_map():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -222,7 +233,7 @@ def test_execution_of_remove_in_map():
assert expected_item == item
def test_execution_of_remove_in_list():
def test_execution_of_remove_in_list(table):
update_expression = "Remove itemmap.itemlist[1]"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -249,6 +260,7 @@ def test_execution_of_remove_in_list():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -270,7 +282,7 @@ def test_execution_of_remove_in_list():
assert expected_item == item
def test_execution_of_delete_element_from_set():
def test_execution_of_delete_element_from_set(table):
update_expression = "delete s :value"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -285,6 +297,7 @@ def test_execution_of_delete_element_from_set():
expression_attribute_names=None,
expression_attribute_values={":value": {"SS": ["value2", "value5"]}},
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -297,7 +310,7 @@ def test_execution_of_delete_element_from_set():
assert expected_item == item
def test_execution_of_add_number():
def test_execution_of_add_number(table):
update_expression = "add s :value"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -312,6 +325,7 @@ def test_execution_of_add_number():
expression_attribute_names=None,
expression_attribute_values={":value": {"N": "10"}},
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -324,7 +338,7 @@ def test_execution_of_add_number():
assert expected_item == item
def test_execution_of_add_set_to_a_number():
def test_execution_of_add_set_to_a_number(table):
update_expression = "add s :value"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -340,6 +354,7 @@ def test_execution_of_add_set_to_a_number():
expression_attribute_names=None,
expression_attribute_values={":value": {"SS": ["s1"]}},
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -355,7 +370,7 @@ def test_execution_of_add_set_to_a_number():
assert True
def test_execution_of_add_to_a_set():
def test_execution_of_add_to_a_set(table):
update_expression = "ADD s :value"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -370,6 +385,7 @@ def test_execution_of_add_to_a_set():
expression_attribute_names=None,
expression_attribute_values={":value": {"SS": ["value2", "value5"]}},
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
expected_item = Item(
@ -398,7 +414,7 @@ def test_execution_of_add_to_a_set():
],
)
def test_execution_of__delete_element_from_set_invalid_value(
expression_attribute_values, unexpected_data_type
expression_attribute_values, unexpected_data_type, table
):
"""A delete statement must use a value of type SS in order to delete elements from a set."""
update_expression = "delete s :value"
@ -416,6 +432,7 @@ def test_execution_of__delete_element_from_set_invalid_value(
expression_attribute_names=None,
expression_attribute_values=expression_attribute_values,
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
assert False, "Must raise exception"
@ -424,7 +441,7 @@ def test_execution_of__delete_element_from_set_invalid_value(
assert e.operand_type == unexpected_data_type
def test_execution_of_delete_element_from_a_string_attribute():
def test_execution_of_delete_element_from_a_string_attribute(table):
"""A delete statement must use a value of type SS in order to delete elements from a set."""
update_expression = "delete s :value"
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -441,6 +458,7 @@ def test_execution_of_delete_element_from_a_string_attribute():
expression_attribute_names=None,
expression_attribute_values={":value": {"SS": ["value2"]}},
item=item,
table=table,
).validate()
UpdateExpressionExecutor(validated_ast, item, None).execute()
assert False, "Must raise exception"

View file

@ -7,6 +7,7 @@ from moto.dynamodb2.exceptions import (
ExpressionAttributeNameNotDefined,
IncorrectOperandType,
InvalidUpdateExpressionInvalidDocumentPath,
EmptyKeyAttributeException,
)
from moto.dynamodb2.models import Item, DynamoType
from moto.dynamodb2.parsing.ast_nodes import (
@ -18,7 +19,28 @@ from moto.dynamodb2.parsing.expressions import UpdateExpressionParser
from moto.dynamodb2.parsing.validators import UpdateExpressionValidator
def test_validation_of_update_expression_with_keyword():
def test_validation_of_empty_string_key_val(table):
with pytest.raises(EmptyKeyAttributeException):
update_expression = "set forum_name=:NewName"
update_expression_values = {":NewName": {"S": ""}}
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
hash_key=DynamoType({"S": "forum_name"}),
hash_key_type="TYPE",
range_key=None,
range_key_type=None,
attrs={"forum_name": {"S": "hello"}},
)
UpdateExpressionValidator(
update_expression_ast,
expression_attribute_names=None,
expression_attribute_values=update_expression_values,
item=item,
table=table,
).validate()
def test_validation_of_update_expression_with_keyword(table):
try:
update_expression = "SET myNum = path + :val"
update_expression_values = {":val": {"N": "3"}}
@ -35,6 +57,7 @@ def test_validation_of_update_expression_with_keyword():
expression_attribute_names=None,
expression_attribute_values=update_expression_values,
item=item,
table=table,
).validate()
assert False, "No exception raised"
except AttributeIsReservedKeyword as e:
@ -44,7 +67,9 @@ def test_validation_of_update_expression_with_keyword():
@pytest.mark.parametrize(
"update_expression", ["SET a = #b + :val2", "SET a = :val2 + #b",]
)
def test_validation_of_a_set_statement_with_incorrect_passed_value(update_expression):
def test_validation_of_a_set_statement_with_incorrect_passed_value(
update_expression, table
):
"""
By running permutations it shows that values are replaced prior to resolving attributes.
@ -65,12 +90,15 @@ def test_validation_of_a_set_statement_with_incorrect_passed_value(update_expres
expression_attribute_names={"#b": "ok"},
expression_attribute_values={":val": {"N": "3"}},
item=item,
table=table,
).validate()
except ExpressionAttributeValueNotDefined as e:
assert e.attribute_value == ":val2"
def test_validation_of_update_expression_with_attribute_that_does_not_exist_in_item():
def test_validation_of_update_expression_with_attribute_that_does_not_exist_in_item(
table,
):
"""
When an update expression tries to get an attribute that does not exist it must throw the appropriate exception.
@ -92,6 +120,7 @@ def test_validation_of_update_expression_with_attribute_that_does_not_exist_in_i
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
assert False, "No exception raised"
except AttributeDoesNotExist:
@ -100,7 +129,7 @@ def test_validation_of_update_expression_with_attribute_that_does_not_exist_in_i
@pytest.mark.parametrize("update_expression", ["SET a = #c", "SET a = #c + #d",])
def test_validation_of_update_expression_with_attribute_name_that_is_not_defined(
update_expression,
update_expression, table,
):
"""
When an update expression tries to get an attribute name that is not provided it must throw an exception.
@ -122,13 +151,14 @@ def test_validation_of_update_expression_with_attribute_name_that_is_not_defined
expression_attribute_names={"#b": "ok"},
expression_attribute_values=None,
item=item,
table=table,
).validate()
assert False, "No exception raised"
except ExpressionAttributeNameNotDefined as e:
assert e.not_defined_attribute_name == "#c"
def test_validation_of_if_not_exists_not_existing_invalid_replace_value():
def test_validation_of_if_not_exists_not_existing_invalid_replace_value(table):
try:
update_expression = "SET a = if_not_exists(b, a.c)"
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -144,6 +174,7 @@ def test_validation_of_if_not_exists_not_existing_invalid_replace_value():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
assert False, "No exception raised"
except AttributeDoesNotExist:
@ -172,7 +203,7 @@ def get_set_action_value(ast):
return dynamo_value
def test_validation_of_if_not_exists_not_existing_value():
def test_validation_of_if_not_exists_not_existing_value(table):
update_expression = "SET a = if_not_exists(b, a)"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -187,12 +218,15 @@ def test_validation_of_if_not_exists_not_existing_value():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"S": "A"})
def test_validation_of_if_not_exists_with_existing_attribute_should_return_attribute():
def test_validation_of_if_not_exists_with_existing_attribute_should_return_attribute(
table,
):
update_expression = "SET a = if_not_exists(b, a)"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -207,12 +241,13 @@ def test_validation_of_if_not_exists_with_existing_attribute_should_return_attri
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"S": "B"})
def test_validation_of_if_not_exists_with_existing_attribute_should_return_value():
def test_validation_of_if_not_exists_with_existing_attribute_should_return_value(table):
update_expression = "SET a = if_not_exists(b, :val)"
update_expression_values = {":val": {"N": "4"}}
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -228,12 +263,15 @@ def test_validation_of_if_not_exists_with_existing_attribute_should_return_value
expression_attribute_names=None,
expression_attribute_values=update_expression_values,
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"N": "3"})
def test_validation_of_if_not_exists_with_non_existing_attribute_should_return_value():
def test_validation_of_if_not_exists_with_non_existing_attribute_should_return_value(
table,
):
update_expression = "SET a = if_not_exists(b, :val)"
update_expression_values = {":val": {"N": "4"}}
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -249,12 +287,13 @@ def test_validation_of_if_not_exists_with_non_existing_attribute_should_return_v
expression_attribute_names=None,
expression_attribute_values=update_expression_values,
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"N": "4"})
def test_validation_of_sum_operation():
def test_validation_of_sum_operation(table):
update_expression = "SET a = a + b"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -269,12 +308,13 @@ def test_validation_of_sum_operation():
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"N": "7"})
def test_validation_homogeneous_list_append_function():
def test_validation_homogeneous_list_append_function(table):
update_expression = "SET ri = list_append(ri, :vals)"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -289,6 +329,7 @@ def test_validation_homogeneous_list_append_function():
expression_attribute_names=None,
expression_attribute_values={":vals": {"L": [{"S": "i3"}, {"S": "i4"}]}},
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType(
@ -296,7 +337,7 @@ def test_validation_homogeneous_list_append_function():
)
def test_validation_hetereogenous_list_append_function():
def test_validation_hetereogenous_list_append_function(table):
update_expression = "SET ri = list_append(ri, :vals)"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -311,12 +352,13 @@ def test_validation_hetereogenous_list_append_function():
expression_attribute_names=None,
expression_attribute_values={":vals": {"L": [{"N": "3"}]}},
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"L": [{"S": "i1"}, {"S": "i2"}, {"N": "3"}]})
def test_validation_list_append_function_with_non_list_arg():
def test_validation_list_append_function_with_non_list_arg(table):
"""
Must error out:
Invalid UpdateExpression: Incorrect operand type for operator or function;
@ -339,13 +381,14 @@ def test_validation_list_append_function_with_non_list_arg():
expression_attribute_names=None,
expression_attribute_values={":vals": {"S": "N"}},
item=item,
table=table,
).validate()
except IncorrectOperandType as e:
assert e.operand_type == "S"
assert e.operator_or_function == "list_append"
def test_sum_with_incompatible_types():
def test_sum_with_incompatible_types(table):
"""
Must error out:
Invalid UpdateExpression: Incorrect operand type for operator or function; operator or function: +, operand type: S'
@ -367,13 +410,14 @@ def test_sum_with_incompatible_types():
expression_attribute_names=None,
expression_attribute_values={":val": {"S": "N"}, ":val2": {"N": "3"}},
item=item,
table=table,
).validate()
except IncorrectOperandType as e:
assert e.operand_type == "S"
assert e.operator_or_function == "+"
def test_validation_of_subraction_operation():
def test_validation_of_subraction_operation(table):
update_expression = "SET ri = :val - :val2"
update_expression_ast = UpdateExpressionParser.make(update_expression)
item = Item(
@ -388,12 +432,13 @@ def test_validation_of_subraction_operation():
expression_attribute_names=None,
expression_attribute_values={":val": {"N": "1"}, ":val2": {"N": "3"}},
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"N": "-2"})
def test_cannot_index_into_a_string():
def test_cannot_index_into_a_string(table):
"""
Must error out:
The document path provided in the update expression is invalid for update'
@ -413,13 +458,16 @@ def test_cannot_index_into_a_string():
expression_attribute_names=None,
expression_attribute_values={":Item": {"S": "string_update"}},
item=item,
table=table,
).validate()
assert False, "Must raise exception"
except InvalidUpdateExpressionInvalidDocumentPath:
assert True
def test_validation_set_path_does_not_need_to_be_resolvable_when_setting_a_new_attribute():
def test_validation_set_path_does_not_need_to_be_resolvable_when_setting_a_new_attribute(
table,
):
"""If this step just passes we are happy enough"""
update_expression = "set d=a"
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -435,12 +483,15 @@ def test_validation_set_path_does_not_need_to_be_resolvable_when_setting_a_new_a
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
dynamo_value = get_set_action_value(validated_ast)
assert dynamo_value == DynamoType({"N": "3"})
def test_validation_set_path_does_not_need_to_be_resolvable_but_must_be_creatable_when_setting_a_new_attribute():
def test_validation_set_path_does_not_need_to_be_resolvable_but_must_be_creatable_when_setting_a_new_attribute(
table,
):
try:
update_expression = "set d.e=a"
update_expression_ast = UpdateExpressionParser.make(update_expression)
@ -456,6 +507,7 @@ def test_validation_set_path_does_not_need_to_be_resolvable_but_must_be_creatabl
expression_attribute_names=None,
expression_attribute_values=None,
item=item,
table=table,
).validate()
assert False, "Must raise exception"
except InvalidUpdateExpressionInvalidDocumentPath: