From 8c6a0e3f29c1a02468810069271ebdfb1b8a64a8 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Sat, 12 Sep 2026 19:18:27 +0200 Subject: [PATCH] =?UTF-8?q?fix(config):=20refuser=20les=20fen=C3=AAtres=20?= =?UTF-8?q?de=20synchronisation=20n=C3=A9gatives?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contraint SYNC_PAST_DAYS et SYNC_FUTURE_DAYS à ge=0 et documente l'effet réel de 0 jour (le jour courant reste inclus). Refs #14 --- .env.example | 2 +- GUIDE_DEV_PYTHON.md | 8 +-- pronote_sync/config/settings.py | 4 +- tests/unit/test_config.py | 123 +++++++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index b268642..6e40ad2 100644 --- a/.env.example +++ b/.env.example @@ -32,7 +32,7 @@ CALDAV_CALENDAR_PATH=/pronote-sync/ # Autoriser HTTP (non-HTTPS) pour un serveur CalDAV local (localhost uniquement) CALDAV_ALLOW_INSECURE_HTTP=false -# Fenêtre de synchronisation (jours) +# Fenêtre de synchronisation (jours) — entier >= 0 ; 0 = aucune journée supplémentaire de ce côté (le jour courant reste inclus). Valeurs négatives refusées au chargement (ValidationError). SYNC_PAST_DAYS=7 SYNC_FUTURE_DAYS=30 diff --git a/GUIDE_DEV_PYTHON.md b/GUIDE_DEV_PYTHON.md index 96f2116..4cd4bcf 100644 --- a/GUIDE_DEV_PYTHON.md +++ b/GUIDE_DEV_PYTHON.md @@ -299,8 +299,8 @@ d'un besoin réel et testé. | `PRONOTE_AGENDA_SOURCE` | Source pour l'agenda (`auto`, `ical`, `pronotepy`). | `auto` | `Literal` | | `PRONOTE_HOMEWORK_SOURCE` | Source pour les devoirs (`auto`, `ical`, `pronotepy`). | `auto` | `Literal` | | `PRONOTE_MESSAGES_SOURCE` | Source pour les messages (`pronotepy` uniquement). | `pronotepy` | `Literal` | -| `SYNC_PAST_DAYS` | Nombre de jours dans le passé pour la sync CalDAV. | `7` | `int` | -| `SYNC_FUTURE_DAYS` | Nombre de jours dans le futur pour la sync CalDAV. | `30` | `int` | +| `SYNC_PAST_DAYS` | Nombre de jours dans le passé pour la sync CalDAV, entier `>= 0` (`0` = aucune journée supplémentaire de ce côté ; le jour courant reste inclus). | `7` | `int` | +| `SYNC_FUTURE_DAYS` | Nombre de jours dans le futur pour la sync CalDAV, entier `>= 0` (`0` = aucune journée supplémentaire de ce côté ; le jour courant reste inclus). | `30` | `int` | | `THEORETICAL_AGENDA_PATH` | Chemin vers le fichier JSON de l'agenda théorique. | `None` | `str \| None`| | `SCHOOL_HOLIDAYS_PATH` | Chemin vers le fichier JSON des vacances scolaires. | `None` | `str \| None`| | `THEORETICAL_WEEK_ANCHOR_DATE` | Date de référence pour la parité des semaines (paire/impaire). | `None` | `date \| None`| @@ -421,8 +421,8 @@ class CalDAVSettings(BaseSettings): password: SecretStr | None = None calendar_path: str = "/pronote-sync/" allow_insecure_http: bool = False - sync_past_days: int = 7 - sync_future_days: int = 30 + sync_past_days: int = Field(default=7, ge=0) + sync_future_days: int = Field(default=30, ge=0) class AISettings(BaseSettings): diff --git a/pronote_sync/config/settings.py b/pronote_sync/config/settings.py index 7964114..ce417c5 100644 --- a/pronote_sync/config/settings.py +++ b/pronote_sync/config/settings.py @@ -293,8 +293,8 @@ class AppSettings(BaseSettings): school_holidays_path: str | None = None theoretical_week_anchor_date: date | None = None theoretical_week_anchor_type: Literal["even", "odd"] | None = None - sync_past_days: int = 7 - sync_future_days: int = 30 + sync_past_days: int = Field(default=7, ge=0) + sync_future_days: int = Field(default=30, ge=0) class Settings(BaseSettings): diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 81efb2d..1cc098d 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -10,10 +10,10 @@ from __future__ import annotations from typing import TYPE_CHECKING import pytest -from pydantic import SecretStr +from pydantic import SecretStr, ValidationError from pronote_sync.config.env import load_settings -from pronote_sync.config.settings import PronoteSettings, Settings +from pronote_sync.config.settings import AppSettings, PronoteSettings, Settings if TYPE_CHECKING: from _pytest.monkeypatch import MonkeyPatch @@ -207,4 +207,121 @@ def test_qr_pin_in_redaction_secrets(monkeypatch: MonkeyPatch) -> None: assert "**********" in repr(settings.pronote.qr_pin) -# Ensure trailing newline +def test_sync_past_days_negative_direct_instantiation() -> None: + """Vérifie que ``sync_past_days`` négatif lève ``ValidationError`` à l'instanciation. + + :return: None + """ + with pytest.raises(ValidationError): + AppSettings(sync_past_days=-1) + + +def test_sync_future_days_negative_direct_instantiation() -> None: + """Vérifie que ``sync_future_days`` négatif lève ``ValidationError`` à l'instanciation. + + :return: None + """ + with pytest.raises(ValidationError): + AppSettings(sync_future_days=-1) + + +def test_sync_past_days_zero_accepted() -> None: + """Vérifie que ``sync_past_days=0`` est accepté. + + :return: None + """ + settings = AppSettings(sync_past_days=0) + assert settings.sync_past_days == 0 + + +def test_sync_future_days_zero_accepted() -> None: + """Vérifie que ``sync_future_days=0`` est accepté. + + :return: None + """ + settings = AppSettings(sync_future_days=0) + assert settings.sync_future_days == 0 + + +def test_sync_past_days_positive_accepted() -> None: + """Vérifie que ``sync_past_days`` positif est accepté. + + :return: None + """ + settings = AppSettings(sync_past_days=7) + assert settings.sync_past_days == 7 + + +def test_sync_future_days_positive_accepted() -> None: + """Vérifie que ``sync_future_days`` positif est accepté. + + :return: None + """ + settings = AppSettings(sync_future_days=30) + assert settings.sync_future_days == 30 + + +def test_sync_past_days_negative_env_loading(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``SYNC_PAST_DAYS=-1`` lève ``ValidationError`` via chargement env. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("SYNC_PAST_DAYS", "-1") + with pytest.raises(ValidationError): + load_settings() + + +def test_sync_future_days_negative_env_loading(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``SYNC_FUTURE_DAYS=-1`` lève ``ValidationError`` via chargement env. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("SYNC_FUTURE_DAYS", "-1") + with pytest.raises(ValidationError): + load_settings() + + +def test_sync_past_days_zero_env_loading(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``SYNC_PAST_DAYS=0`` est accepté via chargement env. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("SYNC_PAST_DAYS", "0") + settings = load_settings() + assert settings.app.sync_past_days == 0 + + +def test_sync_future_days_zero_env_loading(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``SYNC_FUTURE_DAYS=0`` est accepté via chargement env. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("SYNC_FUTURE_DAYS", "0") + settings = load_settings() + assert settings.app.sync_future_days == 0 + + +def test_sync_past_days_positive_env_loading(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``SYNC_PAST_DAYS`` positif est accepté via chargement env. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("SYNC_PAST_DAYS", "7") + settings = load_settings() + assert settings.app.sync_past_days == 7 + + +def test_sync_future_days_positive_env_loading(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``SYNC_FUTURE_DAYS`` positif est accepté via chargement env. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("SYNC_FUTURE_DAYS", "30") + settings = load_settings() + assert settings.app.sync_future_days == 30