fix: PRONOTE_ENT optionnel pour les connexions pronotepy directes

PRONOTE_ENT était incorrectement traité comme obligatoire pour pronotepy. Rend ENT optionnel pour les connexions directes, conformément à la spécification. Corrige le mode pronotepy explicite et le mode auto sans iCal. 10 tests de régression ajoutés.

Co-authored-by: Antoine Van Elstraete <antoine@van-elstraete.net>
Co-committed-by: Antoine Van Elstraete <antoine@van-elstraete.net>
This commit was merged in pull request #2.
This commit is contained in:
2026-09-08 20:54:03 +02:00
committed by Antoine Van Elstraete
parent c851f67172
commit 82b9877aad
4 changed files with 385 additions and 17 deletions

View File

@@ -309,7 +309,9 @@ def test_missing_credentials_raises(empty_pronote_settings: PronoteSettings) ->
"""
client = PronoteClient(empty_pronote_settings)
with pytest.raises(ValueError, match="pronote_url, username, password et ent sont requis"):
with pytest.raises(
ValueError, match="pronote_url, username et password sont requis pour pronotepy"
):
client._connect()
@@ -365,6 +367,132 @@ def test_connect_parent_account_type(
pronotepy.Client.assert_not_called() # type: ignore[attr-defined]
def test_connect_without_ent_but_with_required_credentials(
mocker: pytest_mock.MockerFixture,
) -> None:
"""Vérifie que _connect() fonctionne sans ent mais avec les autres identifiants requis.
Ce test valide que PRONOTE_ENT est optionnel pour une connexion directe Pronote.
:param mocker: Fixture pytest-mock pour le mocking.
:return: None
"""
from unittest.mock import Mock
from pronote_sync.config.settings import PronoteSettings
from pronote_sync.sources.pronote.client import PronoteClient
# Settings sans ent mais avec les autres champs requis
settings = PronoteSettings(
pronote_url="https://pronote.example.com",
username="testuser",
password=SecretStr("testpass"),
ent=None, # Explicitement None
account_type="parent",
)
mock_client = mocker.MagicMock()
mock_client_class = Mock(return_value=mock_client)
mocker.patch("pronotepy.ParentClient", new=mock_client_class)
mocker.patch("pronotepy.Client")
client = PronoteClient(settings)
connected_client = client._connect()
# Should not raise ValueError about missing ent
assert connected_client is mock_client
# Verify ParentClient was called with ent=None
mock_client_class.assert_called_once_with(
pronote_url="https://pronote.example.com",
username="testuser",
password="testpass", # pragma: allowlist secret
ent=None, # ent should be None, not resolved
)
def test_connect_missing_required_credentials_still_raises(
mocker: pytest_mock.MockerFixture,
) -> None:
"""Vérifie que _connect() lève ValueError si pronote_url, username ou password manquent.
Ce test valide que l'erreur ne mentionne plus ent comme requis.
:param mocker: Fixture pytest-mock pour le mocking.
:return: None
"""
from pronote_sync.config.settings import PronoteSettings
from pronote_sync.sources.pronote.client import PronoteClient
# Settings avec ent mais sans pronote_url
settings = PronoteSettings(
pronote_url=None,
username="testuser",
password=SecretStr("testpass"),
ent=None,
account_type="parent",
)
client = PronoteClient(settings)
with pytest.raises(ValueError) as exc_info:
client._connect()
# Error should NOT mention ent as required
assert "pronote_url, username et password sont requis" in str(exc_info.value)
assert "ent" not in str(exc_info.value)
def test_connect_missing_username_raises(mocker: pytest_mock.MockerFixture) -> None:
"""Vérifie que _connect() lève ValueError si username manque.
:param mocker: Fixture pytest-mock pour le mocking.
:return: None
"""
from pronote_sync.config.settings import PronoteSettings
from pronote_sync.sources.pronote.client import PronoteClient
settings = PronoteSettings(
pronote_url="https://pronote.example.com",
username=None,
password=SecretStr("testpass"),
ent=None,
account_type="parent",
)
client = PronoteClient(settings)
with pytest.raises(ValueError) as exc_info:
client._connect()
assert "pronote_url, username et password sont requis" in str(exc_info.value)
def test_connect_missing_password_raises(mocker: pytest_mock.MockerFixture) -> None:
"""Vérifie que _connect() lève ValueError si password manque.
:param mocker: Fixture pytest-mock pour le mocking.
:return: None
"""
from pronote_sync.config.settings import PronoteSettings
from pronote_sync.sources.pronote.client import PronoteClient
settings = PronoteSettings(
pronote_url="https://pronote.example.com",
username="testuser",
password=None,
ent=None,
account_type="parent",
)
client = PronoteClient(settings)
with pytest.raises(ValueError) as exc_info:
client._connect()
assert "pronote_url, username et password sont requis" in str(exc_info.value)
def test_connect_student_account_type(
mocker: pytest_mock.MockerFixture, pronote_settings: PronoteSettings
) -> None:
@@ -398,6 +526,57 @@ def test_connect_student_account_type(
pronotepy.ParentClient.assert_not_called() # type: ignore[attr-defined]
def test_connect_with_ent_resolution_still_works(
mocker: pytest_mock.MockerFixture,
) -> None:
"""Vérifie que _resolve_ent est appelé et fonctionne quand ent est fourni.
Ce test valide que lorsque ent est fourni, il est toujours résolu via _resolve_ent.
:param mocker: Fixture pytest-mock pour le mocking.
:return: None
"""
from unittest.mock import Mock
from pronote_sync.config.settings import PronoteSettings
from pronote_sync.sources.pronote.client import PronoteClient
settings = PronoteSettings(
pronote_url="https://pronote.example.com",
username="testuser",
password=SecretStr("testpass"),
ent="bordeaux", # ent est fourni
account_type="parent",
)
mock_client = mocker.MagicMock()
mock_client_class = Mock(return_value=mock_client)
mocker.patch("pronotepy.ParentClient", new=mock_client_class)
# Mock _resolve_ent to return a mock resolver
mock_resolver = Mock()
mocker.patch(
"pronote_sync.sources.pronote.client._resolve_ent",
return_value=mock_resolver,
)
client = PronoteClient(settings)
_ = client._connect()
# _resolve_ent should have been called
from pronote_sync.sources.pronote.client import _resolve_ent as resolve_ent_func
resolve_ent_func.assert_called_once_with("bordeaux") # type: ignore[attr-defined]
# ParentClient should have been called with the resolved ent
mock_client_class.assert_called_once_with(
pronote_url="https://pronote.example.com",
username="testuser",
password="testpass", # pragma: allowlist secret
ent=mock_resolver,
)
def test_get_messages_degraded_on_error(
mocker: pytest_mock.MockerFixture, pronote_settings: PronoteSettings
) -> None: