Files
pdf-ocr-hotfolder/tests/test_startup_toml_error.py
techadmin cd803a3dfe feat: stille Datenverlust-Pfade geschlossen, gemeinsame Shell-Lib (v0.7.0)
Vor dem Rollout durchgesehen und die verbliebenen Stellen geschlossen, an
denen etwas schiefgehen konnte, ohne dass es irgendwo sichtbar wurde.

Datenverlust:
- veraPDF: das in [verapdf].binary konfigurierte Programm wird im Preflight
  geprueft. Bisher galt bei falschem Pfad JEDE Datei als "nicht konform" —
  Ergebnis nach error/, Original geloescht (Default delete). run_verapdf()
  trennt jetzt ausserdem ein echtes FAIL-Urteil von einer Stoerung
  (VeraPdfUnavailable: nicht startbar, abgestuerzt, kein PASS/FAIL in der
  Ausgabe). Bei Stoerung wandern Original UND Ergebnis nach error/, das
  Original wird nicht entsorgt.
- Gleichnamige Dateien wurden in outgoing/, error/ und beim Ordner-Upload
  mit abweichendem target kommentarlos ueberschrieben. Jetzt Zeitstempel
  daneben, mit Warnung; ProcessResult.output traegt den echten Pfad.

Robustheit:
- Kaputtes oder nicht lesbares TOML beim Start: Exit 2 statt Traceback.
- RestartPreventExitStatus=2 in der Unit — Exit 2 (Config/Preflight) laeuft
  nicht mehr endlos neu, die Instanz bleibt sichtbar failed stehen.
- Toter watchdog-Observer wird erkannt: Exit 3, systemd setzt den Watch neu
  auf. Vorher blieb die Unit "active" und verarbeitete nichts mehr.
- Relative Pfade in [paths]/archive_dir/target sind ein Config-Fehler statt
  still unter /opt zu landen.
- Fehler beim Archivieren entwertet den Durchlauf nicht mehr: Upload und
  Mail laufen, Sichtbarkeit ueber log.error + "OK mit Warnung"-Mail.
- Nicht-PDFs in incoming/ werden beim Start-Scan gesammelt gemeldet.
- Logging explizit nach stdout (die Doku versprach das schon).

Struktur:
- Neue lib/common.sh, von install.sh und update.sh gesourct. Die doppelte
  venv_is_healthy() gibt es nur noch einmal, in der gruendlichen Fassung —
  die schlanke in install.sh haette eine nach einem Distro-Sprung kaputte
  venv als gesund durchgewunken (nachgewiesen).
- install.sh warnt in Containern, wenn systemd-journald nicht laeuft.

Doku: Dateisystem-Festlegung (ext4/xfs/zfs, kein CIFS/NFS wegen inotify),
Debian 13 in LXC auf Proxmox scheitert an journald (243/CREDENTIALS,
AppArmor blockiert sd-mkdcreds) inkl. Abhilfe, echte Speicher-Messwerte,
Exit-Code-Tabelle.

254 Tests gruen (vorher 152).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 00:59:17 +02:00

111 lines
3.6 KiB
Python

"""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)