From f96571f52072e94122c3d4b2e9a1cf8648ca87c5 Mon Sep 17 00:00:00 2001 From: iziram Date: Thu, 22 Dec 2022 21:36:09 +0100 Subject: [PATCH] =?UTF-8?q?test=20api=20assiduite=20+=20correction=20probl?= =?UTF-8?q?=C3=A8mes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/assiduites.py | 101 +++--- app/scodoc/sco_utils.py | 5 +- tests/api/test_api_assiduites.py | 330 ++++++++++++++++++ .../fakedatabase/create_test_api_database.py | 32 ++ 4 files changed, 416 insertions(+), 52 deletions(-) create mode 100644 tests/api/test_api_assiduites.py diff --git a/app/api/assiduites.py b/app/api/assiduites.py index c78ccab1..e2853dae 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -188,33 +188,29 @@ def assiduites(etudid: int = None, with_query: bool = False): @login_required @scodoc @permission_required(Permission.ScoView) -def assiduites_formsemestre(formsemestre_id: int = None, with_query: bool = False): - if formsemestre_id is not None: - formsemestre: FormSemestre = None - formsemestre_id = int(formsemestre_id) - formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() +def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False): + formsemestre: FormSemestre = None + formsemestre_id = int(formsemestre_id) + formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() - if formsemestre is None: - json_error(404, "le paramètre 'formsemestre_id' n'existe pas") + if formsemestre is None: + return json_error(404, "le paramètre 'formsemestre_id' n'existe pas") - etuds = formsemestre.etuds.all() - etuds_id = [etud.id for etud in etuds] + etuds = formsemestre.etuds.all() + etuds_id = [etud.id for etud in etuds] - assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) - assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) + assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) + assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) - if with_query: - assiduites = filter_manager(request, assiduites) + if with_query: + assiduites = filter_manager(request, assiduites) - data_set: List[dict] = [] - for ass in assiduites.all(): - data = ass.to_dict() - data_set.append(change_etat(data)) + data_set: List[dict] = [] + for ass in assiduites.all(): + data = ass.to_dict() + data_set.append(change_etat(data)) - return jsonify(data_set) - - else: - return json_error(404, "le paramètre 'formsemestre_id doit être renseigné'") + return jsonify(data_set) @bp.route( @@ -239,28 +235,24 @@ def assiduites_formsemestre(formsemestre_id: int = None, with_query: bool = Fals def count_assiduites_formsemestre( formsemestre_id: int = None, with_query: bool = False ): - if formsemestre_id is not None: - formsemestre: FormSemestre = None - formsemestre_id = int(formsemestre_id) - formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() + formsemestre: FormSemestre = None + formsemestre_id = int(formsemestre_id) + formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() - if formsemestre is None: - json_error(404, "le paramètre 'formsemestre_id' n'existe pas") + if formsemestre is None: + return json_error(404, "le paramètre 'formsemestre_id' n'existe pas") - etuds = formsemestre.etuds.all() - etuds_id = [etud.id for etud in etuds] + etuds = formsemestre.etuds.all() + etuds_id = [etud.id for etud in etuds] - assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) - assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) - metric: str = "all" - filter: dict = {} - if with_query: - metric, filter = count_manager(request) + assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) + assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) + metric: str = "all" + filter: dict = {} + if with_query: + metric, filter = count_manager(request) - return jsonify(scass.get_assiduites_stats(assiduites, metric, filter)) - - else: - return json_error(404, "le paramètre 'formsemestre_id doit être renseigné'") + return jsonify(scass.get_assiduites_stats(assiduites, metric, filter)) @bp.route("/assiduite//create", methods=["POST"], defaults={"batch": False}) @@ -274,7 +266,9 @@ def count_assiduites_formsemestre( "/assiduite//create/batch", methods=["POST"], defaults={"batch": True} ) @scodoc +@login_required @permission_required(Permission.ScoView) +# @permission_required(Permission.ScoAssiduiteChange) def create(etudid: int = None, batch: bool = False): """ Création d'une assiduité pour l'étudiant (etudid) @@ -402,30 +396,34 @@ def create_singular( ) @login_required @scodoc -@permission_required(Permission.ScoAssiduiteChange) +@permission_required(Permission.ScoView) +# @permission_required(Permission.ScoAssiduiteChange) def delete(batch: bool = False): """ Suppression d'une assiduité à partir de son id """ - output = {} if batch: assiduites: list[int] = request.get_json(force=True).get("batch", []) - output = {"errors": [], "success": []} + output = {"errors": {}, "success": {}} - for ass in assiduites: + for i, ass in enumerate(assiduites): code, msg = delete_singular(ass, db) if code == 404: - output["errors"].append({ass: msg}) + output["errors"][f"{i}"] = msg else: - output["success"].append({ass: msg}) + output["success"][f"{i}"] = {"OK": True} + db.session.commit() + return jsonify(output) + else: code, msg = delete_singular( request.get_json(force=True).get("assiduiteid", -1), db ) - output[code] = msg - - db.session.commit() - return jsonify(output) + if code == 404: + return json_error(code, msg) + if code == 200: + db.session.commit() + return jsonify({"OK": True}) def delete_singular(assiduite_id: int, db): @@ -440,7 +438,8 @@ def delete_singular(assiduite_id: int, db): @api_web_bp.route("/assiduite//edit", methods=["POST"]) @login_required @scodoc -@permission_required(Permission.ScoAssiduiteChange) +@permission_required(Permission.ScoView) +# @permission_required(Permission.ScoAssiduiteChange) def edit(assiduiteid: int): """ Edition d'une assiduité à partir de son id @@ -473,7 +472,7 @@ def edit(assiduiteid: int): if moduleimpl_id < 0 or not Assiduite.verif_moduleimpl( moduleimpl_id, assiduite.etudid ): - raise Exception + errors.append("param 'moduleimpl_id': etud non inscrit") assiduite.moduleimpl_id = moduleimpl_id except: diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 54de152b..ea8d3288 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -160,7 +160,10 @@ def localize_datetime(date: datetime.datetime or str) -> datetime.datetime: first_assiduite = Assiduite.query.first() if first_assiduite is not None: new_date = date.replace(tzinfo=first_assiduite.date_debut.tzinfo) - + else: + # TOTALK: Paramètre permettant d'avoir l'UTC par défaut + tmp = is_iso_formated("2022-01-01T08:00:00+01:00", True) + new_date = date.replace(tzinfo=tmp.tzinfo) return new_date diff --git a/tests/api/test_api_assiduites.py b/tests/api/test_api_assiduites.py new file mode 100644 index 00000000..33a7767d --- /dev/null +++ b/tests/api/test_api_assiduites.py @@ -0,0 +1,330 @@ +""" +Test de l'api Assiduité + +Ecrit par HARTMANN Matthias + +""" + +from tests.api.setup_test_api import GET, POST_JSON, api_headers, APIError +from random import randint + +ETUDID = 1 +FAUX = 42069 +FORMSEMESTREID = 1 +MODULE = 1 + + +ASSIDUITES_FIELDS = { + "assiduiteid": int, + "etudid": int, + "moduleimpl_id": int, + "date_debut": str, + "date_fin": str, + "etat": str, +} + +CREATE_FIELD = {"assiduiteid": int} +BATCH_FIELD = {"errors": dict, "success": dict} + +COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": float} + +TO_REMOVE = [] + + +def check_fields(data, fields=ASSIDUITES_FIELDS): + assert set(data.keys()) == set(fields.keys()) + for key in data: + if key == "moduleimpl_id": + assert isinstance(data[key], fields[key]) or data[key] is None + else: + assert isinstance(data[key], fields[key]) + + +def check_failure_get(path, headers, err=None): + try: + data = GET(path=path, headers=headers) + # ^ Renvoi un 404 + except APIError as api_err: + if err is not None: + assert api_err.payload["message"] == err + else: + raise APIError("Le GET n'aurait pas du fonctionner") + + +def check_failure_post(path, headers, data, err=None): + try: + data = POST_JSON(path=path, headers=headers, data=data) + # ^ Renvoi un 404 + except APIError as api_err: + if err is not None: + assert api_err.payload["message"] == err + else: + raise APIError("Le GET n'aurait pas du fonctionner") + + +def create_data(etat: str, day: str, module=None): + data = { + "date_debut": f"2022-01-{day}T08:00", + "date_fin": f"2022-01-{day}T10:00", + "etat": etat, + } + + if module is not None: + data["moduleimpl_id"] = module + + return data + + +def test_route_assiduite(api_headers): + + # Bon fonctionnement == id connu + data = GET(path="/assiduite/1", headers=api_headers) + check_fields(data) + + # Mauvais Fonctionnement == id inconnu + + check_failure_get( + f"/assiduite/{FAUX}", + api_headers, + "assiduité inexistante", + ) + + +def test_route_count_assiduites(api_headers): + + # Bon fonctionnement + + data = GET(path=f"/assiduites/{ETUDID}/count", headers=api_headers) + check_fields(data, COUNT_FIELDS) + + metrics = {"heure", "compte"} + data = GET( + path=f"/assiduites/{ETUDID}/count/query?metric={','.join(metrics)}", + headers=api_headers, + ) + + assert set(data.keys()) == metrics + + # Mauvais fonctionnement + + check_failure_get(f"/assiduites/{FAUX}/count", api_headers) + + +def test_route_assiduites(api_headers): + + # Bon fonctionnement + + data = GET(path=f"/assiduites/{ETUDID}", headers=api_headers) + assert isinstance(data, list) + for ass in data: + check_fields(ass, ASSIDUITES_FIELDS) + + data = GET(path=f"/assiduites/{ETUDID}/query?", headers=api_headers) + assert isinstance(data, list) + for ass in data: + check_fields(ass, ASSIDUITES_FIELDS) + + # Mauvais fonctionnement + check_failure_get(f"/assiduites/{FAUX}", api_headers) + check_failure_get(f"/assiduites/{FAUX}/query?", api_headers) + + +def test_route_formsemestre_assiduites(api_headers): + + # Bon fonctionnement + + data = GET(path=f"/assiduites/formsemestre/{FORMSEMESTREID}", headers=api_headers) + assert isinstance(data, list) + for ass in data: + check_fields(ass, ASSIDUITES_FIELDS) + + data = GET( + path=f"/assiduites/formsemestre/{FORMSEMESTREID}/query?", headers=api_headers + ) + assert isinstance(data, list) + for ass in data: + check_fields(ass, ASSIDUITES_FIELDS) + + # Mauvais fonctionnement + check_failure_get( + f"/assiduites/formsemestre/{FAUX}", + api_headers, + err="le paramètre 'formsemestre_id' n'existe pas", + ) + check_failure_get( + f"/assiduites/formsemestre/{FAUX}/query?", + api_headers, + err="le paramètre 'formsemestre_id' n'existe pas", + ) + + +def test_route_count_formsemestre_assiduites(api_headers): + + # Bon fonctionnement + + data = GET( + path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count", headers=api_headers + ) + check_fields(data, COUNT_FIELDS) + metrics = {"heure", "compte"} + data = GET( + path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count/query?metric={','.join(metrics)}", + headers=api_headers, + ) + assert set(data.keys()) == metrics + + # Mauvais fonctionnement + check_failure_get( + f"/assiduites/formsemestre/{FAUX}/count", + api_headers, + err="le paramètre 'formsemestre_id' n'existe pas", + ) + check_failure_get( + f"/assiduites/formsemestre/{FAUX}/count/query?", + api_headers, + err="le paramètre 'formsemestre_id' n'existe pas", + ) + + +def test_route_create(api_headers): + + # -== Sans batch ==- + + # Bon fonctionnement + data = create_data("present", "01") + + res = POST_JSON(f"/assiduite/{ETUDID}/create", data, api_headers) + check_fields(res, CREATE_FIELD) + TO_REMOVE.append(res["assiduiteid"]) + + data2 = create_data("absent", "02", MODULE) + res = POST_JSON(f"/assiduite/{ETUDID}/create", data2, api_headers) + check_fields(res, CREATE_FIELD) + TO_REMOVE.append(res["assiduiteid"]) + + # Mauvais fonctionnement + check_failure_post(f"/assiduite/{FAUX}/create", api_headers, data) + check_failure_post( + f"/assiduite/{ETUDID}/create", + api_headers, + data, + err="La période sélectionnée est déjà couverte par une autre assiduite", + ) + check_failure_post( + f"/assiduite/{ETUDID}/create", + api_headers, + create_data("absent", "03", FAUX), + err="L'étudiant ne participe pas au moduleimpl sélectionné", + ) + + # -== Avec batch ==- + + # Bon Fonctionnement + + etats = ["present", "absent", "retard"] + data = { + "batch": [ + create_data(etats[d % 3], 10 + d, MODULE if d % 2 else None) + for d in range(randint(3, 5)) + ] + } + + res = POST_JSON(f"/assiduite/{ETUDID}/create/batch", data, api_headers) + check_fields(res, BATCH_FIELD) + for dat in res["success"]: + check_fields(res["success"][dat], CREATE_FIELD) + TO_REMOVE.append(res["success"][dat]["assiduiteid"]) + + # Mauvais Fonctionnement + + data2 = { + "batch": [ + create_data("present", "01"), + create_data("present", "25", FAUX), + create_data("blabla", 26), + create_data("absent", 32), + ] + } + + res = POST_JSON(f"/assiduite/{ETUDID}/create/batch", data2, api_headers) + check_fields(res, BATCH_FIELD) + assert len(res["errors"]) == 4 + + assert ( + res["errors"]["0"] + == "La période sélectionnée est déjà couverte par une autre assiduite" + ) + assert res["errors"]["1"] == "L'étudiant ne participe pas au moduleimpl sélectionné" + assert res["errors"]["2"] == "param 'etat': invalide" + assert ( + res["errors"]["3"] + == "param 'date_debut': format invalide, param 'date_fin': format invalide" + ) + + +def test_route_edit(api_headers): + + # Bon fonctionnement + + data = {"etat": "retard", "moduleimpl_id": MODULE} + res = POST_JSON(f"/assiduite/{TO_REMOVE[0]}/edit", data, api_headers) + assert res == {"OK": True} + + data["moduleimpl_id"] = None + res = POST_JSON(f"/assiduite/{TO_REMOVE[1]}/edit", data, api_headers) + assert res == {"OK": True} + + # Mauvais fonctionnement + + check_failure_post(f"/assiduite/{FAUX}/edit", api_headers, data) + data["etat"] = "blabla" + check_failure_post( + f"/assiduite/{TO_REMOVE[2]}/edit", + api_headers, + data, + err="param 'etat': invalide", + ) + + +def test_route_delete(api_headers): + # -== Sans batch ==- + + # Bon fonctionnement + data = {"assiduiteid": TO_REMOVE[0]} + + res = POST_JSON(f"/assiduite/delete", data, api_headers) + assert res == {"OK": True} + + # Mauvais fonctionnement + check_failure_post( + f"/assiduite/delete", + api_headers, + {"assiduiteid": FAUX}, + err="Assiduite non existante", + ) + # -== Avec batch ==- + + # Bon Fonctionnement + + data = {"batch": TO_REMOVE[1:]} + + res = POST_JSON(f"/assiduite/delete/batch", data, api_headers) + check_fields(res, BATCH_FIELD) + for dat in res["success"]: + assert res["success"][dat] == {"OK": True} + + # Mauvais Fonctionnement + + data2 = { + "batch": [ + FAUX, + FAUX + 1, + FAUX + 2, + ] + } + + res = POST_JSON(f"/assiduite/delete/batch", data2, api_headers) + check_fields(res, BATCH_FIELD) + assert len(res["errors"]) == 3 + + assert all([res["errors"][i] == "Assiduite non existante" for i in res["errors"]]) diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py index f62f4f72..74e07a23 100644 --- a/tools/fakedatabase/create_test_api_database.py +++ b/tools/fakedatabase/create_test_api_database.py @@ -21,6 +21,7 @@ from app import models from app.models import departements from app.models import ( Absence, + Assiduite, Departement, Formation, FormSemestre, @@ -378,6 +379,36 @@ def create_logos(): ) +def ajouter_assiduites(formsemestre: FormSemestre): + """ + Ajoute des assiduités semi-aléatoires à chaque étudiant du semestre + """ + MODS = [moduleimpl.id for moduleimpl in formsemestre.modimpls] + MODS.append(None) + from app.scodoc.sco_utils import localize_datetime + + for etud in formsemestre.etuds: + + base_date = datetime.datetime(2022, 9, random.randint(1, 30), 8, 0, 0) + base_date = localize_datetime(base_date) + + for i in range(random.randint(1, 5)): + etat = random.randint(0, 2) + module = random.choice(MODS) + deb_date = base_date + datetime.timedelta(days=i) + fin_date = deb_date + datetime.timedelta(hours=i) + + code = Assiduite.create_assiduite(etud, deb_date, fin_date, etat, module) + + assert isinstance( + code, Assiduite + ), "Erreur dans la génération des assiduités" + + db.session.add(code) + + db.session.commit() + + def init_test_database(): """Appelé par la commande `flask init-test-database` @@ -398,6 +429,7 @@ def init_test_database(): saisie_notes_evaluations(formsemestre, user_lecteur) add_absences(formsemestre) create_etape_apo(formsemestre) + ajouter_assiduites(formsemestre) create_logos() # à compléter # - groupes