Browse Source

changing user information

master
Julio Biason 10 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. 118
      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" }
## Single User [/user/{username}/]
## Single User [/user/{token}/]
Manage a single user.
+ Parameters
+ username ... Username used in the creation process.
+ token ... The user token
### Update information [POST]
@ -69,7 +70,7 @@ change will require only the "token" and "password" fields.
+ Request (application/json)
{ "token": "userToken", "full_name": "Full name", "password": "hash" }
{ "full_name": "Full name", "password": "hash" }
+ Response 200 (application/json)
@ -79,18 +80,14 @@ change will require only the "token" and "password" fields.
{ "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]
User removal is only allowed to the user themselves or by a system admin.
+ Request (application/json)
{ "token": "userToken" }
+ Response 200 (application/json)
{ "status": "OK" }

37
luncho/blueprints/users.py

@ -3,6 +3,8 @@
"""User management."""
import logging
from flask import Blueprint
from flask import request
from flask import jsonify
@ -10,10 +12,13 @@ from flask import jsonify
from sqlalchemy.exc import IntegrityError
from luncho.helpers import ForceJSON
from luncho.helpers import JSONError
from luncho.server import User
from luncho.server import db
LOG = logging.getLogger('luncho.blueprints.users')
users = Blueprint('users', __name__)
@ -35,7 +40,31 @@ def create_user():
return jsonify(status='OK')
except IntegrityError:
resp = jsonify(status='ERROR',
error='username already exists')
resp.status_code = 409
return resp
return JSONError(409, 'Username already exists')
@users.route('<token>/', methods=['POST'])
@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."""
resp = jsonify(status='ERROR',
message=message,
error=message,
**kwargs)
resp.status_code = status
return resp

5
luncho/server.py

@ -48,18 +48,13 @@ class User(db.Model):
self.fullname = fullname
self.passhash = passhash
self.token = token
self.issued_date = issued_date
self.validated = validated
self.created_at = datetime.datetime.now()
def get_token(self):
"""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
self.token = self._token()
self.issued_date = datetime.date.today()
db.session.commit()
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')

118
tests/users_tests.py

@ -8,32 +8,21 @@ from luncho import server
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):
server.db.drop_all(bind=None)
class TestUsers(LunchoTests):
"""Test users request."""
def test_create_user(self):
"""Simple user creation."""
request = {'username': 'username',
'full_name': 'full name',
'password': 'hash'}
rv = self.app.put('/user/',
data=json.dumps(request),
content_type='application/json')
rv = self.put('/user/', request)
self.assertEqual(rv.status_code, 200)
self.assertEqual(json.loads(rv.data), {'status': 'OK'})
self.assertStatusCode(rv, 200)
self.assertJson({'status': 'OK'}, rv.data)
# db check
self.assertIsNotNone(User.query.filter_by(username='username').first())
@ -47,37 +36,92 @@ class TestUsers(unittest.TestCase):
request = {'username': 'username',
'full_name': 'full name',
'password': 'hash'}
rv = self.app.put('/user/',
data=json.dumps(request),
content_type='application/json')
rv = self.put('/user/', data=request)
expected = {"status": "ERROR",
"error": "username already exists"}
self.assertEqual(rv.status_code, 409)
self.assertEqual(json.loads(rv.data), expected)
"error": "Username already exists"}
self.assertStatusCode(rv, 409)
self.assertJson(expected, rv.data)
def test_no_json(self):
"""Check the status when doing a request that it's not JSON."""
rv = self.app.put('/user/',
data='',
content_type='text/html')
rv = self.put('/user/', '')
expected = {"error": "Request MUST be in JSON format",
"status": "ERROR"}
self.assertEqual(rv.status_code, 400)
self.assertEqual(json.loads(rv.data), expected)
self.assertStatusCode(rv, 400)
self.assertJson(expected, rv.data)
def test_missing_fields(self):
"""Send a request with missing fields."""
request = {'password': 'hash'}
rv = self.app.put('/user/',
data=json.dumps(request),
content_type='application/json')
resp = {'error': 'Missing fields: username, full_name',
'status': 'ERROR'}
self.assertEqual(rv.status_code, 400)
self.assertEqual(json.loads(rv.data), resp)
rv = self.put('/user/', request)
expected = {'error': 'Missing fields: username, full_name',
'status': 'ERROR'}
self.assertStatusCode(rv, 400)
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__':
unittest.main()

Loading…
Cancel
Save