Files
notytex/backend/tests/unit/test_student_report_service.py
Bertrand Benjamin f76b033d55
All checks were successful
Build and Publish Docker Images / Build Frontend Image (push) Successful in 2m56s
Build and Publish Docker Images / Build Backend Image (push) Successful in 3m5s
Build and Publish Docker Images / Build Summary (push) Successful in 3s
feat(mail): restauration de l'envoie de mail
2025-12-04 06:04:13 +01:00

368 lines
13 KiB
Python

"""
Tests unitaires pour le service de génération de rapports.
"""
import pytest
from domain.services import StudentReportService, generate_report_html
class TestStudentReportService:
"""Tests pour le service de génération de rapports."""
@pytest.fixture
def report_service(self):
"""Fixture du service de rapport."""
return StudentReportService()
@pytest.fixture
def sample_assessment_data(self):
"""Données d'évaluation de test."""
return {
'title': 'Contrôle de Mathématiques',
'description': 'Chapitre 3 - Fonctions',
'date': '2025-01-15',
'trimester': 2,
'class_name': '6ème A',
'coefficient': 1.0
}
@pytest.fixture
def sample_exercises_data(self):
"""Données d'exercices de test."""
return [
{
'id': 1,
'title': 'Exercice 1',
'description': 'Calculs',
'order': 1,
'elements': [
{
'id': 101,
'exercise_id': 1,
'label': 'Question 1a',
'description': 'Calcul simple',
'skill': 'Calculer',
'domain_name': 'Nombres',
'domain_color': '#3b82f6',
'grading_type': 'notes',
'max_points': 2.0
},
{
'id': 102,
'exercise_id': 1,
'label': 'Question 1b',
'description': 'Calcul complexe',
'skill': 'Calculer',
'domain_name': 'Nombres',
'domain_color': '#3b82f6',
'grading_type': 'notes',
'max_points': 3.0
}
]
},
{
'id': 2,
'title': 'Exercice 2',
'description': 'Compétences',
'order': 2,
'elements': [
{
'id': 201,
'exercise_id': 2,
'label': 'Compétence graphique',
'description': 'Tracer un graphe',
'skill': 'Représenter',
'domain_name': 'Géométrie',
'domain_color': '#22c55e',
'grading_type': 'score',
'max_points': 3.0
}
]
}
]
@pytest.fixture
def sample_student_data(self):
"""Données d'élève de test."""
return {
'id': 1,
'first_name': 'Marie',
'last_name': 'Dupont',
'email': 'marie.dupont@test.com'
}
@pytest.fixture
def sample_grades(self):
"""Notes de test."""
return {
1: [ # Student 1
{'element_id': 101, 'value': '1.5', 'comment': ''},
{'element_id': 102, 'value': '2.5', 'comment': 'Bon travail'},
{'element_id': 201, 'value': '2', 'comment': ''}
],
2: [ # Student 2
{'element_id': 101, 'value': '2', 'comment': ''},
{'element_id': 102, 'value': '1', 'comment': ''},
{'element_id': 201, 'value': '3', 'comment': ''}
]
}
def test_generate_student_report(
self, report_service, sample_assessment_data,
sample_student_data, sample_grades, sample_exercises_data
):
"""Test de génération d'un rapport complet."""
report = report_service.generate_student_report(
sample_assessment_data,
sample_student_data,
sample_grades,
sample_exercises_data
)
# Vérifier la structure du rapport
assert 'assessment' in report
assert 'student' in report
assert 'results' in report
assert 'exercises' in report
assert 'competences' in report
assert 'domains' in report
assert 'class_statistics' in report
# Vérifier les données de l'évaluation
assert report['assessment']['title'] == 'Contrôle de Mathématiques'
assert report['assessment']['trimester'] == 2
# Vérifier les données de l'élève
assert report['student']['full_name'] == 'Marie Dupont'
assert report['student']['email'] == 'marie.dupont@test.com'
# Vérifier les résultats
# Score exercice 1: 1.5 + 2.5 = 4.0
# Score exercice 2: 2 (score) -> 2/3 * 3 = 2.0
# Total: 6.0
# Student 2: 2 + 1 + (3/3)*3 = 6.0 (égalité)
assert report['results']['total_score'] == 6.0
assert report['results']['total_max_points'] == 8.0
assert report['results']['percentage'] == 75.0
assert report['results']['position'] == 1 # 1er ex-aequo (même score)
assert report['results']['total_students'] == 2
def test_generate_report_with_special_values(
self, report_service, sample_assessment_data,
sample_exercises_data
):
"""Test avec valeurs spéciales (., d)."""
student_data = {
'id': 1,
'first_name': 'Jean',
'last_name': 'Martin',
'email': 'jean@test.com'
}
grades = {
1: [
{'element_id': 101, 'value': '.', 'comment': 'Pas de réponse'},
{'element_id': 102, 'value': 'd', 'comment': 'Dispensé'},
{'element_id': 201, 'value': '1', 'comment': ''}
]
}
report = report_service.generate_student_report(
sample_assessment_data,
student_data,
grades,
sample_exercises_data
)
# . compte comme 0, d ne compte pas
# Total: 0 + 1 (score 1/3 * 3) = 1.0
# Max: 2 + 3 = 5.0 (le dispensé ne compte pas)
assert report['results']['total_score'] == 1.0
assert report['results']['total_max_points'] == 5.0
def test_competences_performance(
self, report_service, sample_assessment_data,
sample_student_data, sample_grades, sample_exercises_data
):
"""Test du calcul des performances par compétence."""
report = report_service.generate_student_report(
sample_assessment_data,
sample_student_data,
sample_grades,
sample_exercises_data
)
competences = {c['name']: c for c in report['competences']}
# Compétence "Calculer": 1.5 + 2.5 = 4.0 / 5.0
assert 'Calculer' in competences
assert competences['Calculer']['score'] == 4.0
assert competences['Calculer']['max_points'] == 5.0
assert competences['Calculer']['percentage'] == 80.0
# Compétence "Représenter": 2.0 / 3.0
assert 'Représenter' in competences
assert competences['Représenter']['score'] == 2.0
assert competences['Représenter']['max_points'] == 3.0
def test_domains_performance(
self, report_service, sample_assessment_data,
sample_student_data, sample_grades, sample_exercises_data
):
"""Test du calcul des performances par domaine."""
report = report_service.generate_student_report(
sample_assessment_data,
sample_student_data,
sample_grades,
sample_exercises_data
)
domains = {d['name']: d for d in report['domains']}
# Domaine "Nombres": 4.0 / 5.0
assert 'Nombres' in domains
assert domains['Nombres']['score'] == 4.0
assert domains['Nombres']['color'] == '#3b82f6'
# Domaine "Géométrie": 2.0 / 3.0
assert 'Géométrie' in domains
assert domains['Géométrie']['score'] == 2.0
assert domains['Géométrie']['color'] == '#22c55e'
def test_student_not_in_grades(
self, report_service, sample_assessment_data,
sample_exercises_data
):
"""Test avec un élève sans notes."""
student_data = {
'id': 999,
'first_name': 'Unknown',
'last_name': 'Student',
'email': 'unknown@test.com'
}
grades = {
1: [{'element_id': 101, 'value': '2', 'comment': ''}]
}
with pytest.raises(ValueError, match="n'a pas de notes"):
report_service.generate_student_report(
sample_assessment_data,
student_data,
grades,
sample_exercises_data
)
class TestGenerateReportHTML:
"""Tests pour la génération HTML des rapports."""
@pytest.fixture
def sample_report_data(self):
"""Données de rapport pour le HTML."""
return {
'assessment': {
'title': 'Test Évaluation',
'description': 'Description test',
'date': '2025-01-15',
'trimester': 2,
'class_name': '6ème A',
'coefficient': 1.0
},
'student': {
'full_name': 'Marie Dupont',
'first_name': 'Marie',
'last_name': 'Dupont',
'email': 'marie@test.com'
},
'results': {
'total_score': 15.0,
'total_max_points': 20.0,
'percentage': 75.0,
'position': 5,
'total_students': 25
},
'exercises': [
{
'title': 'Exercice 1',
'description': 'Test',
'score': 8.0,
'max_points': 10.0,
'percentage': 80.0,
'elements': [
{
'label': 'Q1',
'description': '',
'skill': '',
'domain': '',
'raw_value': '4',
'calculated_score': 4.0,
'max_points': 5.0,
'grading_type': 'notes',
'score_label': '',
'comment': ''
}
]
}
],
'competences': [],
'domains': [],
'class_statistics': {
'count': 25,
'mean': 12.5,
'median': 13.0,
'min': 5.0,
'max': 19.0,
'std_dev': 3.2
}
}
def test_generate_html_contains_student_name(self, sample_report_data):
"""Le HTML contient le prénom de l'élève."""
html = generate_report_html(sample_report_data)
assert 'Marie' in html
def test_generate_html_contains_assessment_title(self, sample_report_data):
"""Le HTML contient le titre de l'évaluation."""
html = generate_report_html(sample_report_data)
assert 'Test Évaluation' in html
def test_generate_html_contains_score(self, sample_report_data):
"""Le HTML contient le score."""
html = generate_report_html(sample_report_data)
assert '15.0' in html or '15' in html
assert '20.0' in html or '20' in html
assert '75' in html # percentage
def test_generate_html_contains_statistics(self, sample_report_data):
"""Le HTML ne contient plus les statistiques de classe (supprimées)."""
html = generate_report_html(sample_report_data)
# Les statistiques de classe ont été supprimées du template
assert 'Statistiques de la classe' not in html
assert 'Position :' not in html
def test_generate_html_with_message(self, sample_report_data):
"""Le HTML inclut le message personnalisé."""
message = "Excellent travail cette année !"
html = generate_report_html(sample_report_data, message)
assert message in html
def test_generate_html_no_dynamic_color(self):
"""Le HTML n'a plus de couleur dynamique basée sur le score."""
report_data = {
'assessment': {'title': 'Test', 'date': '2025-01-01', 'trimester': 1, 'class_name': 'Test', 'coefficient': 1},
'student': {'full_name': 'Test', 'first_name': 'Test', 'last_name': 'Test', 'email': 'test@test.com'},
'results': {'total_score': 18, 'total_max_points': 20, 'percentage': 90.0, 'position': 1, 'total_students': 10},
'exercises': [],
'competences': [],
'domains': [],
'class_statistics': {'count': 10, 'mean': 12, 'median': 12, 'min': 5, 'max': 18, 'std_dev': 2}
}
html = generate_report_html(report_data)
# Les couleurs dynamiques (vert/jaune/rouge) ont été supprimées
assert '#22c55e' not in html # pas de vert
assert '#f6d32d' not in html # pas de jaune
assert '#ef4444' not in html # pas de rouge
# Le score utilise maintenant une couleur neutre
assert 'background-color: #f3f4f6' in html # gris neutre