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>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user