Skip to content

Commit

Permalink
global: circulation specific user search
Browse files Browse the repository at this point in the history
* Adds a first REST API implementation for
  *invenio_accounts.models.User* that handles current
  *invenio-circulation* needs.
  • Loading branch information
mvesper committed Sep 7, 2016
1 parent 8987531 commit 3e6f7f4
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 16 deletions.
3 changes: 3 additions & 0 deletions invenio_accounts_rest/ext.py
Expand Up @@ -26,6 +26,8 @@

from __future__ import absolute_import, print_function

from .views import create_blueprint


class InvenioAccountsREST(object):
"""Invenio-Accounts-REST extension."""
Expand All @@ -38,6 +40,7 @@ def __init__(self, app=None):
def init_app(self, app):
"""Flask application initialization."""
self.init_config(app)
app.register_blueprint(create_blueprint())
app.extensions['invenio-accounts-rest'] = self

def init_config(self, app):
Expand Down
56 changes: 51 additions & 5 deletions invenio_accounts_rest/views.py
Expand Up @@ -26,9 +26,55 @@

from __future__ import absolute_import, print_function

from flask import Blueprint
import json

blueprint = Blueprint(
'invenio_accounts_rest',
__name__,
)
from flask import Blueprint, request
from invenio_accounts.models import User
from invenio_oauth2server import require_api_auth
from invenio_rest import ContentNegotiatedMethodView
from sqlalchemy import String, cast


def accounts_serializer(*args, **kwargs):
"""Basic serializer for invenio_accounts.models.User data."""
return json.dumps([{'id': u.id, 'email': u.email} for u in args])


def create_blueprint():
"""Create invenio-accounts REST blueprint."""
blueprint = Blueprint(
'invenio_accounts_rest',
__name__,
)

accounts_resource = AccountsResource.as_view(
'accounts_resource',
serializers={'application/json': accounts_serializer},
default_media_type='application/json'
)

blueprint.add_url_rule(
'/users/',
view_func=accounts_resource,
methods=['GET'],
)

return blueprint


class AccountsResource(ContentNegotiatedMethodView):
"""MethodView implementation."""

def __init__(self, serializers, default_media_type):
"""Constructor."""
super(AccountsResource, self).__init__(
serializers, default_media_type=default_media_type)

@require_api_auth()
def get(self):
"""Get accounts/users/?q=."""
query = request.args.get('q')
return User.query.filter(
(User.email == query) |
(cast(User.id, String) == query)
).all()
4 changes: 4 additions & 0 deletions setup.py
Expand Up @@ -60,6 +60,10 @@

install_requires = [
'Flask-BabelEx>=0.9.2',
'cryptography<=1.5',
'invenio-db>=1.0.0b1',
'invenio-oauth2server>=1.0.0a9',
'invenio-rest>=1.0.0a9',
]

packages = find_packages()
Expand Down
66 changes: 62 additions & 4 deletions tests/conftest.py
Expand Up @@ -27,15 +27,73 @@

from __future__ import absolute_import, print_function

import tempfile

import pytest
from flask import Flask
from flask_security.utils import encrypt_password
from invenio_accounts import InvenioAccounts
from invenio_db import db as db_
from invenio_db import InvenioDB
from invenio_oauth2server import InvenioOAuth2Server
from invenio_oauth2server.models import Token
from sqlalchemy_utils.functions import create_database, database_exists
from werkzeug.local import LocalProxy

from invenio_accounts_rest import InvenioAccountsREST


@pytest.fixture()
@pytest.yield_fixture()
def app():
"""Flask application fixture."""
app = Flask('testapp')
instance_path = tempfile.mkdtemp()

app = Flask(__name__, instance_path=instance_path)
app.config.update(
TESTING=True
OAUTH2SERVER_CLIENT_ID_SALT_LEN=40,
OAUTH2SERVER_CLIENT_SECRET_SALT_LEN=60,
OAUTH2SERVER_TOKEN_PERSONAL_SALT_LEN=60,
SECRET_KEY='changeme',
TESTING=True,
)
return app

InvenioOAuth2Server(app)
InvenioAccounts(app)
InvenioAccountsREST(app)
InvenioDB(app)

with app.app_context():
yield app


@pytest.yield_fixture()
def db(app):
"""Database fixture."""
if not database_exists(str(db_.engine.url)):
create_database(str(db_.engine.url))

db_.create_all()
yield db_
db_.session.remove()
db_.drop_all()


@pytest.yield_fixture()
def access_token(app, db):
"""Access token fixture."""
_datastore = LocalProxy(lambda: app.extensions['security'].datastore)
kwargs = dict(email='admin@inveniosoftware.org', password='123456',
active=True)
kwargs['password'] = encrypt_password(kwargs['password'])
user = _datastore.create_user(**kwargs)

db.session.commit()
token = Token.create_personal(
'test-personal-{0}'.format(user.id),
user.id,
scopes=[],
is_internal=True,
).access_token
db.session.commit()

yield token
7 changes: 0 additions & 7 deletions tests/test_invenio_accounts_rest.py
Expand Up @@ -31,7 +31,6 @@
from flask_babelex import Babel

from invenio_accounts_rest import InvenioAccountsREST
from invenio_accounts_rest.views import blueprint


def test_version():
Expand All @@ -51,9 +50,3 @@ def test_init():
assert 'invenio-accounts-rest' not in app.extensions
ext.init_app(app)
assert 'invenio-accounts-rest' in app.extensions


def test_view(app):
"""Test view."""
InvenioAccountsREST(app)
app.register_blueprint(blueprint)
63 changes: 63 additions & 0 deletions tests/test_user_search.py
@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.

"""Module user search tests."""

import json

from flask import url_for
from invenio_accounts.models import User


def test_user_search(app, db, access_token):
"""Test REST API for circulation specific user search."""
# The access_token fixture generates a user
user = User.query.all()[0]
with app.test_request_context():
with app.test_client() as client:
# Search while not being authorized
url = url_for('invenio_accounts_rest.accounts_resource')
url += '?q={0}'.format(user.id + 1)
url += '&access_token=foo'
res = client.get(url)

assert res.status_code == 401

# Search for non existing user
url = url_for('invenio_accounts_rest.accounts_resource')
url += '?q={0}'.format(user.id + 1)
url += '&access_token=' + access_token
res = client.get(url)

assert res.status_code == 200
assert len(json.loads(res.data.decode('utf-8'))) == 0

# Search for existing user
url = url_for('invenio_accounts_rest.accounts_resource')
url += '?q={0}'.format(user.id)
url += '&access_token=' + access_token
res = client.get(url)

assert res.status_code == 200
assert len(json.loads(res.data.decode('utf-8'))) == 1

0 comments on commit 3e6f7f4

Please sign in to comment.