add functionals test for coordinator view to year-group and project supervisors endpoints and add many term of defences endpoint for coordinator view

This commit is contained in:
dominik24c 2023-01-06 22:35:47 +01:00
parent df107cfc7d
commit badbad7cf9
16 changed files with 353 additions and 9 deletions

View File

@ -132,6 +132,60 @@ def create_term_of_defence(examination_schedule_id: int, data: dict) -> dict:
return {"message": "Term of defence was created!"} return {"message": "Term of defence was created!"}
@bp.post('/<int:examination_schedule_id>/add-term-of-defences/')
@bp.input(TermOfDefenceSchema)
@bp.output(MessageSchema)
def create_many_term_of_defences(examination_schedule_id: int, data: dict) -> dict:
if not data:
abort(400, "You have passed empty data!")
ex = get_examination_schedule_by_id(examination_schedule_id)
yg_id = ex.year_group_id
project_supervisors_ids = data.pop('project_supervisors')
project_supervisors = ProjectSupervisor.query.filter(
or_(*[ProjectSupervisor.id == i for i in project_supervisors_ids])).filter(YearGroup.id == yg_id).all()
if len(project_supervisors) != len(project_supervisors_ids):
abort(404, "Project Supervisors didn't exist!")
start_date = data['start_date']
end_date = data['end_date']
if not (ex.start_date.timestamp() <= start_date.timestamp() and ex.end_date.timestamp() >= end_date.timestamp()):
abort(400, "Invalid date range!")
if end_date <= start_date:
abort(400, "End date must be greater than start date!")
delta_time = end_date - start_date
delta_time_in_minutes = delta_time.total_seconds() / 60
if delta_time_in_minutes % ex.duration_time != 0:
abort(400, "Invalid duration time!")
td = TermOfDefence.query.filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \
filter(
or_(and_(TermOfDefence.start_date >= start_date,
TermOfDefence.start_date < end_date,
TermOfDefence.end_date >= end_date),
and_(TermOfDefence.start_date <= start_date,
TermOfDefence.end_date > start_date,
TermOfDefence.end_date <= end_date))).first()
if td is not None:
abort(400, "This term of defence is taken! You choose other date!")
# create many here
dates = generate_range_dates(start_date, end_date, ex.duration_time)
for start_date in dates:
end_date = start_date + datetime.timedelta(minutes=ex.duration_time)
td = TermOfDefence(start_date=start_date, end_date=end_date, examination_schedule_id=examination_schedule_id)
td.members_of_committee = project_supervisors
db.session.add(td)
db.session.commit()
return {"message": "Term of defences was created!"}
@bp.put('/<int:examination_schedule_id>/update/<int:term_of_defence_id>/') @bp.put('/<int:examination_schedule_id>/update/<int:term_of_defence_id>/')
@bp.input(TermOfDefenceSchema) @bp.input(TermOfDefenceSchema)
@bp.output(MessageSchema) @bp.output(MessageSchema)

View File

@ -56,7 +56,7 @@ def list_project_supervisors_by_year_group(year_group_id: int, query: dict) -> d
@bp.post("/") @bp.post("/")
@bp.input(ProjectSupervisorCreateSchema) @bp.input(ProjectSupervisorCreateSchema)
@bp.output(MessageSchema) @bp.output(MessageSchema, status_code=201)
def create_project_supervisor(data: dict) -> dict: def create_project_supervisor(data: dict) -> dict:
first_name = data['first_name'] first_name = data['first_name']
last_name = data['last_name'] last_name = data['last_name']
@ -72,12 +72,12 @@ def create_project_supervisor(data: dict) -> dict:
return {"message": "Project Supervisor was created!", "id": project_supervisor.id} return {"message": "Project Supervisor was created!", "id": project_supervisor.id}
@bp.get("/<int:id>/detail") @bp.get("/<int:id>/detail/")
@bp.output(ProjectSupervisorSchema) @bp.output(ProjectSupervisorSchema)
def detail_project_supervisor(id: int) -> ProjectSupervisor: def detail_project_supervisor(id: int) -> ProjectSupervisor:
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first() project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
if project_supervisor is None: if project_supervisor is None:
abort(400, f"Project Supervisor with id {id} doesn't exist!") abort(404, 'Not found project supervisor!')
return project_supervisor return project_supervisor

View File

@ -11,7 +11,7 @@ bp = APIBlueprint("year_group", __name__, url_prefix="/year-group")
@bp.post('/') @bp.post('/')
@bp.input(YearGroupSchema) @bp.input(YearGroupSchema)
@bp.output(MessageSchema, status_code=200) @bp.output(MessageSchema, status_code=201)
def create_year_group(data: dict) -> dict: def create_year_group(data: dict) -> dict:
name = data['name'] name = data['name']
mode = data['mode'] mode = data['mode']

View File

@ -19,13 +19,13 @@ class ProjectSupervisorsPaginationSchema(Schema):
class ProjectSupervisorCreateSchema(Schema): class ProjectSupervisorCreateSchema(Schema):
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
email = fields.Str(validate=validate.Length(min=1, max=255), required=True) email = fields.Str(validate=[validate.Length(min=1, max=255), validate.Email()], required=True)
class ProjectSupervisorEditSchema(Schema): class ProjectSupervisorEditSchema(Schema):
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
email = fields.Str(validate=validate.Length(min=0, max=255), required=True) email = fields.Str(validate=[validate.Length(min=0, max=255), validate.Email()], required=True)
class ProjectSupervisorYearGroupSchema(Schema): class ProjectSupervisorYearGroupSchema(Schema):

View File

@ -10,7 +10,7 @@ def validate_mode(value: str) -> str:
class YearGroupSchema(Schema): class YearGroupSchema(Schema):
name = fields.Str(validate=validate.Regexp(r'^\d{4}/\d{4}$'), required=True) name = fields.Str(validate=validate.Regexp(r'^\d{4}\/\d{4}$'), required=True)
mode = fields.Str(validate=validate_mode, required=True) mode = fields.Str(validate=validate_mode, required=True)

View File

@ -1,6 +1,7 @@
from typing import Generator from typing import Generator
import pytest import pytest
from apiflask import APIFlask
from flask import Flask from flask import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from flask.ctx import AppContext from flask.ctx import AppContext
@ -22,5 +23,16 @@ def test_app_ctx_with_db(test_app) -> Generator[AppContext, None, None]:
@pytest.fixture() @pytest.fixture()
def test_client(test_app: Flask) -> FlaskClient: def test_client() -> FlaskClient:
return test_app.test_client() app = create_app("testing")
with app.app_context():
db.create_all()
return app.test_client()
@pytest.fixture()
def test_app_with_context() -> Generator[APIFlask, None, None]:
app = create_app("testing")
with app.app_context():
db.create_all()
yield app

47
backend/tests/factory.py Normal file
View File

@ -0,0 +1,47 @@
from factory import alchemy, Sequence
from factory.faker import Faker
from factory.fuzzy import FuzzyInteger, FuzzyChoice
from ..app.dependencies import db
from ..app.students.models import Student, Group
from ..app.project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors
class ProjectSupervisorFactory(alchemy.SQLAlchemyModelFactory):
class Meta:
model = ProjectSupervisor
sqlalchemy_session = db.session
first_name = Faker('first_name')
last_name = Faker('last_name')
email = Faker('email')
class YearGroupProjectSupervisorsFactory(alchemy.SQLAlchemyModelFactory):
class Meta:
model = YearGroupProjectSupervisors
sqlalchemy_session = db.session
limit_group = 4
class GroupFactory(alchemy.SQLAlchemyModelFactory):
class Meta:
model = Group
sqlalchemy_session = db.session
name = Sequence(lambda n: f'Group-{n}')
points_for_first_term = FuzzyInteger(1, 5)
points_for_second_term = FuzzyInteger(1, 5)
#
# class StudentFactory(alchemy.SQLAlchemyModelFactory):
# class Meta:
# model = Student
# sqlalchemy_session = db.session
#
# first_name = Faker('first_name')
# last_name = Faker('last_name')
# email = Faker('email')
# index = Sequence(lambda n: 400_000 + n)
# # group = RelatedFactory(GroupFactory)
# mode = FuzzyChoice([True, False])

View File

@ -0,0 +1,25 @@
from typing import List
from .factory import ProjectSupervisorFactory, YearGroupProjectSupervisorsFactory
from ..app.dependencies import db
from ..app.project_supervisor.models import YearGroup
from ..app.base.mode import ModeGroups
def create_year_group(data: dict = None) -> YearGroup:
if data is None:
data = {'mode': ModeGroups.STATIONARY.value, 'name': '2022/2023'}
yg = YearGroup(**data)
db.session.add(yg)
db.session.commit()
return yg
def create_project_supervisors(yg: YearGroup, amount: int) -> List[ProjectSupervisorFactory]:
ps = [ProjectSupervisorFactory() for _ in range(amount)]
db.session.add_all(ps)
db.session.commit()
db.session.add_all(
[YearGroupProjectSupervisorsFactory(limit_group=3, year_group_id=yg.id, project_supervisor_id=p.id) for p in ps])
db.session.commit()
return ps

View File

@ -0,0 +1,78 @@
from ...utils import _test_case_client, _test_case_client_without_response, assert_model_changes
from ...fake_data import create_project_supervisors, create_year_group
from ....app.dependencies import db
from ....app.project_supervisor.models import ProjectSupervisor
valid_data = {
'first_name': 'John',
'last_name': 'Smith',
'email': 'johnsmith@gmail.com'
}
def create_dummy_ps() -> ProjectSupervisor:
ps = ProjectSupervisor(**valid_data)
db.session.add(ps)
db.session.commit()
return ps
def test_list_project_supervisors(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
year_group = create_year_group()
create_project_supervisors(year_group, 25)
data = _test_case_client_without_response(client, '/api/coordinator/project_supervisor/?per_page=10', None, 200,
method='get')
assert data.get('max_pages') == 3
assert len(data.get('project_supervisors')) == 10
def test_list_project_supervisors_by_year_group(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
year_group = create_year_group()
year_group_2 = create_year_group()
create_project_supervisors(year_group, 12)
create_project_supervisors(year_group_2, 24)
data = _test_case_client_without_response(client,
f'/api/coordinator/project_supervisor/{year_group.id}/?per_page=10',
None, 200, method='get')
assert data.get('max_pages') == 2
assert len(data.get('project_supervisors')) == 10
def test_create_project_supervisors(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
_test_case_client(client, '/api/coordinator/project_supervisor/', valid_data,
'Project Supervisor was created!', 201, method='post')
def test_create_project_supervisors_with_invalid_data(test_app_with_context) -> None:
data = {
'first_name': 'John',
'last_name': 'Smith',
'email': 'johnsmitl.com'
}
with test_app_with_context.test_client() as client:
_test_case_client(client, '/api/coordinator/project_supervisor/', data,
'Validation error', 400, method='post')
def test_create_project_supervisors_if_project_supervisor_has_already_exist(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
create_dummy_ps()
_test_case_client(client, '/api/coordinator/project_supervisor/', valid_data,
'Project Supervisor has already exists!', 400, method='post', key='error')
def test_detail_project_supervisor(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
ps = create_dummy_ps()
data = _test_case_client_without_response(client, f'/api/coordinator/project_supervisor/{ps.id}/detail/',
None, 200, method='get')
assert_model_changes(ps, data)
def test_detail_project_supervisor_if_project_supervisor_doesnt_exist(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
_test_case_client(client, f'/api/coordinator/project_supervisor/23/detail/', None,
'Not found project supervisor!', 404, method='get', key='error')

View File

@ -0,0 +1,99 @@
from ...fake_data import create_year_group
from ...utils import _test_case_client_without_response, _test_case_client, assert_model_changes
from ....app.base.mode import ModeGroups
valid_data = {
'mode': ModeGroups.STATIONARY.value,
'name': '2022/2023'
}
new_data = {
'mode': ModeGroups.NON_STATIONARY.value,
'name': '2021/2022'
}
example_data = {
'mode': ModeGroups.STATIONARY.value,
'name': '2021/2022'
}
def test_create_year_group(test_client) -> None:
_test_case_client(test_client, '/api/coordinator/year-group/', valid_data, 'Year group was created!', 201)
def test_create_year_group_with_invalid_name(test_client) -> None:
data = {
'mode': ModeGroups.STATIONARY.value,
'name': '2022/203232a'
}
_test_case_client(test_client, '/api/coordinator/year-group/', data, 'Validation error', 400)
def test_create_year_group_with_invalid_mode(test_client) -> None:
data = {
'mode': 'xxxx',
'name': '2022/2033'
}
_test_case_client(test_client, '/api/coordinator/year-group/', data, 'Validation error', 400)
def test_create_year_group_if_year_group_already_exists(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
create_year_group(valid_data)
_test_case_client(client, '/api/coordinator/year-group/', valid_data, 'Year group has already exists!', 400,
'error')
def test_delete_year_group(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
yg = create_year_group(valid_data)
_test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', None, 'Year group was deleted!', 202,
method='delete')
def test_delete_year_group_if_year_group_not_exists(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
_test_case_client(client, '/api/coordinator/year-group/1/', None, 'Year group doesn\'t exist!', 404,
method='delete', key='error')
def test_update_year_group(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
yg = create_year_group(valid_data)
_test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', new_data, "Year group was updated!", 200,
method='put', key='message')
assert_model_changes(yg, new_data)
def test_update_year_group_with_invalid_data(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
yg = create_year_group(valid_data)
_test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', {'name': '', 'mode': ''}, 'Validation error',
400, method='put', key='message')
assert_model_changes(yg, valid_data)
def test_update_year_group_with_invalid_route_param_year_group_id(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
_test_case_client(client, f'/api/coordinator/year-group/23/', new_data, 'Not found year group!',
404, method='put', key='error')
def test_update_year_group_with_valid_data_and_year_group_which_has_already_exist(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
create_year_group(new_data)
yg = create_year_group(valid_data)
_test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', new_data, 'Year group has already exists!',
400, method='put', key='error')
assert_model_changes(yg, valid_data)
def test_list_year_group(test_app_with_context) -> None:
with test_app_with_context.test_client() as client:
ygs = [create_year_group(data) for data in (valid_data, new_data, example_data)]
data = _test_case_client_without_response(client, '/api/coordinator/year-group/', None, 200, method='get')
assert data.get('max_pages') == 1
for year_group_data in data.get('year_groups'):
yg_id = year_group_data.get('id')
assert_model_changes(list(filter(lambda yg: yg.id == yg_id, ygs))[0], year_group_data)

29
backend/tests/utils.py Normal file
View File

@ -0,0 +1,29 @@
from typing import Union
from flask.testing import FlaskClient
from ..app.dependencies import db
def assert_model_changes(model: db.Model, expected_data: dict) -> None:
for key, val in expected_data.items():
assert getattr(model, key) == val
def _test_case_client_without_response(test_client: FlaskClient, url: str, data: Union[dict, None], status_code: int,
method: str = 'post') -> dict:
method_func = getattr(test_client, method)
if data is not None:
response = method_func(url, json=data)
else:
response = method_func(url)
assert response.status_code == status_code
return response.json
def _test_case_client(test_client: FlaskClient, url: str, data: Union[dict, None], message: str, status_code: int,
key: str = 'message', method: str = 'post') -> 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