"""Tests for Grade parsing with missing optional fields. Some Pronote instances (e.g. 0941093c.index-education.net) do not include fields like noteMax, noteMin, coefficient, etc. in their grade JSON data. These tests verify that Grade objects can be constructed without crashing when those fields are absent. """ import unittest from unittest.mock import MagicMock from pronotepy.dataClasses import Grade, Period def _make_grade_json( include_noteMax: bool = True, include_noteMin: bool = True, include_coefficient: bool = True, include_commentaire: bool = True, include_estBonus: bool = True, include_estFacultatif: bool = True, include_estRamenerSur20: bool = True, include_moyenne: bool = True, ) -> dict: """Build a grade JSON dict with optional fields toggled on/off.""" # Minimal required fields json_dict: dict = { "N": "001", "note": {"V": "15"}, "bareme": {"V": "20"}, "baremeParDefaut": {"V": "20"}, "date": {"V": "15/01/2026"}, "service": {"V": {"N": "101", "L": "MATHEMATIQUES", "estServiceGroupe": False}}, "periode": {"V": {"N": "test_period_id"}}, } if include_noteMax: json_dict["noteMax"] = {"V": "19"} if include_noteMin: json_dict["noteMin"] = {"V": "5"} if include_coefficient: json_dict["coefficient"] = "1" if include_commentaire: json_dict["commentaire"] = "Good work" if include_estBonus: json_dict["estBonus"] = False if include_estFacultatif: json_dict["estFacultatif"] = False if include_estRamenerSur20: json_dict["estRamenerSur20"] = False if include_moyenne: json_dict["moyenne"] = {"V": "12"} return json_dict class TestGradeMissingFields(unittest.TestCase): """Test that Grade parsing handles missing optional fields gracefully.""" @classmethod def setUpClass(cls) -> None: # Create a mock Period instance so the period resolver can find it mock_period = MagicMock(spec=Period) mock_period.id = "test_period_id" Period.instances.add(mock_period) @classmethod def tearDownClass(cls) -> None: # Clean up mock period instances Period.instances = {p for p in Period.instances if not isinstance(p, MagicMock)} def test_all_fields_present(self) -> None: """Parsing works when all fields are present.""" json_dict = _make_grade_json() grade = Grade(json_dict) self.assertEqual(grade.grade, "15") self.assertEqual(grade.max, "19") self.assertEqual(grade.min, "5") self.assertEqual(grade.coefficient, "1") self.assertEqual(grade.comment, "Good work") self.assertFalse(grade.is_bonus) self.assertFalse(grade.is_optionnal) self.assertFalse(grade.is_out_of_20) def test_missing_noteMax(self) -> None: """Parsing does not crash when noteMax is missing.""" json_dict = _make_grade_json(include_noteMax=False) grade = Grade(json_dict) self.assertIsNone(grade.max) # Other fields should still be parsed correctly self.assertEqual(grade.grade, "15") self.assertEqual(grade.min, "5") def test_missing_noteMin(self) -> None: """Parsing does not crash when noteMin is missing.""" json_dict = _make_grade_json(include_noteMin=False) grade = Grade(json_dict) self.assertIsNone(grade.min) def test_missing_coefficient(self) -> None: """Parsing does not crash when coefficient is missing.""" json_dict = _make_grade_json(include_coefficient=False) grade = Grade(json_dict) self.assertIsNone(grade.coefficient) def test_missing_commentaire(self) -> None: """Parsing does not crash when commentaire is missing.""" json_dict = _make_grade_json(include_commentaire=False) grade = Grade(json_dict) self.assertIsNone(grade.comment) def test_missing_estBonus(self) -> None: """Parsing does not crash when estBonus is missing.""" json_dict = _make_grade_json(include_estBonus=False) grade = Grade(json_dict) self.assertFalse(grade.is_bonus) def test_missing_estFacultatif(self) -> None: """Parsing does not crash when estFacultatif is missing.""" json_dict = _make_grade_json(include_estFacultatif=False) grade = Grade(json_dict) self.assertFalse(grade.is_optionnal) def test_missing_estRamenerSur20(self) -> None: """Parsing does not crash when estRamenerSur20 is missing.""" json_dict = _make_grade_json(include_estRamenerSur20=False) grade = Grade(json_dict) self.assertFalse(grade.is_out_of_20) def test_missing_moyenne(self) -> None: """Parsing does not crash when moyenne is missing.""" json_dict = _make_grade_json(include_moyenne=False) grade = Grade(json_dict) self.assertIsNone(grade.average) def test_all_optional_fields_missing(self) -> None: """Parsing does not crash when ALL optional fields are missing. This simulates the worst case scenario where a Pronote instance returns only the bare minimum grade data. """ json_dict = _make_grade_json( include_noteMax=False, include_noteMin=False, include_coefficient=False, include_commentaire=False, include_estBonus=False, include_estFacultatif=False, include_estRamenerSur20=False, include_moyenne=False, ) grade = Grade(json_dict) self.assertEqual(grade.grade, "15") self.assertEqual(grade.out_of, "20") self.assertIsNone(grade.max) self.assertIsNone(grade.min) self.assertIsNone(grade.coefficient) self.assertIsNone(grade.comment) self.assertFalse(grade.is_bonus) self.assertFalse(grade.is_optionnal) self.assertFalse(grade.is_out_of_20) self.assertIsNone(grade.average) if __name__ == "__main__": unittest.main()