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!"}
@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.input(TermOfDefenceSchema)
@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.input(ProjectSupervisorCreateSchema)
@bp.output(MessageSchema)
@bp.output(MessageSchema, status_code=201)
def create_project_supervisor(data: dict) -> dict:
first_name = data['first_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}
@bp.get("/<int:id>/detail")
@bp.get("/<int:id>/detail/")
@bp.output(ProjectSupervisorSchema)
def detail_project_supervisor(id: int) -> ProjectSupervisor:
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
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

View File

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

View File

@ -19,13 +19,13 @@ class ProjectSupervisorsPaginationSchema(Schema):
class ProjectSupervisorCreateSchema(Schema):
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)
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):
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)
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):

View File

@ -10,7 +10,7 @@ def validate_mode(value: str) -> str:
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)

View File

@ -1,6 +1,7 @@
from typing import Generator
import pytest
from apiflask import APIFlask
from flask import Flask
from flask.testing import FlaskClient
from flask.ctx import AppContext
@ -22,5 +23,16 @@ def test_app_ctx_with_db(test_app) -> Generator[AppContext, None, None]:
@pytest.fixture()
def test_client(test_app: Flask) -> FlaskClient:
return test_app.test_client()
def test_client() -> FlaskClient:
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