Commit dff4ede5 authored by Barbora Kompišová's avatar Barbora Kompišová
Browse files

role, tests

parent 7b850957
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -84,7 +84,7 @@ class RoleSchema(Schema):
    users = fields.Nested(UserSchema, only=('id', 'uco', 'email', 'username'), many=True)
    course = fields.Nested(CourseSchema, only=('id', 'name', 'codename'))
    # this could be done prettier (e.g. flattening the structure, showing only set fields)
    permissions = fields.Nested('portal.rest.RolePermissionsSchema', exclude=('id', 'role'))
    permissions = fields.Nested('portal.rest.RolePermissionsSchema')


class RolePermissionsSchema(Schema):
+14 −11
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@ from flask import Blueprint, request
from flask_restful import Api, Resource

from portal.database.models import Role
from portal.rest import role_schema, roles_schema, users_schema
from portal.rest import role_schema, roles_schema, users_schema, user_list_update_schema, permissions_schema
from portal.service import service
from portal.service.errors import PortalAPIError
from portal.tools.decorators import error_handler
@@ -50,7 +50,6 @@ class RoleResource(Resource):


class RoleList(Resource):

    @error_handler
    def get(self, cid):
        log.info(f"GET to {request.url}")
@@ -80,7 +79,7 @@ class RolePermissions(Resource):
        log.info(f"GET to {request.url}")
        course = service.find_course(cid)
        role = service.find_role(course, rid)
        return roles_schema.dump(role.permissions)
        return permissions_schema.dump(role.permissions)

    @error_handler
    def put(self, cid, rid):
@@ -92,7 +91,7 @@ class RolePermissions(Resource):
        if not json_data:
            raise PortalAPIError(400, message='No data provided for update of role permissions.')

        data = role_schema.load(json_data)[0]
        data = permissions_schema.load(json_data)[0]

        role.set_permissions(**data)
        log.debug(f"Updated permissions for role {rid} in course {cid} to {data}.")
@@ -115,18 +114,22 @@ class RoleUsersList(Resource):
        role = service.find_role(course, rid)
        json_data = request.get_json()

        data = users_schema.load(json_data)[0]
        data = user_list_update_schema.load(json_data)[0]

        # TODO: use ids, move to service?
        # everything from users_add is added, THEN everything from users_remove is subtracted
        users_old = set(role.users)
        users_add = data.get('add')
        users_remove = data.get('remove')
        if users_add is not None:
            users_old.union(users_add)
        if users_remove is not None:
            users_old.difference(users_remove)

        users = set()
        if users_add:
            to_add = service.find_users(data['add'])
            users = users_old.union(to_add)
        if users_remove:
            to_remove = service.find_users(data['remove'])
            users = users_old.difference(to_remove)

        role.users = list(users)
        service.write_entity(role)
        log.info(f"Updated users of role {rid} to {data}.")
        return '', 204

+1 −6
Original line number Diff line number Diff line
@@ -2,16 +2,11 @@ from datetime import timedelta

from flask_jwt_extended import create_access_token

from portal.database.models import Group, Course, User, Role, Project
from portal.database.models import Course, User, Project
from tools.time import NOW, strip_seconds, portal_timezone
from . import utils
import json

'''

 * `[POST] /courses/<cid>/projects/<pid>/submissions` : Create submission
'''


def test_list(client):
    cpp = Course.query.filter_by(codename="PB161").first()
+286 −0
Original line number Diff line number Diff line
from portal.database.models import Course, Role, User
from . import utils
import json


def test_list(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    cpp_roles = len(cpp.roles)
    response = client.get(f'/courses/{cpp.codename}/roles/')
    assert response.status_code == 200
    assert response.mimetype == 'application/json'

    roles = json.loads(str(response.get_data().decode("utf-8")))
    assert len(roles) == cpp_roles
    cpp_updated = Course.query.filter_by(codename="PB161").first()
    for r in roles:
        utils.assert_role_in(cpp_updated.roles, r)


def test_create(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    cpp_roles = len(cpp.roles)

    request_dict = {
        "name": "assistant",
        "description": "teacher assistant"
    }
    request_json = json.dumps(request_dict)
    response = client.post(f"/courses/{cpp.codename}/roles/", data=request_json,
                           headers={"content-type": "application/json"})
    assert response.status_code == 201
    assert response.mimetype == 'application/json'

    new_role = json.loads(str(response.get_data().decode("utf-8")))
    cpp_updated = Course.query.filter_by(codename="PB161").first()
    assert len(cpp_updated.roles) == cpp_roles + 1
    assert new_role['name'] == "assistant"
    assert new_role['description'] == "teacher assistant"
    assert new_role['id']
    utils.assert_role_in(cpp_updated.roles, new_role)


def test_read(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    response = client.get(f"/courses/{cpp.codename}/roles/{r.name}")
    assert response.status_code == 200
    assert response.mimetype == 'application/json'

    role = json.loads(str(response.get_data().decode("utf-8")))

    utils.assert_role(r, role)


def test_update(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    r_name = r.name
    request_dict = dict(
        name="new role name",
        description="new role desc",
    )
    request_json = json.dumps(request_dict)
    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}", data=request_json,
                          headers={"content-type": "application/json"})
    assert response.status_code == 204
    assert response.mimetype == 'application/json'
    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.name == "new role name").first()
    assert r_updated
    assert r_updated.description == "new role desc"
    assert not Role.query.filter(Role.course_id == cpp.id).filter(Role.name == r_name).first()


def test_delete(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    r_name = r.name
    cpp_roles = len(cpp.roles)
    response = client.delete(f"/courses/{cpp.codename}/roles/{r.name}")

    assert response.status_code == 204
    assert response.mimetype == 'application/json'
    cpp_updated = Course.query.filter_by(codename="PB161").first()
    assert len(cpp_updated.roles) == cpp_roles - 1
    assert not Role.query.filter(Role.course_id == cpp.id).filter(Role.name == r_name).first()


def test_users_list(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    response = client.get(f"/courses/{cpp.codename}/roles/{r.id}/users")

    assert response.status_code == 200
    assert response.mimetype == 'application/json'
    users = json.loads(str(response.get_data().decode("utf-8")))
    assert len(users) == len(r.users)
    for user in users:
        utils.assert_user_in(r.users, user)


def test_users_update_add(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    r_users = len(r.users)
    user = User.query.filter_by(username="xbar").first()
    assert user not in r.users

    request_dict = dict(
        add=[user.id]
    )
    request_json = json.dumps(request_dict)
    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}/users", data=request_json,
                          headers={"content-type": "application/json"})

    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert len(r_updated.users) == r_users + 1
    assert user in r_updated.users


def test_users_update_add_duplicate(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    r_users = len(r.users)
    user = r.users[0]
    assert user in r.users

    request_dict = dict(
        add=[user.id]
    )
    request_json = json.dumps(request_dict)
    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}/users", data=request_json,
                          headers={"content-type": "application/json"})

    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert len(r_updated.users) == r_users
    assert user in r_updated.users


def test_users_update_remove(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    r_users = len(r.users)
    user = r.users[0]
    assert user in r.users

    request_dict = dict(
        remove=[user.id]
    )
    request_json = json.dumps(request_dict)
    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}/users", data=request_json,
                          headers={"content-type": "application/json"})

    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert len(r_updated.users) == r_users - 1
    assert user not in r_updated.users


def test_users_update_remove_user_not_in(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    r_users = len(r.users)
    user = User.query.filter_by(username="xbar").first()
    assert user not in r.users

    request_dict = dict(
        remove=[user.id]
    )
    request_json = json.dumps(request_dict)
    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}/users", data=request_json,
                          headers={"content-type": "application/json"})

    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert len(r_updated.users) == r_users
    assert user not in r_updated.users


def test_add_user(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    role_users = len(r.users)
    u = User.query.filter_by(username="xbar").first()
    assert not u.groups

    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}/users/{u.username}")
    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert u in r_updated.users
    assert len(r_updated.users) == role_users + 1


def test_add_user_duplicate(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    role_users = len(r.users)
    u = r.users[0]
    assert u in r.users

    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}/users/{u.username}")
    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert u in r_updated.users
    assert len(r_updated.users) == role_users


def test_remove_user(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    role_users = len(r.users)
    u = r.users[0]

    response = client.delete(f"/courses/{cpp.codename}/roles/{r.name}/users/{u.username}")
    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert u not in r_updated.users
    assert len(r_updated.users) == role_users - 1


def test_remove_user_not_in(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    role_users = len(r.users)
    u = User.query.filter_by(username="xbar").first()
    assert not u.groups

    response = client.delete(f"/courses/{cpp.codename}/roles/{r.name}/users/{u.username}")
    assert response.status_code == 400
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter(Role.id == r.id).first()
    assert u not in r_updated.users
    assert len(r_updated.users) == role_users


def test_permissions_list(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]

    response = client.get(f"/courses/{cpp.codename}/roles/{r.name}/permissions")
    assert response.status_code == 200
    assert response.mimetype == 'application/json'

    data = json.loads(str(response.get_data().decode("utf-8")))
    utils.assert_role_permissions(r.permissions, data)


def test_permissions_update(client):
    cpp = Course.query.filter_by(codename="PB161").first()
    r = cpp.roles[0]
    r_name = r.name

    request_dict = dict(
        viewCourseFull=True,
        writeRole=True,
        readAllSubmissions=True
    )
    request_json = json.dumps(request_dict, cls=utils.DateTimeEncoder)

    response = client.put(f"/courses/{cpp.codename}/roles/{r.name}/permissions", data=request_json,
                          headers={"content-type": "application/json"})
    assert response.status_code == 204
    assert response.mimetype == 'application/json'

    r_updated = Role.query.filter(Role.course_id == cpp.id).filter_by(name=r_name).first()
    assert r_updated.permissions.viewCourseFull
    assert r_updated.permissions.writeRole
    assert r_updated.permissions.readAllSubmissions

+43 −0
Original line number Diff line number Diff line
@@ -120,6 +120,49 @@ def assert_project_config(expected: ProjectConfig, actual: dict):
    assert expected.archive_from == actual['archive_from']


def assert_role_permissions(expected: Role, actual: dict):
    assert expected.id == actual['id']
    assert expected.role.id == actual['role']['id']
    assert expected.viewCourseLimited == actual['viewCourseLimited']
    assert expected.viewCourseFull == actual['viewCourseFull']
    assert expected.updateCourse == actual['updateCourse']
    assert expected.setNotesAccessToken == actual['setNotesAccessToken']
    assert expected.assignRoles == actual['assignRoles']
    assert expected.writeRole == actual['writeRole']
    assert expected.readRole == actual['readRole']
    assert expected.writeProject == actual['writeProject']
    assert expected.deleteProject == actual['deleteProject']
    assert expected.archiveProject == actual['archiveProject']
    assert expected.writeGroup == actual['writeGroup']
    assert expected.readGroup == actual['readGroup']
    assert expected.deleteGroup == actual['deleteGroup']
    assert expected.readAllSubmissions == actual['readAllSubmissions']
    assert expected.readOwnSubmissions == actual['readOwnSubmissions']
    assert expected.readGroupSubmissions == actual['readGroupSubmissions']
    assert expected.viewAllSubmissionFiles == actual['viewAllSubmissionFiles']
    assert expected.createSubmission == actual['createSubmission']
    assert expected.resubmitSubmission == actual['resubmitSubmission']
    assert expected.readAllReviews == actual['readAllReviews']
    assert expected.readOwnReviews == actual['readOwnReviews']
    assert expected.writeOwnReview == actual['writeOwnReview']
    assert expected.writeAllReview == actual['writeAllReview']
    assert expected.evaluateSubmission == actual['evaluateSubmission']


def compare_role(expected: Role, actual: dict):
    return expected.id == actual['id'] \
           and expected.name == actual['name'] \
           and expected.description == actual['description'] \
           and expected.course_id == actual['course']['id']


def assert_role_in(role_list: list, role: dict):
    for r in role_list:
        if compare_role(r, role):
            return True
    return False


# source: https://stackoverflow.com/questions/11875770/how-to-overcome-datetime-datetime-not-json-serializable
class DateTimeEncoder(json.JSONEncoder):
    def default(self, o):