""" Test de l'api Assiduité Ecrit par HARTMANN Matthias """ import datetime from random import randint import re from types import NoneType from app.scodoc import sco_utils as scu from tests.api.setup_test_api import ( GET, POST, DEPT_ACRONYM, APIError, api_headers, api_admin_headers, check_failure_get, check_failure_post, check_fields, ) ETUDID = 1 FAUX = 42069 FORMSEMESTREID = 1 MODULE = 1 ASSIDUITES_FIELDS = { "assiduite_id": int, "etudid": int, "code_nip": str, "moduleimpl_id": int, "date_debut": str, "date_fin": str, "etat": str, "desc": str, "entry_date": str, "user_id": (int, NoneType), "user_name": (str, NoneType), "user_nom_complet": (str, NoneType), "est_just": bool, "external_data": dict, } ASSIDUITES_EVALUATIONS_FIELDS = {"evaluation_id": int, "assiduites": list} CREATE_FIELD = {"assiduite_id": int} BATCH_FIELD = {"errors": list, "success": list} COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": int | float} TO_REMOVE = [] def create_data(etat: str, day: str, module: int = None, desc: str = None): """ Permet de créer un dictionnaire assiduité Args: etat (str): l'état de l'assiduité (PRESENT,ABSENT,RETARD) day (str): Le jour de l'assiduité module (int, optional): Le moduleimpl_id associé desc (str, optional): Une description de l'assiduité (eg: motif retard ) Returns: dict: la représentation d'une assiduité """ 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 if desc is not None: data["desc"] = desc return data def test_route_assiduite(api_headers): """test de la route /assiduite/""" # Bon fonctionnement == id connu data = GET(path="/assiduite/1", headers=api_headers, dept=DEPT_ACRONYM) check_fields(data, fields=ASSIDUITES_FIELDS) # Mauvais Fonctionnement == id inconnu check_failure_get( f"/assiduite/{FAUX}", api_headers, ) def test_route_assiduites_count(api_headers): """test de la route /assiduites//count""" # Bon fonctionnement data = GET( path=f"/assiduites/{ETUDID}/count", headers=api_headers, dept=DEPT_ACRONYM ) check_fields(data, COUNT_FIELDS) metrics = {"heure", "compte"} data = GET( path=f"/assiduites/{ETUDID}/count/query?metric={','.join(metrics)}", headers=api_headers, dept=DEPT_ACRONYM, ) assert set(data.keys()) == metrics # Mauvais fonctionnement check_failure_get(f"/assiduites/{FAUX}/count", api_headers) def test_route_assiduites(api_headers): """test de la route /assiduites/""" # Bon fonctionnement data = GET(path=f"/assiduites/{ETUDID}", headers=api_headers, dept=DEPT_ACRONYM) assert isinstance(data, list) for ass in data: check_fields(ass, ASSIDUITES_FIELDS) data = GET( path=f"/assiduites/{ETUDID}/query?", headers=api_headers, dept=DEPT_ACRONYM ) 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_assiduites_evaluations(api_headers): """test de la route /assiduites//evaluations""" # Bon fonctionnement data = GET( path=f"/assiduites/{ETUDID}/evaluations", headers=api_headers, dept=DEPT_ACRONYM ) assert isinstance(data, list) for evals in data: check_fields(evals, ASSIDUITES_EVALUATIONS_FIELDS) for assi in evals["assiduites"]: check_fields(assi, ASSIDUITES_FIELDS) # Mauvais fonctionnement check_failure_get(f"/assiduites/{FAUX}/evaluations", api_headers) def test_route_evaluations_assiduites(api_headers): """test de la route /evaluation//assiduites""" # Bon fonctionnement evaluation_id = 1 data = GET( path=f"/evaluation/{evaluation_id}/assiduites", headers=api_headers, dept=DEPT_ACRONYM, ) assert isinstance(data, dict) for key, val in data.items(): assert isinstance(key, str), "Erreur les clés ne sont pas des strings" assert isinstance(val, list), "Erreur, les valeurs ne sont pas des listes" for assi in val: check_fields(assi, ASSIDUITES_FIELDS) # Mauvais fonctionnement check_failure_get(f"/evaluation/{FAUX}/assiduites", api_headers) def test_route_formsemestre_assiduites(api_headers): """test de la route /assiduites/formsemestre/""" # Bon fonctionnement data = GET( path=f"/assiduites/formsemestre/{FORMSEMESTREID}", headers=api_headers, dept=DEPT_ACRONYM, ) assert isinstance(data, list) for ass in data: check_fields(ass, ASSIDUITES_FIELDS) data = GET( path=f"/assiduites/formsemestre/{FORMSEMESTREID}/query?", headers=api_headers, dept=DEPT_ACRONYM, ) 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): """test de la route /assiduites/formsemestre//count""" # Bon fonctionnement data = GET( path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count", headers=api_headers, dept=DEPT_ACRONYM, ) print("data: ", data) check_fields(data, COUNT_FIELDS) metrics = {"heure", "compte"} data = GET( path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count/query?metric={','.join(metrics)}", headers=api_headers, dept=DEPT_ACRONYM, ) 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_admin_headers): """test de la route /assiduite//create""" # -== Unique ==- # Bon fonctionnement data = create_data("present", "03") res = POST( f"/assiduite/{ETUDID}/create", [data], api_admin_headers, dept=DEPT_ACRONYM ) check_fields(res, BATCH_FIELD) assert len(res["success"]) == 1 TO_REMOVE.append(res["success"][0]["message"]["assiduite_id"]) data_get = GET( path=f'/assiduite/{res["success"][0]["message"]["assiduite_id"]}', headers=api_admin_headers, dept=DEPT_ACRONYM, ) check_fields(data_get, fields=ASSIDUITES_FIELDS) # la date de début est sans fournie sans timezone, mais celle renvoyé avec. # Compare en ajoutant la timezone serveur: assert scu.localize_datetime( datetime.datetime.fromisoformat(data["date_debut"]) ) == datetime.datetime.fromisoformat(data_get["date_debut"]) # Création avec timezone (comme le fait assiduite.js) data["date_debut"] = "2024-10-28T10:00:00.000Z" data["date_fin"] = "2024-10-28T12:00:00.000Z" res = POST( f"/assiduite/{ETUDID}/create", [data], api_admin_headers, dept=DEPT_ACRONYM ) check_fields(res, BATCH_FIELD) assert len(res["success"]) == 1 TO_REMOVE.append(res["success"][0]["message"]["assiduite_id"]) data_get = GET( path=f'/assiduite/{res["success"][0]["message"]["assiduite_id"]}', headers=api_admin_headers, dept=DEPT_ACRONYM, ) check_fields(data_get, fields=ASSIDUITES_FIELDS) assert scu.localize_datetime( datetime.datetime.fromisoformat(data["date_debut"]) ) == datetime.datetime.fromisoformat(data_get["date_debut"]) # Absence avec module data2 = create_data("absent", "04", MODULE, "desc") res = POST( f"/assiduite/{ETUDID}/create", [data2], api_admin_headers, dept=DEPT_ACRONYM ) check_fields(res, BATCH_FIELD) assert len(res["success"]) == 1 TO_REMOVE.append(res["success"][0]["message"]["assiduite_id"]) # Mauvais fonctionnement check_failure_post(f"/assiduite/{FAUX}/create", api_admin_headers, [data]) res = POST( f"/assiduite/{ETUDID}/create", [data], api_admin_headers, dept=DEPT_ACRONYM ) check_fields(res, BATCH_FIELD) assert len(res["errors"]) == 1 assert ( res["errors"][0]["message"] == "Duplication: la période rentre en conflit avec une plage enregistrée" ) res = POST( f"/assiduite/{ETUDID}/create", [create_data("absent", "05", FAUX)], api_admin_headers, dept=DEPT_ACRONYM, ) check_fields(res, BATCH_FIELD) assert len(res["errors"]) == 1 assert res["errors"][0]["message"] == "param 'moduleimpl_id': invalide" # -== Multiple ==- # Bon Fonctionnement etats = ["present", "absent", "retard"] data = [ create_data(etats[d % 3], 10 + d, MODULE if d % 2 else None) for d in range(randint(2, 4)) ] res = POST( f"/assiduite/{ETUDID}/create", data, api_admin_headers, dept=DEPT_ACRONYM ) check_fields(res, BATCH_FIELD) for dat in res["success"]: check_fields(dat["message"], CREATE_FIELD) TO_REMOVE.append(dat["message"]["assiduite_id"]) # Mauvais Fonctionnement data2 = [ create_data("present", "03"), create_data("present", "25", FAUX), create_data("blabla", 26), create_data("absent", 32), create_data("absent", "01"), ] res = POST( f"/assiduite/{ETUDID}/create", data2, api_admin_headers, dept=DEPT_ACRONYM ) check_fields(res, BATCH_FIELD) assert len(res["errors"]) == 5 assert ( res["errors"][0]["message"] == "Duplication: la période rentre en conflit avec une plage enregistrée" ) assert res["errors"][1]["message"] == "param 'moduleimpl_id': invalide" assert res["errors"][2]["message"] == "param 'etat': invalide" assert ( res["errors"][3]["message"] == "param 'date_debut': format invalide, param 'date_fin': format invalide" ) assert res["errors"][4]["message"] == "La date de début n'est pas un jour travaillé" def test_route_edit(api_admin_headers): """test de la route /assiduite//edit""" # Bon fonctionnement data = {"etat": "retard", "moduleimpl_id": MODULE} res = POST( f"/assiduite/{TO_REMOVE[0]}/edit", data, api_admin_headers, dept=DEPT_ACRONYM ) assert res == {"OK": True} data["moduleimpl_id"] = None res = POST( f"/assiduite/{TO_REMOVE[1]}/edit", data, api_admin_headers, dept=DEPT_ACRONYM ) assert res == {"OK": True} # Mauvais fonctionnement check_failure_post(f"/assiduite/{FAUX}/edit", api_admin_headers, data) data["etat"] = "blabla" check_failure_post( f"/assiduite/{TO_REMOVE[2]}/edit", api_admin_headers, data, err="param 'etat': invalide", ) def test_route_delete(api_admin_headers): """test de la route /assiduite/delete""" # -== Unique ==- # Bon fonctionnement data = TO_REMOVE[0] res = POST("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM) check_fields(res, BATCH_FIELD) for dat in res["success"]: assert dat["message"] == "OK" # Mauvais fonctionnement res = POST("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM) check_fields(res, BATCH_FIELD) assert len(res["errors"]) == 1 # -== Multiple ==- # Bon Fonctionnement data = TO_REMOVE[1:] res = POST("/assiduite/delete", data, api_admin_headers, dept=DEPT_ACRONYM) check_fields(res, BATCH_FIELD) for dat in res["success"]: assert dat["message"] == "OK" # Mauvais Fonctionnement data2 = [ FAUX, FAUX + 1, FAUX + 2, ] res = POST("/assiduite/delete", data2, api_admin_headers, dept=DEPT_ACRONYM) check_fields(res, BATCH_FIELD) assert len(res["errors"]) == 3 assert all(i["message"] == "Assiduite non existante" for i in res["errors"]) def test_date_time_offset(api_headers): """test de la route /assiduites/date_time_offset""" reply = GET( path="/assiduite/date_time_offset/2024-10-29", headers=api_headers, dept=DEPT_ACRONYM, raw=True, ) # offset ISO 8601 de la forme +/-hh:mm assert re.match(r"^(Z|[+-]\d{2}:\d{2})$", reply.text)