Browse Source

changing user information

master
Julio Biason 11 years ago
parent
commit
dcead00b37
  1. 13
      apiary.apib
  2. 37
      luncho/blueprints/users.py
  3. 2
      luncho/helpers.py
  4. 5
      luncho/server.py
  5. 66
      tests/base.py
  6. 114
      tests/users_tests.py

13
apiary.apib

@ -54,12 +54,13 @@ forbidden to create new groups. They can still vote, though.
{ "status": "ERROR", "error": "username already exists" } { "status": "ERROR", "error": "username already exists" }
## Single User [/user/{username}/] ## Single User [/user/{token}/]
Manage a single user. Manage a single user.
+ Parameters + Parameters
+ username ... Username used in the creation process. + username ... Username used in the creation process.
+ token ... The user token
### Update information [POST] ### Update information [POST]
@ -69,7 +70,7 @@ change will require only the "token" and "password" fields.
+ Request (application/json) + Request (application/json)
{ "token": "userToken", "full_name": "Full name", "password": "hash" } { "full_name": "Full name", "password": "hash" }
+ Response 200 (application/json) + Response 200 (application/json)
@ -79,18 +80,14 @@ change will require only the "token" and "password" fields.
{ "status": "ERROR", "error": "Invalid token" } { "status": "ERROR", "error": "Invalid token" }
+ Response 401 (application/json) + Response 404 (application/json)
{ "status": "ERROR", "error": "User is not admin or not the same user" } { "status": "ERROR", "error": "User not found (via token)"}
### Remove user [DELETE] ### Remove user [DELETE]
User removal is only allowed to the user themselves or by a system admin. User removal is only allowed to the user themselves or by a system admin.
+ Request (application/json)
{ "token": "userToken" }
+ Response 200 (application/json) + Response 200 (application/json)
{ "status": "OK" } { "status": "OK" }

37
luncho/blueprints/users.py

@ -3,6 +3,8 @@
"""User management.""" """User management."""
import logging
from flask import Blueprint from flask import Blueprint
from flask import request from flask import request
from flask import jsonify from flask import jsonify
@ -10,10 +12,13 @@ from flask import jsonify
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from luncho.helpers import ForceJSON from luncho.helpers import ForceJSON
from luncho.helpers import JSONError
from luncho.server import User from luncho.server import User
from luncho.server import db from luncho.server import db
LOG = logging.getLogger('luncho.blueprints.users')
users = Blueprint('users', __name__) users = Blueprint('users', __name__)
@ -35,7 +40,31 @@ def create_user():
return jsonify(status='OK') return jsonify(status='OK')
except IntegrityError: except IntegrityError:
resp = jsonify(status='ERROR', return JSONError(409, 'Username already exists')
error='username already exists')
resp.status_code = 409 @users.route('<token>/', methods=['POST'])
return resp @ForceJSON()
def update_user(token):
"""Update user information. Request can have the following fields:
{ "full_name": "Full name", "password": "hash" }
Any other field will be ignored; only fields that need to be changed
must be send."""
json = request.get_json(force=True)
user = User.query.filter_by(token=token).first()
if not user:
return JSONError(404, 'User not found (via token)')
if not user.valid_token(token):
return JSONError(400, 'Invalid token')
if 'full_name' in json:
LOG.debug('Fullname = {fullname}'.format(fullname=json['full_name']))
user.fullname = json['full_name']
if 'password' in json:
LOG.debug('Passhash = {password}'.format(password=json['password']))
user.passhash = json['password']
db.session.commit()
return jsonify(status='OK')

2
luncho/helpers.py

@ -54,7 +54,7 @@ def JSONError(status, message, **kwargs):
:return: A response with the JSON and the status code.""" :return: A response with the JSON and the status code."""
resp = jsonify(status='ERROR', resp = jsonify(status='ERROR',
message=message, error=message,
**kwargs) **kwargs)
resp.status_code = status resp.status_code = status
return resp return resp

5
luncho/server.py

@ -48,18 +48,13 @@ class User(db.Model):
self.fullname = fullname self.fullname = fullname
self.passhash = passhash self.passhash = passhash
self.token = token self.token = token
self.issued_date = issued_date
self.validated = validated self.validated = validated
self.created_at = datetime.datetime.now() self.created_at = datetime.datetime.now()
def get_token(self): def get_token(self):
"""Generate a user token or return the current one for the day.""" """Generate a user token or return the current one for the day."""
if self.token and self.issued_date == datetime.date.today():
return self.token
# create a token for the day # create a token for the day
self.token = self._token() self.token = self._token()
self.issued_date = datetime.date.today()
db.session.commit() db.session.commit()
return self._token() return self._token()

66
tests/base.py

@ -0,0 +1,66 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import unittest
import json
from luncho import server
class LunchoTests(unittest.TestCase):
"""Base testing for all Lunch-o tests."""
# ------------------------------------------------------------
# Test set up and tear down
# ------------------------------------------------------------
def setUp(self):
# leave the database blank to make it in memory
server.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
server.app.config['TESTING'] = True
self.app = server.app.test_client()
server.db.create_all()
def tearDown(self):
server.db.drop_all(bind=None)
# ------------------------------------------------------------
# Common assertions for lunch-o
# ------------------------------------------------------------
def assertJson(self, expected, response):
"""Compare JSONs."""
if not isinstance(response, dict):
response = json.loads(response)
for key in expected:
if not key in response:
self.fail('Key {key} missing in response'.format(
key=key))
if response[key] != expected[key]:
self.fail('Key {key} differs: Expected "{expected}", '
'response "{response}"'.format(
key=key,
expected=expected[key],
response=response[key]))
def assertStatusCode(self, response, status):
"""Check the status code of the response."""
self.assertEqual(response.status_code, status)
# ------------------------------------------------------------
# Easy way to convert the data to JSON and do requests
# ------------------------------------------------------------
def post(self, url, data):
"""Send a POST request to the URL."""
return self.app.post(url,
data=json.dumps(data),
content_type='application/json')
def put(self, url, data):
"""Send a PUT request to the URL."""
return self.app.put(url,
data=json.dumps(data),
content_type='application/json')

114
tests/users_tests.py

@ -8,32 +8,21 @@ from luncho import server
from luncho.server import User from luncho.server import User
from base import LunchoTests
class TestUsers(unittest.TestCase):
"""Test users request."""
def setUp(self):
# leave the database blank to make it in memory
server.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
server.app.config['TESTING'] = True
self.app = server.app.test_client()
server.db.create_all()
def tearDown(self): class TestUsers(LunchoTests):
server.db.drop_all(bind=None) """Test users request."""
def test_create_user(self): def test_create_user(self):
"""Simple user creation.""" """Simple user creation."""
request = {'username': 'username', request = {'username': 'username',
'full_name': 'full name', 'full_name': 'full name',
'password': 'hash'} 'password': 'hash'}
rv = self.app.put('/user/', rv = self.put('/user/', request)
data=json.dumps(request),
content_type='application/json')
self.assertEqual(rv.status_code, 200) self.assertStatusCode(rv, 200)
self.assertEqual(json.loads(rv.data), {'status': 'OK'}) self.assertJson({'status': 'OK'}, rv.data)
# db check # db check
self.assertIsNotNone(User.query.filter_by(username='username').first()) self.assertIsNotNone(User.query.filter_by(username='username').first())
@ -47,37 +36,92 @@ class TestUsers(unittest.TestCase):
request = {'username': 'username', request = {'username': 'username',
'full_name': 'full name', 'full_name': 'full name',
'password': 'hash'} 'password': 'hash'}
rv = self.app.put('/user/', rv = self.put('/user/', data=request)
data=json.dumps(request),
content_type='application/json')
expected = {"status": "ERROR", expected = {"status": "ERROR",
"error": "username already exists"} "error": "Username already exists"}
self.assertStatusCode(rv, 409)
self.assertEqual(rv.status_code, 409) self.assertJson(expected, rv.data)
self.assertEqual(json.loads(rv.data), expected)
def test_no_json(self): def test_no_json(self):
"""Check the status when doing a request that it's not JSON.""" """Check the status when doing a request that it's not JSON."""
rv = self.app.put('/user/', rv = self.put('/user/', '')
data='',
content_type='text/html')
expected = {"error": "Request MUST be in JSON format", expected = {"error": "Request MUST be in JSON format",
"status": "ERROR"} "status": "ERROR"}
self.assertEqual(rv.status_code, 400) self.assertStatusCode(rv, 400)
self.assertEqual(json.loads(rv.data), expected) self.assertJson(expected, rv.data)
def test_missing_fields(self): def test_missing_fields(self):
"""Send a request with missing fields."""
request = {'password': 'hash'} request = {'password': 'hash'}
rv = self.app.put('/user/', rv = self.put('/user/', request)
data=json.dumps(request),
content_type='application/json')
resp = {'error': 'Missing fields: username, full_name', expected = {'error': 'Missing fields: username, full_name',
'status': 'ERROR'} 'status': 'ERROR'}
self.assertEqual(rv.status_code, 400) self.assertStatusCode(rv, 400)
self.assertEqual(json.loads(rv.data), resp) self.assertJson(expected, rv.data)
class TestExistingUsers(LunchoTests):
"""Tests for existing users."""
def setUp(self):
super(TestExistingUsers, self).setUp()
self.user = User(username='test',
fullname='Test User',
passhash='hash')
server.db.session.add(self.user)
server.db.session.commit()
self.user.get_token()
def tearDown(self):
super(TestExistingUsers, self).tearDown()
def test_update_details(self):
"""Update user details."""
request = {'full_name': 'New User Name',
'password': 'newhash'}
rv = self.post('/user/{token}/'.format(token=self.user.token),
request)
expected = {'status': 'OK'}
self.assertStatusCode(rv, 200)
self.assertJson(expected, rv.data)
# check in the database
user = User.query.filter_by(username='test').first()
self.assertEqual(user.fullname, request['full_name'])
self.assertEqual(user.passhash, request['password'])
def test_wrong_token(self):
"""Send a request with an unexisting token."""
request = {'full_name': 'New User Name',
'password': 'newhash'}
rv = self.post('/user/{token}/'.format(token='no-token'),
request)
expected = {'status': 'ERROR',
'error': 'User not found (via token)'}
self.assertStatusCode(rv, 404)
self.assertJson(expected, rv.data)
def test_expired_token(self):
"""Send a token that exists but it's not valid for today."""
# the token is not valid by our standards, but it will be found and
# and the token for today will not be valid
self.user.token = 'expired'
server.db.session.commit()
request = {'full_name': 'New User Name',
'password': 'newhash'}
rv = self.post('/user/{token}/'.format(token=self.user.token),
request)
expected = {'status': 'ERROR',
'error': 'Invalid token'}
self.assertStatusCode(rv, 400)
self.assertJson(expected, rv.data)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

Loading…
Cancel
Save