"""Tests unitaires pour le calendrier des vacances scolaires. Ce module contient les tests pour les classes :class:`SchoolHolidayCalendar`, :class:`HolidayPeriod` et :class:`SchoolHolidayFile` du module :mod:`pronote_sync.sources.theoretical.holidays`. """ from __future__ import annotations import json from datetime import date from pathlib import Path import pytest from pronote_sync.errors import PronoteSyncError from pronote_sync.sources.theoretical.holidays import SchoolHolidayCalendar class TestSchoolHolidayCalendar: """Tests pour la classe SchoolHolidayCalendar.""" def test_load_valid_file(self, tmp_path: Path) -> None: """Teste le chargement d'un fichier JSON valide. :assert: is_holiday retourne True pour une date dans une période. """ holiday_data = { "zone": "A", "school_year": "2026-2027", "periods": [ { "start_date": "2026-10-17", "end_date": "2026-11-02", "label": "Toussaint", } ], } file_path = tmp_path / "holidays.json" file_path.write_text(json.dumps(holiday_data), encoding="utf-8") calendar = SchoolHolidayCalendar(file_path) # Date dans la période de Toussaint assert calendar.is_holiday(date(2026, 10, 20)) is True def test_date_outside_periods(self, tmp_path: Path) -> None: """Teste qu'une date en dehors des périodes retourne False. :assert: is_holiday retourne False pour une date hors période. """ holiday_data = { "zone": "A", "school_year": "2026-2027", "periods": [ { "start_date": "2026-10-17", "end_date": "2026-11-02", "label": "Toussaint", } ], } file_path = tmp_path / "holidays.json" file_path.write_text(json.dumps(holiday_data), encoding="utf-8") calendar = SchoolHolidayCalendar(file_path) # Date en dehors de la période assert calendar.is_holiday(date(2026, 9, 1)) is False def test_start_date_inclusive(self, tmp_path: Path) -> None: """Teste que la date de début est incluse dans la période. :assert: is_holiday retourne True pour une date égale à start_date. """ holiday_data = { "zone": "A", "school_year": "2026-2027", "periods": [ { "start_date": "2026-10-17", "end_date": "2026-11-02", "label": "Toussaint", } ], } file_path = tmp_path / "holidays.json" file_path.write_text(json.dumps(holiday_data), encoding="utf-8") calendar = SchoolHolidayCalendar(file_path) assert calendar.is_holiday(date(2026, 10, 17)) is True def test_end_date_inclusive(self, tmp_path: Path) -> None: """Teste que la date de fin est incluse dans la période. :assert: is_holiday retourne True pour une date égale à end_date. """ holiday_data = { "zone": "A", "school_year": "2026-2027", "periods": [ { "start_date": "2026-10-17", "end_date": "2026-11-02", "label": "Toussaint", } ], } file_path = tmp_path / "holidays.json" file_path.write_text(json.dumps(holiday_data), encoding="utf-8") calendar = SchoolHolidayCalendar(file_path) assert calendar.is_holiday(date(2026, 11, 2)) is True def test_file_not_found(self, tmp_path: Path) -> None: """Teste qu'un fichier introuvable lève une PronoteSyncError. :assert: PronoteSyncError est levée pour un fichier introuvable. """ file_path = tmp_path / "nonexistent.json" with pytest.raises(PronoteSyncError) as exc_info: SchoolHolidayCalendar(file_path) assert "introuvable" in str(exc_info.value) # Vérifier qu'aucun secret n'est fuité dans le message d'erreur assert "nonexistent" not in str(exc_info.value) or "introuvable" in str(exc_info.value) def test_invalid_json(self, tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: """Teste qu'un fichier JSON invalide lève une PronoteSyncError. :assert: PronoteSyncError est levée pour un JSON invalide. """ file_path = tmp_path / "invalid.json" file_path.write_text("{ invalid json }", encoding="utf-8") with pytest.raises(PronoteSyncError) as exc_info: SchoolHolidayCalendar(file_path) assert "invalide" in str(exc_info.value) def test_invalid_period_dates(self, tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: """Teste qu'une période avec end_date < start_date lève une ValidationError. :assert: PronoteSyncError est levée pour des dates de période invalides. """ holiday_data = { "zone": "A", "school_year": "2026-2027", "periods": [ { "start_date": "2026-11-02", "end_date": "2026-10-17", # Inversé "label": "Toussaint", } ], } file_path = tmp_path / "holidays.json" file_path.write_text(json.dumps(holiday_data), encoding="utf-8") with pytest.raises(PronoteSyncError): SchoolHolidayCalendar(file_path) def test_empty_periods(self, tmp_path: Path) -> None: """Teste qu'un fichier avec des périodes vides retourne toujours False. :assert: is_holiday retourne False pour toutes les dates. """ holiday_data = { "zone": "A", "school_year": "2026-2027", "periods": [], } file_path = tmp_path / "holidays.json" file_path.write_text(json.dumps(holiday_data), encoding="utf-8") calendar = SchoolHolidayCalendar(file_path) assert calendar.is_holiday(date(2026, 10, 20)) is False assert calendar.is_holiday(date(2026, 1, 1)) is False def test_load_from_fixture(self) -> None: """Teste le chargement du fichier de fixture et vérifie une date connue. :assert: is_holiday retourne True pour une date de vacances connue. """ fixture_path = Path(__file__).parent.parent / "fixtures" / "school_holidays.json" calendar = SchoolHolidayCalendar(fixture_path) # Date dans les vacances de Toussaint (17 oct - 2 nov 2026) assert calendar.is_holiday(date(2026, 10, 20)) is True # Date dans les vacances de Noël (19 déc 2026 - 4 janv 2027) assert calendar.is_holiday(date(2026, 12, 25)) is True # Date en dehors des vacances assert calendar.is_holiday(date(2026, 9, 1)) is False