From ea39814b8179a98487264597d12ec0ecf91c87fd Mon Sep 17 00:00:00 2001 From: dominik24c Date: Sun, 8 Jan 2023 20:26:41 +0100 Subject: [PATCH] add functional tests of group endpoints for coordinator view --- backend/app/coordinator/routes/groups.py | 47 ++-- backend/app/coordinator/utils.py | 15 ++ backend/tests/factory.py | 2 - backend/tests/fake_data.py | 16 +- .../coordinator/test_groups.py | 209 ++++++++++++++++++ .../coordinator/test_students.py | 3 +- backend/tests/utils.py | 9 + 7 files changed, 275 insertions(+), 26 deletions(-) create mode 100644 backend/tests/functional_tests/coordinator/test_groups.py diff --git a/backend/app/coordinator/routes/groups.py b/backend/app/coordinator/routes/groups.py index 3573267..706cdff 100644 --- a/backend/app/coordinator/routes/groups.py +++ b/backend/app/coordinator/routes/groups.py @@ -8,7 +8,7 @@ from ..schemas import GroupEditSchema, GroupsPaginationSchema, GroupCreateSchema DetailGroupSchema from ...dependencies import db from ...base.utils import paginate_models -from ..utils import load_weight_for_project_grade_sheet, calculate_points_for_one_term +from ..utils import attach_points_for_first_and_second_term_to_group_models bp = APIBlueprint("groups", __name__, url_prefix="/groups") @@ -21,23 +21,11 @@ def list_groups(year_group_id: int, query: dict) -> dict: page = query.get('page') per_page = query.get('per_page') - weights = load_weight_for_project_grade_sheet() - groups_query = Group.search_by_name(year_group_id, search_name) data = paginate_models(page, groups_query, per_page) items = data['items'] - pgs = [] - for g in items: - if len(g.project_grade_sheet) == 0: - pgs.append(None) - else: - pgs.append(g.project_grade_sheet[0]) - calculated_points = calculate_points_for_one_term(weights, pgs) - - for group, points in zip(items, calculated_points): - group.points_for_first_term = points[0] - group.points_for_second_term = points[1] + attach_points_for_first_and_second_term_to_group_models(items) return { "groups": items, @@ -55,13 +43,13 @@ def create_group(year_group_id: int, data: dict) -> dict: yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() if yg is None: - abort(404, "YearGroup doesn't exist!") + abort(404, "Not found year group!") project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() limit_student_per_group = current_app.config.get('LIMIT_STUDENTS_PER_GROUP') if project_supervisor is None: - abort(400, f"Project Supervisor with id {project_supervisor_id} doesnt exist") + abort(404, f"Not found project supervisor!") elif limit_student_per_group is not None and limit_student_per_group < len(students_indexes): abort(400, f"Too much students you want add to group, " f"The group can have only {limit_student_per_group} students") @@ -84,10 +72,13 @@ def create_group(year_group_id: int, data: dict) -> dict: if len(students_without_groups) > 0: abort(400, "One or more students have already belonged to group!") + students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() + if len(students) != len(students_indexes): + abort(404, "Not found students!") + db.session.add(group) db.session.commit() - students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() for student in students: group.students.append(student) @@ -135,10 +126,24 @@ def edit_group(id: int, data: dict) -> dict: if group is None: abort(404, f"Not found group!") - students = db.session.query(Student).filter(Student.index.in_(data['students'])).all() - group.students = students - group.name = data['name'] - group.project_supervisor_id = data['project_supervisor_id'] + students_indexes = data.get('students') + name = data.get('name') + project_supervisor_id = data.get('project_supervisor_id') + + if students_indexes is not None: + students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() + if len(students_indexes) != len(students): + abort(404, 'Not found students!') + group.students = students + + if name is not None: + group.name = name + + if project_supervisor_id is not None: + ps = ProjectSupervisor.query.filter(ProjectSupervisor.id == project_supervisor_id).first() + if ps is None: + abort(404, "Not found project supervisor!") + group.project_supervisor_id = project_supervisor_id db.session.commit() return {"message": "Group was updated!"} diff --git a/backend/app/coordinator/utils.py b/backend/app/coordinator/utils.py index 00c2b9d..f140e88 100644 --- a/backend/app/coordinator/utils.py +++ b/backend/app/coordinator/utils.py @@ -207,3 +207,18 @@ def calculate_points_for_one_term(weights: dict, project_grade_sheets: List[Proj terms.append((round(fp, 2) * 100, round(sp, 2) * 100)) return terms + + +def attach_points_for_first_and_second_term_to_group_models(items: List[Group]) -> None: + weights = load_weight_for_project_grade_sheet() + pgs = [] + for g in items: + if len(g.project_grade_sheet) == 0: + pgs.append(None) + else: + pgs.append(g.project_grade_sheet[0]) + calculated_points = calculate_points_for_one_term(weights, pgs) + + for group, points in zip(items, calculated_points): + group.points_for_first_term = points[0] + group.points_for_second_term = points[1] diff --git a/backend/tests/factory.py b/backend/tests/factory.py index 703d8a3..14a17de 100644 --- a/backend/tests/factory.py +++ b/backend/tests/factory.py @@ -50,5 +50,3 @@ class YearGroupStudentsFactory(alchemy.SQLAlchemyModelFactory): class Meta: model = YearGroupStudents sqlalchemy_session = db.session - # year_group_id - # student_index diff --git a/backend/tests/fake_data.py b/backend/tests/fake_data.py index 5bf1d6a..863ef8d 100644 --- a/backend/tests/fake_data.py +++ b/backend/tests/fake_data.py @@ -1,7 +1,7 @@ from typing import List from .factory import ProjectSupervisorFactory, YearGroupProjectSupervisorsFactory, \ - StudentFactory, YearGroupStudentsFactory + StudentFactory, YearGroupStudentsFactory, GroupFactory from ..app.dependencies import db from ..app.project_supervisor.models import YearGroup, ProjectSupervisor from ..app.students.models import Group, Student, YearGroupStudents @@ -58,3 +58,17 @@ def create_student(data: dict, year_group_id: int) -> Student: db.session.add(YearGroupStudents(year_group_id=year_group_id, student_index=st.index)) db.session.commit() return st + + +def create_groups(yg: YearGroup, amount: int) -> List[Group]: + groups = [GroupFactory(year_group_id=yg.id) for _ in range(amount)] + db.session.add_all(groups) + db.session.commit() + return groups + + +def create_group(data: dict, yg: YearGroup) -> Group: + group = Group(**data, year_group_id=yg.id) + db.session.add(group) + db.session.commit() + return group diff --git a/backend/tests/functional_tests/coordinator/test_groups.py b/backend/tests/functional_tests/coordinator/test_groups.py new file mode 100644 index 0000000..498ad52 --- /dev/null +++ b/backend/tests/functional_tests/coordinator/test_groups.py @@ -0,0 +1,209 @@ +import copy + +from flask import current_app + +from ...utils import _test_case_client, _test_case_client_without_response, assert_model_changes, _test_case_group +from ...fake_data import create_year_group, create_groups, create_group, create_students, create_project_supervisors +from ....app.dependencies import db +from ....app.students.models import Group +from ....app.project_supervisor.models import YearGroupProjectSupervisors + +valid_data = { + 'name': 'System Pri' +} + +new_data = { + 'name': 'Mobile app' +} + +invalid_data = { + 'name': 'Mobile app v2', + 'students': [123_344, 455_444], + 'project_supervisor_id': 1 +} + + +def test_list_groups(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + yg = create_year_group() + create_groups(yg, 33) + data = _test_case_client_without_response(client, f'/api/coordinator/groups/{yg.id}/?per_page=10', None, 200, + method='get') + assert data.get('max_pages') == 4 + assert len(data.get('groups')) == 10 + + +def test_detail_group(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + yg = create_year_group() + group = create_group(valid_data, yg) + data = _test_case_client_without_response(client, f'/api/coordinator/groups/{group.id}/detail/', None, 200, + method='get') + assert_model_changes(group, data) + + +def test_detail_group_if_group_doesnt_exist(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + _test_case_client(client, '/api/coordinator/groups/11/detail/', None, 'Not found group!', 404, method='get', + key='error') + + +def test_delete_group(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + yg = create_year_group() + group = create_group(valid_data, yg) + _test_case_client(client, f'/api/coordinator/groups/{group.id}/', None, 'Group was deleted!', 202, + method='delete') + + +def test_delete_group_if_group_doesnt_exist(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + _test_case_client(client, '/api/coordinator/groups/32/', None, 'Not found group!', 404, method='delete', + key='error') + + +def test_edit_group(test_app_with_context) -> None: + data = copy.copy(new_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + students = create_students(yg, 3) + ps = create_project_supervisors(yg, 1)[0] + group = create_group(valid_data, yg) + data['students'] = [student.index for student in students] + data['project_supervisor_id'] = ps.id + + _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Group was updated!', 200, + method='put') + _test_case_group(group, data) + + +def test_edit_group_with_invalid_project_supervisor_id(test_app_with_context) -> None: + data = copy.copy(new_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + students = create_students(yg, 3) + group = create_group(valid_data, yg) + data['students'] = [student.index for student in students] + data['project_supervisor_id'] = 10 + _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Not found project supervisor!', 404, + method='put', key='error') + + +def test_edit_group_with_invalid_data(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + yg = create_year_group() + group = create_group(valid_data, yg) + data = {'students': [123_4356, 243_533, 434_343]} + _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Validation error', 400, method='put') + + +def test_edit_group_with_invalid_student_indexes(test_app_with_context) -> None: + data = copy.deepcopy(new_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + group = create_group(valid_data, yg) + data['students'] = [123_456, 243_533, 434_343] + _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Not found students!', 404, + method='put', key='error') + + +def test_edit_group_if_group_doesnt_exist(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + _test_case_client(client, '/api/coordinator/groups/333/', new_data, 'Not found group!', 404, + method='put', key='error') + + +def test_edit_group_if_you_pass_empty_data(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + _test_case_client(client, '/api/coordinator/groups/333/', {}, 'You have passed empty data!', 400, + method='put', key='error') + + +def test_create_group(test_app_with_context) -> None: + data = copy.deepcopy(new_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + students = create_students(yg, 3) + ps = create_project_supervisors(yg, 1)[0] + data['students'] = [student.index for student in students] + data['project_supervisor_id'] = ps.id + + _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, 'Group was created!', 201, + method='post') + assert Group.query.count() == 1 + _test_case_group(Group.query.first(), data) + + +def test_create_group_if_year_group_doesnt_exist(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + _test_case_client(client, '/api/coordinator/groups/22/', invalid_data, 'Not found year group!', 404, + method='post', key='error') + + +def test_create_group_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: + with test_app_with_context.test_client() as client: + yg = create_year_group() + _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', invalid_data, 'Not found project supervisor!', + 404, method='post', key='error') + + +def test_create_group_if_you_exceed_the_group_limit(test_app_with_context) -> None: + data = copy.deepcopy(invalid_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + ps = create_project_supervisors(yg, 1)[0] + data['project_supervisor_id'] = ps.id + limit_group = current_app.config.get('LIMIT_STUDENTS_PER_GROUP') + + data['students'].extend([999_000 + i for i in range(limit_group + 4)]) + + _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, + f"Too much students you want add to group, The group can have only {limit_group}" + " students", 400, method='post', key='error') + + +def test_create_group_if_students_doesnt_exist(test_app_with_context) -> None: + data = copy.deepcopy(invalid_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + ps = create_project_supervisors(yg, 1)[0] + data['project_supervisor_id'] = ps.id + _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, "Not found students!", 404, method='post', + key='error') + + +def test_create_group_if_at_least_one_student_belong_to_other_group(test_app_with_context) -> None: + data = copy.deepcopy(invalid_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + ps = create_project_supervisors(yg, 1)[0] + group = create_group(valid_data, yg) + data['project_supervisor_id'] = ps.id + student = create_students(yg, 1)[0] + group.students.append(student) + db.session.commit() + + data['students'].extend([student.index]) + _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, + "One or more students have already belonged to group!", 400, method='post', key='error') + + +def test_create_group_if_limit_of_group_was_exceed_for_project_supervisor(test_app_with_context) -> None: + data = copy.deepcopy(invalid_data) + with test_app_with_context.test_client() as client: + yg = create_year_group() + ps = create_project_supervisors(yg, 1)[0] + data['project_supervisor_id'] = ps.id + + ygps = YearGroupProjectSupervisors.query.filter_by(project_supervisor_id=ps.id, year_group_id=yg.id). \ + first() + limit_group = ygps.limit_group + + groups = create_groups(yg, limit_group) + for group in groups: + group.project_supervisor_id = ps.id + db.session.commit() + + _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, + "Can't create new group, project supervisor achieved a limit of groups", + 400, method='post', key='error') diff --git a/backend/tests/functional_tests/coordinator/test_students.py b/backend/tests/functional_tests/coordinator/test_students.py index 2da546e..d887d5b 100644 --- a/backend/tests/functional_tests/coordinator/test_students.py +++ b/backend/tests/functional_tests/coordinator/test_students.py @@ -1,8 +1,7 @@ import copy from ...utils import _test_case_client, _test_case_client_without_response, assert_model_changes -from ...fake_data import create_project_supervisors, create_year_group, create_students, create_student -from ....app.dependencies import db +from ...fake_data import create_year_group, create_students, create_student valid_data = { 'first_name': 'Albert', diff --git a/backend/tests/utils.py b/backend/tests/utils.py index 2b4ae38..878547a 100644 --- a/backend/tests/utils.py +++ b/backend/tests/utils.py @@ -3,6 +3,7 @@ from typing import Union from flask.testing import FlaskClient from ..app.dependencies import db +from ..app.students.models import Group def assert_model_changes(model: db.Model, expected_data: dict) -> None: @@ -27,3 +28,11 @@ def _test_case_client(test_client: FlaskClient, url: str, data: Union[dict, None response_data = _test_case_client_without_response(test_client, url, data, status_code, method) assert key in response_data.keys() assert response_data.get(key) == message + + +def _test_case_group(group: Group, data: dict) -> None: + assert group.name == data['name'] + assert group.project_supervisor_id == data['project_supervisor_id'] + + for st in group.students: + assert st.index in data['students']