Drop the AGPL license in favor of a source-available license. See the blog post [1] for details. [1] https://www.scylladb.com/2024/12/18/why-were-moving-to-a-source-available-license/
1188 lines
63 KiB
Python
1188 lines
63 KiB
Python
# Copyright 2019-present ScyllaDB
|
|
#
|
|
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
|
|
|
# Tests for the "Expected" parameter used to make certain operations (PutItem,
|
|
# UpdateItem and DeleteItem) conditional on the existing attribute values.
|
|
# "Expected" is the older version of ConditionExpression parameter, which
|
|
# is tested by the separate test_condition_expression.py.
|
|
|
|
import pytest
|
|
from botocore.exceptions import ClientError
|
|
from test.alternator.util import random_string
|
|
|
|
# Most of the tests in this file check that the "Expected" parameter works for
|
|
# the UpdateItem operation. It should also work the same for the PutItem and
|
|
# DeleteItem operations, and we'll make a small effort verifying that at
|
|
# the end of the file.
|
|
|
|
# Somewhat pedanticly, DynamoDB forbids using old-style Expected together
|
|
# with new-style UpdateExpression... Expected can only be used with
|
|
# AttributeUpdates (and for UpdateExpression, ConditionExpression should be
|
|
# used).
|
|
def test_update_expression_and_expected(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
UpdateExpression='SET a = :val1',
|
|
ExpressionAttributeValues={':val1': 1})
|
|
with pytest.raises(ClientError, match='ValidationException.*UpdateExpression'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
UpdateExpression='SET a = :val1',
|
|
ExpressionAttributeValues={':val1': 2},
|
|
Expected={'a': {'ComparisonOperator': 'EQ',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
|
|
# The following string of tests test the various types of Expected conditions
|
|
# on a single attribute. This condition is defined using ComparisonOperator
|
|
# (there are many types of those!) or by Value or Exists, and we need to check
|
|
# all these types of conditions.
|
|
#
|
|
# In each case we have tests for the "true" case of the condition, meaning
|
|
# that the condition evaluates to true and the update is supposed to happen,
|
|
# and the "false" case, where the condition evaluates to false, so the update
|
|
# doesn't happen and we get a ConditionalCheckFailedException instead.
|
|
|
|
# Tests for Expected with ComparisonOperator = "EQ":
|
|
def test_update_expected_1_eq_true(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
# Case where expected and update are on the same attribute:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'EQ',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 2}
|
|
# Case where expected and update are on different attribute:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'EQ',
|
|
'AttributeValueList': [2]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 2, 'b': 3}
|
|
# For EQ, AttributeValueList must have a single element
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'EQ',
|
|
'AttributeValueList': [2, 3]}}
|
|
)
|
|
|
|
# Check that set equality is checked correctly. Unlike string equality (for
|
|
# example), it cannot be done with just naive string comparison of the JSON
|
|
# representation, and we need to allow for any order.
|
|
def test_update_expected_1_eq_set(test_table_s):
|
|
p = random_string()
|
|
# Because boto3 sorts the set values we give it, in order to generate a
|
|
# set with a different order, we need to build it incrementally.
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': set(['dog', 'chinchilla']), 'Action': 'PUT'}})
|
|
test_table_s.update_item(Key={'p': p},
|
|
UpdateExpression='ADD a :val1',
|
|
ExpressionAttributeValues={':val1': set(['cat', 'mouse'])})
|
|
# Sanity check - the attribute contains the set we think it does
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['a'] == set(['chinchilla', 'cat', 'dog', 'mouse'])
|
|
# Now finally check that "Expected"'s equality check knows the equality too.
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'EQ',
|
|
'AttributeValueList': [set(['chinchilla', 'cat', 'dog', 'mouse'])]}}
|
|
)
|
|
assert 'b' in test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']
|
|
|
|
def test_update_expected_1_eq_false(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'EQ',
|
|
'AttributeValueList': [2]}}
|
|
)
|
|
# If the compared value has a different type, it results in the
|
|
# condition failing normally (it's not a validation error).
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'EQ',
|
|
'AttributeValueList': ['dog']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1}
|
|
|
|
# Tests for Expected with ComparisonOperator = "NE":
|
|
def test_update_expected_1_ne_true(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NE',
|
|
'AttributeValueList': [2]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 3}
|
|
# For NE, AttributeValueList must have a single element
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NE',
|
|
'AttributeValueList': [2, 3]}}
|
|
)
|
|
# If the types are different, this is considered "not equal":
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NE',
|
|
'AttributeValueList': ["1"]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 4}
|
|
# If the attribute does not exist at all, this is also considered "not equal":
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'q': {'ComparisonOperator': 'NE',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 5}
|
|
|
|
def test_update_expected_1_ne_false(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NE',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "LE":
|
|
def test_update_expected_1_le(test_table_s):
|
|
p = random_string()
|
|
# LE should work for string, number, and binary type
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 'cat', 'Action': 'PUT'},
|
|
'c': {'Value': bytearray('cat', 'utf-8'), 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [2]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': ['dog']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [bytearray('dog', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [0]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': ['aardvark']}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [bytearray('aardvark', 'utf-8')]}}
|
|
)
|
|
# If the types are different, this is also considered false
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': ["1"]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# For LE, AttributeValueList must have a single element
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [2, 3]}}
|
|
)
|
|
|
|
# Comparison operators like le work only on numbers, strings or bytes.
|
|
# As noted in issue #8043, if any other type is included in *the query*,
|
|
# the result should be a ValidationException, but if the wrong type appears
|
|
# in the item, not the query, the result is a failed condition.
|
|
def test_update_expected_1_le_validation(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': [1,2], 'Action': 'PUT'}})
|
|
# Bad type (a list) in the query. Result is ValidationException.
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [[1,2,3]]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [3]}}
|
|
)
|
|
assert not 'z' in test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']
|
|
|
|
# Tests for Expected with ComparisonOperator = "LT":
|
|
def test_update_expected_1_lt(test_table_s):
|
|
p = random_string()
|
|
# LT should work for string, number, and binary type
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 'cat', 'Action': 'PUT'},
|
|
'c': {'Value': bytearray('cat', 'utf-8'), 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': [2]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': ['dog']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': [bytearray('dog', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': [0]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': ['aardvark']}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': [bytearray('aardvark', 'utf-8')]}}
|
|
)
|
|
# If the types are different, this is also considered false
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': ["1"]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# For LT, AttributeValueList must have a single element
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': [2, 3]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "GE":
|
|
def test_update_expected_1_ge(test_table_s):
|
|
p = random_string()
|
|
# GE should work for string, number, and binary type
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 'cat', 'Action': 'PUT'},
|
|
'c': {'Value': bytearray('cat', 'utf-8'), 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': [0]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': ['aardvark']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': [bytearray('aardvark', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': [3]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': ['dog']}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': [bytearray('dog', 'utf-8')]}}
|
|
)
|
|
# If the types are different, this is also considered false
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': ["1"]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# For GE, AttributeValueList must have a single element
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': [2, 3]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "GT":
|
|
def test_update_expected_1_gt(test_table_s):
|
|
p = random_string()
|
|
# GT should work for string, number, and binary type
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 'cat', 'Action': 'PUT'},
|
|
'c': {'Value': bytearray('cat', 'utf-8'), 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': [0]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': ['aardvark']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': [bytearray('aardvark', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': [3]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': [1]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': ['dog']}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': [bytearray('dog', 'utf-8')]}}
|
|
)
|
|
# If the types are different, this is also considered false
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': ["1"]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# For GE, AttributeValueList must have a single element
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': [2, 3]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "NOT_NULL":
|
|
def test_update_expected_1_not_null(test_table_s):
|
|
# Note that despite its name, the "NOT_NULL" comparison operator doesn't check if
|
|
# the attribute has the type "NULL", or an empty value. Rather it is explicitly
|
|
# documented to check if the attribute exists at all.
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 'cat', 'Action': 'PUT'},
|
|
'c': {'Value': None, 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_NULL', 'AttributeValueList': []}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'NOT_NULL', 'AttributeValueList': []}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'NOT_NULL', 'AttributeValueList': []}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'q': {'ComparisonOperator': 'NOT_NULL', 'AttributeValueList': []}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
# For NOT_NULL, AttributeValueList must be empty
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_NULL', 'AttributeValueList': [2]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "NULL":
|
|
def test_update_expected_1_null(test_table_s):
|
|
# Note that despite its name, the "NULL" comparison operator doesn't check if
|
|
# the attribute has the type "NULL", or an empty value. Rather it is explicitly
|
|
# documented to check if the attribute exists at all.
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 'cat', 'Action': 'PUT'},
|
|
'c': {'Value': None, 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'q': {'ComparisonOperator': 'NULL', 'AttributeValueList': []}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NULL', 'AttributeValueList': []}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'NULL', 'AttributeValueList': []}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'NULL', 'AttributeValueList': []}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
# For NULL, AttributeValueList must be empty
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NULL', 'AttributeValueList': [2]}}
|
|
)
|
|
|
|
# When ComparisonOperator = "NULL", AttributeValueList should be empty if it
|
|
# exists, but as this test verifies, it may also be missing completely.
|
|
def test_update_expected_1_null_missing_list(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NULL'}})
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['a'] == 2
|
|
|
|
# Tests for Expected with ComparisonOperator = "CONTAINS":
|
|
def test_update_expected_1_contains(test_table_s):
|
|
# true cases. CONTAINS can be used for two unrelated things: check substrings
|
|
# (in string or binary) and membership (in set or list).
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 'hello', 'Action': 'PUT'},
|
|
'b': {'Value': set([2, 4, 7]), 'Action': 'PUT'},
|
|
'c': {'Value': [2, 4, 7], 'Action': 'PUT'},
|
|
'd': {'Value': bytearray('hi there', 'utf-8'), 'Action': 'PUT'}})
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': ['ell']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [4]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
# The CONTAINS documentation uses confusing wording on whether it works
|
|
# only on sets, or also on lists. In fact, it does work on lists:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [4]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'d': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [bytearray('here', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': ['dog']}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'q': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'d': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [bytearray('dog', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
# For CONTAINS, AttributeValueList must have just one item, and it must be
|
|
# a string, number or binary
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [2, 3]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': []}}
|
|
)
|
|
# Strangely, while ConditionExpression's contains() allows the argument
|
|
# to be of any type and checks if the attribute is perhaps a list
|
|
# containing that item, Expected's "CONTAINS" is more limited, and
|
|
# refuses a list as the argument (to be searched in a list of lists)
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'CONTAINS', 'AttributeValueList': [[1]]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "NOT_CONTAINS":
|
|
def test_update_expected_1_not_contains(test_table_s):
|
|
# true cases. NOT_CONTAINS can be used for two unrelated things: check substrings
|
|
# (in string or binary) and membership (in set or list).
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 'hello', 'Action': 'PUT'},
|
|
'b': {'Value': set([2, 4, 7]), 'Action': 'PUT'},
|
|
'c': {'Value': [2, 4, 7], 'Action': 'PUT'},
|
|
'd': {'Value': bytearray('hi there', 'utf-8'), 'Action': 'PUT'}})
|
|
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': ['dog']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 7, 'Action': 'PUT'}},
|
|
Expected={'d': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [bytearray('dog', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 7
|
|
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': ['ell']}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [4]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [4]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'d': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [bytearray('here', 'utf-8')]}}
|
|
)
|
|
# Surprisingly, if an attribute does not exist at all, NOT_CONTAINS
|
|
# fails, rather than succeeding. This is surprising because it means in
|
|
# this case both CONTAINS and NOT_CONTAINS are false, and because "NE" does not
|
|
# behave this way (if the attribute does not exist, NE succeeds).
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'q': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [1]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 7
|
|
# For NOT_CONTAINS, AttributeValueList must have just one item, and it must be
|
|
# a string, number or binary
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [2, 3]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': []}}
|
|
)
|
|
# Strangely, while ConditionExpression's contains() allows the argument
|
|
# to be of any type and checks if the attribute is perhaps a list
|
|
# containing that item, Expected's "CONTAINS" is more limited, and
|
|
# refuses a list as the argument (to be searched in a list of lists)
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'NOT_CONTAINS', 'AttributeValueList': [[1]]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "BEGINS_WITH":
|
|
def test_update_expected_1_begins_with_true(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 'hello', 'Action': 'PUT'},
|
|
'd': {'Value': bytearray('hi there', 'utf-8'), 'Action': 'PUT'}})
|
|
# Case where expected and update are on different attribute:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BEGINS_WITH',
|
|
'AttributeValueList': ['hell']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['b'] == 3
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'d': {'ComparisonOperator': 'BEGINS_WITH',
|
|
'AttributeValueList': [bytearray('hi', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['b'] == 4
|
|
# For BEGINS_WITH, AttributeValueList must have a single element
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BEGINS_WITH',
|
|
'AttributeValueList': ['hell', 'heaven']}}
|
|
)
|
|
|
|
def test_update_expected_1_begins_with_false(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 'hello', 'Action': 'PUT'},
|
|
'x': {'Value': 3, 'Action': 'PUT'}})
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BEGINS_WITH',
|
|
'AttributeValueList': ['dog']}}
|
|
)
|
|
# BEGINS_WITH requires String or Binary operand, giving it a number
|
|
# results with a ValidationException (not a normal failed condition):
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BEGINS_WITH',
|
|
'AttributeValueList': [3]}}
|
|
)
|
|
# However, if we try to compare the attribute to a String or Binary, and
|
|
# the attribute value itself is a number, this is just a failed condition:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'x': {'ComparisonOperator': 'BEGINS_WITH',
|
|
'AttributeValueList': ['dog']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 'hello', 'x': 3}
|
|
|
|
# Tests for Expected with ComparisonOperator = "IN":
|
|
def test_update_expected_1_in(test_table_s):
|
|
# Some copies of "IN"'s documentation are outright wrong: "IN" checks
|
|
# whether the attribute value is in the give list of values. It does NOT
|
|
# do the opposite - testing whether certain items are in a set attribute.
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': set([2, 4, 7]), 'Action': 'PUT'},
|
|
'c': {'Value': 3, 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'IN', 'AttributeValueList': [2, 3, 8]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'IN', 'AttributeValueList': [1, 2, 4]}}
|
|
)
|
|
# a bunch of wrong interpretations of what the heck that "IN" does :-(
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'IN', 'AttributeValueList': [2]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'IN', 'AttributeValueList': [1, 2, 4, 7, 8]}}
|
|
)
|
|
# Strangely, all the items in AttributeValueList must be of the same type,
|
|
# we can't check if an item is either the number 3 or the string 'dog',
|
|
# although allowing this case as well would have been easy:
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'IN', 'AttributeValueList': [3, 'dog']}}
|
|
)
|
|
# Empty list is not allowed
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'IN', 'AttributeValueList': []}}
|
|
)
|
|
# Non-scalar attribute values are not allowed
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'IN', 'AttributeValueList': [[1], [2]]}}
|
|
)
|
|
|
|
# Tests for Expected with ComparisonOperator = "BETWEEN":
|
|
def test_update_expected_1_between(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 2, 'Action': 'PUT'},
|
|
'b': {'Value': 'cat', 'Action': 'PUT'},
|
|
'c': {'Value': bytearray('cat', 'utf-8'), 'Action': 'PUT'},
|
|
'd': {'Value': set([2, 4, 7]), 'Action': 'PUT'}})
|
|
# true cases:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [1, 3]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [1, 2]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [2, 3]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': ['aardvark', 'dog']}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 6, 'Action': 'PUT'}},
|
|
Expected={'c': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [bytearray('aardvark', 'utf-8'), bytearray('dog', 'utf-8')]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 6
|
|
# false cases:
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [0, 1]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': ['cat', 'dog']}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'q': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [0, 100]}})
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 6
|
|
# The given AttributeValueList array must contain exactly two items of the
|
|
# same type, and in the right order. Any other input is considered a validation
|
|
# error:
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': []}})
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [2, 3, 4]}})
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [4, 3]}})
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': ['dog', 'aardvark']}})
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [4, 'dog']}})
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'d': {'ComparisonOperator': 'BETWEEN', 'AttributeValueList': [set([1]), set([2])]}})
|
|
|
|
# BETWEEN work only on numbers, strings or bytes. As noted in issue #8043,
|
|
# if any other type is included in *the query*, the result should be a
|
|
# ValidationException, but if the wrong type appears in the item, not the
|
|
# query, the result is a failed condition.
|
|
# BETWEEN should also generate ValidationException if the two ends of the
|
|
# range are not of the same type or not in the correct order, but this
|
|
# already is tested in the test above (test_update_expected_1_between).
|
|
def test_update_expected_1_between_validation(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': [1,2], 'Action': 'PUT'}})
|
|
# Bad type (a list) in the query. Result is ValidationException.
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'a': {'ComparisonOperator': 'BETWEEN',
|
|
'AttributeValueList': [[1,2,3], [2,3,4]]}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 17, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'BETWEEN',
|
|
'AttributeValueList': [1,2]}}
|
|
)
|
|
assert not 'z' in test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']
|
|
|
|
|
|
##############################################################################
|
|
# Instead of ComparisonOperator and AttributeValueList, one can specify either
|
|
# Value or Exists:
|
|
def test_update_expected_1_value_true(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'Value': 1}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 2}
|
|
|
|
def test_update_expected_1_value_false(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'Value': 2}}
|
|
)
|
|
# If the expected attribute is completely missing, the condition also fails
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'z': {'Value': 1}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1}
|
|
|
|
def test_update_expected_1_exists_true(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
# Surprisingly, the "Exists: True" cannot be used to confirm that the
|
|
# attribute had *any* old value (use the NOT_NULL comparison operator
|
|
# for that). It can only be used together with "Value", and in that case
|
|
# doesn't mean a thing.
|
|
# Only "Exists: False" has an interesting meaning.
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True}}
|
|
)
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'c': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 1}}
|
|
)
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'d': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'z': {'Exists': False}}
|
|
)
|
|
# Exists: False cannot be used together with a Value:
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'c': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': False, 'Value': 1}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'c': 3, 'd': 4}
|
|
|
|
def test_update_expected_1_exists_false(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': False}}
|
|
)
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 2}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1}
|
|
|
|
# Test that it's not allowed to combine ComparisonOperator and Exists or Value
|
|
def test_update_expected_operator_clash(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': False, 'ComparisonOperator': 'EQ', 'AttributeValueList': [3]}})
|
|
with pytest.raises(ClientError, match='ValidationException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'b': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'a': {'Value': 3, 'ComparisonOperator': 'EQ', 'AttributeValueList': [3]}})
|
|
|
|
# All the previous tests involved a single condition on a single attribute.
|
|
# The following tests involving multiple conditions on multiple attributes.
|
|
# ConditionalOperator defaults to AND, and can also be set to OR.
|
|
|
|
def test_update_expected_multi_true(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 2, 'Action': 'PUT'}})
|
|
# Test several conditions with default "AND" operator
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 1},
|
|
'b': {'ComparisonOperator': 'EQ', 'AttributeValueList': [2]},
|
|
'c': {'Exists': False}})
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 2, 'z': 3}
|
|
# Same with explicit "AND" operator
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 1},
|
|
'b': {'ComparisonOperator': 'EQ', 'AttributeValueList': [2]},
|
|
'c': {'Exists': False}},
|
|
ConditionalOperator="AND")
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 2, 'z': 4}
|
|
# With "OR" operator, it's enough that just one conditions is true
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 74},
|
|
'b': {'ComparisonOperator': 'EQ', 'AttributeValueList': [999]},
|
|
'c': {'Exists': False}},
|
|
ConditionalOperator="OR")
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 2, 'z': 5}
|
|
|
|
def test_update_expected_multi_false(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'},
|
|
'b': {'Value': 2, 'Action': 'PUT'},
|
|
'c': {'Value': 3, 'Action': 'PUT'}})
|
|
# Test several conditions, one of them false, with default "AND" operator
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 1},
|
|
'b': {'ComparisonOperator': 'EQ', 'AttributeValueList': [3]},
|
|
'd': {'Exists': False}})
|
|
# Same with explicit "AND" operator
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 1},
|
|
'b': {'ComparisonOperator': 'EQ', 'AttributeValueList': [3]},
|
|
'd': {'Exists': False}},
|
|
ConditionalOperator="AND")
|
|
# With "OR" operator, all the conditions need to be false to fail
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'a': {'Exists': True, 'Value': 74},
|
|
'b': {'ComparisonOperator': 'EQ', 'AttributeValueList': [999]},
|
|
'c': {'Exists': False}},
|
|
ConditionalOperator='OR')
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'b': 2, 'c': 3}
|
|
|
|
# Verify the behaviour of an empty Expected parameter:
|
|
def test_update_expected_empty(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
# An empty Expected array results in a successful update:
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={})
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1, 'z': 3}
|
|
# Trying with ConditionalOperator complains that you can't have
|
|
# ConditionalOperator without Expected (despite Expected existing, though empty).
|
|
with pytest.raises(ClientError, match='ValidationException.*ConditionalOperator'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={}, ConditionalOperator='OR')
|
|
with pytest.raises(ClientError, match='ValidationException.*ConditionalOperator'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={}, ConditionalOperator='AND')
|
|
|
|
# Specifying ConditionalOperator is forbidden if the "Expected" Attribute
|
|
# is missing:
|
|
def test_conditional_operator_expected_missing(test_table_s):
|
|
p = random_string()
|
|
with pytest.raises(ClientError, match='ValidationException.*ConditionalOperator'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
ConditionalOperator='OR')
|
|
with pytest.raises(ClientError, match='ValidationException.*ConditionalOperator'):
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
ConditionalOperator='AND')
|
|
|
|
# All of the above tests tested "Expected" with the UpdateItem operation.
|
|
# We now want to test that it works also with the PutItem and DeleteItems
|
|
# operations. We don't need to check again all the different sub-cases tested
|
|
# above - we can assume that exactly the same code gets used to test the
|
|
# expected value. So we just need one test for each operation, to verify that
|
|
# this code actually gets called.
|
|
|
|
def test_delete_item_expected(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.delete_item(Key={'p': p}, Expected={'a': {'Value': 2}})
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 1}
|
|
test_table_s.delete_item(Key={'p': p}, Expected={'a': {'Value': 1}})
|
|
assert not 'Item' in test_table_s.get_item(Key={'p': p}, ConsistentRead=True)
|
|
|
|
def test_put_item_expected(test_table_s):
|
|
p = random_string()
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'a': {'Value': 1, 'Action': 'PUT'}})
|
|
test_table_s.put_item(Item={'p': p, 'a': 2}, Expected={'a': {'Value': 1}})
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item'] == {'p': p, 'a': 2}
|
|
with pytest.raises(ClientError, match='ConditionalCheckFailedException'):
|
|
test_table_s.put_item(Item={'p': p, 'a': 3}, Expected={'a': {'Value': 1}})
|
|
|
|
# Reproducer for issue #6573: binary strings should be ordered as unsigned
|
|
# bytes, i.e., byte 128 comes after 127, not before as with signed bytes.
|
|
# Test the five ordering operators: LT, LE, GT, GE, BETWEEN
|
|
def test_update_expected_unsigned_bytes(test_table_s):
|
|
p = random_string()
|
|
test_table_s.put_item(Item={'p': p, 'b': bytearray([127])})
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 1, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'LT',
|
|
'AttributeValueList': [bytearray([128])]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 1
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 2, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'LE',
|
|
'AttributeValueList': [bytearray([128])]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 2
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 3, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'BETWEEN',
|
|
'AttributeValueList': [bytearray([126]), bytearray([128])]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 3
|
|
|
|
test_table_s.put_item(Item={'p': p, 'b': bytearray([128])})
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 4, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'GT',
|
|
'AttributeValueList': [bytearray([127])]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 4
|
|
test_table_s.update_item(Key={'p': p},
|
|
AttributeUpdates={'z': {'Value': 5, 'Action': 'PUT'}},
|
|
Expected={'b': {'ComparisonOperator': 'GE',
|
|
'AttributeValueList': [bytearray([127])]}}
|
|
)
|
|
assert test_table_s.get_item(Key={'p': p}, ConsistentRead=True)['Item']['z'] == 5
|