Files
notytex/backend/tests/unit/test_student_report_service.py
Bertrand Benjamin 2b08eb534a Migration v1 (Flask) -> v2 (FastAPI + Vue.js) complétée
 Changements majeurs:
- Suppression complète du code Flask legacy
- Migration backend FastAPI vers racine /backend
- Migration frontend Vue.js vers racine /frontend
- Suppression de notytex-v2/ (code monté à la racine)

 Validations:
- Backend démarre correctement (port 8000)
- API /api/v2/health répond healthy
- 99/99 tests unitaires passent
- Frontend configuré avec proxy Vite

📝 Documentation:
- README.md réécrit pour v2
- Instructions de démarrage mises à jour
- .gitignore adapté pour backend/frontend/

🎯 Architecture finale:
notytex/
├── backend/     # FastAPI + SQLAlchemy + Pydantic
├── frontend/    # Vue 3 + Vite + TailwindCSS
├── docs/        # Documentation
└── school_management.db  # Base de données (inchangée)

Jalon 6 complété: Application v2 prête pour utilisation!
2025-11-25 21:09:47 +01:00

370 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',
'raw_value': '4',
'score_label': '',
'max_points': 5.0
}
]
}
],
'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 contient les statistiques."""
html = generate_report_html(sample_report_data)
assert '12.5' in html # mean
assert '13.0' in html or '13' in html # median
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_color_code_green(self):
"""Couleur verte pour les bons scores (>= 80%)."""
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)
assert '#22c55e' in html # green
def test_generate_html_color_code_red(self):
"""Couleur rouge pour les mauvais scores (< 40%)."""
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': 5, 'total_max_points': 20, 'percentage': 25.0, 'position': 10, '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)
assert '#ef4444' in html # red