Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd97402bf |
@@ -23,8 +23,6 @@ PRONOTE_AUTH_MODE=password
|
|||||||
# PRONOTE_AUTH_MODE=qr_token
|
# PRONOTE_AUTH_MODE=qr_token
|
||||||
# PRONOTE_QR_CODE_FILE=/path/to/qr_code.json
|
# PRONOTE_QR_CODE_FILE=/path/to/qr_code.json
|
||||||
# PRONOTE_QR_PIN=1234
|
# PRONOTE_QR_PIN=1234
|
||||||
# PIN de second facteur du compte Pronote, distinct du PIN de déchiffrement du QR code
|
|
||||||
# PRONOTE_ACCOUNT_PIN=
|
|
||||||
|
|
||||||
# --- CalDAV ---
|
# --- CalDAV ---
|
||||||
CALDAV_URL=https://caldav.example.com/calendars/user/pronote/
|
CALDAV_URL=https://caldav.example.com/calendars/user/pronote/
|
||||||
|
|||||||
+11
-9
@@ -66,11 +66,6 @@ processus. Le mode `PRONOTE_AUTH_MODE=qr_token` est incompatible avec cette gara
|
|||||||
le refuse avant toute connexion afin de ne pas désynchroniser le token local du token distant.
|
le refuse avant toute connexion afin de ne pas désynchroniser le token local du token distant.
|
||||||
Le dry-run ne remplace pas une vérification des paramètres réellement chargés.
|
Le dry-run ne remplace pas une vérification des paramètres réellement chargés.
|
||||||
|
|
||||||
En mode `PRONOTE_AUTH_MODE=qr_token`, `PRONOTE_QR_PIN` déchiffre le QR code
|
|
||||||
exporté depuis le site web Pronote. Si le compte exige un second facteur,
|
|
||||||
configurez aussi `PRONOTE_ACCOUNT_PIN` avec le PIN du compte. Ce PIN est
|
|
||||||
transmis uniquement à `pronotepy` lors de l'enrôlement QR et des connexions par
|
|
||||||
token ; il n'est jamais écrit dans `.pronote_auth_state.json` ni dans les logs.
|
|
||||||
En mode `PRONOTE_AUTH_MODE=qr_token`, le fichier
|
En mode `PRONOTE_AUTH_MODE=qr_token`, le fichier
|
||||||
`.pronote_auth_state.json` et son verrou frère sont créés dans le répertoire
|
`.pronote_auth_state.json` et son verrou frère sont créés dans le répertoire
|
||||||
de travail du service (par exemple `/var/lib/pronote-sync`) avec le mode
|
de travail du service (par exemple `/var/lib/pronote-sync`) avec le mode
|
||||||
@@ -110,10 +105,17 @@ sudo systemctl start pronote-sync.service
|
|||||||
sudo systemctl status pronote-sync.service
|
sudo systemctl status pronote-sync.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Une exécution en échec laisse l'unité `pronote-sync.service` en état `failed`.
|
La CLI expose un contrat de sortie stable : `0` signifie une exécution complète,
|
||||||
La supervision de l'hôte doit donc déclencher une alerte sur cet état ou sur un
|
`2` une exécution dégradée (les données Pronote sont disponibles mais une étape
|
||||||
échec du timer/service ; le transport de cette alerte (courriel, XMPP ou système
|
optionnelle, CalDAV ou XMPP a échoué), et `1` un échec critique. Tout code non
|
||||||
de supervision) relève de l'exploitation locale.
|
nul laisse l'unité `pronote-sync.service` en état `failed` ; la supervision doit
|
||||||
|
donc alerter sur cet état ou sur le code de sortie. Le code `2` permet de
|
||||||
|
distinguer automatiquement une alerte dégradée d'une panne critique, sans lire
|
||||||
|
les journaux.
|
||||||
|
|
||||||
|
Le `--dry-run` n'écrit ni dans CalDAV/XMPP ni dans l'état local. Il conserve le
|
||||||
|
même contrat de codes : `0` si la simulation est complète, `2` si elle est
|
||||||
|
dégradée et `1` si elle est critique.
|
||||||
|
|
||||||
## Journaux et alertes
|
## Journaux et alertes
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from pydantic import SecretStr
|
|||||||
|
|
||||||
from pronote_sync.config.env import load_settings
|
from pronote_sync.config.env import load_settings
|
||||||
from pronote_sync.config.settings import Settings
|
from pronote_sync.config.settings import Settings
|
||||||
|
from pronote_sync.errors import ErrorSeverity, PipelineError
|
||||||
from pronote_sync.pipeline.run import PipelineRunner
|
from pronote_sync.pipeline.run import PipelineRunner
|
||||||
from pronote_sync.utils.logging import setup_logging
|
from pronote_sync.utils.logging import setup_logging
|
||||||
from pronote_sync.utils.redaction import redact_secrets
|
from pronote_sync.utils.redaction import redact_secrets
|
||||||
@@ -19,6 +20,26 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
_LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
|
_LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
|
||||||
|
|
||||||
|
# Contrat stable pour systemd et les outils de supervision.
|
||||||
|
EXIT_SUCCESS = 0
|
||||||
|
EXIT_CRITICAL = 1
|
||||||
|
EXIT_DEGRADED = 2
|
||||||
|
|
||||||
|
|
||||||
|
def _pipeline_exit_code(data: object | None, errors: Sequence[PipelineError]) -> int:
|
||||||
|
"""Convertit le résultat du pipeline en code de sortie supervisable.
|
||||||
|
|
||||||
|
:param data: Données normalisées produites, ou ``None`` en cas d'échec critique.
|
||||||
|
:param errors: Erreurs et avertissements de l'exécution.
|
||||||
|
:return: ``0`` si complet, ``2`` si dégradé, ``1`` si critique.
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
if data is None or any(error.severity == ErrorSeverity.CRITICAL for error in errors):
|
||||||
|
return EXIT_CRITICAL
|
||||||
|
if errors:
|
||||||
|
return EXIT_DEGRADED
|
||||||
|
return EXIT_SUCCESS
|
||||||
|
|
||||||
|
|
||||||
def _parse_arguments(arguments: Sequence[str] | None = None) -> argparse.Namespace:
|
def _parse_arguments(arguments: Sequence[str] | None = None) -> argparse.Namespace:
|
||||||
"""Analyse les options de lancement du programme.
|
"""Analyse les options de lancement du programme.
|
||||||
@@ -120,7 +141,7 @@ def main(arguments: Sequence[str] | None = None) -> int:
|
|||||||
bruts afin de préserver le diagnostic sans exposer de secret.
|
bruts afin de préserver le diagnostic sans exposer de secret.
|
||||||
|
|
||||||
:param arguments: Arguments optionnels, principalement utiles aux appels programmatiques.
|
:param arguments: Arguments optionnels, principalement utiles aux appels programmatiques.
|
||||||
:return: ``0`` en cas de succès, ``1`` sinon (après analyse des arguments).
|
:return: Code machine-readable : ``0`` complet, ``2`` dégradé, ``1`` critique.
|
||||||
:rtype: int
|
:rtype: int
|
||||||
:raises SystemExit: Si argparse rejette les arguments (code de sortie 2).
|
:raises SystemExit: Si argparse rejette les arguments (code de sortie 2).
|
||||||
"""
|
"""
|
||||||
@@ -147,9 +168,7 @@ def main(arguments: Sequence[str] | None = None) -> int:
|
|||||||
secrets = _settings_secrets(settings)
|
secrets = _settings_secrets(settings)
|
||||||
for error in errors:
|
for error in errors:
|
||||||
logger.error("%s", redact_secrets(error.message, extra_secrets=secrets))
|
logger.error("%s", redact_secrets(error.message, extra_secrets=secrets))
|
||||||
if data is None:
|
return _pipeline_exit_code(data, errors)
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class PronoteSettings(BaseSettings):
|
|||||||
auth_mode: Literal["password", "qr_token"] = "password"
|
auth_mode: Literal["password", "qr_token"] = "password"
|
||||||
qr_code_file: str | None = None
|
qr_code_file: str | None = None
|
||||||
qr_pin: SecretStr | None = None
|
qr_pin: SecretStr | None = None
|
||||||
account_pin: SecretStr | None = None
|
|
||||||
|
|
||||||
@field_serializer("ical_url")
|
@field_serializer("ical_url")
|
||||||
def _serialize_ical_url(self, value: SecretStr | None) -> str | None:
|
def _serialize_ical_url(self, value: SecretStr | None) -> str | None:
|
||||||
@@ -73,18 +72,6 @@ class PronoteSettings(BaseSettings):
|
|||||||
return None
|
return None
|
||||||
return "**********"
|
return "**********"
|
||||||
|
|
||||||
@field_serializer("account_pin")
|
|
||||||
def _serialize_account_pin(self, value: SecretStr | None) -> str | None:
|
|
||||||
"""Masque le PIN du compte lors de la sérialisation.
|
|
||||||
|
|
||||||
:param value: Valeur du PIN de second facteur du compte.
|
|
||||||
:return: ``"**********"`` si la valeur est définie, ``None`` sinon.
|
|
||||||
:rtype: str | None
|
|
||||||
"""
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
return "**********"
|
|
||||||
|
|
||||||
|
|
||||||
class CalDAVSettings(BaseSettings):
|
class CalDAVSettings(BaseSettings):
|
||||||
"""Paramètres d'accès au serveur CalDAV de destination.
|
"""Paramètres d'accès au serveur CalDAV de destination.
|
||||||
@@ -341,7 +328,6 @@ class Settings(BaseSettings):
|
|||||||
self.pronote.ical_url,
|
self.pronote.ical_url,
|
||||||
self.pronote.password,
|
self.pronote.password,
|
||||||
self.pronote.qr_pin,
|
self.pronote.qr_pin,
|
||||||
self.pronote.account_pin,
|
|
||||||
self.caldav.url,
|
self.caldav.url,
|
||||||
self.caldav.password,
|
self.caldav.password,
|
||||||
self.xmpp.password,
|
self.xmpp.password,
|
||||||
|
|||||||
@@ -119,9 +119,6 @@ def _collect_auth_secrets(client: PronoteClient) -> list[str]:
|
|||||||
# PIN QR
|
# PIN QR
|
||||||
if settings.qr_pin is not None:
|
if settings.qr_pin is not None:
|
||||||
secrets.append(settings.qr_pin.get_secret_value())
|
secrets.append(settings.qr_pin.get_secret_value())
|
||||||
# PIN de second facteur du compte
|
|
||||||
if settings.account_pin is not None:
|
|
||||||
secrets.append(settings.account_pin.get_secret_value())
|
|
||||||
# Contenu du fichier QR (jeton, login, url)
|
# Contenu du fichier QR (jeton, login, url)
|
||||||
if settings.qr_code_file is not None:
|
if settings.qr_code_file is not None:
|
||||||
try:
|
try:
|
||||||
@@ -340,12 +337,7 @@ class PronoteClient:
|
|||||||
creds = self._auth_state.load()
|
creds = self._auth_state.load()
|
||||||
if creds is not None:
|
if creds is not None:
|
||||||
try:
|
try:
|
||||||
account_pin = (
|
client = client_class.token_login(**creds)
|
||||||
self._settings.account_pin.get_secret_value()
|
|
||||||
if self._settings.account_pin is not None
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
client = client_class.token_login(**creds, account_pin=account_pin)
|
|
||||||
if client.logged_in:
|
if client.logged_in:
|
||||||
self._client = client
|
self._client = client
|
||||||
self._persist_credentials()
|
self._persist_credentials()
|
||||||
@@ -429,16 +421,10 @@ class PronoteClient:
|
|||||||
app_uuid = f"pronote-sync-{uuid4().hex}"
|
app_uuid = f"pronote-sync-{uuid4().hex}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
account_pin = (
|
|
||||||
self._settings.account_pin.get_secret_value()
|
|
||||||
if self._settings.account_pin is not None
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
client = client_class.qrcode_login(
|
client = client_class.qrcode_login(
|
||||||
qr_code=qr_data,
|
qr_code=qr_data,
|
||||||
pin=pin_value,
|
pin=pin_value,
|
||||||
uuid=app_uuid,
|
uuid=app_uuid,
|
||||||
account_pin=account_pin,
|
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|||||||
+23
-3
@@ -52,10 +52,10 @@ def test_main_runs_composition_root_in_dry_run_with_requested_log_level(
|
|||||||
runner.run.assert_called_once_with()
|
runner.run.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
def test_main_preserves_configured_dry_run_and_returns_success_with_warnings(
|
def test_main_preserves_configured_dry_run_and_returns_degraded_with_warnings(
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Sans option, la CLI préserve le dry-run configuré et accepte les avertissements."""
|
"""Sans option, la CLI préserve le dry-run configuré et signale l'état dégradé."""
|
||||||
from pronote_sync.cli.main import main
|
from pronote_sync.cli.main import main
|
||||||
|
|
||||||
settings = Settings(app=AppSettings(dry_run=True, log_level="WARNING"))
|
settings = Settings(app=AppSettings(dry_run=True, log_level="WARNING"))
|
||||||
@@ -72,12 +72,32 @@ def test_main_preserves_configured_dry_run_and_returns_success_with_warnings(
|
|||||||
|
|
||||||
exit_code = main([])
|
exit_code = main([])
|
||||||
|
|
||||||
assert exit_code == 0
|
assert exit_code == 2
|
||||||
assert setup_logging.call_args_list == [mocker.call("INFO"), mocker.call("WARNING")]
|
assert setup_logging.call_args_list == [mocker.call("INFO"), mocker.call("WARNING")]
|
||||||
composition_root.assert_called_once_with(settings, dry_run=None)
|
composition_root.assert_called_once_with(settings, dry_run=None)
|
||||||
runner.run.assert_called_once_with()
|
runner.run.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("step", ["caldav_sync", "send"])
|
||||||
|
def test_main_returns_degraded_code_for_caldav_or_xmpp_failure(
|
||||||
|
mocker: MockerFixture,
|
||||||
|
step: str,
|
||||||
|
) -> None:
|
||||||
|
"""Les échecs récupérables CalDAV et XMPP sont observables par le code 2."""
|
||||||
|
from pronote_sync.cli.main import main
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
|
mocker.patch("pronote_sync.cli.main.load_settings", return_value=settings)
|
||||||
|
runner = mocker.Mock()
|
||||||
|
runner.run.return_value = (
|
||||||
|
mocker.Mock(spec=PronoteData),
|
||||||
|
[PipelineWarning(f"Échec récupérable de {step}", step=step)],
|
||||||
|
)
|
||||||
|
mocker.patch("pronote_sync.cli.main.PipelineRunner.from_settings", return_value=runner)
|
||||||
|
|
||||||
|
assert main([]) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_main_returns_failure_and_redacts_pipeline_secrets_at_debug_level(
|
def test_main_returns_failure_and_redacts_pipeline_secrets_at_debug_level(
|
||||||
mocker: MockerFixture,
|
mocker: MockerFixture,
|
||||||
capsys: pytest.CaptureFixture[str],
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
|||||||
@@ -207,21 +207,4 @@ def test_qr_pin_in_redaction_secrets(monkeypatch: MonkeyPatch) -> None:
|
|||||||
assert "**********" in repr(settings.pronote.qr_pin)
|
assert "**********" in repr(settings.pronote.qr_pin)
|
||||||
|
|
||||||
|
|
||||||
def test_account_pin_loaded_as_secretstr_and_redacted(monkeypatch: MonkeyPatch) -> None:
|
|
||||||
"""Vérifie que ``PRONOTE_ACCOUNT_PIN`` est secret et expurgé.
|
|
||||||
|
|
||||||
:param monkeypatch: Fixture pytest pour modifier temporairement l'environnement.
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
monkeypatch.setenv("PRONOTE_ACCOUNT_PIN", "account-pin-42")
|
|
||||||
settings = load_settings()
|
|
||||||
|
|
||||||
assert isinstance(settings.pronote.account_pin, SecretStr)
|
|
||||||
assert settings.pronote.account_pin.get_secret_value() == "account-pin-42"
|
|
||||||
assert settings.pronote.account_pin in settings.redaction_secrets()
|
|
||||||
assert "account-pin-42" not in str(settings)
|
|
||||||
assert "account-pin-42" not in settings.model_dump_json()
|
|
||||||
assert "**********" in settings.model_dump_json()
|
|
||||||
|
|
||||||
|
|
||||||
# Ensure trailing newline
|
# Ensure trailing newline
|
||||||
|
|||||||
@@ -674,10 +674,8 @@ def test_connect_password_mode_unchanged(
|
|||||||
assert mock_client_class.call_count == 1
|
assert mock_client_class.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("account_pin", [None, "account-pin-42"])
|
|
||||||
def test_connect_qr_token_with_persisted_creds(
|
def test_connect_qr_token_with_persisted_creds(
|
||||||
mocker: pytest_mock.MockerFixture,
|
mocker: pytest_mock.MockerFixture,
|
||||||
account_pin: str | None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Vérifie le login par token persisté en mode qr_token.
|
"""Vérifie le login par token persisté en mode qr_token.
|
||||||
|
|
||||||
@@ -685,7 +683,6 @@ def test_connect_qr_token_with_persisted_creds(
|
|||||||
``token_login`` et le token rotate est resauvegardé.
|
``token_login`` et le token rotate est resauvegardé.
|
||||||
|
|
||||||
:param mocker: Fixture pytest-mock pour le mocking.
|
:param mocker: Fixture pytest-mock pour le mocking.
|
||||||
:param account_pin: PIN de second facteur facultatif.
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
creds = {
|
creds = {
|
||||||
@@ -710,29 +707,23 @@ def test_connect_qr_token_with_persisted_creds(
|
|||||||
ent=None,
|
ent=None,
|
||||||
account_type="parent",
|
account_type="parent",
|
||||||
auth_mode="qr_token",
|
auth_mode="qr_token",
|
||||||
account_pin=SecretStr(account_pin) if account_pin is not None else None,
|
|
||||||
)
|
)
|
||||||
client = PronoteClient(settings, auth_state=auth_state)
|
client = PronoteClient(settings, auth_state=auth_state)
|
||||||
connected = client._connect()
|
connected = client._connect()
|
||||||
|
|
||||||
assert connected is mock_client
|
assert connected is mock_client
|
||||||
pronotepy.ParentClient.token_login.assert_called_once_with( # type: ignore[attr-defined]
|
pronotepy.ParentClient.token_login.assert_called_once_with(**creds) # type: ignore[attr-defined]
|
||||||
**creds, account_pin=account_pin
|
|
||||||
)
|
|
||||||
auth_state.save.assert_called_once_with(rotated_creds)
|
auth_state.save.assert_called_once_with(rotated_creds)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("account_pin", [None, "account-pin-42"])
|
|
||||||
def test_connect_qr_token_no_creds_with_qr_code(
|
def test_connect_qr_token_no_creds_with_qr_code(
|
||||||
mocker: pytest_mock.MockerFixture,
|
mocker: pytest_mock.MockerFixture,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
account_pin: str | None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Vérifie l'enrôlement initial par QR code quand aucun token n'est persisté.
|
"""Vérifie l'enrôlement initial par QR code quand aucun token n'est persisté.
|
||||||
|
|
||||||
:param mocker: Fixture pytest-mock pour le mocking.
|
:param mocker: Fixture pytest-mock pour le mocking.
|
||||||
:param tmp_path: Répertoire temporaire de test.
|
:param tmp_path: Répertoire temporaire de test.
|
||||||
:param account_pin: PIN de second facteur facultatif.
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
qr_file = tmp_path / "qr_code.json"
|
qr_file = tmp_path / "qr_code.json"
|
||||||
@@ -769,7 +760,6 @@ def test_connect_qr_token_no_creds_with_qr_code(
|
|||||||
auth_mode="qr_token",
|
auth_mode="qr_token",
|
||||||
qr_code_file=str(qr_file),
|
qr_code_file=str(qr_file),
|
||||||
qr_pin=SecretStr("123456"),
|
qr_pin=SecretStr("123456"),
|
||||||
account_pin=SecretStr(account_pin) if account_pin is not None else None,
|
|
||||||
)
|
)
|
||||||
client = PronoteClient(settings, auth_state=auth_state)
|
client = PronoteClient(settings, auth_state=auth_state)
|
||||||
connected = client._connect()
|
connected = client._connect()
|
||||||
@@ -779,7 +769,6 @@ def test_connect_qr_token_no_creds_with_qr_code(
|
|||||||
qrcode_login.assert_called_once() # type: ignore[attr-defined]
|
qrcode_login.assert_called_once() # type: ignore[attr-defined]
|
||||||
kwargs = qrcode_login.call_args.kwargs # type: ignore[attr-defined]
|
kwargs = qrcode_login.call_args.kwargs # type: ignore[attr-defined]
|
||||||
assert kwargs["pin"] == "123456"
|
assert kwargs["pin"] == "123456"
|
||||||
assert kwargs["account_pin"] == account_pin
|
|
||||||
assert kwargs["qr_code"] == {
|
assert kwargs["qr_code"] == {
|
||||||
"login": "testuser",
|
"login": "testuser",
|
||||||
"jeton": "qr-jeton",
|
"jeton": "qr-jeton",
|
||||||
@@ -1178,7 +1167,6 @@ def test_no_raw_secrets_in_logs(
|
|||||||
sentinel_token = "SENTINEL_RAW_TOKEN_ALPHA"
|
sentinel_token = "SENTINEL_RAW_TOKEN_ALPHA"
|
||||||
sentinel_pin = "SENTINEL_RAW_PIN_BRAVO"
|
sentinel_pin = "SENTINEL_RAW_PIN_BRAVO"
|
||||||
sentinel_jeton = "SENTINEL_RAW_JETON_CHARLIE"
|
sentinel_jeton = "SENTINEL_RAW_JETON_CHARLIE"
|
||||||
sentinel_account_pin = "SENTINEL_RAW_ACCOUNT_PIN_DELTA"
|
|
||||||
|
|
||||||
qr_file = tmp_path / "qr_code.json"
|
qr_file = tmp_path / "qr_code.json"
|
||||||
qr_file.write_text(
|
qr_file.write_text(
|
||||||
@@ -1202,8 +1190,7 @@ def test_no_raw_secrets_in_logs(
|
|||||||
mocker.patch(
|
mocker.patch(
|
||||||
"pronotepy.ParentClient.token_login",
|
"pronotepy.ParentClient.token_login",
|
||||||
side_effect=pronotepy.PronoteAPIError(
|
side_effect=pronotepy.PronoteAPIError(
|
||||||
f"login refusé {sentinel_token} puis {sentinel_pin} puis {sentinel_jeton} "
|
f"login refusé {sentinel_token} puis {sentinel_pin} puis {sentinel_jeton}"
|
||||||
f"puis {sentinel_account_pin}"
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1216,7 +1203,6 @@ def test_no_raw_secrets_in_logs(
|
|||||||
auth_mode="qr_token",
|
auth_mode="qr_token",
|
||||||
qr_code_file=str(qr_file),
|
qr_code_file=str(qr_file),
|
||||||
qr_pin=SecretStr(sentinel_pin),
|
qr_pin=SecretStr(sentinel_pin),
|
||||||
account_pin=SecretStr(sentinel_account_pin),
|
|
||||||
)
|
)
|
||||||
client = PronoteClient(settings, auth_state=auth_state)
|
client = PronoteClient(settings, auth_state=auth_state)
|
||||||
|
|
||||||
@@ -1228,12 +1214,10 @@ def test_no_raw_secrets_in_logs(
|
|||||||
assert sentinel_token not in message
|
assert sentinel_token not in message
|
||||||
assert sentinel_pin not in message
|
assert sentinel_pin not in message
|
||||||
assert sentinel_jeton not in message
|
assert sentinel_jeton not in message
|
||||||
assert sentinel_account_pin not in message
|
|
||||||
assert caplog.text
|
assert caplog.text
|
||||||
assert sentinel_token not in caplog.text
|
assert sentinel_token not in caplog.text
|
||||||
assert sentinel_pin not in caplog.text
|
assert sentinel_pin not in caplog.text
|
||||||
assert sentinel_jeton not in caplog.text
|
assert sentinel_jeton not in caplog.text
|
||||||
assert sentinel_account_pin not in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
# --- Persistence of credentials after data operations ---
|
# --- Persistence of credentials after data operations ---
|
||||||
|
|||||||
Reference in New Issue
Block a user