Compare commits
1 Commits
fix/dry-ru
...
feat/qr-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
fa87832125
|
@@ -126,9 +126,6 @@ class PipelineRunner:
|
|||||||
:rtype: PipelineRunner
|
:rtype: PipelineRunner
|
||||||
"""
|
"""
|
||||||
effective_dry_run = settings.app.dry_run if dry_run is None else dry_run
|
effective_dry_run = settings.app.dry_run if dry_run is None else dry_run
|
||||||
if effective_dry_run and settings.pronote.auth_mode == "qr_token":
|
|
||||||
raise ValueError("Le mode qr_token n'est pas compatible avec le dry-run.")
|
|
||||||
persistence_enabled = not effective_dry_run
|
|
||||||
theoretical_provider = get_theoretical_provider(
|
theoretical_provider = get_theoretical_provider(
|
||||||
settings.app.theoretical_agenda_path,
|
settings.app.theoretical_agenda_path,
|
||||||
settings.app.school_holidays_path,
|
settings.app.school_holidays_path,
|
||||||
@@ -139,9 +136,7 @@ class PipelineRunner:
|
|||||||
AgendaComparator(theoretical_provider) if theoretical_provider is not None else None
|
AgendaComparator(theoretical_provider) if theoretical_provider is not None else None
|
||||||
)
|
)
|
||||||
blog_client = BlogRSSClient(settings.blog.rss_url) if settings.blog.enabled else None
|
blog_client = BlogRSSClient(settings.blog.rss_url) if settings.blog.enabled else None
|
||||||
blog_state = (
|
blog_state = BlogRSSState() if settings.blog.enabled else None
|
||||||
BlogRSSState(persistence_enabled=persistence_enabled) if settings.blog.enabled else None
|
|
||||||
)
|
|
||||||
return cls(
|
return cls(
|
||||||
settings=settings,
|
settings=settings,
|
||||||
pronote_fetcher=PronoteFetcher(
|
pronote_fetcher=PronoteFetcher(
|
||||||
@@ -149,9 +144,7 @@ class PipelineRunner:
|
|||||||
PronoteClient(
|
PronoteClient(
|
||||||
settings.pronote,
|
settings.pronote,
|
||||||
auth_state=(
|
auth_state=(
|
||||||
PronoteAuthState(persistence_enabled=persistence_enabled)
|
PronoteAuthState() if settings.pronote.auth_mode == "qr_token" else None
|
||||||
if settings.pronote.auth_mode == "qr_token"
|
|
||||||
else None
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -37,24 +37,15 @@ class BlogRSSState:
|
|||||||
|
|
||||||
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
||||||
:class:`~pathlib.Path`). ``".blog_rss_state.json"`` par défaut.
|
:class:`~pathlib.Path`). ``".blog_rss_state.json"`` par défaut.
|
||||||
:param persistence_enabled: Si ``False``, charge l'état existant mais ne
|
|
||||||
modifie jamais le fichier d'état. ``True`` par défaut.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, state_file: Path | str = ".blog_rss_state.json") -> None:
|
||||||
self,
|
|
||||||
state_file: Path | str = ".blog_rss_state.json",
|
|
||||||
persistence_enabled: bool = True,
|
|
||||||
) -> None:
|
|
||||||
"""Initialise le gestionnaire d'état depuis le fichier JSON.
|
"""Initialise le gestionnaire d'état depuis le fichier JSON.
|
||||||
|
|
||||||
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
||||||
:class:`~pathlib.Path`). ``".blog_rss_state.json"`` par défaut.
|
:class:`~pathlib.Path`). ``".blog_rss_state.json"`` par défaut.
|
||||||
:param persistence_enabled: Si ``False``, charge l'état existant mais
|
|
||||||
désactive toutes les écritures sur disque. ``True`` par défaut.
|
|
||||||
"""
|
"""
|
||||||
self._state_file = Path(state_file)
|
self._state_file = Path(state_file)
|
||||||
self._persistence_enabled = persistence_enabled
|
|
||||||
self._known_guids: set[str] = set()
|
self._known_guids: set[str] = set()
|
||||||
self._etag: str | None = None
|
self._etag: str | None = None
|
||||||
self._last_modified: str | None = None
|
self._last_modified: str | None = None
|
||||||
@@ -107,8 +98,6 @@ class BlogRSSState:
|
|||||||
d'erreur d'écriture, une erreur est journalisée sans être
|
d'erreur d'écriture, une erreur est journalisée sans être
|
||||||
propagée et le fichier temporaire est supprimé.
|
propagée et le fichier temporaire est supprimé.
|
||||||
"""
|
"""
|
||||||
if not self._persistence_enabled:
|
|
||||||
return
|
|
||||||
payload = {
|
payload = {
|
||||||
"version": _STATE_VERSION,
|
"version": _STATE_VERSION,
|
||||||
"known_guids": sorted(self._known_guids),
|
"known_guids": sorted(self._known_guids),
|
||||||
|
|||||||
@@ -38,15 +38,9 @@ class PronoteAuthState:
|
|||||||
|
|
||||||
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
||||||
:class:`~pathlib.Path`). ``".pronote_auth_state.json"`` par défaut.
|
:class:`~pathlib.Path`). ``".pronote_auth_state.json"`` par défaut.
|
||||||
:param persistence_enabled: Si ``False``, charge l'état existant mais ne
|
|
||||||
modifie jamais le fichier d'état. ``True`` par défaut.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, state_file: Path | str = ".pronote_auth_state.json") -> None:
|
||||||
self,
|
|
||||||
state_file: Path | str = ".pronote_auth_state.json",
|
|
||||||
persistence_enabled: bool = True,
|
|
||||||
) -> None:
|
|
||||||
"""Initialise le gestionnaire d'état d'authentification Pronote.
|
"""Initialise le gestionnaire d'état d'authentification Pronote.
|
||||||
|
|
||||||
Le fichier d'état n'est pas créé à l'initialisation : il n'est écrit
|
Le fichier d'état n'est pas créé à l'initialisation : il n'est écrit
|
||||||
@@ -54,14 +48,8 @@ class PronoteAuthState:
|
|||||||
|
|
||||||
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
:param state_file: Chemin du fichier d'état JSON (``str`` ou
|
||||||
:class:`~pathlib.Path`). ``".pronote_auth_state.json"`` par défaut.
|
:class:`~pathlib.Path`). ``".pronote_auth_state.json"`` par défaut.
|
||||||
:param persistence_enabled: Si ``False``, charge l'état existant mais
|
|
||||||
désactive toutes les écritures ou suppressions sur disque. ``True``
|
|
||||||
par défaut.
|
|
||||||
"""
|
"""
|
||||||
self._state_file = Path(state_file)
|
self._state_file = Path(state_file)
|
||||||
self._persistence_enabled = persistence_enabled
|
|
||||||
self._in_memory_credentials: dict[str, str] | None = None
|
|
||||||
self._in_memory_state_cleared = False
|
|
||||||
|
|
||||||
def load(self) -> dict[str, str] | None:
|
def load(self) -> dict[str, str] | None:
|
||||||
"""Charge les credentials d'authentification depuis le fichier d'état.
|
"""Charge les credentials d'authentification depuis le fichier d'état.
|
||||||
@@ -77,10 +65,6 @@ class PronoteAuthState:
|
|||||||
aucun état valide n'est disponible.
|
aucun état valide n'est disponible.
|
||||||
:rtype: dict[str, str] | None
|
:rtype: dict[str, str] | None
|
||||||
"""
|
"""
|
||||||
if self._in_memory_state_cleared:
|
|
||||||
return None
|
|
||||||
if self._in_memory_credentials is not None:
|
|
||||||
return self._in_memory_credentials.copy()
|
|
||||||
if not self._state_file.exists():
|
if not self._state_file.exists():
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Fichier d'état d'authentification Pronote %s absent, aucun token à charger.",
|
"Fichier d'état d'authentification Pronote %s absent, aucun token à charger.",
|
||||||
@@ -142,10 +126,6 @@ class PronoteAuthState:
|
|||||||
:raises PronoteSyncError: Si l'écriture ou le remplacement du fichier
|
:raises PronoteSyncError: Si l'écriture ou le remplacement du fichier
|
||||||
échoue.
|
échoue.
|
||||||
"""
|
"""
|
||||||
if not self._persistence_enabled:
|
|
||||||
self._in_memory_credentials = credentials.copy()
|
|
||||||
self._in_memory_state_cleared = False
|
|
||||||
return
|
|
||||||
payload: dict[str, Any] = {
|
payload: dict[str, Any] = {
|
||||||
"version": _STATE_VERSION,
|
"version": _STATE_VERSION,
|
||||||
"credentials": credentials,
|
"credentials": credentials,
|
||||||
@@ -202,16 +182,10 @@ class PronoteAuthState:
|
|||||||
"""Supprime le fichier d'état d'authentification.
|
"""Supprime le fichier d'état d'authentification.
|
||||||
|
|
||||||
Si le fichier n'existe pas, la méthode ne fait rien et aucune erreur
|
Si le fichier n'existe pas, la méthode ne fait rien et aucune erreur
|
||||||
n'est levée. Lorsque la persistance est désactivée, elle efface
|
n'est levée.
|
||||||
uniquement les credentials conservés en mémoire et ne modifie jamais
|
|
||||||
le fichier d'état.
|
|
||||||
|
|
||||||
:raises OSError: Si la suppression du fichier existant échoue.
|
:raises OSError: Si la suppression du fichier existant échoue.
|
||||||
"""
|
"""
|
||||||
self._in_memory_credentials = None
|
|
||||||
if not self._persistence_enabled:
|
|
||||||
self._in_memory_state_cleared = True
|
|
||||||
return
|
|
||||||
if not self._state_file.exists():
|
if not self._state_file.exists():
|
||||||
return
|
return
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|||||||
@@ -8,13 +8,7 @@ from typing import Any, cast
|
|||||||
import pytest
|
import pytest
|
||||||
from pydantic import SecretStr
|
from pydantic import SecretStr
|
||||||
|
|
||||||
from pronote_sync.config.settings import (
|
from pronote_sync.config.settings import AISettings, AppSettings, PronoteSettings, Settings
|
||||||
AISettings,
|
|
||||||
AppSettings,
|
|
||||||
BlogSettings,
|
|
||||||
PronoteSettings,
|
|
||||||
Settings,
|
|
||||||
)
|
|
||||||
from pronote_sync.errors import PipelineCriticalError, PipelineWarning, PronoteAuthRotationError
|
from pronote_sync.errors import PipelineCriticalError, PipelineWarning, PronoteAuthRotationError
|
||||||
from pronote_sync.models.agenda import Lesson, LessonStatus, SchoolEvent
|
from pronote_sync.models.agenda import Lesson, LessonStatus, SchoolEvent
|
||||||
from pronote_sync.models.blog import BlogArticle
|
from pronote_sync.models.blog import BlogArticle
|
||||||
@@ -428,47 +422,6 @@ def test_from_settings_password_mode_passes_auth_state_none(
|
|||||||
assert constructed[0][1] is None
|
assert constructed[0][1] is None
|
||||||
|
|
||||||
|
|
||||||
def test_from_settings_rejects_qr_token_dry_run_before_constructing_pronote_client(
|
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
|
||||||
) -> None:
|
|
||||||
"""QR-token dry-run fails before authentication or data dependencies are created."""
|
|
||||||
import pronote_sync.pipeline.run as run_module
|
|
||||||
|
|
||||||
qr_pin_sentinel = "qr-pin-must-not-appear"
|
|
||||||
constructed: list[object] = []
|
|
||||||
|
|
||||||
class FailingClient:
|
|
||||||
"""Pronote client sentinel that makes unexpected construction explicit."""
|
|
||||||
|
|
||||||
def __init__(self, settings: PronoteSettings, *, auth_state: object) -> None:
|
|
||||||
"""Record and reject any unexpected client construction.
|
|
||||||
|
|
||||||
:param settings: Pronote settings supplied by the composition root.
|
|
||||||
:param auth_state: Authentication state supplied by the composition root.
|
|
||||||
"""
|
|
||||||
del settings, auth_state
|
|
||||||
constructed.append(object())
|
|
||||||
raise AssertionError("PronoteClient must not be constructed for QR-token dry-run")
|
|
||||||
|
|
||||||
monkeypatch.setattr(run_module, "PronoteClient", FailingClient)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError) as exc_info:
|
|
||||||
PipelineRunner.from_settings(
|
|
||||||
Settings(
|
|
||||||
app=AppSettings(dry_run=True),
|
|
||||||
pronote=PronoteSettings(
|
|
||||||
auth_mode="qr_token",
|
|
||||||
qr_pin=SecretStr(qr_pin_sentinel),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert "qr_token" in str(exc_info.value)
|
|
||||||
assert "dry-run" in str(exc_info.value)
|
|
||||||
assert qr_pin_sentinel not in str(exc_info.value)
|
|
||||||
assert constructed == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_from_settings_qr_token_mode_passes_auth_state_instance(
|
def test_from_settings_qr_token_mode_passes_auth_state_instance(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -496,64 +449,6 @@ def test_from_settings_qr_token_mode_passes_auth_state_instance(
|
|||||||
assert isinstance(constructed[0][1], PronoteAuthState)
|
assert isinstance(constructed[0][1], PronoteAuthState)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [False, True])
|
|
||||||
def test_from_settings_configures_source_state_persistence_for_dry_run(
|
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
|
||||||
dry_run: bool,
|
|
||||||
) -> None:
|
|
||||||
"""Composition disables source-state persistence only in dry-run mode."""
|
|
||||||
import pronote_sync.pipeline.run as run_module
|
|
||||||
|
|
||||||
blog_persistence: list[bool] = []
|
|
||||||
auth_persistence: list[bool] = []
|
|
||||||
|
|
||||||
class RecordingBlogState:
|
|
||||||
"""Blog state factory recording its persistence configuration."""
|
|
||||||
|
|
||||||
def __init__(self, *, persistence_enabled: bool = True) -> None:
|
|
||||||
"""Record the requested persistence setting.
|
|
||||||
|
|
||||||
:param persistence_enabled: Whether disk writes are enabled.
|
|
||||||
"""
|
|
||||||
blog_persistence.append(persistence_enabled)
|
|
||||||
|
|
||||||
class RecordingAuthState:
|
|
||||||
"""Authentication state factory recording its persistence configuration."""
|
|
||||||
|
|
||||||
def __init__(self, *, persistence_enabled: bool = True) -> None:
|
|
||||||
"""Record the requested persistence setting.
|
|
||||||
|
|
||||||
:param persistence_enabled: Whether disk writes are enabled.
|
|
||||||
"""
|
|
||||||
auth_persistence.append(persistence_enabled)
|
|
||||||
|
|
||||||
class RecordingClient:
|
|
||||||
"""Pronote client constructor accepting the injected auth state."""
|
|
||||||
|
|
||||||
def __init__(self, settings: PronoteSettings, *, auth_state: object) -> None:
|
|
||||||
"""Accept the composition-root dependencies.
|
|
||||||
|
|
||||||
:param settings: Pronote settings.
|
|
||||||
:param auth_state: Injected authentication state.
|
|
||||||
"""
|
|
||||||
del settings, auth_state
|
|
||||||
|
|
||||||
monkeypatch.setattr(run_module, "BlogRSSState", RecordingBlogState)
|
|
||||||
monkeypatch.setattr(run_module, "PronoteAuthState", RecordingAuthState)
|
|
||||||
monkeypatch.setattr(run_module, "PronoteClient", RecordingClient)
|
|
||||||
|
|
||||||
PipelineRunner.from_settings(
|
|
||||||
Settings(
|
|
||||||
app=AppSettings(dry_run=dry_run),
|
|
||||||
blog=BlogSettings(enabled=True),
|
|
||||||
pronote=PronoteSettings(auth_mode="qr_token"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert blog_persistence == [not dry_run]
|
|
||||||
assert auth_persistence == [not dry_run]
|
|
||||||
|
|
||||||
|
|
||||||
def test_runner_reuses_ical_download_and_parse_within_one_run(
|
def test_runner_reuses_ical_download_and_parse_within_one_run(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
pipeline_inputs: tuple[Lesson, Homework],
|
pipeline_inputs: tuple[Lesson, Homework],
|
||||||
|
|||||||
@@ -35,49 +35,6 @@ def test_state_file_absent_empty_state(tmp_path: Path) -> None:
|
|||||||
assert state.get_cache_headers() == (None, None)
|
assert state.get_cache_headers() == (None, None)
|
||||||
|
|
||||||
|
|
||||||
def test_disabled_persistence_keeps_updates_in_memory_without_creating_file(tmp_path: Path) -> None:
|
|
||||||
"""Vérifie que la persistance désactivée conserve l'état uniquement en mémoire.
|
|
||||||
|
|
||||||
:param tmp_path: Fixture pytest pour un répertoire temporaire.
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
state_file = tmp_path / "state.json"
|
|
||||||
state = BlogRSSState(state_file, persistence_enabled=False)
|
|
||||||
|
|
||||||
state.add_guids(["guid-1"])
|
|
||||||
state.update_cache_headers("etag-123", "Wed, 01 Sep 2026 GMT")
|
|
||||||
|
|
||||||
assert state.get_known_guids() == frozenset({"guid-1"})
|
|
||||||
assert state.get_cache_headers() == ("etag-123", "Wed, 01 Sep 2026 GMT")
|
|
||||||
assert not state_file.exists()
|
|
||||||
|
|
||||||
|
|
||||||
def test_disabled_persistence_preserves_existing_file(tmp_path: Path) -> None:
|
|
||||||
"""Vérifie que la persistance désactivée ne modifie pas l'état déjà stocké.
|
|
||||||
|
|
||||||
:param tmp_path: Fixture pytest pour un répertoire temporaire.
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
state_file = tmp_path / "state.json"
|
|
||||||
original_content = json.dumps(
|
|
||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"known_guids": ["existing-guid"],
|
|
||||||
"etag": "old-etag",
|
|
||||||
"last_modified": "Tue, 31 Aug 2026 GMT",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
state_file.write_text(original_content, encoding="utf-8")
|
|
||||||
state = BlogRSSState(state_file, persistence_enabled=False)
|
|
||||||
|
|
||||||
state.add_guids(["new-guid"])
|
|
||||||
state.update_cache_headers("new-etag", "Wed, 01 Sep 2026 GMT")
|
|
||||||
|
|
||||||
assert state.get_known_guids() == frozenset({"existing-guid", "new-guid"})
|
|
||||||
assert state.get_cache_headers() == ("new-etag", "Wed, 01 Sep 2026 GMT")
|
|
||||||
assert state_file.read_text(encoding="utf-8") == original_content
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_guids_persists(tmp_path: Path) -> None:
|
def test_add_guids_persists(tmp_path: Path) -> None:
|
||||||
"""Vérifie que l'ajout de GUID persiste dans le fichier JSON.
|
"""Vérifie que l'ajout de GUID persiste dans le fichier JSON.
|
||||||
|
|
||||||
|
|||||||
@@ -35,90 +35,6 @@ def test_load_no_file_returns_none(tmp_path: Path) -> None:
|
|||||||
assert state.load() is None
|
assert state.load() is None
|
||||||
|
|
||||||
|
|
||||||
def test_disabled_persistence_keeps_credentials_in_memory_without_creating_file(
|
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
|
||||||
"""Vérifie que la persistance désactivée conserve les credentials en mémoire.
|
|
||||||
|
|
||||||
:param tmp_path: Fixture pytest pour un répertoire temporaire.
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
state_file = tmp_path / "auth.json"
|
|
||||||
credentials = {
|
|
||||||
"pronote_url": "https://example.com/pronote",
|
|
||||||
"username": "parent-1",
|
|
||||||
"password": "token-123", # pragma: allowlist secret
|
|
||||||
"uuid": "uuid-456",
|
|
||||||
}
|
|
||||||
state = PronoteAuthState(state_file, persistence_enabled=False)
|
|
||||||
|
|
||||||
state.save(credentials)
|
|
||||||
|
|
||||||
assert state.load() == credentials
|
|
||||||
assert not state_file.exists()
|
|
||||||
|
|
||||||
|
|
||||||
def test_disabled_persistence_preserves_existing_file(tmp_path: Path) -> None:
|
|
||||||
"""Vérifie que la persistance désactivée garde les nouveaux credentials en mémoire.
|
|
||||||
|
|
||||||
:param tmp_path: Fixture pytest pour un répertoire temporaire.
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
state_file = tmp_path / "auth.json"
|
|
||||||
original_credentials = {
|
|
||||||
"pronote_url": "https://example.com/pronote",
|
|
||||||
"username": "parent-1",
|
|
||||||
"password": "old-token", # pragma: allowlist secret
|
|
||||||
"uuid": "old-uuid",
|
|
||||||
}
|
|
||||||
original_content = json.dumps({"version": 1, "credentials": original_credentials}).encode()
|
|
||||||
new_credentials = {
|
|
||||||
"pronote_url": "https://example.com/pronote",
|
|
||||||
"username": "parent-1",
|
|
||||||
"password": "new-token", # pragma: allowlist secret
|
|
||||||
"uuid": "new-uuid",
|
|
||||||
}
|
|
||||||
state_file.write_bytes(original_content)
|
|
||||||
state = PronoteAuthState(state_file, persistence_enabled=False)
|
|
||||||
|
|
||||||
assert state.load() == original_credentials
|
|
||||||
|
|
||||||
state.save(new_credentials)
|
|
||||||
|
|
||||||
assert state.load() == new_credentials
|
|
||||||
assert state_file.read_bytes() == original_content
|
|
||||||
|
|
||||||
|
|
||||||
def test_disabled_persistence_clear_discards_in_memory_credentials_only(tmp_path: Path) -> None:
|
|
||||||
"""Vérifie que clear oublie l'état en mémoire sans modifier le fichier existant.
|
|
||||||
|
|
||||||
:param tmp_path: Fixture pytest pour un répertoire temporaire.
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
state_file = tmp_path / "auth.json"
|
|
||||||
original_credentials = {
|
|
||||||
"pronote_url": "https://example.com/pronote",
|
|
||||||
"username": "parent-1",
|
|
||||||
"password": "old-token", # pragma: allowlist secret
|
|
||||||
"uuid": "old-uuid",
|
|
||||||
}
|
|
||||||
original_content = json.dumps({"version": 1, "credentials": original_credentials}).encode()
|
|
||||||
credentials = {
|
|
||||||
"pronote_url": "https://example.com/pronote",
|
|
||||||
"username": "parent-1",
|
|
||||||
"password": "new-token", # pragma: allowlist secret
|
|
||||||
"uuid": "new-uuid",
|
|
||||||
}
|
|
||||||
state_file.write_bytes(original_content)
|
|
||||||
state = PronoteAuthState(state_file, persistence_enabled=False)
|
|
||||||
|
|
||||||
state.save(credentials)
|
|
||||||
state.clear()
|
|
||||||
|
|
||||||
assert state.load() is None
|
|
||||||
assert state_file.read_bytes() == original_content
|
|
||||||
|
|
||||||
|
|
||||||
def test_save_then_load_roundtrip(tmp_path: Path) -> None:
|
def test_save_then_load_roundtrip(tmp_path: Path) -> None:
|
||||||
"""Vérifie que des credentials sauvegardés sont rechargés à l'identique.
|
"""Vérifie que des credentials sauvegardés sont rechargés à l'identique.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user