"""Kaputtes TOML beim NORMALEN Dienststart (nicht nur bei --check-config). `--check-config` fing `tomllib.TOMLDecodeError` schon immer ab, der Startpfad in `main()` aber nicht: ein Tippfehler in der Instanz-Config ergab einen nackten Traceback. Zusammen mit `Restart=on-failure` in der Unit lief die Instanz damit in einen Neustart-Loop. """ from __future__ import annotations import sys from pathlib import Path import pytest from pdf_ocr_hotfolder.__main__ import main # Verschiedene Arten, eine TOML kaputt zu machen BROKEN_TOMLS = [ pytest.param('[paths]\nincoming = "/tmp/in\n', id="unbalancierte-quotes"), pytest.param("[paths\nincoming = \n", id="unvollstaendige-sektion"), pytest.param('[paths]\nincoming "/tmp/in"\n', id="fehlendes-gleich"), pytest.param('[paths]\nincoming = "/a"\n[paths]\nincoming = "/b"\n', id="doppelte-sektion"), ] def _run(monkeypatch, cfg: Path, *extra_args: str) -> int: monkeypatch.setattr( sys, "argv", ["pdf-ocr-hotfolder", "--config", str(cfg), *extra_args], ) return main() @pytest.mark.parametrize("content", BROKEN_TOMLS) def test_broken_toml_returns_2_on_normal_start( tmp_path: Path, monkeypatch, capsys, content: str) -> None: """Dienststart ohne --once: Exit 2, keine Exception nach außen.""" cfg = tmp_path / "instanz.toml" cfg.write_text(content) assert _run(monkeypatch, cfg) == 2 err = capsys.readouterr().err assert "FEHLER" in err assert "TOML" in err assert str(cfg) in err @pytest.mark.parametrize("content", BROKEN_TOMLS) def test_broken_toml_returns_2_with_once( tmp_path: Path, monkeypatch, capsys, content: str) -> None: """Auch --once darf nicht mit Traceback aussteigen.""" cfg = tmp_path / "instanz.toml" cfg.write_text(content) assert _run(monkeypatch, cfg, "--once") == 2 assert "TOML" in capsys.readouterr().err def test_broken_toml_message_names_position( tmp_path: Path, monkeypatch, capsys) -> None: """Die Meldung muss dem Kunden sagen, WO es klemmt. Zeile/Spalte kommen ab Python 3.14 aus den Exception-Attributen, darunter stecken sie im Meldungstext von tomllib. Beide Wege müssen in der Ausgabe landen. """ cfg = tmp_path / "instanz.toml" cfg.write_text('[paths]\nincoming = "/tmp/in"\nworking = "/tmp/w\n') assert _run(monkeypatch, cfg) == 2 err = capsys.readouterr().err.lower() assert "zeile 3" in err or "line 3" in err def test_broken_toml_start_and_check_config_agree( tmp_path: Path, monkeypatch, capsys) -> None: """Startpfad und --check-config liefern denselben Exit-Code und Text.""" cfg = tmp_path / "instanz.toml" cfg.write_text('[paths]\nincoming = "/tmp/in\n') assert _run(monkeypatch, cfg) == 2 start_err = capsys.readouterr().err assert _run(monkeypatch, cfg, "--check-config") == 2 check_err = capsys.readouterr().err marker = f"{cfg} ist kein gültiges TOML" assert marker in start_err assert marker in check_err def test_unreadable_config_returns_2(tmp_path: Path, monkeypatch, capsys) -> None: """Config existiert, ist aber nicht lesbar → Exit 2 statt Traceback.""" cfg = tmp_path / "instanz.toml" cfg.write_text('[paths]\nincoming = "/tmp/in"\n') cfg.chmod(0o000) try: # Als root greifen Dateirechte nicht — dann ist der Test gegenstandslos try: cfg.open("rb").close() pytest.skip("Datei trotz chmod 000 lesbar (root?)") except PermissionError: pass assert _run(monkeypatch, cfg) == 2 assert "nicht lesbar" in capsys.readouterr().err finally: cfg.chmod(0o644)