diff --git a/cit/__init__.py b/cit/__init__.py index 5a62b01..920d7f5 100644 --- a/cit/__init__.py +++ b/cit/__init__.py @@ -2,7 +2,7 @@ from flask import Flask, render_template, request, g, session from flask.ext.admin import Admin, BaseView, expose -from flask.ext.admin.contrib.sqla import ModelView +from flask.ext.admin.contrib.geoa import ModelView from authomatic.providers import oauth2 from authomatic import Authomatic @@ -48,9 +48,8 @@ def func(): def load_user(): - if 'user_id' not in session.keys(): - g.user = None - else: + g.user = None + if 'user_id' in session.keys(): g.user = User.query.filter_by(id=session['user_id']).first() @@ -73,7 +72,7 @@ def create_app(config='config.ProductionDevelopmentConfig'): app.register_blueprint(comments_bp, url_prefix='/comments') app.register_blueprint(organizations_bp, url_prefix='/organizations') - admin = Admin(app) + admin = Admin(app, name='CIT-Admin') # add admin views. admin.add_view(AdminView(User, db.session)) diff --git a/cit/auth/controllers.py b/cit/auth/controllers.py index 486d305..98eb5e3 100644 --- a/cit/auth/controllers.py +++ b/cit/auth/controllers.py @@ -58,7 +58,7 @@ def user_info(): def logout(): session.pop('authomatic:fb:state', None) session.pop('user_id', None) - return jsonify({'status': 0}) + return jsonify({}), 200 @auth_bp.route('/user/profile/', methods=['POST']) diff --git a/cit/auth/models.py b/cit/auth/models.py index 3219e00..6c40bde 100644 --- a/cit/auth/models.py +++ b/cit/auth/models.py @@ -50,7 +50,7 @@ class Spatial_ref_sys(db.Model): class Vote(db.Model): id = db.Column(db.Integer, primary_key=True) - author_id = db.Column(db.Integer, db.ForeignKey("user.id")) + author_id = db.Column(db.Integer, db.ForeignKey('user.id')) vote = db.Column(db.Boolean) # This is used to discriminate between the linked tables. @@ -61,9 +61,9 @@ class Vote(db.Model): target = generic_relationship(target_type, target_id) - author = db.relationship("User") + author = db.relationship('User') - def __init__(self, author="", vote="", target=""): + def __init__(self, author='', vote='', target=''): self.author = author self.vote = vote self.target = target diff --git a/cit/comments/controllers.py b/cit/comments/controllers.py index 93fd9b8..7ba5455 100644 --- a/cit/comments/controllers.py +++ b/cit/comments/controllers.py @@ -2,8 +2,6 @@ from ..utils import login_required from .models import Comment -from ..issues.models import Issue -from ..auth.models import User from ..db import db comments_bp = Blueprint('comments', __name__) @@ -13,26 +11,33 @@ @login_required def comment_add(): comment_id = 0 - error = 400 - if request.form['issue_id']: - comment = Comment(author=g.user, issue_id=request.form['issue_id'], + status = 400 + + if request.form['issue_id'] and request.form['msg']: + comment = Comment(author_id=g.user.id, + issue_id=request.form['issue_id'], message=request.form['msg']) db.session.add(comment) db.session.commit() comment_id = comment.id - error = 201 - return jsonify({'id': comment_id}), error + status = 201 + return jsonify({'id': comment_id}), status -@comments_bp.route('/comments//', methods=['DELETE']) +@comments_bp.route('//', methods=['DELETE']) @login_required def delete_comment(comment_id): + db.session.begin_nested() comment_query = db.session.query(Comment) comment_filtered = comment_query.filter(Comment.id == comment_id) result = comment_filtered.delete(synchronize_session='fetch') if result: db.session.commit() - return jsonify({'status': 0}) + status = 204 + message = 'comment was successfully deleted' else: db.session.rollback() - return jsonify({'msg': 'comment not found', 'status': 1}) + status = 404 + message = 'comment not found' + + return jsonify({'msg': message}), status diff --git a/cit/comments/models.py b/cit/comments/models.py index bef56ef..f806218 100644 --- a/cit/comments/models.py +++ b/cit/comments/models.py @@ -3,15 +3,15 @@ class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) - author_id = db.Column(db.Integer, db.ForeignKey("user.id")) - issue_id = db.Column(db.Integer, db.ForeignKey("issue.id")) + author_id = db.Column(db.Integer, db.ForeignKey('user.id')) + issue_id = db.Column(db.Integer, db.ForeignKey('issue.id')) message = db.Column(db.String(400)) - author = db.relationship("User") - issue = db.relationship("Issue") + author = db.relationship('User') + issue = db.relationship('Issue') - def __init__(self, author="", issue_id=None, message=""): - self.author = author + def __init__(self, author_id=None, issue_id=None, message=''): + self.author_id = author_id self.issue_id = issue_id self.message = message diff --git a/cit/organizations/controllers.py b/cit/organizations/controllers.py index 283ba20..f7e5934 100644 --- a/cit/organizations/controllers.py +++ b/cit/organizations/controllers.py @@ -1,10 +1,11 @@ -from flask import redirect, render_template, request, make_response, g -from flask import Blueprint, session, jsonify -from urllib import quote +from flask import request, g +from flask import Blueprint, jsonify from .models import Organization from ..db import db from ..utils import login_required, admin_required +import sqlalchemy.exc as sqlalchemy_exc +import sqlalchemy.orm.exc as sqlalchemy_orm_exc organizations_bp = Blueprint('organizations', __name__) @@ -27,27 +28,41 @@ def organization_create(): @organizations_bp.route('/', methods=['GET']) def organizations_info(): - organization_query = db.session.query(Organization) - organization_names = [] + organization_query = db.session.query(Organization) + organizations_list = [] if organization_query: - for org in organization_query: - organization_dict = {} - organization_dict.update({ - 'id': org.id, - 'name': org.name - }) - organization_names.append(organization_dict) - return jsonify(organizations=organization_names) - else: - return jsonify({}) - - -@organizations_bp.route('/organizations//add-user/', methods=['POST']) + organizations_list = \ + [{'id': org.id, 'name': org.name} for org in organization_query] + return jsonify(organizations=organizations_list) + + +@organizations_bp.route('//add-user/', + methods=['POST']) @login_required def organization_user_add(org_id): user = g.user - org = db.session.query(Organization).filter(Organization.id == org_id) - user.organizations.append(org.first()) - db.session.add(user) - db.session.commit() - return jsonify({'status': 0}), 201 + try: + db.session.begin_nested() + org = db.session.query(Organization).filter(Organization.id == org_id) + user.organizations.append(org.first()) + db.session.add(user) + db.session.commit() + status = 201 + message = 'user-organization relationship was established' + except sqlalchemy_exc.IntegrityError: + db.session.rollback() + status = 409 + message = 'user-organization relationship was not established ' + \ + 'due to the IntegrityError transaction committing.\n' + \ + 'Please check whether the specific relationship is' + \ + 'already present in database.' + except sqlalchemy_orm_exc.FlushError: + db.session.rollback() + status = 404 + message = 'user-organization relationship was not established ' + \ + 'due to the FlushError transaction committing.\n' + \ + 'Please check whether the specific organization is' + \ + 'present in database.' + + return jsonify({'user_id': user.id, 'org_id': org_id, + 'message': message}), status diff --git a/cit/organizations/models.py b/cit/organizations/models.py index 18125f0..9c3e34d 100644 --- a/cit/organizations/models.py +++ b/cit/organizations/models.py @@ -1,4 +1,3 @@ -from flask import g from geoalchemy2 import Geography from ..db import db @@ -8,7 +7,7 @@ class Organization(db.Model): name = db.Column(db.String(120)) address = db.Column(Geography(geometry_type='GEOMETRY')) - def __init__(self, name="", address=""): + def __init__(self, name='', address=''): self.name = name self.address = address diff --git a/init_db.py b/init_db.py index 3ea9b57..958e43c 100644 --- a/init_db.py +++ b/init_db.py @@ -19,7 +19,7 @@ def populate_target(self, values): mixer = MyOwnMixer() -class InitDB(): +class InitDB: def __init__(self, app): self.app = app @@ -51,9 +51,10 @@ def generate_test_data(self): description=mixer.RANDOM, coordinates='POINT(49.839357 24.028398)') db.session.add(issue) + db.session.flush() comment = mixer.blend(Comment, - author=user, - issue=issue, + author_id=user.id, + issue_id=issue.id, message=mixer.RANDOM) db.session.add(comment) photo = mixer.blend(Photo, diff --git a/requirements.txt b/requirements.txt index 311cb03..5299cf2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -e git+git@github.com:peterhudec/authomatic.git@a3058fac78827d66f4d3a2e4e5739ee95cff0261#egg=Authomatic-master Flask==0.10.1 -Flask-Admin==1.1.0 +Flask-Admin==1.2.0 Flask-Login==0.2.11 Flask-RESTful==0.3.2 Flask-SQLAlchemy==2.0 diff --git a/test.py b/test.py index aabc69a..4ef3f37 100644 --- a/test.py +++ b/test.py @@ -11,12 +11,16 @@ ISOLATION_LEVEL_READ_COMMITTED from cit import Comment import json +from cit.auth.models import User, organization_relationships +from cit.organizations.models import Organization engine = create_engine(database_uri(Config.host, Config.username, Config.password, 'postgres')) class TestCase(Base): + app = None + @classmethod def setUpClass(cls): cls.app = create_app('config.TestingConfig') @@ -32,32 +36,38 @@ def setUpClass(cls): session.execute('DROP DATABASE ' + TestingConfig.db_name) session.connection().connection.\ set_isolation_level(ISOLATION_LEVEL_READ_COMMITTED) + print('Old testing DB "cit_test" has been dropped') except sqlalchemy_exc.DBAPIError: - print('Testing DB "cit_test" is absent. New DB "cit_test" \ -will be created') - finally: - session.commit() - session.rollback() - session.close_all() - engine.dispose() + message = 'Testing DB "cit_test" is absent. New DB "cit_test" ' \ + 'will be created' + print(message) session.connection().connection.\ set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) session.execute('CREATE DATABASE ' + TestingConfig.db_name) session.connection().connection.\ set_isolation_level(ISOLATION_LEVEL_READ_COMMITTED) + print('New testing DB "cit_test" has been created') db.engine.execute('CREATE EXTENSION postgis;') db.engine.execute('CREATE EXTENSION plv8;') db.create_all() init_db.InitDB(cls.app).generate_test_data() + user = User.query.filter_by(id=1).first() + init_db.InitDB(cls.app).make_user_as_admin(user.fb_id) + init_db.InitDB(cls.app).generate_test_data() + pass @classmethod def tearDownClass(cls): + db.session.close() db.session.remove() drop_database(db.engine.url) db.get_engine(cls.app).dispose() + message = 'All tests has been run. ' \ + 'Testing DB "cit_test" has been dropped.' + print(message) def setUp(self): self._ctx = self.app.test_request_context() @@ -72,19 +82,30 @@ def tearDown(self): class TestModel(TestCase): - def test_adding_comments_unlogged_user(self): + def test_add_comment_unauth_user(self): comment = dict(issue_id=1, msg='test test') response = self.client.post('/comments/', data=comment) - assert response.status_code == 401 + message_init = 'A problem with adding comment by unauthorised user.\n' + + message = message_init + \ + "response.status_code isn't equal to 401." + TestCase.assertEqual(self, response.status_code, 401, msg=message) - def test_adding_comments_logged_user(self): + message = message_init + \ + "response.data doesn't contain message 'Permission denied'." + TestCase.assertIn(self, 'Permission denied', + response.data, msg=message) + + def test_add_comment_auth_user_1(self): + user_id = 2 # this user doesn't have superuser rights + issue_id = 1 with self.client as c: with c.session_transaction() as sess: - sess['user_id'] = 1 - comment = dict(issue_id=1, msg='test test') - response = self.client.post('/comments/', data=comment) + sess['user_id'] = user_id + comment = dict(issue_id=issue_id, msg='test test') + response = c.post('/comments/', data=comment) with c.session_transaction() as sess: sess.pop('user_id', None) @@ -92,10 +113,300 @@ def test_adding_comments_logged_user(self): comment_id = response_in_json['id'] comment_from_db = Comment.query.filter_by(id=comment_id).first() - assert response.status_code == 201 + message = 'A problem with adding comment by authorised user.\n' + \ + "response.status_code isn't equal to 201." + TestCase.assertEqual(self, response.status_code, 201, msg=message) - assert (str(comment['issue_id']) == comment_from_db.issue_id) and\ + message = 'A problem with adding comment by authorised user to DB.' + expr = (str(comment['issue_id']) == comment_from_db.issue_id) and\ (comment['msg'] == comment_from_db.message) + TestCase.assertTrue(self, expr, msg=message) + + def test_add_comment_auth_user_2(self): + user_id = 1 + issue_id = 3 # this issue doesn't exist + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + comment = dict(issue_id=issue_id, msg="this issue doesn't exist") + response = c.post('/comments/', data=comment) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + message_init = 'A problem with comment making on nonexistent issue.\n' + + message = message_init + "response.status_code isn't equal to 205." + TestCase.assertEqual(self, response.status_code, 205, msg=message) + + message = message_init + \ + "response.data doesn't contain substring " + \ + "'issue doesn't exist'" + TestCase.assertIn(self, "'issue doesn't exist'", + response.data, msg=message) + + def test_del_comment_unauth_user(self): + comment_id = 2 + response = self.client.delete('/comments/%d/' % comment_id) + + message_init = 'A problem with deleting comment by' + \ + 'unauthorised user.\n' + + message = message_init + \ + "response.status_code isn't equal to 401." + TestCase.assertEqual(self, response.status_code, 401, msg=message) + + message = message_init + \ + "response.data doesn't contain message 'Permission denied'." + TestCase.assertIn(self, 'Permission denied', + response.data, msg=message) + + def test_del_comment_auth_user_1(self): + # the func tests comment deleting by a user who didn't create + # this comment + user_id = 2 + comment_id = 1 + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + response = c.delete('/comments/%d/' % comment_id) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + comment_from_db = Comment.query.filter_by(id=comment_id).first() + + message = 'Test a status of comment deleting by a user who has no ' + \ + 'permission for this operation.\n' + \ + "response.status_code isn't equal to 403." + TestCase.assertEqual(self, response.status_code, 403, msg=message) + + message = 'A comment was successfully deleted by unauthorised user.' + TestCase.assertIsNotNone(self, comment_from_db, msg=message) + + def test_del_comment_auth_user_2(self): + user_id = 2 + comment_id = 2 + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + response = c.delete('/comments/%d/' % comment_id) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + comment_from_db = Comment.query.filter_by(id=comment_id).first() + + message = 'A status of comment deleting by comment author\n' + \ + "isn't equal to 204." + TestCase.assertEqual(self, response.status_code, 204, msg=message) + + message = 'A comment was successfully deleted by authorised user.' + TestCase.assertIsNone(self, comment_from_db, msg=message) + + def test_del_comment_auth_user_3(self): + # a test for superuser privilege to delete user comments. + user_id = 1 # this user has the superuser rights + comment_id = 2 + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + response = c.delete('/comments/%d/' % comment_id) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + comment_from_db = Comment.query.filter_by(id=comment_id).first() + + message = 'A status of comment deleting by superuser\n' + \ + "isn't equal to 204." + TestCase.assertEqual(self, response.status_code, 204, msg=message) + + message = 'A comment was successfully deleted by superuser.' + TestCase.assertIsNone(self, comment_from_db, msg=message) + + def test_del_notexist_comment(self): + user_id = 2 + comment_id = 3 # the comment with id = 3 doesn't exist + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + response = c.delete('/comments/%d/' % comment_id) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + message = 'A status of deleting nonexistent comment' + \ + "isn't equal to 404." + TestCase.assertEqual(self, response.status_code, 404, msg=message) + + def test_adding_organization_unauth_user(self): + organization = dict(name='Test Organization', + address='POINT(49.836134 24.023151)') + response = self.client.post('/organizations/', data=organization) + + message_init = "A user isn't authorised.\n" + \ + 'A problem with test connected to organization adding by ' + \ + 'nonsuperuser.\n' + + message = message_init + "response.status_code isn't equal to 401." + TestCase.assertEqual(self, response.status_code, 401, msg=message) + + message = message_init + \ + "response.data doesn't contain message " + \ + "'Admin permissions required'." + TestCase.assertIn(self, 'Admin permissions required', + response.data, msg=message) + + def test_adding_organization_nonsuperuser(self): + user_id = 2 # this user doesn't have superuser rights + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + organization = dict(name='Test Organization', + address='POINT(49.836134 24.023151)') + response = \ + self.client.post('/organizations/', + data=json.dumps(organization), + content_type='application/json') + with c.session_transaction() as sess: + sess.pop('user_id', None) + + message_init = "A user is authorised but isn't a superuser.\n" + \ + 'A problem with test connected to organization adding by ' + \ + 'nonsuperuser.\n' + + message = message_init + \ + "response.status_code isn't equal to 401." + TestCase.assertEqual(self, response.status_code, 401, msg=message) + + message = message_init + \ + "response.data doesn't contain message " + \ + "'Admin permissions required'." + TestCase.assertIn(self, 'Admin permissions required', + response.data, msg=message) + + def test_adding_organization_superuser(self): + user_id = 1 # this user has the superuser rights + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + organization = dict(name='Test Organization', + address='POINT(49.836134 24.023151)') + response = \ + self.client.post('/organizations/', + data=json.dumps(organization), + content_type='application/json') + with c.session_transaction() as sess: + sess.pop('user_id', None) + + response_in_json = json.loads(response.data) + organization_id = response_in_json['id'] + org_from_db = Organization.query.filter_by(id=organization_id).first() + + message = 'A user has the superuser rights.\n' + \ + 'A problem with test concerned to organization ' + \ + 'adding by a superuser.\n' + \ + "response.status_code isn't equal to 201." + TestCase.assertEqual(self, response.status_code, 201, msg=message) + + message = 'A problem with adding organization by a superuser to DB.' + expr = (str(organization['name']) == org_from_db.name) and\ + (organization['address'] == org_from_db.address) + TestCase.assertTrue(self, expr, msg=message) + + def test_adding_user_organization_rel_unauth_user(self): + org_id = 1 + response = self.client.post('/organizations/%d/add-user/' % org_id) + + message_init = 'A problem with adding user to user-organization ' + \ + 'relationship by unauthorised user.\n' + + message = message_init + \ + "response.status_code isn't equal to 401." + TestCase.assertEqual(self, response.status_code, 401, msg=message) + + message = message_init + \ + "response.data doesn't contain message 'Permission denied'." + TestCase.assertIn(self, 'Permission denied', + response.data, msg=message) + + def test_adding_user_organization_rel_auth_user_1(self): + user_id = 2 + org_id = 1 + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + response = c.post('/organizations/%d/add-user/' % org_id) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + response_in_json = json.loads(response.data) + message_init = 'A problem with adding user to user-organization ' + \ + 'relationship by authorised user.\n' + + message = message_init + \ + "response.data doesn't contain substring 'was established'" + TestCase.assertIn(self, 'was established', response.data, msg=message) + + message = message_init + \ + "response_in_json['user_id'] != user_id" + TestCase.assertEqual(self, response_in_json['user_id'], + user_id, msg=message) + + message = message_init + \ + "response_in_json['org_id'] != org_id" + TestCase.assertEqual(self, response_in_json['org_id'], + org_id, msg=message) + + message = message_init + \ + "'organization_relationships' table doesn't contain the raw" + \ + ' (%d, %d)' % (user_id, org_id) + x = db.session.query(organization_relationships).\ + filter(organization_relationships.c.organization_id == org_id).\ + filter(organization_relationships.c.user_id == user_id).all() + TestCase.assertEqual(self, x, [(user_id, org_id)], msg=message) + + message = message_init + \ + "response.status_code isn't equal to 201." + TestCase.assertEqual(self, response.status_code, 201, msg=message) + + def test_adding_user_organization_rel_auth_user_2(self): + user_id = 2 + org_id = 2 + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + response = c.post('/organizations/%d/add-user/' % org_id) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + message_init = 'A problem with creation user-organization' + \ + 'relationship that already exists.\n' + + message = message_init + \ + "response.data doesn't contain substring 'IntegrityError'" + TestCase.assertIn(self, 'IntegrityError', response.data, msg=message) + + message = message_init + \ + "response.status_code isn't equal to 409." + TestCase.assertEqual(self, response.status_code, 409, msg=message) + + def test_adding_user_organization_rel_auth_user_3(self): + user_id = 1 + org_id = 3 # such organization doesn't exist + with self.client as c: + with c.session_transaction() as sess: + sess['user_id'] = user_id + response = c.post('/organizations/%d/add-user/' % org_id) + with c.session_transaction() as sess: + sess.pop('user_id', None) + + message_init = 'A problem with creation user-organization' + \ + "relationship where the organization doesn't exist.\n" + + message = message_init + \ + "response.data doesn't contain substring 'FlushError'" + TestCase.assertIn(self, 'FlushError', response.data, msg=message) + + message = message_init + \ + "response.status_code isn't equal to 404." + TestCase.assertEqual(self, response.status_code, 404, msg=message) if __name__ == "__main__": import unittest