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:
parent
df107cfc7d
commit
badbad7cf9
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
47
backend/tests/factory.py
Normal 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])
|
25
backend/tests/fake_data.py
Normal file
25
backend/tests/fake_data.py
Normal 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
|
0
backend/tests/functional_tests/__init__.py
Normal file
0
backend/tests/functional_tests/__init__.py
Normal 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')
|
@ -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)
|
0
backend/tests/functional_tests/students/__init__.py
Normal file
0
backend/tests/functional_tests/students/__init__.py
Normal file
29
backend/tests/utils.py
Normal file
29
backend/tests/utils.py
Normal 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
|
Loading…
Reference in New Issue
Block a user