WIP: PR assiduites -> bac à sable prod #649

Closed
iziram wants to merge 84 commits from iziram/ScoDoc:assiduites into bac_a_sable_prod
10 changed files with 1703 additions and 7 deletions
Showing only changes of commit 9484bc74a3 - Show all commits

View File

@ -7,6 +7,7 @@
Usage: Usage:
cd /opt/scodoc/tests/api cd /opt/scodoc/tests/api
python make_samples.py [entry_names] python make_samples.py [entry_names]
python make_samples.py -i <filepath> [entrynames]
si entry_names est spécifié, la génération est restreints aux exemples cités. expl: `python make_samples departements departement-formsemestres` si entry_names est spécifié, la génération est restreints aux exemples cités. expl: `python make_samples departements departement-formsemestres`
doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets) doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets)
@ -37,7 +38,6 @@ Quand la structure est complète, on génére tous les fichiers textes
- le résultat - le résultat
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples) qui est créé ou écrasé si déjà existant Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples) qui est créé ou écrasé si déjà existant
TODO: ajouter un argument au script permettant de ne générer qu'un seul fichier (exemple: `python make_samples.py nom_exemple`)
""" """
import os import os
@ -65,7 +65,7 @@ from setup_test_api import (
) )
DATA_DIR = "/tmp/samples/" DATA_DIR = "/tmp/samples/"
SAMPLES_FILENAME = "tests/ressources/samples.csv" SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"
class Sample: class Sample:
@ -180,11 +180,13 @@ class Samples:
file.close() file.close()
def make_samples(): def make_samples(samples_filename):
if len(sys.argv) == 1: if len(sys.argv) == 1:
entry_names = None entry_names = None
else: elif len(sys.argv) >= 3 and sys.argv[1] == "-i":
entry_names = sys.argv[1:] samples_filename = sys.argv[2]
entry_names = sys.argv[3:] if len(sys.argv) > 3 else None
if os.path.exists(DATA_DIR): if os.path.exists(DATA_DIR):
if not os.path.isdir(DATA_DIR): if not os.path.isdir(DATA_DIR):
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire" raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
@ -197,7 +199,7 @@ def make_samples():
samples = Samples(entry_names) samples = Samples(entry_names)
df = read_csv( df = read_csv(
SAMPLES_FILENAME, samples_filename,
sep=";", sep=";",
quotechar='"', quotechar='"',
dtype={ dtype={
@ -217,4 +219,4 @@ def make_samples():
if not CHECK_CERTIFICATE: if not CHECK_CERTIFICATE:
urllib3.disable_warnings() urllib3.disable_warnings()
make_samples() make_samples(SAMPLES_FILENAME)

View File

@ -0,0 +1,392 @@
"""
Test de l'api Assiduité
Ecrit par HARTMANN Matthias
"""
from random import randint
from tests.api.setup_test_api import GET, POST_JSON, APIError, api_headers
ETUDID = 1
FAUX = 42069
FORMSEMESTREID = 1
MODULE = 1
ASSIDUITES_FIELDS = {
"assiduite_id": int,
"etudid": int,
"moduleimpl_id": int,
"date_debut": str,
"date_fin": str,
"etat": str,
"desc": str,
"entry_date": str,
"user_id": str,
"est_just": bool,
}
CREATE_FIELD = {"assiduite_id": int}
BATCH_FIELD = {"errors": dict, "success": dict}
COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": float}
TO_REMOVE = []
def check_fields(data: dict, fields: dict = None):
"""
Cette fonction permet de vérifier que le dictionnaire data
contient les bonnes clés et les bons types de valeurs.
Args:
data (dict): un dictionnaire (json de retour de l'api)
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
"""
if fields is None:
fields = ASSIDUITES_FIELDS
assert set(data.keys()) == set(fields.keys())
for key in data:
if key in ("moduleimpl_id", "desc", "user_id"):
assert isinstance(data[key], fields[key]) or data[key] is None
else:
assert isinstance(data[key], fields[key])
def check_failure_get(path: str, headers: dict, err: str = None):
"""
Cette fonction vérifiée que la requête GET renvoie bien un 404
Args:
path (str): la route de l'api
headers (dict): le token d'auth de l'api
err (str, optional): L'erreur qui est sensée être fournie par l'api.
Raises:
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
"""
try:
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: str, headers: dict, data: dict, err: str = None):
"""
Cette fonction vérifiée que la requête POST renvoie bien un 404
Args:
path (str): la route de l'api
headers (dict): le token d'auth
data (dict): un dictionnaire (json) à envoyer
err (str, optional): L'erreur qui est sensée être fournie par l'api.
Raises:
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
"""
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: 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)
check_fields(data)
# Mauvais Fonctionnement == id inconnu
check_failure_get(
f"/assiduite/{FAUX}",
api_headers,
)
def test_route_count_assiduites(api_headers):
"""test de la route /assiduites/<etudid:int>/count"""
# 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):
"""test de la route /assiduites/<etudid:int>"""
# 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):
"""test de la route /assiduites/formsemestre/<formsemestre_id:int>"""
# 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):
"""test de la route /assiduites/formsemestre/<formsemestre_id:int>/count"""
# 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):
"""test de la route /assiduite/<etudid:int>/create"""
# -== Unique ==-
# Bon fonctionnement
data = create_data("present", "01")
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data], api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["success"]) == 1
TO_REMOVE.append(res["success"]["0"]["assiduite_id"])
data2 = create_data("absent", "02", MODULE, "desc")
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data2], api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["success"]) == 1
TO_REMOVE.append(res["success"]["0"]["assiduite_id"])
# Mauvais fonctionnement
check_failure_post(f"/assiduite/{FAUX}/create", api_headers, [data])
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data], api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 1
assert (
res["errors"]["0"]
== "Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
)
res = POST_JSON(
f"/assiduite/{ETUDID}/create", [create_data("absent", "03", FAUX)], api_headers
)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 1
assert res["errors"]["0"] == "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(3, 5))
]
res = POST_JSON(f"/assiduite/{ETUDID}/create", 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]["assiduite_id"])
# Mauvais Fonctionnement
data2 = [
create_data("present", "01"),
create_data("present", "25", FAUX),
create_data("blabla", 26),
create_data("absent", 32),
]
res = POST_JSON(f"/assiduite/{ETUDID}/create", data2, api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 4
assert (
res["errors"]["0"]
== "Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
)
assert res["errors"]["1"] == "param 'moduleimpl_id': invalide"
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):
"""test de la route /assiduite/<assiduite_id:int>/edit"""
# 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):
"""test de la route /assiduite/delete"""
# -== Unique ==-
# Bon fonctionnement
data = TO_REMOVE[0]
res = POST_JSON("/assiduite/delete", [data], api_headers)
check_fields(res, BATCH_FIELD)
for dat in res["success"]:
assert res["success"][dat] == {"OK": True}
# Mauvais fonctionnement
res = POST_JSON("/assiduite/delete", [data], api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 1
# -== Multiple ==-
# Bon Fonctionnement
data = TO_REMOVE[1:]
res = POST_JSON("/assiduite/delete", data, api_headers)
check_fields(res, BATCH_FIELD)
for dat in res["success"]:
assert res["success"][dat] == {"OK": True}
# Mauvais Fonctionnement
data2 = [
FAUX,
FAUX + 1,
FAUX + 2,
]
res = POST_JSON("/assiduite/delete", 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"]])

View File

@ -0,0 +1 @@
test de l'importation des fichiers / archive justificatif

View File

@ -0,0 +1 @@
test de l'importation des fichiers / archive justificatif

View File

@ -0,0 +1,469 @@
"""
Test de l'api justificatif
Ecrit par HARTMANN Matthias
"""
from random import randint
import requests
from tests.api.setup_test_api import (
API_URL,
CHECK_CERTIFICATE,
GET,
POST_JSON,
APIError,
api_headers,
)
ETUDID = 1
FAUX = 42069
JUSTIFICATIFS_FIELDS = {
"justif_id": int,
"etudid": int,
"date_debut": str,
"date_fin": str,
"etat": str,
"raison": str,
"entry_date": str,
"fichier": str,
"user_id": int,
}
CREATE_FIELD = {"justif_id": int, "couverture": list}
BATCH_FIELD = {"errors": dict, "success": dict}
TO_REMOVE = []
def check_fields(data, fields: dict = None):
"""
Cette fonction permet de vérifier que le dictionnaire data
contient les bonnes clés et les bons types de valeurs.
Args:
data (dict): un dictionnaire (json de retour de l'api)
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
"""
if fields is None:
fields = JUSTIFICATIFS_FIELDS
assert set(data.keys()) == set(fields.keys())
for key in data:
if key in ("raison", "fichier", "user_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):
"""
Cette fonction vérifiée que la requête GET renvoie bien un 404
Args:
path (str): la route de l'api
headers (dict): le token d'auth de l'api
err (str, optional): L'erreur qui est sensée être fournie par l'api.
Raises:
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
"""
try:
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):
"""
Cette fonction vérifiée que la requête POST renvoie bien un 404
Args:
path (str): la route de l'api
headers (dict): le token d'auth
data (dict): un dictionnaire (json) à envoyer
err (str, optional): L'erreur qui est sensée être fournie par l'api.
Raises:
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
"""
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 POST n'aurait pas du fonctionner")
def create_data(etat: str, day: str, raison: str = None):
"""
Permet de créer un dictionnaire assiduité
Args:
etat (str): l'état du justificatif (VALIDE,NON_VALIDE,MODIFIE, ATTENTE)
day (str): Le jour du justificatif
raison (str, optional): Une description du justificatif (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 raison is not None:
data["desc"] = raison
return data
def test_route_justificatif(api_headers):
"""test de la route /justificatif/<justif_id:int>"""
# Bon fonctionnement == id connu
data = GET(path="/justificatif/1", headers=api_headers)
check_fields(data)
# Mauvais Fonctionnement == id inconnu
check_failure_get(
f"/justificatif/{FAUX}",
api_headers,
)
def test_route_justificatifs(api_headers):
"""test de la route /justificatifs/<etudid:int>"""
# Bon fonctionnement
data = GET(path=f"/justificatifs/{ETUDID}", headers=api_headers)
assert isinstance(data, list)
for just in data:
check_fields(just, JUSTIFICATIFS_FIELDS)
data = GET(path=f"/justificatifs/{ETUDID}/query?", headers=api_headers)
assert isinstance(data, list)
for just in data:
check_fields(just, JUSTIFICATIFS_FIELDS)
# Mauvais fonctionnement
check_failure_get(f"/justificatifs/{FAUX}", api_headers)
check_failure_get(f"/justificatifs/{FAUX}/query?", api_headers)
def test_route_create(api_headers):
"""test de la route /justificatif/<justif_id:int>/create"""
# -== Unique ==-
# Bon fonctionnement
data = create_data("valide", "01")
res = POST_JSON(f"/justificatif/{ETUDID}/create", [data], api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["success"]) == 1
TO_REMOVE.append(res["success"]["0"]["justif_id"])
data2 = create_data("modifie", "02", "raison")
res = POST_JSON(f"/justificatif/{ETUDID}/create", [data2], api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["success"]) == 1
TO_REMOVE.append(res["success"]["0"]["justif_id"])
# Mauvais fonctionnement
check_failure_post(f"/justificatif/{FAUX}/create", api_headers, [data])
res = POST_JSON(
f"/justificatif/{ETUDID}/create",
[create_data("absent", "03")],
api_headers,
)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 1
assert res["errors"]["0"] == "param 'etat': invalide"
# -== Multiple ==-
# Bon Fonctionnement
etats = ["valide", "modifie", "non_valide", "attente"]
data = [
create_data(etats[d % 4], 10 + d, "raison" if d % 2 else None)
for d in range(randint(3, 5))
]
res = POST_JSON(f"/justificatif/{ETUDID}/create", 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]["justif_id"])
# Mauvais Fonctionnement
data2 = [
create_data(None, "25"),
create_data("blabla", 26),
create_data("valide", 32),
]
res = POST_JSON(f"/justificatif/{ETUDID}/create", data2, api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 3
assert res["errors"]["0"] == "param 'etat': manquant"
assert res["errors"]["1"] == "param 'etat': invalide"
assert (
res["errors"]["2"]
== "param 'date_debut': format invalide, param 'date_fin': format invalide"
)
def test_route_edit(api_headers):
"""test de la route /justificatif/<justif_id:int>/edit"""
# Bon fonctionnement
data = {"etat": "modifie", "raison": "test"}
res = POST_JSON(f"/justificatif/{TO_REMOVE[0]}/edit", data, api_headers)
assert isinstance(res, dict) and "couverture" in res.keys()
data["raison"] = None
res = POST_JSON(f"/justificatif/{TO_REMOVE[1]}/edit", data, api_headers)
assert isinstance(res, dict) and "couverture" in res.keys()
# Mauvais fonctionnement
check_failure_post(f"/justificatif/{FAUX}/edit", api_headers, data)
data["etat"] = "blabla"
check_failure_post(
f"/justificatif/{TO_REMOVE[2]}/edit",
api_headers,
data,
err="param 'etat': invalide",
)
def test_route_delete(api_headers):
"""test de la route /justificatif/delete"""
# -== Unique ==-
# Bon fonctionnement
data = TO_REMOVE[0]
res = POST_JSON("/justificatif/delete", [data], api_headers)
check_fields(res, BATCH_FIELD)
for dat in res["success"]:
assert res["success"][dat] == {"OK": True}
# Mauvais fonctionnement
res = POST_JSON("/justificatif/delete", [data], api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 1
# -== Multiple ==-
# Bon Fonctionnement
data = TO_REMOVE[1:]
res = POST_JSON("/justificatif/delete", data, api_headers)
check_fields(res, BATCH_FIELD)
for dat in res["success"]:
assert res["success"][dat] == {"OK": True}
# Mauvais Fonctionnement
data2 = [
FAUX,
FAUX + 1,
FAUX + 2,
]
res = POST_JSON("/justificatif/delete", data2, api_headers)
check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 3
assert all([res["errors"][i] == "Justificatif non existant" for i in res["errors"]])
# Gestion de l'archivage
def send_file(justif_id: int, filename: str, headers):
"""
Envoi un fichier vers la route d'importation
"""
with open(filename, "rb") as file:
url: str = API_URL + f"/justificatif/{justif_id}/import"
req = requests.post(
url,
files={filename: file},
headers=headers,
verify=CHECK_CERTIFICATE,
)
if req.status_code != 200:
raise APIError(f"erreur status={req.status_code} !", req.json())
return req.json()
def check_failure_send(
justif_id: int,
headers,
filename: str = "tests/api/test_api_justificatif.txt",
err: str = None,
):
"""
Vérifie si l'envoie d'un fichier renvoie bien un 404
Args:
justif_id (int): l'id du justificatif
headers (dict): token d'auth de l'api
filename (str, optional): le chemin vers le fichier.
Defaults to "tests/api/test_api_justificatif.txt".
err (str, optional): l'erreur attendue.
Raises:
APIError: Si l'envoie fonction (mauvais comportement)
"""
try:
send_file(justif_id, filename, headers)
# ^ Renvoi un 404
except APIError as api_err:
if err is not None:
assert api_err.payload["message"] == err
else:
raise APIError("Le POST n'aurait pas du fonctionner")
def test_import_justificatif(api_headers):
"""test de la route /justificatif/<justif_id:int>/import"""
# Bon fonctionnement
filename: str = "tests/api/test_api_justificatif.txt"
resp: dict = send_file(1, filename, api_headers)
assert "filename" in resp
assert resp["filename"] == "test_api_justificatif.txt"
filename: str = "tests/api/test_api_justificatif2.txt"
resp: dict = send_file(1, filename, api_headers)
assert "filename" in resp
assert resp["filename"] == "test_api_justificatif2.txt"
# Mauvais fonctionnement
check_failure_send(FAUX, api_headers)
def test_list_justificatifs(api_headers):
"""test de la route /justificatif/<justif_id:int>/list"""
# Bon fonctionnement
res: list = GET("/justificatif/1/list", api_headers)
assert isinstance(res, list)
assert len(res) == 2
res: list = GET("/justificatif/2/list", api_headers)
assert isinstance(res, list)
assert len(res) == 0
# Mauvais fonctionnement
check_failure_get(f"/justificatif/{FAUX}/list", api_headers)
def post_export(justif_id: int, fname: str, api_headers):
"""
Envoie une requête poste sans data et la retourne
Args:
id (int): justif_id
fname (str): nom du fichier (coté serv)
api_headers (dict): token auth de l'api
Returns:
request: la réponse de l'api
"""
url: str = API_URL + f"/justificatif/{justif_id}/export/{fname}"
res = requests.post(url, headers=api_headers)
return res
def test_export(api_headers):
"""test de la route /justificatif/<justif_id:int>/export/<filename:str>"""
# Bon fonctionnement
assert post_export(1, "test_api_justificatif.txt", api_headers).status_code == 200
# Mauvais fonctionnement
assert (
post_export(FAUX, "test_api_justificatif.txt", api_headers).status_code == 404
)
assert post_export(1, "blabla.txt", api_headers).status_code == 404
assert post_export(2, "blabla.txt", api_headers).status_code == 404
def test_remove_justificatif(api_headers):
"""test de la route /justificatif/<justif_id:int>/remove"""
# Bon fonctionnement
filename: str = "tests/api/test_api_justificatif.txt"
send_file(2, filename, api_headers)
filename: str = "tests/api/test_api_justificatif2.txt"
send_file(2, filename, api_headers)
res: dict = POST_JSON("/justificatif/1/remove", {"remove": "all"}, api_headers)
assert res == {"response": "removed"}
assert len(GET("/justificatif/1/list", api_headers)) == 0
res: dict = POST_JSON(
"/justificatif/2/remove",
{"remove": "list", "filenames": ["test_api_justificatif2.txt"]},
api_headers,
)
assert res == {"response": "removed"}
assert len(GET("/justificatif/2/list", api_headers)) == 1
res: dict = POST_JSON(
"/justificatif/2/remove",
{"remove": "list", "filenames": ["test_api_justificatif.txt"]},
api_headers,
)
assert res == {"response": "removed"}
assert len(GET("/justificatif/2/list", api_headers)) == 0
# Mauvais fonctionnement
check_failure_post("/justificatif/2/remove", api_headers, {})
check_failure_post(f"/justificatif/{FAUX}/remove", api_headers, {"remove": "all"})
check_failure_post("/justificatif/1/remove", api_headers, {"remove": "all"})
def test_justifies(api_headers):
"""test la route /justificatif/<justif_id:int>/justifies"""
# Bon fonctionnement
res: list = GET("/justificatif/1/justifies", api_headers)
assert isinstance(res, list)
# Mauvais fonctionnement
check_failure_get(f"/justificatif/{FAUX}/justifies", api_headers)

3
tests/api/test_api_permissions.py Normal file → Executable file
View File

@ -60,6 +60,9 @@ def test_permissions(api_headers):
"role_name": "Ens", "role_name": "Ens",
"uid": 1, "uid": 1,
"version": "long", "version": "long",
"assiduite_id": 1,
"justif_id": 1,
"etudids": "1",
} }
for rule in api_rules: for rule in api_rules:
path = rule.build(args)[1] path = rule.build(args)[1]

View File

@ -0,0 +1,26 @@
"entry_name";"url";"permission";"method";"content"
"assiduite";"/assiduite/1";"ScoView";"GET";
"assiduites";"/assiduites/1";"ScoView";"GET";
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?moduleimpl_id=1";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
"assiduite_create";"/assiduite/1/create";"ScoView";"POST";"[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""}]"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"":""absent""}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""moduleimpl_id"":2}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
"assiduite_delete";"/assiduite/delete";"ScoView";"POST";"[2,2,3]"
"justificatif";"/justificatif/1";"ScoView";"GET";
"justificatifs";"/justificatifs/1";"ScoView";"GET";
"justificatifs";"/justificatifs/1/query?etat=attente";"ScoView";"GET";
"justificatif_create";"/justificatif/1/create";"ScoView";"POST";"[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""attente""}]"
"justificatif_edit";"/justificatif/1/edit";"ScoView";"POST";"{""etat"":""valide""}"
"justificatif_edit";"/justificatif/1/edit";"ScoView";"POST";"{""raison"":""MEDIC""}"
"justificatif_delete";"/justificatif/delete";"ScoView";"POST";"[2,2,3]"
1 entry_name url permission method content
2 assiduite /assiduite/1 ScoView GET
3 assiduites /assiduites/1 ScoView GET
4 assiduites /assiduites/1/query?etat=retard ScoView GET
5 assiduites /assiduites/1/query?moduleimpl_id=1 ScoView GET
6 assiduites_count /assiduites/1/count ScoView GET
7 assiduites_count /assiduites/1/count/query?etat=retard ScoView GET
8 assiduites_count /assiduites/1/count/query?etat=present,retard&metric=compte,heure ScoView GET
9 assiduites_formsemestre /assiduites/formsemestre/1 ScoView GET
10 assiduites_formsemestre /assiduites/formsemestre/1/query?etat=retard ScoView GET
11 assiduites_formsemestre /assiduites/formsemestre/1/query?moduleimpl_id=1 ScoView GET
12 assiduites_formsemestre_count /assiduites/formsemestre/1/count ScoView GET
13 assiduites_formsemestre_count /assiduites/formsemestre/1/count/query?etat=retard ScoView GET
14 assiduites_formsemestre_count /assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure ScoView GET
15 assiduite_create /assiduite/1/create ScoView POST [{"date_debut": "2022-10-27T08:00","date_fin": "2022-10-27T10:00","etat": "absent"}]
16 assiduite_edit /assiduite/1/edit ScoView POST {"etat":"absent"}
17 assiduite_edit /assiduite/1/edit ScoView POST {"moduleimpl_id":2}
18 assiduite_edit /assiduite/1/edit ScoView POST {"etat": "retard","moduleimpl_id":3}
19 assiduite_delete /assiduite/delete ScoView POST [2,2,3]
20 justificatif /justificatif/1 ScoView GET
21 justificatifs /justificatifs/1 ScoView GET
22 justificatifs /justificatifs/1/query?etat=attente ScoView GET
23 justificatif_create /justificatif/1/create ScoView POST [{"date_debut": "2022-10-27T08:00","date_fin": "2022-10-27T10:00","etat": "attente"}]
24 justificatif_edit /justificatif/1/edit ScoView POST {"etat":"valide"}
25 justificatif_edit /justificatif/1/edit ScoView POST {"raison":"MEDIC"}
26 justificatif_delete /justificatif/delete ScoView POST [2,2,3]

View File

@ -1,4 +1,24 @@
"entry_name";"url";"permission";"method";"content" "entry_name";"url";"permission";"method";"content"
"assiduite";"/assiduite/1";"ScoView";"GET";
"assiduites";"/assiduites/1";"ScoView";"GET";
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?moduleimpl_id=1";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
"assiduite_create";"/assiduite/1/create";"ScoView";"POST";"{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""}"
"assiduite_create";"/assiduite/1/create/batch";"ScoView";"POST";"{""batch"":[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""},{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""retard""},{""date_debut"": ""2022-10-27T11:00"",""date_fin"": ""2022-10-27T13:00"",""etat"": ""present""}]}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"":""absent""}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""moduleimpl_id"":2}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
"assiduite_delete";"/assiduite/delete";"ScoView";"POST";"{""assiduite_id"": 1}"
"assiduite_delete";"/assiduite/delete/batch";"ScoView";"POST";"{""batch"":[2,2,3]}"
"departements";"/departements";"ScoView";"GET"; "departements";"/departements";"ScoView";"GET";
"departements-ids";"/departements_ids";"ScoView";"GET"; "departements-ids";"/departements_ids";"ScoView";"GET";
"departement";"/departement/TAPI";"ScoView";"GET"; "departement";"/departement/TAPI";"ScoView";"GET";
1 entry_name url permission method content
2 assiduite /assiduite/1 ScoView GET
3 assiduites /assiduites/1 ScoView GET
4 assiduites /assiduites/1/query?etat=retard ScoView GET
5 assiduites /assiduites/1/query?moduleimpl_id=1 ScoView GET
6 assiduites_count /assiduites/1/count ScoView GET
7 assiduites_count /assiduites/1/count/query?etat=retard ScoView GET
8 assiduites_count /assiduites/1/count/query?etat=present,retard&metric=compte,heure ScoView GET
9 assiduites_formsemestre /assiduites/formsemestre/1 ScoView GET
10 assiduites_formsemestre /assiduites/formsemestre/1/query?etat=retard ScoView GET
11 assiduites_formsemestre /assiduites/formsemestre/1/query?moduleimpl_id=1 ScoView GET
12 assiduites_formsemestre_count /assiduites/formsemestre/1/count ScoView GET
13 assiduites_formsemestre_count /assiduites/formsemestre/1/count/query?etat=retard ScoView GET
14 assiduites_formsemestre_count /assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure ScoView GET
15 assiduite_create /assiduite/1/create ScoView POST {"date_debut": "2022-10-27T08:00","date_fin": "2022-10-27T10:00","etat": "absent"}
16 assiduite_create /assiduite/1/create/batch ScoView POST {"batch":[{"date_debut": "2022-10-27T08:00","date_fin": "2022-10-27T10:00","etat": "absent"},{"date_debut": "2022-10-27T08:00","date_fin": "2022-10-27T10:00","etat": "retard"},{"date_debut": "2022-10-27T11:00","date_fin": "2022-10-27T13:00","etat": "present"}]}
17 assiduite_edit /assiduite/1/edit ScoView POST {"etat":"absent"}
18 assiduite_edit /assiduite/1/edit ScoView POST {"moduleimpl_id":2}
19 assiduite_edit /assiduite/1/edit ScoView POST {"etat": "retard","moduleimpl_id":3}
20 assiduite_delete /assiduite/delete ScoView POST {"assiduite_id": 1}
21 assiduite_delete /assiduite/delete/batch ScoView POST {"batch":[2,2,3]}
22 departements /departements ScoView GET
23 departements-ids /departements_ids ScoView GET
24 departement /departement/TAPI ScoView GET

View File

@ -0,0 +1,728 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""
Tests unitaires vérifiant le bon fonctionnement du modèle Assiduité et de
ses fonctions liées
Ecrit par HARTMANN Matthias (en s'inspirant de tests.unit.test_abs_count.py par Fares Amer )
"""
from tests.unit import sco_fake_gen
from app import db
from app.scodoc import sco_formsemestre
import app.scodoc.sco_assiduites as scass
from app.models import Assiduite, Justificatif, Identite, FormSemestre, ModuleImpl
from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.sco_utils as scu
from app.scodoc.sco_abs import (
get_abs_count_in_interval,
get_assiduites_count_in_interval,
)
from app.scodoc import sco_abs_views
from tools import migrate_abs_to_assiduites, downgrade_module
class BiInt(int, scu.BiDirectionalEnum):
"""Classe pour tester la classe BiDirectionalEnum"""
A = 1
B = 2
def test_bi_directional_enum(test_client):
"""Test le bon fonctionnement de la classe BiDirectionalEnum"""
assert BiInt.get("A") == BiInt.get("a") == BiInt.A == 1
assert BiInt.get("B") == BiInt.get("b") == BiInt.B == 2
assert BiInt.get("blabla") is None
assert BiInt.get("blabla", -1) == -1
assert isinstance(BiInt.inverse(), dict)
assert BiInt.inverse()[1] == BiInt.A and BiInt.inverse()[2] == BiInt.B
def test_general(test_client):
"""tests général du modèle assiduite"""
g_fake = sco_fake_gen.ScoFake(verbose=False)
# Création d'une formation (1)
formation_id = g_fake.create_formation()
ue_id = g_fake.create_ue(
formation_id=formation_id, acronyme="T1", titre="UE TEST 1"
)
matiere_id = g_fake.create_matiere(ue_id=ue_id, titre="test matière")
module_id_1 = g_fake.create_module(
matiere_id=matiere_id, code="Mo1", coefficient=1.0, titre="test module"
)
module_id_2 = g_fake.create_module(
matiere_id=matiere_id, code="Mo2", coefficient=1.0, titre="test module2"
)
# Création semestre (2)
formsemestre_id_1 = g_fake.create_formsemestre(
formation_id=formation_id,
semestre_id=1,
date_debut="01/09/2022",
date_fin="31/12/2022",
)
formsemestre_id_2 = g_fake.create_formsemestre(
formation_id=formation_id,
semestre_id=2,
date_debut="01/01/2023",
date_fin="31/07/2023",
)
formsemestre_id_3 = g_fake.create_formsemestre(
formation_id=formation_id,
semestre_id=3,
date_debut="01/01/2024",
date_fin="31/07/2024",
)
formsemestre_1 = sco_formsemestre.get_formsemestre(formsemestre_id_1)
formsemestre_2 = sco_formsemestre.get_formsemestre(formsemestre_id_2)
formsemestre_3 = sco_formsemestre.get_formsemestre(formsemestre_id_3)
# Création des modulesimpls (4, 2 par semestre)
moduleimpl_1_1 = g_fake.create_moduleimpl(
module_id=module_id_1,
formsemestre_id=formsemestre_id_1,
)
moduleimpl_1_2 = g_fake.create_moduleimpl(
module_id=module_id_2,
formsemestre_id=formsemestre_id_1,
)
moduleimpl_2_1 = g_fake.create_moduleimpl(
module_id=module_id_1,
formsemestre_id=formsemestre_id_2,
)
moduleimpl_2_2 = g_fake.create_moduleimpl(
module_id=module_id_2,
formsemestre_id=formsemestre_id_2,
)
moduleimpls = [
moduleimpl_1_1,
moduleimpl_1_2,
moduleimpl_2_1,
moduleimpl_2_2,
]
moduleimpls = [
ModuleImpl.query.filter_by(id=mi_id).first() for mi_id in moduleimpls
]
# Création des étudiants (3)
etuds_dict = [
g_fake.create_etud(code_nip=None, prenom=f"etud{i}") for i in range(3)
]
etuds = []
for etud in etuds_dict:
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_1, etud=etud)
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_2, etud=etud)
etuds.append(Identite.query.filter_by(id=etud["id"]).first())
assert None not in etuds, "Problème avec la conversion en Identite"
# Etudiant faux
etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux")
etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first()
verif_migration_abs_assiduites()
ajouter_assiduites(etuds, moduleimpls, etud_faux)
justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
verifier_comptage_et_filtrage_assiduites(
etuds, moduleimpls, (formsemestre_1, formsemestre_2, formsemestre_3)
)
verifier_filtrage_justificatifs(etuds[0], justificatifs)
editer_supprimer_assiduites(etuds, moduleimpls)
editer_supprimer_justificatif(etuds[0])
def verif_migration_abs_assiduites():
"""Vérification que le script de migration fonctionne correctement"""
downgrade_module(assiduites=True, justificatifs=True)
etudid: int = 1
for debut, fin, demijournee in [
(
"02/01/2023",
"10/01/2023",
2,
), # 2 assiduités 02/01: 08h -> 06/01: 18h & assiduités 09/01: 08h -> 10/01: 18h | 14dj
("16/01/2023", "16/01/2023", 1), # 1 assiduité 16/01: 08h -> 16/01: 12h | 1dj
("19/01/2023", "19/01/2023", 0), # 1 assiduité 19/01: 12h -> 19/01: 18h | 1dj
("18/01/2023", "18/01/2023", 2), # 1 assiduité 18/01: 08h -> 18/01: 18h | 2dj
("23/01/2023", "23/01/2023", 0), # 1 assiduité 23/01: 12h -> 24/01: 18h | 3dj
("24/01/2023", "24/01/2023", 2),
]:
sco_abs_views.doSignaleAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
# --- Justification de certaines absences
for debut, fin, demijournee in [
(
"02/01/2023",
"10/01/2023",
2,
), # 2 justificatif 02/01: 08h -> 06/01: 18h & justificatif 09/01: 08h -> 10/01: 18h | 14dj
(
"19/01/2023",
"19/01/2023",
0,
), # 1 justificatif 19/01: 12h -> 19/01: 18h | 1dj
(
"18/01/2023",
"18/01/2023",
2,
), # 1 justificatif 18/01: 08h -> 18/01: 18h | 2dj
]:
sco_abs_views.doJustifAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
migrate_abs_to_assiduites()
assert Assiduite.query.count() == 6, "Erreur migration assiduites"
assert Justificatif.query.count() == 4, "Erreur migration justificatifs"
essais_cache(etudid)
downgrade_module(assiduites=True, justificatifs=True)
def essais_cache(etudid):
"""Vérification des fonctionnalités du cache TODO:WIP"""
date_deb: str = "2023-01-01T07:00"
date_fin: str = "2023-03-31T19:00"
abs_count_no_cache: int = get_abs_count_in_interval(etudid, date_deb, date_fin)
abs_count_cache = get_abs_count_in_interval(etudid, date_deb, date_fin)
assiduites_count_no_cache = get_assiduites_count_in_interval(
etudid, date_deb, date_fin
)
assiduites_count_cache = get_assiduites_count_in_interval(
etudid, date_deb, date_fin
)
assert (
abs_count_cache
== abs_count_no_cache
== assiduites_count_cache
== assiduites_count_no_cache
== (21, 17)
), "Erreur cache"
def ajouter_justificatifs(etud):
"""test de l'ajout des justificatifs"""
obj_justificatifs = [
{
"etat": scu.EtatJustificatif.ATTENTE,
"deb": "2022-09-03T08:00+01:00",
"fin": "2022-09-03T09:59:59+01:00",
"raison": None,
},
{
"etat": scu.EtatJustificatif.VALIDE,
"deb": "2023-01-03T07:00+01:00",
"fin": "2023-01-03T11:00+01:00",
"raison": None,
},
{
"etat": scu.EtatJustificatif.VALIDE,
"deb": "2022-09-03T10:00:00+01:00",
"fin": "2022-09-03T12:00+01:00",
"raison": None,
},
{
"etat": scu.EtatJustificatif.NON_VALIDE,
"deb": "2022-09-03T14:00:00+01:00",
"fin": "2022-09-03T15:00+01:00",
"raison": "Description",
},
{
"etat": scu.EtatJustificatif.MODIFIE,
"deb": "2023-01-03T11:30+01:00",
"fin": "2023-01-03T12:00+01:00",
"raison": None,
},
]
justificatifs = [
Justificatif.create_justificatif(
etud,
scu.is_iso_formated(just["deb"], True),
scu.is_iso_formated(just["fin"], True),
just["etat"],
just["raison"],
)
for just in obj_justificatifs
]
# Vérification de la création des justificatifs
assert [
justi for justi in justificatifs if not isinstance(justi, Justificatif)
] == [], "La création des justificatifs de base n'est pas OK"
# Vérification de la gestion des erreurs
test_assiduite = {
"etat": scu.EtatJustificatif.ATTENTE,
"deb": "2023-01-03T11:00:01+01:00",
"fin": "2023-01-03T12:00+01:00",
"raison": "Description",
}
return justificatifs
def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justificatif]):
"""
- vérifier le filtrage des justificatifs (etat, debut, fin)
"""
# Vérification du filtrage classique
# Etat
assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "valide").count() == 2
), "Filtrage de l'état 'valide' mauvais"
assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "attente").count() == 1
), "Filtrage de l'état 'attente' mauvais"
assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 1
), "Filtrage de l'état 'modifie' mauvais"
assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "non_valide").count()
== 1
), "Filtrage de l'état 'non_valide' mauvais"
assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "valide,modifie").count()
== 3
), "Filtrage de l'état 'valide,modifie' mauvais"
assert (
scass.filter_justificatifs_by_etat(
etud.justificatifs, "valide,modifie,attente"
).count()
== 4
), "Filtrage de l'état 'valide,modifie,attente' mauvais"
assert (
scass.filter_justificatifs_by_etat(
etud.justificatifs, "valide,modifie,attente,non_valide"
).count()
== 5
), "Filtrage de l'état 'valide,modifie,attente,_non_valide' mauvais"
assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "autre").count() == 0
), "Filtrage de l'état 'autre' mauvais"
# Dates
assert (
scass.filter_by_date(etud.justificatifs, Justificatif).count() == 5
), "Filtrage 'Toute Date' mauvais 1"
date = scu.localize_datetime("2022-09-01T10:00+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
== 5
), "Filtrage 'Toute Date' mauvais 2"
date = scu.localize_datetime("2022-09-03T08:00+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
== 5
), "Filtrage 'date début' mauvais 3"
date = scu.localize_datetime("2022-09-03T08:00:01+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
== 5
), "Filtrage 'date début' mauvais 4"
date = scu.localize_datetime("2022-09-03T10:00+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
== 4
), "Filtrage 'date début' mauvais 5"
date = scu.localize_datetime("2022-09-01T10:00+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
== 0
), "Filtrage 'Toute Date' mauvais 6"
date = scu.localize_datetime("2022-09-03T08:00+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
== 1
), "Filtrage 'date début' mauvais 7"
date = scu.localize_datetime("2022-09-03T10:00:01+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
== 2
), "Filtrage 'date début' mauvais 8"
date = scu.localize_datetime("2023-01-03T12:00+01:00")
assert (
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
== 5
), "Filtrage 'date début' mauvais 9"
# Justifications des assiduites
assert len(scass.justifies(justificatifs[2])) == 1, "Justifications mauvais"
assert len(scass.justifies(justificatifs[0])) == 0, "Justifications mauvais"
def editer_supprimer_justificatif(etud: Identite):
"""
Troisième Partie:
- Vérification de l'édition des justificatifs
- Vérification de la suppression des justificatifs
"""
justi: Justificatif = etud.justificatifs.first()
# Modification de l'état
justi.etat = scu.EtatJustificatif.MODIFIE
# Modification du moduleimpl
justi.date_debut = scu.localize_datetime("2023-02-03T11:00:01+01:00")
justi.date_fin = scu.localize_datetime("2023-02-03T12:00:01+01:00")
db.session.add(justi)
db.session.commit()
# Vérification du changement
assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 2
), "Edition de justificatif mauvais"
assert (
scass.filter_by_date(
etud.justificatifs,
Justificatif,
date_deb=scu.localize_datetime("2023-02-01T11:00:00+01:00"),
).count()
== 1
), "Edition de justificatif mauvais 2"
# Supression d'une assiduité
db.session.delete(justi)
db.session.commit()
assert etud.justificatifs.count() == 4, "Supression de justificatif mauvais"
def editer_supprimer_assiduites(etuds: list[Identite], moduleimpls: list[int]):
"""
Troisième Partie:
- Vérification de l'édition des assiduitées
- Vérification de la suppression des assiduitées
"""
ass1: Assiduite = etuds[0].assiduites.first()
ass2: Assiduite = etuds[1].assiduites.first()
ass3: Assiduite = etuds[2].assiduites.first()
# Modification de l'état
ass1.etat = scu.EtatAssiduite.RETARD
db.session.add(ass1)
# Modification du moduleimpl
ass2.moduleimpl_id = moduleimpls[0].id
db.session.add(ass2)
db.session.commit()
# Vérification du changement
assert (
scass.filter_assiduites_by_etat(etuds[0].assiduites, "retard").count() == 4
), "Edition d'assiduité mauvais"
assert (
scass.filter_by_module_impl(etuds[1].assiduites, moduleimpls[0].id).count() == 2
), "Edition d'assiduité mauvais"
# Supression d'une assiduité
db.session.delete(ass3)
db.session.commit()
assert etuds[2].assiduites.count() == 6, "Supression d'assiduité mauvais"
def ajouter_assiduites(
etuds: list[Identite], moduleimpls: list[ModuleImpl], etud_faux: Identite
):
"""
Première partie:
- Ajoute 6 assiduités à chaque étudiant
- 2 présence (semestre 1 et 2)
- 2 retard (semestre 2)
- 2 absence (semestre 1)
- Vérifie la création des assiduités
"""
for etud in etuds:
obj_assiduites = [
{
"etat": scu.EtatAssiduite.PRESENT,
"deb": "2022-09-03T08:00+01:00",
"fin": "2022-09-03T10:00+01:00",
"moduleimpl": None,
"desc": None,
},
{
"etat": scu.EtatAssiduite.PRESENT,
"deb": "2023-01-03T08:00+01:00",
"fin": "2023-01-03T10:00+01:00",
"moduleimpl": moduleimpls[2],
"desc": None,
},
{
"etat": scu.EtatAssiduite.ABSENT,
"deb": "2022-09-03T10:00:01+01:00",
"fin": "2022-09-03T11:00+01:00",
"moduleimpl": moduleimpls[0],
"desc": None,
},
{
"etat": scu.EtatAssiduite.ABSENT,
"deb": "2022-09-03T14:00:00+01:00",
"fin": "2022-09-03T15:00+01:00",
"moduleimpl": moduleimpls[1],
"desc": "Description",
},
{
"etat": scu.EtatAssiduite.RETARD,
"deb": "2023-01-03T11:00:01+01:00",
"fin": "2023-01-03T12:00+01:00",
"moduleimpl": moduleimpls[3],
"desc": None,
},
{
"etat": scu.EtatAssiduite.RETARD,
"deb": "2023-01-04T11:00:01+01:00",
"fin": "2023-01-04T12:00+01:00",
"moduleimpl": moduleimpls[3],
"desc": "Description",
},
{
"etat": scu.EtatAssiduite.RETARD,
"deb": "2022-11-04T11:00:01+01:00",
"fin": "2022-12-05T12:00+01:00",
"moduleimpl": None,
"desc": "Description",
},
]
assiduites = [
Assiduite.create_assiduite(
etud,
scu.is_iso_formated(ass["deb"], True),
scu.is_iso_formated(ass["fin"], True),
ass["etat"],
ass["moduleimpl"],
ass["desc"],
)
for ass in obj_assiduites
]
# Vérification de la création des assiduités
assert [
ass for ass in assiduites if not isinstance(ass, Assiduite)
] == [], "La création des assiduités de base n'est pas OK"
# Vérification de la gestion des erreurs
test_assiduite = {
"etat": scu.EtatAssiduite.RETARD,
"deb": "2023-01-04T11:00:01+01:00",
"fin": "2023-01-04T12:00+01:00",
"moduleimpl": moduleimpls[3],
"desc": "Description",
}
try:
Assiduite.create_assiduite(
etuds[0],
scu.is_iso_formated(test_assiduite["deb"], True),
scu.is_iso_formated(test_assiduite["fin"], True),
test_assiduite["etat"],
test_assiduite["moduleimpl"],
test_assiduite["desc"],
)
except ScoValueError as excp:
assert (
excp.args[0]
== "Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
)
try:
Assiduite.create_assiduite(
etud_faux,
scu.is_iso_formated(test_assiduite["deb"], True),
scu.is_iso_formated(test_assiduite["fin"], True),
test_assiduite["etat"],
test_assiduite["moduleimpl"],
test_assiduite["desc"],
)
except ScoValueError as excp:
assert excp.args[0] == "L'étudiant n'est pas inscrit au moduleimpl"
def verifier_comptage_et_filtrage_assiduites(
etuds: list[Identite], moduleimpls: list[int], formsemestres: tuple[int]
):
"""
Deuxième partie:
- vérifier les valeurs du comptage (compte, heure, journée, demi-journée)
- vérifier le filtrage des assiduites (etat, debut, fin, module, formsemestre)
"""
etu1, etu2, etu3 = etuds
mod11, mod12, mod21, mod22 = moduleimpls
# Vérification du comptage classique
comptage = scass.get_assiduites_stats(etu1.assiduites)
assert comptage["compte"] == 6 + 1, "la métrique 'Comptage' n'est pas bien calculée"
assert (
comptage["journee"] == 3 + 22
), "la métrique 'Journée' n'est pas bien calculée"
assert (
comptage["demi"] == 4 + 43
), "la métrique 'Demi-Journée' n'est pas bien calculée"
assert comptage["heure"] == float(
8 + 169
), "la métrique 'Heure' n'est pas bien calculée"
# Vérification du filtrage classique
# Etat
assert (
scass.filter_assiduites_by_etat(etu2.assiduites, "present").count() == 2
), "Filtrage de l'état 'présent' mauvais"
assert (
scass.filter_assiduites_by_etat(etu2.assiduites, "retard").count() == 3
), "Filtrage de l'état 'retard' mauvais"
assert (
scass.filter_assiduites_by_etat(etu2.assiduites, "absent").count() == 2
), "Filtrage de l'état 'absent' mauvais"
assert (
scass.filter_assiduites_by_etat(etu2.assiduites, "absent,retard").count() == 5
), "Filtrage de l'état 'absent,retard' mauvais"
assert (
scass.filter_assiduites_by_etat(
etu2.assiduites, "absent,retard,present"
).count()
== 7
), "Filtrage de l'état 'absent,retard,present' mauvais"
assert (
scass.filter_assiduites_by_etat(etu2.assiduites, "autre").count() == 0
), "Filtrage de l'état 'autre' mauvais"
# Module
assert (
scass.filter_by_module_impl(etu3.assiduites, mod11.id).count() == 1
), "Filtrage par 'Moduleimpl' mauvais"
assert (
scass.filter_by_module_impl(etu3.assiduites, mod12.id).count() == 1
), "Filtrage par 'Moduleimpl' mauvais"
assert (
scass.filter_by_module_impl(etu3.assiduites, mod21.id).count() == 1
), "Filtrage par 'Moduleimpl' mauvais"
assert (
scass.filter_by_module_impl(etu3.assiduites, mod22.id).count() == 2
), "Filtrage par 'Moduleimpl' mauvais"
assert (
scass.filter_by_module_impl(etu3.assiduites, None).count() == 2
), "Filtrage par 'Moduleimpl' mauvais"
assert (
scass.filter_by_module_impl(etu3.assiduites, 152).count() == 0
), "Filtrage par 'Moduleimpl' mauvais"
# Formsemestre
formsemestres = [
FormSemestre.query.filter_by(id=fms["id"]).first() for fms in formsemestres
]
assert (
scass.filter_by_formsemestre(etu1.assiduites, formsemestres[0]).count() == 4
), "Filtrage 'Formsemestre' mauvais"
assert (
scass.filter_by_formsemestre(etu1.assiduites, formsemestres[1]).count() == 3
), "Filtrage 'Formsemestre' mauvais"
assert (
scass.filter_by_formsemestre(etu1.assiduites, formsemestres[2]).count() == 0
), "Filtrage 'Formsemestre' mauvais"
# Date début
assert (
scass.filter_by_date(etu2.assiduites, Assiduite).count() == 7
), "Filtrage 'Date début' mauvais 1"
date = scu.localize_datetime("2022-09-01T10:00+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
), "Filtrage 'Date début' mauvais 2"
date = scu.localize_datetime("2022-09-03T10:00+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
), "Filtrage 'Date début' mauvais 3"
date = scu.localize_datetime("2022-09-03T16:00+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 4
), "Filtrage 'Date début' mauvais 4"
# Date Fin
date = scu.localize_datetime("2022-09-01T10:00+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 0
), "Filtrage 'Date fin' mauvais 1"
date = scu.localize_datetime("2022-09-03T10:00+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 1
), "Filtrage 'Date fin' mauvais 2"
date = scu.localize_datetime("2022-09-03T10:00:01+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 2
), "Filtrage 'Date fin' mauvais 3"
date = scu.localize_datetime("2022-09-03T16:00+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 3
), "Filtrage 'Date fin' mauvais 4"
date = scu.localize_datetime("2023-01-04T16:00+01:00")
assert (
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 7
), "Filtrage 'Date fin' mauvais 5"

View File

@ -21,11 +21,13 @@ from app import models
from app.models import departements from app.models import departements
from app.models import ( from app.models import (
Absence, Absence,
Assiduite,
Departement, Departement,
Formation, Formation,
FormSemestre, FormSemestre,
FormSemestreEtape, FormSemestreEtape,
Identite, Identite,
Justificatif,
ModuleImpl, ModuleImpl,
NotesNotes, NotesNotes,
) )
@ -37,6 +39,7 @@ from app.scodoc import (
sco_groups, sco_groups,
) )
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import localize_datetime
from tools.fakeportal.gen_nomprenoms import nomprenom from tools.fakeportal.gen_nomprenoms import nomprenom
random.seed(12345678) # tests reproductibles random.seed(12345678) # tests reproductibles
@ -378,6 +381,56 @@ def create_logos():
) )
def ajouter_assiduites_justificatifs(formsemestre: FormSemestre):
"""
Ajoute des assiduités semi-aléatoires à chaque étudiant du semestre
"""
MODS = [moduleimpl for moduleimpl in formsemestre.modimpls]
MODS.append(None)
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)
moduleimpl = 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, moduleimpl
)
assert isinstance(
code, Assiduite
), "Erreur dans la génération des assiduités"
db.session.add(code)
for i in range(random.randint(0, 2)):
etat = random.randint(0, 3)
deb_date = base_date + datetime.timedelta(days=i)
fin_date = deb_date + datetime.timedelta(hours=8)
raison = random.choice(["raison", None])
code = Justificatif.create_justificatif(
etud=etud,
date_debut=deb_date,
date_fin=fin_date,
etat=etat,
raison=raison,
)
assert isinstance(
code, Justificatif
), "Erreur dans la génération des justificatifs"
db.session.add(code)
db.session.commit()
def init_test_database(): def init_test_database():
"""Appelé par la commande `flask init-test-database` """Appelé par la commande `flask init-test-database`
@ -398,6 +451,7 @@ def init_test_database():
saisie_notes_evaluations(formsemestre, user_lecteur) saisie_notes_evaluations(formsemestre, user_lecteur)
add_absences(formsemestre) add_absences(formsemestre)
create_etape_apo(formsemestre) create_etape_apo(formsemestre)
ajouter_assiduites_justificatifs(formsemestre)
create_logos() create_logos()
# à compléter # à compléter
# - groupes # - groupes