diff --git a/pronote_sync/pipeline/run.py b/pronote_sync/pipeline/run.py index 4c873e0..02fe79d 100644 --- a/pronote_sync/pipeline/run.py +++ b/pronote_sync/pipeline/run.py @@ -126,6 +126,8 @@ class PipelineRunner: :rtype: PipelineRunner """ 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( settings.app.theoretical_agenda_path, diff --git a/tests/integration/test_pipeline_runner.py b/tests/integration/test_pipeline_runner.py index 1eb830b..18a5e48 100644 --- a/tests/integration/test_pipeline_runner.py +++ b/tests/integration/test_pipeline_runner.py @@ -428,6 +428,47 @@ def test_from_settings_password_mode_passes_auth_state_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( monkeypatch: pytest.MonkeyPatch, ) -> None: