예제 #1
0
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
예제 #2
0
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)
예제 #3
0
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()
예제 #4
0
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
예제 #6
0
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"
예제 #7
0
 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']))
예제 #8
0
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())
예제 #9
0
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)
예제 #10
0
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)
예제 #11
0
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)
예제 #12
0
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
예제 #13
0
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, "*****@*****.**")]
예제 #14
0
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
예제 #15
0
 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']))
예제 #16
0
 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)
예제 #17
0
 def respond(request):
     assert request == expected
     item = User(email="*****@*****.**")
     return {
         "Count": 1,
         "ScannedCount": 2,
         "Items": [engine._dump(User, item)]}
예제 #18
0
 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')
예제 #19
0
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
예제 #20
0
 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]
     }
예제 #21
0
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)
예제 #22
0
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
예제 #23
0
 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
예제 #24
0
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()
예제 #25
0
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
예제 #26
0
 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)
예제 #27
0
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]
예제 #28
0
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)
예제 #29
0
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)
예제 #30
0
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