def test_save_set_only(engine): user = User(id=uuid.uuid4()) # Expect a SET on email user.email = "*****@*****.**" expected = { "Key": { "id": { "S": str(user.id) } }, "ExpressionAttributeNames": { "#n0": "email" }, "TableName": "User", "UpdateExpression": "SET #n0=:v1", "ExpressionAttributeValues": { ":v1": { "S": "*****@*****.**" } } } engine.save(user) engine.client.update_item.assert_called_once_with(expected)
def test_save_update_multiple(User, engine): user1 = User(id=uuid.uuid4(), age=4) user2 = User(id=uuid.uuid4(), age=5) expected = [ {"UpdateExpression": "SET #n0=:v1", "Key": {"id": {"S": str(user1.id)}}, "TableName": "User", "ExpressionAttributeNames": {"#n0": "age"}, "ExpressionAttributeValues": {":v1": {"N": "4"}}}, {"UpdateExpression": "SET #n0=:v1", "Key": {"id": {"S": str(user2.id)}}, "TableName": "User", "ExpressionAttributeNames": {"#n0": "age"}, "ExpressionAttributeValues": {":v1": {"N": "5"}}} ] calls = 0 def validate(item): nonlocal calls calls += 1 assert item in expected expected.remove(item) engine.client.update_item = validate engine.save((user1, user2)) assert calls == 2
def test_save_atomic_new(engine): """atomic save on new object should expect no columns to exist""" user = User(id=uuid.uuid4()) expected = { 'ExpressionAttributeNames': { '#n0': 'age', '#n3': 'j', '#n1': 'email', '#n4': 'name', '#n2': 'id' }, 'Key': { 'id': { 'S': str(user.id) } }, 'TableName': 'User', 'ConditionExpression': ('((attribute_not_exists(#n0)) AND (attribute_not_exists(#n1)) ' 'AND (attribute_not_exists(#n2)) AND (attribute_not_exists(#n3))' ' AND (attribute_not_exists(#n4)))') } engine.config["atomic"] = True engine.save(user) engine.client.update_item.assert_called_once_with(expected)
def test_save_condition(User, engine): user_id = uuid.uuid4() user = User(id=user_id) condition = User.id.is_(None) expected = {"TableName": "User", "ExpressionAttributeNames": {"#n0": "id"}, "ConditionExpression": "(attribute_not_exists(#n0))", "Key": {"id": {"S": str(user_id)}}} def validate(item): assert item == expected engine.client.update_item = validate engine.save(user, condition=condition)
def test_save_multiple(User, engine): user1 = User(id=uuid.uuid4()) user2 = User(id=uuid.uuid4()) expected = [ {"Key": {"id": {"S": str(user1.id)}}, "TableName": "User"}, {"Key": {"id": {"S": str(user2.id)}}, "TableName": "User"}] calls = 0 def validate(item): assert item in expected nonlocal calls calls += 1 engine.client.update_item = validate engine.save((user1, user2)) assert calls == 2
def test_save_update_condition_key_only(User, engine): """ Even when the diff is empty, an UpdateItem should be issued (in case this is really a create - the item doesn't exist yet) """ user = User(id=uuid.uuid4()) condition = User.id.is_(None) expected = {"ConditionExpression": "(attribute_not_exists(#n0))", "TableName": "User", "ExpressionAttributeNames": {"#n0": "id"}, "Key": {"id": {"S": str(user.id)}}} def validate(item): assert item == expected engine.client.update_item = validate engine.save(user, condition=condition)
def test_save_update_condition(User, engine): """ Non-empty diff """ user = User(id=uuid.uuid4(), age=4) condition = User.id.is_(None) expected = {"ConditionExpression": "(attribute_not_exists(#n2))", "ExpressionAttributeNames": {"#n2": "id", "#n0": "age"}, "TableName": "User", "Key": {"id": {"S": str(user.id)}}, "ExpressionAttributeValues": {":v1": {"N": "4"}}, "UpdateExpression": "SET #n0=:v1"} def validate(item): assert item == expected engine.client.update_item = validate engine.save(user, condition=condition)
def test_save_single_with_condition(engine): user = User(id=uuid.uuid4()) condition = User.id.is_(None) expected = { "TableName": "User", "ExpressionAttributeNames": { "#n0": "id" }, "ConditionExpression": "(attribute_not_exists(#n0))", "Key": { "id": { "S": str(user.id) } } } engine.save(user, condition=condition) engine.client.update_item.assert_called_once_with(expected)
def test_save_multiple_condition(User, engine): users = [User(id=uuid.uuid4()) for _ in range(3)] condition = User.id.is_(None) expected = [{"ConditionExpression": "(attribute_not_exists(#n0))", "ExpressionAttributeNames": {"#n0": "id"}, "Key": {"id": {"S": str(user.id)}}, "TableName": "User"} for user in users] calls = 0 def validate(item): assert item in expected nonlocal calls calls += 1 engine.client.update_item = validate engine.save(users, condition=condition) assert calls == 3
def test_save_update_del_field(User, engine): user = User(id=uuid.uuid4(), age=4) # Manually snapshot so we think age is persisted bloop.tracking.sync(user, engine) # Expect to see a REMOVE on age, and a SET on email del user.age expected = {"Key": {"id": {"S": str(user.id)}}, "ExpressionAttributeNames": {"#n0": "age"}, "TableName": "User", "UpdateExpression": "REMOVE #n0"} def validate(item): assert item == expected engine.client.update_item = validate engine.save(user)
def test_save_list_with_condition(engine): users = [User(id=uuid.uuid4()) for _ in range(3)] condition = User.id.is_(None) expected_calls = [{ "ConditionExpression": "(attribute_not_exists(#n0))", "ExpressionAttributeNames": { "#n0": "id" }, "Key": { "id": { "S": str(user.id) } }, "TableName": "User" } for user in users] engine.save(users, condition=condition) for expected in expected_calls: engine.client.update_item.assert_any_call(expected) assert engine.client.update_item.call_count == 3
def test_save_del_only(engine): user = User(id=uuid.uuid4(), age=4) # Expect a REMOVE on age del user.age expected = { "Key": { "id": { "S": str(user.id) } }, "ExpressionAttributeNames": { "#n0": "age" }, "TableName": "User", "UpdateExpression": "REMOVE #n0" } engine.save(user) engine.client.update_item.assert_called_once_with(expected)
def test_save_set_del_field(User, engine): """ UpdateItem can REMOVE fields as well as SET """ user = User(id=uuid.uuid4(), age=4) for field in [User.id, User.age, User.email]: bloop.tracking.mark(user, field) # Expect to see a REMOVE on age, and a SET on email del user.age user.email = "*****@*****.**" expected = {"Key": {"id": {"S": str(user.id)}}, "ExpressionAttributeNames": {"#n0": "email", "#n2": "age"}, "TableName": "User", "UpdateExpression": "SET #n0=:v1 REMOVE #n2", "ExpressionAttributeValues": {":v1": {"S": "*****@*****.**"}}} def validate(item): assert item == expected engine.client.update_item = validate engine.save(user)
def test_save_condition_key_only(engine): """ Even when the diff is empty, an UpdateItem should be issued (in case this is really a create - the item doesn't exist yet) """ user = User(id=uuid.uuid4()) condition = User.id.is_(None) expected = { "ConditionExpression": "(attribute_not_exists(#n0))", "TableName": "User", "ExpressionAttributeNames": { "#n0": "id" }, "Key": { "id": { "S": str(user.id) } } } engine.save(user, condition=condition) engine.client.update_item.assert_called_once_with(expected)
def test_update_noop_save(engine, User): """ Saves should send all fields that have been set, every time """ user = User(id=uuid.uuid4(), age=5) expected = { "Key": {"id": {"S": str(user.id)}}, "TableName": "User", "ExpressionAttributeNames": {"#n0": "age"}, "ExpressionAttributeValues": {":v1": {"N": "5"}}, "UpdateExpression": "SET #n0=:v1"} calls = 0 def validate(item): assert item == expected nonlocal calls calls += 1 engine.client.update_item = validate engine.save(user) engine.save(user) assert calls == 2
def test_save_atomic_new(User, engine): """ When an object is first created, an atomic save should expect no columns to exist. """ user_id = uuid.uuid4() user = User(id=user_id) expected = { 'ExpressionAttributeNames': { '#n0': 'age', '#n3': 'j', '#n1': 'email', '#n4': 'name', '#n2': 'id'}, 'Key': {'id': {'S': str(user_id)}}, 'TableName': 'User', 'ConditionExpression': ( '((attribute_not_exists(#n0)) AND (attribute_not_exists(#n1)) ' 'AND (attribute_not_exists(#n2)) AND (attribute_not_exists(#n3))' ' AND (attribute_not_exists(#n4)))')} def validate(item): assert item == expected engine.client.update_item = validate engine.config["atomic"] = True engine.save(user)
def test_save_twice(engine): """Save sends full local values, not just deltas from last save""" user = User(id=uuid.uuid4(), age=5) expected = { "Key": { "id": { "S": str(user.id) } }, "TableName": "User", "ExpressionAttributeNames": { "#n0": "age" }, "ExpressionAttributeValues": { ":v1": { "N": "5" } }, "UpdateExpression": "SET #n0=:v1" } engine.save(user) engine.save(user) engine.client.update_item.assert_called_with(expected) assert engine.client.update_item.call_count == 2