ScoDoc/tests/api/test_api_assiduites.py

466 lines
13 KiB
Python

"""
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/<assiduite_id:int>"""
# 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/<etudid:int>/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/<etudid:int>"""
# 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/<etudid:int>/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/<int:evaluation_id>/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/<formsemestre_id:int>"""
# 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/<formsemestre_id:int>/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/<etudid:int>/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/<assiduite_id:int>/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)