fix: serialize QR token client operations
This commit is contained in:
@@ -9,6 +9,8 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Generator
|
||||
from contextlib import contextmanager
|
||||
from datetime import date, datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
@@ -1203,6 +1205,91 @@ def test_no_raw_secrets_in_logs(
|
||||
# --- Persistence of credentials after data operations ---
|
||||
|
||||
|
||||
def test_qr_token_lock_covers_login_retrieval_and_credential_persistence(
|
||||
mocker: pytest_mock.MockerFixture,
|
||||
) -> None:
|
||||
"""Vérifie que le verrou d'état couvre tout le cycle QR/token des cours.
|
||||
|
||||
:param mocker: Fixture pytest-mock pour le mocking.
|
||||
:return: None
|
||||
"""
|
||||
events: list[str] = []
|
||||
credentials = {
|
||||
"pronote_url": "https://pronote.example.com",
|
||||
"username": "testuser",
|
||||
"password": "persisted-token", # pragma: allowlist secret
|
||||
"uuid": "persisted-uuid",
|
||||
}
|
||||
auth_state = mocker.MagicMock(spec=PronoteAuthState)
|
||||
|
||||
@contextmanager
|
||||
def record_lock() -> Generator[None]:
|
||||
events.append("lock_acquired")
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
events.append("lock_released")
|
||||
|
||||
def load_credentials() -> dict[str, str]:
|
||||
"""Simule le chargement des credentials persistés."""
|
||||
events.append("load")
|
||||
return credentials
|
||||
|
||||
def get_no_lessons(*_: object) -> list[object]:
|
||||
"""Simule une récupération de cours vide."""
|
||||
events.append("lessons")
|
||||
return []
|
||||
|
||||
def export_credentials() -> dict[str, str]:
|
||||
"""Simule l'export des credentials courantes."""
|
||||
events.append("export")
|
||||
return credentials
|
||||
|
||||
def token_login(**_: object) -> MagicMock:
|
||||
"""Simule le login par token."""
|
||||
events.append("token_login")
|
||||
return mock_client
|
||||
|
||||
auth_state.lock.side_effect = record_lock
|
||||
auth_state.load.side_effect = load_credentials
|
||||
auth_state.save.side_effect = lambda _: events.append("save")
|
||||
|
||||
mock_client = _make_lessons_mock_client(mocker)
|
||||
mock_client.logged_in = True
|
||||
mock_client.lessons.side_effect = get_no_lessons
|
||||
mock_client.export_credentials.side_effect = export_credentials
|
||||
mocker.patch(
|
||||
"pronotepy.ParentClient.token_login",
|
||||
side_effect=token_login,
|
||||
)
|
||||
|
||||
settings = PronoteSettings(
|
||||
url="https://pronote.example.com",
|
||||
username="testuser",
|
||||
password=SecretStr("testpass"),
|
||||
ent=None,
|
||||
account_type="parent",
|
||||
auth_mode="qr_token",
|
||||
)
|
||||
|
||||
lessons = PronoteClient(settings, auth_state=auth_state).get_lessons(
|
||||
date(2024, 9, 1), date(2024, 9, 30)
|
||||
)
|
||||
|
||||
assert lessons == []
|
||||
assert events == [
|
||||
"lock_acquired",
|
||||
"load",
|
||||
"token_login",
|
||||
"export",
|
||||
"save",
|
||||
"lessons",
|
||||
"export",
|
||||
"save",
|
||||
"lock_released",
|
||||
]
|
||||
|
||||
|
||||
def _make_auth_state_mock(mocker: pytest_mock.MockerFixture) -> MagicMock:
|
||||
"""Retourne un mock de PronoteAuthState sans credentials persistés.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user