API evaluation: create avec poids, /delete + tests unitaires + corrections
This commit is contained in:
parent
94f857665c
commit
1d3726a4cd
@ -20,5 +20,5 @@ ignored-classes=Permission,
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=entreprises
|
||||
|
||||
good-names=d,e,f,i,j,k,nt,t,u,ue,v,x,y,z,H,F
|
||||
good-names=d,e,f,i,j,k,n,nt,t,u,ue,v,x,y,z,H,F
|
||||
|
||||
|
@ -5,7 +5,7 @@ from flask import Blueprint
|
||||
from flask import request, g
|
||||
from app import db
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
||||
|
||||
api_bp = Blueprint("api", __name__)
|
||||
api_web_bp = Blueprint("apiweb", __name__)
|
||||
@ -15,12 +15,24 @@ API_CLIENT_ERROR = 400 # erreur dans les paramètres fournis par le client
|
||||
|
||||
|
||||
@api_bp.errorhandler(ScoException)
|
||||
@api_web_bp.errorhandler(ScoException)
|
||||
@api_bp.errorhandler(404)
|
||||
def api_error_handler(e):
|
||||
"erreurs API => json"
|
||||
return scu.json_error(404, message=str(e))
|
||||
|
||||
|
||||
@api_bp.errorhandler(AccessDenied)
|
||||
@api_web_bp.errorhandler(AccessDenied)
|
||||
def permission_denied_error_handler(exc):
|
||||
"""
|
||||
Renvoie message d'erreur pour l'erreur 403
|
||||
"""
|
||||
return scu.json_error(
|
||||
403, f"operation non autorisee ({exc.args[0] if exc.args else ''})"
|
||||
)
|
||||
|
||||
|
||||
def requested_format(default_format="json", allowed_formats=None):
|
||||
"""Extract required format from query string.
|
||||
* default value is json. A list of allowed formats may be provided
|
||||
@ -65,6 +77,7 @@ from app.api import (
|
||||
jury,
|
||||
justificatifs,
|
||||
logos,
|
||||
moduleimpl,
|
||||
partitions,
|
||||
semset,
|
||||
users,
|
||||
|
@ -12,7 +12,7 @@ from flask_json import as_json
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app import log, db
|
||||
from app.api import api_bp as bp, api_web_bp
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.models import Evaluation, ModuleImpl, FormSemestre
|
||||
@ -28,7 +28,7 @@ import app.scodoc.sco_utils as scu
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def evaluation(evaluation_id: int):
|
||||
def get_evaluation(evaluation_id: int):
|
||||
"""Description d'une évaluation.
|
||||
|
||||
{
|
||||
@ -203,13 +203,7 @@ def evaluation_create(moduleimpl_id: int):
|
||||
"visibulletin" : boolean , //default true
|
||||
"publish_incomplete" : boolean , //default false
|
||||
"coefficient" : float, // si non spécifié, 1.0
|
||||
"poids" : [ {
|
||||
"ue_id": int,
|
||||
"poids": float
|
||||
},
|
||||
...
|
||||
] // si non spécifié, tous les poids à 1.0
|
||||
}
|
||||
"poids" : { ue_id : poids } // optionnel
|
||||
}
|
||||
Result: l'évaluation créée.
|
||||
"""
|
||||
@ -220,8 +214,6 @@ def evaluation_create(moduleimpl_id: int):
|
||||
|
||||
try:
|
||||
evaluation = Evaluation.create(moduleimpl=moduleimpl, **data)
|
||||
except AccessDenied:
|
||||
return scu.json_error(403, "opération non autorisée (2)")
|
||||
except ValueError:
|
||||
return scu.json_error(400, "paramètre incorrect")
|
||||
except ScoValueError as exc:
|
||||
@ -231,6 +223,27 @@ def evaluation_create(moduleimpl_id: int):
|
||||
|
||||
db.session.add(evaluation)
|
||||
db.session.commit()
|
||||
# Les poids vers les UEs:
|
||||
poids = data.get("poids")
|
||||
if poids is not None:
|
||||
if not isinstance(poids, dict):
|
||||
log("API error: canceling evaluation creation")
|
||||
db.session.delete(evaluation)
|
||||
db.session.commit()
|
||||
return scu.json_error(
|
||||
400, "paramètre de type incorrect (poids must be a dict)"
|
||||
)
|
||||
try:
|
||||
evaluation.set_ue_poids_dict(data["poids"])
|
||||
except ScoValueError as exc:
|
||||
log("API error: canceling evaluation creation")
|
||||
db.session.delete(evaluation)
|
||||
db.session.commit()
|
||||
return scu.json_error(
|
||||
400,
|
||||
f"erreur enregistrement des poids ({exc.args[0] if exc.args else ''})",
|
||||
)
|
||||
db.session.commit()
|
||||
return evaluation.to_dict_api()
|
||||
|
||||
|
||||
@ -241,4 +254,24 @@ def evaluation_create(moduleimpl_id: int):
|
||||
@permission_required(Permission.ScoEnsView) # permission gérée dans la fonction
|
||||
@as_json
|
||||
def evaluation_delete(evaluation_id: int):
|
||||
pass
|
||||
"""Suppression d'une évaluation.
|
||||
Efface aussi toutes ses notes
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
query.join(ModuleImpl)
|
||||
.join(FormSemestre)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
evaluation = query.first_or_404()
|
||||
dept = evaluation.moduleimpl.formsemestre.departement
|
||||
app.set_sco_dept(dept.acronym)
|
||||
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||
raise AccessDenied("evaluation_delete")
|
||||
|
||||
sco_saisie_notes.evaluation_suppress_alln(
|
||||
evaluation_id=evaluation_id, dialog_confirmed=True
|
||||
)
|
||||
sco_evaluation_db.do_evaluation_delete(evaluation_id)
|
||||
return "ok"
|
||||
|
@ -21,8 +21,6 @@ from app.models import (
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
Formation,
|
||||
FormSemestre,
|
||||
ModuleImpl,
|
||||
UniteEns,
|
||||
)
|
||||
from app.scodoc import sco_formations
|
||||
@ -249,54 +247,6 @@ def referentiel_competences(formation_id: int):
|
||||
return formation.referentiel_competence.to_dict()
|
||||
|
||||
|
||||
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def moduleimpl(moduleimpl_id: int):
|
||||
"""
|
||||
Retourne un moduleimpl en fonction de son id
|
||||
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"module_id": 1,
|
||||
"responsable_id": 2,
|
||||
"moduleimpl_id": 1,
|
||||
"ens": [],
|
||||
"module": {
|
||||
"heures_tp": 0,
|
||||
"code_apogee": "",
|
||||
"titre": "Initiation aux réseaux informatiques",
|
||||
"coefficient": 1,
|
||||
"module_type": 2,
|
||||
"id": 1,
|
||||
"ects": null,
|
||||
"abbrev": "Init aux réseaux informatiques",
|
||||
"ue_id": 1,
|
||||
"code": "R101",
|
||||
"formation_id": 1,
|
||||
"heures_cours": 0,
|
||||
"matiere_id": 1,
|
||||
"heures_td": 0,
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"module_id": 1
|
||||
}
|
||||
}
|
||||
"""
|
||||
query = ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
modimpl: ModuleImpl = query.first_or_404()
|
||||
return modimpl.to_dict(convert_objects=True)
|
||||
|
||||
|
||||
@bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
|
||||
@api_web_bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
|
||||
@login_required
|
||||
|
@ -212,7 +212,7 @@ def bulletins(formsemestre_id: int, version: str = "long"):
|
||||
@as_json
|
||||
def formsemestre_programme(formsemestre_id: int):
|
||||
"""
|
||||
Retourne la liste des Ues, ressources et SAE d'un semestre
|
||||
Retourne la liste des UEs, ressources et SAEs d'un semestre
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
|
||||
|
69
app/api/moduleimpl.py
Normal file
69
app/api/moduleimpl.py
Normal file
@ -0,0 +1,69 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : accès aux moduleimpl
|
||||
"""
|
||||
|
||||
from flask import g
|
||||
from flask_json import as_json
|
||||
from flask_login import login_required
|
||||
|
||||
from app.api import api_bp as bp, api_web_bp
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.models import (
|
||||
FormSemestre,
|
||||
ModuleImpl,
|
||||
)
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def moduleimpl(moduleimpl_id: int):
|
||||
"""
|
||||
Retourne un moduleimpl en fonction de son id
|
||||
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"module_id": 1,
|
||||
"responsable_id": 2,
|
||||
"moduleimpl_id": 1,
|
||||
"ens": [],
|
||||
"module": {
|
||||
"heures_tp": 0,
|
||||
"code_apogee": "",
|
||||
"titre": "Initiation aux réseaux informatiques",
|
||||
"coefficient": 1,
|
||||
"module_type": 2,
|
||||
"id": 1,
|
||||
"ects": null,
|
||||
"abbrev": "Init aux réseaux informatiques",
|
||||
"ue_id": 1,
|
||||
"code": "R101",
|
||||
"formation_id": 1,
|
||||
"heures_cours": 0,
|
||||
"matiere_id": 1,
|
||||
"heures_td": 0,
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"module_id": 1
|
||||
}
|
||||
}
|
||||
"""
|
||||
query = ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
modimpl: ModuleImpl = query.first_or_404()
|
||||
return modimpl.to_dict(convert_objects=True)
|
@ -53,8 +53,8 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
self.store()
|
||||
t2 = time.time()
|
||||
log(
|
||||
f"""ResultatsSemestreBUT: cached formsemestre_id={formsemestre.id
|
||||
} ({(t1-t0):g}s +{(t2-t1):g}s)"""
|
||||
f"""+++ ResultatsSemestreBUT: cached [{formsemestre.id
|
||||
}] ({(t1-t0):g}s +{(t2-t1):g}s) +++"""
|
||||
)
|
||||
|
||||
def compute(self):
|
||||
|
@ -50,8 +50,8 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
||||
self.store()
|
||||
t2 = time.time()
|
||||
log(
|
||||
f"""ResultatsSemestreClassic: cached formsemestre_id={
|
||||
formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s)"""
|
||||
f"""+++ ResultatsSemestreClassic: cached formsemestre_id={
|
||||
formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s) +++"""
|
||||
)
|
||||
# recalculé (aussi rapide que de les cacher)
|
||||
self.moy_min = self.etud_moy_gen.min()
|
||||
|
@ -9,7 +9,7 @@ from flask import g, url_for
|
||||
from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
from app import db
|
||||
from app import db, log
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.events import ScolarNews
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
@ -79,7 +79,9 @@ class Evaluation(db.Model):
|
||||
numero=None,
|
||||
**kw, # ceci pour absorber les éventuel arguments excedentaires
|
||||
):
|
||||
"""Create an evaluation. Check permission and all arguments."""
|
||||
"""Create an evaluation. Check permission and all arguments.
|
||||
Ne crée pas les poids vers les UEs.
|
||||
"""
|
||||
if not moduleimpl.can_edit_evaluation(current_user):
|
||||
raise AccessDenied(
|
||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||
@ -100,11 +102,12 @@ class Evaluation(db.Model):
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=moduleimpl.id,
|
||||
)
|
||||
log(f"created evaluation in {moduleimpl.module.titre_str()}")
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=moduleimpl.id,
|
||||
text=f"""Création d'une évaluation dans <a href="{url}">{
|
||||
moduleimpl.module.titre or '(module sans titre)'}</a>""",
|
||||
moduleimpl.module.titre_str()}</a>""",
|
||||
url=url,
|
||||
)
|
||||
return evaluation
|
||||
@ -275,17 +278,17 @@ class Evaluation(db.Model):
|
||||
return f"{dt.hour}h"
|
||||
|
||||
if self.date_fin is None:
|
||||
return (
|
||||
f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut())}"
|
||||
)
|
||||
return f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut)}"
|
||||
if self.date_debut.date() == self.date_fin.date(): # même jour
|
||||
if self.date_debut.time() == self.date_fin.time():
|
||||
return f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut())}"
|
||||
return (
|
||||
f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut)}"
|
||||
)
|
||||
return f"""le {self.date_debut.strftime('%d/%m/%Y')} de {
|
||||
_h(self.date_debut())} à {_h(self.date_fin())}"""
|
||||
_h(self.date_debut)} à {_h(self.date_fin)}"""
|
||||
# évaluation sur plus d'une journée
|
||||
return f"""du {self.date_debut.strftime('%d/%m/%Y')} à {
|
||||
_h(self.date_debut())} au {self.date_fin.strftime('%d/%m/%Y')} à {_h(self.date_fin())}"""
|
||||
_h(self.date_debut)} au {self.date_fin.strftime('%d/%m/%Y')} à {_h(self.date_fin)}"""
|
||||
|
||||
def heure_debut(self) -> str:
|
||||
"""L'heure de début (sans la date), en ISO.
|
||||
@ -356,6 +359,8 @@ class Evaluation(db.Model):
|
||||
L = []
|
||||
for ue_id, poids in ue_poids_dict.items():
|
||||
ue = db.session.get(UniteEns, ue_id)
|
||||
if ue is None:
|
||||
raise ScoValueError("poids vers une UE inexistante")
|
||||
ue_poids = EvaluationUEPoids(evaluation=self, ue=ue, poids=poids)
|
||||
L.append(ue_poids)
|
||||
db.session.add(ue_poids)
|
||||
|
@ -153,6 +153,10 @@ class Module(db.Model):
|
||||
"""
|
||||
return scu.ModuleType.get_abbrev(self.module_type)
|
||||
|
||||
def titre_str(self) -> str:
|
||||
"Identifiant du module à afficher : abbrev ou titre ou code"
|
||||
return self.abbrev or self.titre or self.code
|
||||
|
||||
def sort_key_apc(self) -> tuple:
|
||||
"""Clé de tri pour avoir
|
||||
présentation par type (res, sae), parcours, type, numéro
|
||||
|
@ -57,8 +57,10 @@ def _pe_view_sem_recap_form(formsemestre_id):
|
||||
poursuites d'études.
|
||||
<br>
|
||||
De nombreux aspects sont paramétrables:
|
||||
<a href="https://scodoc.org/AvisPoursuiteEtudes" target="_blank" rel="noopener">
|
||||
voir la documentation</a>.
|
||||
<a href="https://scodoc.org/AvisPoursuiteEtudes"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
voir la documentation
|
||||
</a>.
|
||||
</p>
|
||||
<form method="post" action="pe_view_sem_recap" id="pe_view_sem_recap_form"
|
||||
enctype="multipart/form-data">
|
||||
|
@ -273,9 +273,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
||||
|
||||
if formsemestre_id is None:
|
||||
# clear all caches
|
||||
log(
|
||||
f"----- invalidate_formsemestre: clearing all caches. pdfonly={pdfonly}-----"
|
||||
)
|
||||
log(f"--- invalidate_formsemestre: clearing all caches. pdfonly={pdfonly}---")
|
||||
formsemestre_ids = [
|
||||
formsemestre.id
|
||||
for formsemestre in FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
@ -285,7 +283,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
||||
formsemestre_id
|
||||
] + sco_cursus.list_formsemestre_utilisateurs_uecap(formsemestre_id)
|
||||
log(
|
||||
f"----- invalidate_formsemestre: clearing {formsemestre_ids}. pdfonly={pdfonly} -----"
|
||||
f"--- invalidate_formsemestre: clearing {formsemestre_ids}. pdfonly={pdfonly} ---"
|
||||
)
|
||||
|
||||
if not pdfonly:
|
||||
|
@ -133,7 +133,7 @@ def do_evaluation_delete(evaluation_id):
|
||||
raise ScoValueError(
|
||||
"Impossible de supprimer cette évaluation: il reste des notes"
|
||||
)
|
||||
|
||||
log(f"deleting evaluation {evaluation}")
|
||||
db.session.delete(evaluation)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -502,15 +502,16 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
"""
|
||||
]
|
||||
# news
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=evaluation.moduleimpl.id,
|
||||
text=f"""Suppression des notes d'une évaluation dans
|
||||
<a class="stdlink" href="{status_url}"
|
||||
>{evaluation.moduleimpl.module.titre or 'module sans titre'}</a>
|
||||
""",
|
||||
url=status_url,
|
||||
)
|
||||
if nb_suppress:
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=evaluation.moduleimpl.id,
|
||||
text=f"""Suppression des notes d'une évaluation dans
|
||||
<a class="stdlink" href="{status_url}"
|
||||
>{evaluation.moduleimpl.module.titre or 'module sans titre'}</a>
|
||||
""",
|
||||
url=status_url,
|
||||
)
|
||||
|
||||
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="link">
|
||||
<a class="stdlink" target="_blank" href="{{
|
||||
<a class="stdlink" target="_blank" rel="noopener noreferrer" href="{{
|
||||
url_for('notes.refcomp_show',
|
||||
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id )
|
||||
}}">référentiel de compétences</a>
|
||||
|
@ -108,6 +108,12 @@ def test_evaluation_create(api_admin_headers):
|
||||
Test /moduleimpl/<int:moduleimpl_id>/evaluation/create
|
||||
"""
|
||||
moduleimpl_id = 20
|
||||
# Nombre d'évaluations initial
|
||||
evaluations = GET(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers
|
||||
)
|
||||
nb_evals = len(evaluations)
|
||||
#
|
||||
e = POST_JSON(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||
{"description": "eval test"},
|
||||
@ -123,6 +129,11 @@ def test_evaluation_create(api_admin_headers):
|
||||
assert e["visibulletin"] is True
|
||||
assert e["publish_incomplete"] is False
|
||||
assert e["coefficient"] == 1.0
|
||||
new_nb_evals = len(
|
||||
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||
)
|
||||
assert new_nb_evals == nb_evals + 1
|
||||
nb_evals = new_nb_evals
|
||||
|
||||
# Avec une erreur
|
||||
check_failure_post(
|
||||
@ -131,7 +142,10 @@ def test_evaluation_create(api_admin_headers):
|
||||
{"evaluation_type": 666},
|
||||
err="paramètre de type incorrect (invalid evaluation_type value)",
|
||||
)
|
||||
|
||||
new_nb_evals = len(
|
||||
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||
)
|
||||
assert new_nb_evals == nb_evals # inchangé
|
||||
# Avec plein de valeurs
|
||||
data = {
|
||||
"coefficient": 12.0,
|
||||
@ -148,11 +162,72 @@ def test_evaluation_create(api_admin_headers):
|
||||
data,
|
||||
api_admin_headers,
|
||||
)
|
||||
e2 = GET(f"/evaluation/{e['id']}", headers=api_admin_headers)
|
||||
e_ret = GET(f"/evaluation/{e['id']}", headers=api_admin_headers)
|
||||
for k, v in data.items():
|
||||
assert e2[k] == v, f"received '{e2[k]}'"
|
||||
assert e_ret[k] == v, f"received '{e_ret[k]}'"
|
||||
|
||||
|
||||
# TODO
|
||||
# - tester creation UE externe
|
||||
# - tester création base test et test API
|
||||
# Avec des poids APC
|
||||
nb_evals = len(
|
||||
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||
)
|
||||
data.update(
|
||||
{
|
||||
"description": "eval test apc erreur",
|
||||
"poids": {"666": 666.0}, # poids erroné: UE inexistante
|
||||
}
|
||||
)
|
||||
check_failure_post(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||
api_admin_headers,
|
||||
data,
|
||||
)
|
||||
new_nb_evals = len(
|
||||
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||
)
|
||||
assert new_nb_evals == nb_evals # inchangé
|
||||
# Avec des poids absurdes
|
||||
data.update({"description": "eval test apc erreur 2", "poids": "nimporte quoi"})
|
||||
check_failure_post(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||
api_admin_headers,
|
||||
data,
|
||||
)
|
||||
new_nb_evals = len(
|
||||
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||
)
|
||||
assert new_nb_evals == nb_evals # inchangé
|
||||
# Avec de bons poids
|
||||
# pour cela il nous faut les UEs de ce formsemestre
|
||||
# sachant que l'on a moduleimpl
|
||||
modimpl = GET(f"/moduleimpl/{moduleimpl_id}", headers=api_admin_headers)
|
||||
formation = GET(
|
||||
f"/formsemestre/{modimpl['formsemestre_id']}/programme",
|
||||
headers=api_admin_headers,
|
||||
)
|
||||
ues = formation["ues"]
|
||||
assert len(ues)
|
||||
ue_ids = [ue["id"] for ue in ues]
|
||||
poids = {ue_id: float(i) + 0.5 for i, ue_id in enumerate(ue_ids)}
|
||||
data.update({"description": "eval avec poids", "poids": poids})
|
||||
e = POST_JSON(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||
data,
|
||||
api_admin_headers,
|
||||
)
|
||||
assert e["poids"]
|
||||
e_ret = GET(f"/evaluation/{e['id']}", headers=api_admin_headers)
|
||||
assert e_ret["poids"] == e["poids"]
|
||||
new_nb_evals = len(
|
||||
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||
)
|
||||
assert new_nb_evals == nb_evals + 1
|
||||
nb_evals = new_nb_evals
|
||||
# Delete
|
||||
ans = POST_JSON(
|
||||
f"/evaluation/{e_ret['id']}/delete",
|
||||
headers=api_admin_headers,
|
||||
)
|
||||
assert ans == "ok"
|
||||
assert nb_evals - 1 == len(
|
||||
GET(f"/moduleimpl/{moduleimpl_id}/evaluations", headers=api_admin_headers)
|
||||
)
|
||||
|
@ -4,6 +4,21 @@
|
||||
# Ce script lance un serveur scodoc sur le port 5555
|
||||
# attend qu'il soit initialisé puis lance les tests client API.
|
||||
#
|
||||
# On peut aussi le lancer avec l'option --dont-start-server
|
||||
# auquel cas il utilise un serveur existant, qui doit avoir été lancé
|
||||
# par ailleurs, par exemple via le script:
|
||||
# tests/api/start_api_server.sh -p 5555
|
||||
#
|
||||
# Toutes les autres options sont passées telles qu'elles à pytest
|
||||
#
|
||||
# Exemples:
|
||||
# - lancer tous les tests API: tools/test_api.sh
|
||||
# - lancer tous les tests, en mode debug (arrêt pdb sur le 1er):
|
||||
# tools/test_api.sh -x --pdb tests/api
|
||||
# - lancer un module de test, en utilisant un server dev existant:
|
||||
# tools/test_api.sh --dont-start-server -x --pdb tests/api/test_api_evaluations.py
|
||||
#
|
||||
#
|
||||
# E. Viennet, Fev 2023
|
||||
|
||||
cd /opt/scodoc
|
||||
@ -16,28 +31,40 @@ SERVER_LOG=/tmp/test_api_server.log
|
||||
|
||||
export SCODOC_URL="http://localhost:${PORT}"
|
||||
|
||||
# ------- Check pas de serveur déjà lancé
|
||||
if nc -z localhost "$PORT"
|
||||
if [ "$1" = "--dont-start-server" ]
|
||||
then
|
||||
fuser -v "$PORT"/tcp
|
||||
echo Server already running on port "$PORT"
|
||||
echo You may want to try: fuser -k "$PORT"/tcp
|
||||
echo aborting tests
|
||||
exit 1
|
||||
START_SERVER=0
|
||||
shift
|
||||
echo "Using existing scodoc server on port $PORT"
|
||||
else
|
||||
START_SERVER=1
|
||||
fi
|
||||
|
||||
tests/api/start_api_server.sh -p "$PORT" &> "$SERVER_LOG" &
|
||||
pid=$!
|
||||
echo "ScoDoc test server logs are in $SERVER_LOG"
|
||||
# Wait for server setup
|
||||
echo -n "Waiting for server"
|
||||
while ! nc -z localhost "$PORT"; do
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
echo Server PID "$pid" running on port "$PORT"
|
||||
# ------------------
|
||||
if [ "$START_SERVER" -eq 1 ]
|
||||
then
|
||||
# ------- Check pas de serveur déjà lancé
|
||||
if nc -z localhost "$PORT"
|
||||
then
|
||||
fuser -v "$PORT"/tcp
|
||||
echo Server already running on port "$PORT"
|
||||
echo You may want to try: fuser -k "$PORT"/tcp
|
||||
echo aborting tests
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tests/api/start_api_server.sh -p "$PORT" &> "$SERVER_LOG" &
|
||||
pid=$!
|
||||
echo "ScoDoc test server logs are in $SERVER_LOG"
|
||||
# Wait for server setup
|
||||
echo -n "Waiting for server"
|
||||
while ! nc -z localhost "$PORT"; do
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
echo Server PID "$pid" running on port "$PORT"
|
||||
# ------------------
|
||||
fi
|
||||
|
||||
if [ "$#" -eq 0 ]
|
||||
then
|
||||
@ -49,9 +76,12 @@ else
|
||||
fi
|
||||
|
||||
# ------------------
|
||||
echo "Killing server"
|
||||
kill "$pid"
|
||||
fuser -k "$PORT"/tcp
|
||||
if [ "$START_SERVER" -eq 1 ]
|
||||
then
|
||||
echo "Killing test server"
|
||||
kill "$pid"
|
||||
fuser -k "$PORT"/tcp
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user