def test_batch_get_one_batch(client): """ A single call when the number of requested items is <= batch size """ # Simulate a full batch client.batch_size = 2 user1 = User(id=uuid.uuid4()) user2 = User(id=uuid.uuid4()) request = {"User": {"Keys": [{"id": {"S": str(user1.id)}}, {"id": {"S": str(user2.id)}}], "ConsistentRead": False}} # When batching input with less keys than the batch size, the request # will look identical expected_request = request response = {"Responses": {"User": [{"id": {"S": str(user1.id)}, "age": {"N": "4"}}, {"id": {"S": str(user2.id)}, "age": {"N": "5"}}]}} # Expected response is a single list of users expected_response = {"User": [{"id": {"S": str(user1.id)}, "age": {"N": "4"}}, {"id": {"S": str(user2.id)}, "age": {"N": "5"}}]} def handle(RequestItems): assert RequestItems == expected_request return response client.boto_client.batch_get_item = handle response = client.batch_get_items(request) assert response == expected_response
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_load_default_init(engine): """ The default model loader uses the model's __init__ method """ User.Meta.init = Mock() User.Meta.init.return_value = User() user_id = uuid.uuid4() now = arrow.now() user = { "id": { "S": str(user_id) }, "j": { "S": now.isoformat() }, "extra_field": { "N": "0.125" } } loaded_user = User._load(user, context={"engine": engine}) assert loaded_user.id == user_id assert loaded_user.joined == now assert not hasattr(loaded_user, "extra_field") # No args, kwargs provided to custom init function User.Meta.init.assert_called_once_with()
def test_load_dump(engine): """ _load and _dump should be symmetric """ user = User(id=uuid.uuid4(), name="name", email="*****@*****.**", age=25, joined=arrow.now()) serialized = { "id": { "S": str(user.id) }, "age": { "N": "25" }, "name": { "S": "name" }, "email": { "S": "*****@*****.**" }, "j": { "S": user.joined.to("utc").isoformat() } } assert User._load(serialized, context={"engine": engine}) == user assert User._dump(user, context={"engine": engine}) == serialized
async def api_register_user(*, email, name, password): if not name or not name.strip(): raise APIValueError('name') if not email or not _RE_EMAIL.match(email): raise APIValueError('email') if not password or not _RE_SHA1.match(password): raise APIValueError('password') # 检查邮箱是否已注册 users = await User.findAll('email=?', [email]) if len(users) > 0: raise APIError('register:failed', 'email', 'Email is already in use.') uid = next_id() sha1_password = '******' % (uid, password) user = User(id=uid, name=name.strip(), email=email, password=hashlib.sha1( sha1_password.encode('utf-8')).hexdigest(), image='http://www.gravatar.com/avatar/%s?d=mm&s=120' % hashlib.md5(email.encode('utf-8')).hexdigest()) await user.save() # make session cookie: r = web.Response() # cookies 24小时有效 r.set_cookie(COOKIE_NAME, user2cookie(user, 86400), max_age=86400, httponly=True) user.password = '******' r.content_type = 'application/json' r.body = json.dumps(user, ensure_ascii=False).encode('utf-8') return r
def test_load_objects(engine): user1 = User(id=uuid.uuid4()) user2 = User(id=uuid.uuid4()) expected = { "User": { "Keys": [{ "id": { "S": str(user1.id) } }, { "id": { "S": str(user2.id) } }], "ConsistentRead": False } } response = { "User": [{ "age": { "N": 5 }, "name": { "S": "foo" }, "id": { "S": str(user1.id) } }, { "age": { "N": 10 }, "name": { "S": "bar" }, "id": { "S": str(user2.id) } }] } def respond(input): assert bloop.util.ordered(input) == bloop.util.ordered(expected) return response engine.client.batch_get_items = respond engine.load((user1, user2)) assert user1.age == 5 assert user1.name == "foo" assert user2.age == 10 assert user2.name == "bar"
def test_direct_permissions(self): user1 = User(username='******') user2 = User(username='******') self.assertEqual(self.pc.permissions(user1), set(['view', 'edit', 'delete'])) self.assertEqual(self.pc.permissions(user2), set(['view'])) self.assertEqual( self.child1.permissions(user1, inherited=self.pc.permissions(user1)), set(['view', 'edit'])) self.assertEqual( self.child1.permissions(user2, inherited=self.pc.permissions(user2)), set(['view']))
def test_context(engine): engine.config["atomic"] = True user_id = uuid.uuid4() user = User(id=user_id, name="foo") expected = { "TableName": "User", "UpdateExpression": "SET #n0=:v1", "ExpressionAttributeValues": { ":v1": { "S": "foo" } }, "ExpressionAttributeNames": { "#n0": "name" }, "Key": { "id": { "S": str(user_id) } } } with engine.context(atomic=False) as eng: eng.save(user) engine.client.update_item.assert_called_once_with(expected) # EngineViews can't bind with pytest.raises(RuntimeError): with engine.context() as eng: eng.bind(base=bloop.new_base())
def test_delete_atomic_condition(atomic): user_id = uuid.uuid4() user = User(id=user_id, email='*****@*****.**') # Manually snapshot so we think age is persisted bloop.tracking.sync(user, atomic) expected = { 'ExpressionAttributeNames': { '#n2': 'id', '#n4': 'name', '#n0': 'email' }, 'ConditionExpression': '((#n0 = :v1) AND (#n2 = :v3) AND (#n4 = :v5))', 'TableName': 'User', 'ExpressionAttributeValues': { ':v5': { 'S': 'foo' }, ':v1': { 'S': '*****@*****.**' }, ':v3': { 'S': str(user_id) } }, 'Key': { 'id': { 'S': str(user_id) } } } atomic.delete(user, condition=User.name.is_("foo")) atomic.client.delete_item.assert_called_once_with(expected)
def test_delete_atomic_new(engine): """atomic delete on new object should expect no columns to exist""" user = User(id=uuid.uuid4()) expected = { 'TableName': 'User', 'ExpressionAttributeNames': { '#n2': 'id', '#n0': 'age', '#n4': 'name', '#n3': 'j', '#n1': 'email' }, 'Key': { 'id': { 'S': str(user.id) } }, '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.delete(user) engine.client.delete_item.assert_called_once_with(expected)
def test_delete_atomic(atomic): user = User(id=uuid.uuid4()) # Manually snapshot so we think age is persisted bloop.tracking.sync(user, atomic) expected = { 'ConditionExpression': '(#n0 = :v1)', 'ExpressionAttributeValues': { ':v1': { 'S': str(user.id) } }, 'TableName': 'User', 'Key': { 'id': { 'S': str(user.id) } }, 'ExpressionAttributeNames': { '#n0': 'id' } } atomic.delete(user) atomic.client.delete_item.assert_called_once_with(expected)
def test_load_snapshots(engine, atomic_mode): """Loading builds a snapshot for future atomic operations""" user = User(id=uuid.uuid4()) # In the case of missing data, load may not return fields # (or in the case of multi-view tables, non-mapped data) engine.client.batch_get_items.return_value = { "User": [{ "age": { "N": 5 }, "id": { "S": str(user.id) }, "extra_field": { "freeform data": "not parsed" } }] } engine.config["atomic"] = atomic_mode engine.load(user) # Cached snapshots are in dumped form expected_condition = ((User.age == { "N": "5" }) & (User.email.is_(None)) & (User.id == { "S": str(user.id) }) & (User.joined.is_(None)) & (User.name.is_(None))) actual_condition = bloop.tracking.get_snapshot(user) assert actual_condition == expected_condition
def test_get_update(): """ hash_key shouldn't be in the dumped SET dict """ user = User(id=uuid.uuid4(), email="*****@*****.**") diff = bloop.tracking.get_update(user) assert "REMOVE" not in diff assert diff["SET"] == [(User.email, "*****@*****.**")]
def test_batch_get_unprocessed(client): """ Re-request unprocessed keys """ user1 = User(id=uuid.uuid4()) request = {"User": {"Keys": [{"id": {"S": str(user1.id)}}], "ConsistentRead": False}} expected_requests = [ {"User": {"Keys": [{"id": {"S": str(user1.id)}}], "ConsistentRead": False}}, {"User": {"Keys": [{"id": {"S": str(user1.id)}}], "ConsistentRead": False}} ] responses = [ {"UnprocessedKeys": {"User": {"Keys": [{"id": {"S": str(user1.id)}}], "ConsistentRead": False}}}, {"Responses": {"User": [{"id": {"S": str(user1.id)}, "age": {"N": "4"}}]}} ] expected_response = {"User": [{"id": {"S": str(user1.id)}, "age": {"N": "4"}}]} calls = 0 def handle(RequestItems): nonlocal calls expected = expected_requests[calls] response = responses[calls] calls += 1 assert RequestItems == expected return response client.boto_client.batch_get_item = handle response = client.batch_get_items(request) assert calls == 2 assert response == expected_response
def test_unmutated_inherited_permissions(self): """The inherited permission set should not be mutated by a permission check""" user = User(username='******') inherited = set(['add-video']) self.assertEqual(self.pc.permissions(user, inherited=inherited), set(['add-video', 'view'])) self.assertEqual(inherited, set(['add-video']))
def test_single_model_in_loadmodels(self): with self.app.test_request_context(): user = User(username=u'user1') self.session.add(user) self.session.commit() self.assertEqual(t_single_model_in_loadmodels(username=u'user1'), g.user)
def respond(request): assert request == expected item = User(email="*****@*****.**") return { "Count": 1, "ScannedCount": 2, "Items": [engine._dump(User, item)]}
def test_load_user_to_g(self): with self.app.test_request_context(): user = User(username=u'baz') self.session.add(user) self.session.commit() self.assertFalse(hasattr(g, 'user')) self.assertEqual(t_load_user_to_g(username=u'baz'), g.user) self.assertRaises(NotFound, t_load_user_to_g, username=u'boo')
def test_batch_get_paginated(client): """ Paginate requests to fit within the max batch size """ # Minimum batch size so we can force pagination with 2 users client.batch_size = 1 user1 = User(id=uuid.uuid4()) user2 = User(id=uuid.uuid4()) request = {"User": {"Keys": [{"id": {"S": str(user1.id)}}, {"id": {"S": str(user2.id)}}], "ConsistentRead": False}} expected_requests = [ {"User": {"Keys": [{"id": {"S": str(user1.id)}}], "ConsistentRead": False}}, {"User": {"Keys": [{"id": {"S": str(user2.id)}}], "ConsistentRead": False}} ] responses = [ {"Responses": {"User": [{"id": {"S": str(user1.id)}, "age": {"N": "4"}}]}}, {"Responses": {"User": [{"id": {"S": str(user2.id)}, "age": {"N": "5"}}]}} ] expected_response = {"User": [{"id": {"S": str(user1.id)}, "age": {"N": "4"}}, {"id": {"S": str(user2.id)}, "age": {"N": "5"}}]} calls = 0 def handle(RequestItems): nonlocal calls expected = expected_requests[calls] response = responses[calls] calls += 1 assert RequestItems == expected return response client.boto_client.batch_get_item = handle response = client.batch_get_items(request) assert calls == 2 assert response == expected_response
def respond(request): token = request.pop("ExclusiveStartKey", None) assert request == expected item = User(id=user_id, name=None) return { "Count": 1, "ScannedCount": 2, "Items": [engine._dump(User, item)], "LastEvaluatedKey": continue_tokens[token] }
def test_delete_new(engine): """ When an object is first created, a non-atomic delete shouldn't expect anything. """ user_id = uuid.uuid4() user = User(id=user_id) expected = {'TableName': 'User', 'Key': {'id': {'S': str(user_id)}}} engine.delete(user) engine.client.delete_item.assert_called_once_with(expected)
def test_equality(): user_id = uuid.uuid4() user = User(id=user_id, name="name", email="*****@*****.**", age=25) same = User(id=user_id, name="name", email="*****@*****.**", age=25) other = User(id=user_id, name="wrong", email="*****@*****.**", age=25) another = User(id=user_id, email="*****@*****.**", age=25) # Wrong type assert not (user == "foo") assert user != "foo" # Attr with different value assert not (user == other) assert user != other # Missing an attr assert not (user == another) assert user != another assert user == same
def respond(request): token = request.pop("ExclusiveStartKey", None) assert request == expected result = { "Count": 1, "ScannedCount": 2, "Items": [engine._dump(User, User(id=user_id, name=token))], "LastEvaluatedKey": continue_tokens[token]} next_token = continue_tokens.get(token, None) if next_token: result["LastEvaluatedKey"] = next_token return result
async def test(loop): await test_orm.create_pool(loop=loop, user='******', password='******', db='awesome') u = User(name='Test', email='*****@*****.**', password='******', image='about:blank') print(u) print(type(u.email)) await u.save()
def test_load_missing_attrs(engine): """ When an instance of a Model is loaded into, existing attributes should be overwritten with new values, or if there is no new value, should be deleted """ obj = User(id=uuid.uuid4(), age=4, name="user") response = {"User": [{"age": {"N": 5}, "id": {"S": str(obj.id)}}]} engine.client.batch_get_items = lambda input: response engine.load(obj) assert obj.age == 5 assert obj.name is None
def test_loadmodel_permissions(self): with self.app.test_request_context(): g.user = User(username='******') self.assertEqual( t_dotted_document_view(document=u'parent', child=1), self.child1) self.assertEqual( t_dotted_document_edit(document=u'parent', child=1), self.child1) self.assertRaises(Forbidden, t_dotted_document_delete, document=u'parent', child=1)
def test_delete_unknown(): """ Even if a field that doesn't exist is deleted, it's marked """ user = User(id=uuid.uuid4()) try: del user.email except AttributeError: # Expected - regardless of the failure to lookup, the remote # should expect a delete pass assert User.email in bloop.tracking._obj_tracking[user]["marked"] diff = bloop.tracking.get_update(user) assert diff["REMOVE"] == [User.email]
def test_load_missing_key(engine): """Trying to load objects with missing hash and range keys raises""" user = User(age=2) with pytest.raises(ValueError): engine.load(user) complex_models = [ ComplexModel(), ComplexModel(name="no range"), ComplexModel(date="no hash") ] for model in complex_models: with pytest.raises(ValueError): engine.load(model)
def test_missing_objects(engine): """ When objects aren't loaded, ObjectsNotFound is raised with a list of missing objects """ # Patch batch_get_items to return no results engine.client.batch_get_items = lambda *a, **kw: {} users = [User(id=uuid.uuid4()) for _ in range(3)] with pytest.raises(bloop.exceptions.NotModified) as excinfo: engine.load(users) assert set(excinfo.value.objects) == set(users)
def test_dump_key(engine): class HashAndRange(bloop.new_base()): foo = bloop.Column(bloop.Integer, hash_key=True) bar = bloop.Column(bloop.Integer, range_key=True) engine.bind(base=HashAndRange) user = User(id=uuid.uuid4()) user_key = {"id": {"S": str(user.id)}} assert bloop.engine.dump_key(engine, user) == user_key obj = HashAndRange(foo=4, bar=5) obj_key = {"bar": {"N": "5"}, "foo": {"N": "4"}} assert bloop.engine.dump_key(engine, obj) == obj_key