Files
notytex/backend/tests/unit/test_grading_calculator.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

251 lines
8.2 KiB
Python

"""
Tests unitaires pour le GradingCalculator.
"""
import pytest
from domain.services.grading_calculator import (
GradingCalculator,
NotesStrategy,
ScoreStrategy,
GradingStrategyFactory,
)
class TestNotesStrategy:
"""Tests pour la stratégie de notation par points."""
def test_calculate_integer(self):
strategy = NotesStrategy()
assert strategy.calculate_score("15", 20) == 15.0
def test_calculate_decimal_point(self):
strategy = NotesStrategy()
assert strategy.calculate_score("15.5", 20) == 15.5
def test_calculate_decimal_comma(self):
strategy = NotesStrategy()
assert strategy.calculate_score("15,5", 20) == 15.5
def test_calculate_zero(self):
strategy = NotesStrategy()
assert strategy.calculate_score("0", 20) == 0.0
def test_calculate_invalid(self):
strategy = NotesStrategy()
assert strategy.calculate_score("invalid", 20) == 0.0
def test_grading_type(self):
strategy = NotesStrategy()
assert strategy.get_grading_type() == "notes"
class TestScoreStrategy:
"""Tests pour la stratégie de notation par compétences."""
def test_calculate_score_0(self):
strategy = ScoreStrategy()
# 0/3 * 6 = 0
assert strategy.calculate_score("0", 6) == 0.0
def test_calculate_score_1(self):
strategy = ScoreStrategy()
# 1/3 * 6 = 2
assert strategy.calculate_score("1", 6) == 2.0
def test_calculate_score_2(self):
strategy = ScoreStrategy()
# 2/3 * 6 = 4
assert strategy.calculate_score("2", 6) == 4.0
def test_calculate_score_3(self):
strategy = ScoreStrategy()
# 3/3 * 6 = 6
assert strategy.calculate_score("3", 6) == 6.0
def test_calculate_score_3_with_max_3(self):
strategy = ScoreStrategy()
# 3/3 * 3 = 3
assert strategy.calculate_score("3", 3) == 3.0
def test_calculate_score_2_with_max_3(self):
strategy = ScoreStrategy()
# 2/3 * 3 = 2
assert strategy.calculate_score("2", 3) == 2.0
def test_calculate_invalid_score(self):
strategy = ScoreStrategy()
assert strategy.calculate_score("4", 6) == 0.0
def test_calculate_negative_score(self):
strategy = ScoreStrategy()
assert strategy.calculate_score("-1", 6) == 0.0
def test_calculate_non_numeric(self):
strategy = ScoreStrategy()
assert strategy.calculate_score("abc", 6) == 0.0
def test_grading_type(self):
strategy = ScoreStrategy()
assert strategy.get_grading_type() == "score"
class TestGradingStrategyFactory:
"""Tests pour la factory de stratégies."""
def test_create_notes_strategy(self):
strategy = GradingStrategyFactory.create("notes")
assert isinstance(strategy, NotesStrategy)
def test_create_score_strategy(self):
strategy = GradingStrategyFactory.create("score")
assert isinstance(strategy, ScoreStrategy)
def test_create_unknown_raises(self):
with pytest.raises(ValueError, match="Type de notation non supporté"):
GradingStrategyFactory.create("unknown")
class TestGradingCalculator:
"""Tests pour le calculateur unifié."""
def test_calculate_notes_score(self):
calc = GradingCalculator()
assert calc.calculate_score("15.5", "notes", 20) == 15.5
def test_calculate_competence_score(self):
calc = GradingCalculator()
# 2/3 * 3 = 2
assert calc.calculate_score("2", "score", 3) == 2.0
def test_special_value_dot(self):
calc = GradingCalculator()
# "." = pas de réponse = 0
assert calc.calculate_score(".", "notes", 20) == 0.0
def test_special_value_d(self):
calc = GradingCalculator()
# "d" = dispensé = None
assert calc.calculate_score("d", "notes", 20) is None
def test_special_value_a(self):
calc = GradingCalculator()
# "a" = absent = 0
assert calc.calculate_score("a", "notes", 20) == 0.0
def test_empty_value(self):
calc = GradingCalculator()
assert calc.calculate_score("", "notes", 20) is None
def test_whitespace_value(self):
calc = GradingCalculator()
assert calc.calculate_score(" ", "notes", 20) is None
def test_is_special_value_dot(self):
calc = GradingCalculator()
assert calc.is_special_value(".") is True
def test_is_special_value_d(self):
calc = GradingCalculator()
assert calc.is_special_value("d") is True
def test_is_special_value_a(self):
calc = GradingCalculator()
assert calc.is_special_value("a") is True
def test_is_special_value_number(self):
calc = GradingCalculator()
assert calc.is_special_value("15") is False
def test_is_counted_in_total_dot(self):
calc = GradingCalculator()
# "." compte dans le total (comme 0)
assert calc.is_counted_in_total(".") is True
def test_is_counted_in_total_d(self):
calc = GradingCalculator()
# "d" ne compte pas (dispensé)
assert calc.is_counted_in_total("d") is False
def test_is_counted_in_total_a(self):
calc = GradingCalculator()
# "a" compte dans le total (comme 0)
assert calc.is_counted_in_total("a") is True
def test_is_counted_in_total_normal(self):
calc = GradingCalculator()
assert calc.is_counted_in_total("15") is True
def test_is_counted_in_total_empty(self):
calc = GradingCalculator()
assert calc.is_counted_in_total("") is False
class TestGradingCalculatorValidation:
"""Tests pour la validation des notes."""
def test_validate_notes_integer(self):
calc = GradingCalculator()
assert calc.validate_grade_value("15", "notes") is True
def test_validate_notes_decimal(self):
calc = GradingCalculator()
assert calc.validate_grade_value("15.5", "notes") is True
def test_validate_notes_comma(self):
calc = GradingCalculator()
assert calc.validate_grade_value("15,5", "notes") is True
def test_validate_notes_negative(self):
calc = GradingCalculator()
assert calc.validate_grade_value("-5", "notes") is False
def test_validate_notes_exceeds_max(self):
calc = GradingCalculator()
assert calc.validate_grade_value("25", "notes", max_points=20) is False
def test_validate_notes_within_max(self):
calc = GradingCalculator()
assert calc.validate_grade_value("18", "notes", max_points=20) is True
def test_validate_score_valid(self):
calc = GradingCalculator()
for score in ["0", "1", "2", "3"]:
assert calc.validate_grade_value(score, "score") is True
def test_validate_score_invalid(self):
calc = GradingCalculator()
assert calc.validate_grade_value("4", "score") is False
assert calc.validate_grade_value("-1", "score") is False
assert calc.validate_grade_value("abc", "score") is False
def test_validate_special_values(self):
calc = GradingCalculator()
assert calc.validate_grade_value(".", "notes") is True
assert calc.validate_grade_value("d", "score") is True
assert calc.validate_grade_value("a", "notes") is True
def test_validate_empty(self):
calc = GradingCalculator()
assert calc.validate_grade_value("", "notes") is True
assert calc.validate_grade_value(" ", "score") is True
class TestGradingCalculatorCustomConfig:
"""Tests avec configuration personnalisée."""
def test_custom_special_values(self):
custom_config = {
"x": {
"label": "Excusé",
"value": 0,
"counts": False
}
}
calc = GradingCalculator(special_values=custom_config)
assert calc.is_special_value("x") is True
assert calc.calculate_score("x", "notes", 20) == 0.0
assert calc.is_counted_in_total("x") is False
# Les valeurs par défaut ne sont plus disponibles
assert calc.is_special_value(".") is False