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,117 @@
|
||||
"""Log-Ziel: der Dienst loggt nach stdout, nicht nach stderr.
|
||||
|
||||
README und docs/INSTALLATION.md versprechen stdout. `logging.basicConfig()`
|
||||
ohne `stream=` nimmt aber stderr. Für journald ist das egal, für den in der
|
||||
Doku beschriebenen Vordergrund-Notbehelf und für jede Weiterleitung der
|
||||
Ausgabe nicht.
|
||||
|
||||
Gleichzeitig muss die Trennung in `--check-config` bleiben: Infos und
|
||||
Warnungen nach stdout, Fehler nach stderr.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import logging
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from pdf_ocr_hotfolder.__main__ import _setup_logging, main
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _fresh_root_logger():
|
||||
"""Root-Logger wie beim echten Dienststart: ohne Handler.
|
||||
|
||||
`logging.basicConfig()` tut nichts, solange der Root-Logger Handler hat —
|
||||
und pytest hängt seinen Capture-Handler dort ein, nachdem die Fixtures
|
||||
gelaufen sind. Deshalb erst hier, direkt um den Aufruf herum, leeren.
|
||||
"""
|
||||
root = logging.getLogger()
|
||||
saved_handlers, saved_level = root.handlers[:], root.level
|
||||
root.handlers = []
|
||||
try:
|
||||
yield root
|
||||
finally:
|
||||
for h in root.handlers:
|
||||
h.close()
|
||||
root.handlers = saved_handlers
|
||||
root.level = saved_level
|
||||
|
||||
|
||||
def test_setup_logging_uses_stdout() -> None:
|
||||
with _fresh_root_logger() as root:
|
||||
_setup_logging("INFO")
|
||||
streams = [h.stream for h in root.handlers
|
||||
if isinstance(h, logging.StreamHandler)]
|
||||
assert streams, "kein StreamHandler konfiguriert"
|
||||
assert all(s is sys.stdout for s in streams)
|
||||
assert not any(s is sys.stderr for s in streams)
|
||||
|
||||
|
||||
def test_log_records_land_on_stdout(monkeypatch) -> None:
|
||||
"""Ein echter Log-Satz muss im stdout-Puffer stehen, nicht im stderr."""
|
||||
out, err = io.StringIO(), io.StringIO()
|
||||
monkeypatch.setattr(sys, "stdout", out)
|
||||
monkeypatch.setattr(sys, "stderr", err)
|
||||
|
||||
with _fresh_root_logger():
|
||||
_setup_logging("INFO")
|
||||
logging.getLogger("pdf_ocr_hotfolder.test").warning("Testmeldung 4711")
|
||||
|
||||
assert "Testmeldung 4711" in out.getvalue()
|
||||
assert "Testmeldung 4711" not in err.getvalue()
|
||||
|
||||
|
||||
def test_setup_logging_respects_level() -> None:
|
||||
with _fresh_root_logger() as root:
|
||||
_setup_logging("WARNING")
|
||||
assert root.level == logging.WARNING
|
||||
|
||||
|
||||
def test_setup_logging_falls_back_on_garbage_level() -> None:
|
||||
"""Ein Tippfehler in [logging].level darf den Start nicht verhindern."""
|
||||
with _fresh_root_logger() as root:
|
||||
_setup_logging("LAUT")
|
||||
assert root.level == logging.INFO
|
||||
|
||||
|
||||
# ---------------- Trennung in --check-config ----------------
|
||||
|
||||
def _cfg(tmp_path: Path) -> Path:
|
||||
cfg = tmp_path / "cfg.toml"
|
||||
cfg.write_text(f"""
|
||||
[paths]
|
||||
incoming = "{tmp_path / 'in'}"
|
||||
outgoing = "{tmp_path / 'out'}"
|
||||
working = "{tmp_path / 'work'}"
|
||||
error = "{tmp_path / 'err'}"
|
||||
""")
|
||||
return cfg
|
||||
|
||||
|
||||
def test_check_config_keeps_info_on_stdout(tmp_path: Path, monkeypatch,
|
||||
capsys) -> None:
|
||||
monkeypatch.setattr(sys, "argv",
|
||||
["pdf-ocr-hotfolder", "--config", str(_cfg(tmp_path)),
|
||||
"--check-config"])
|
||||
with patch("pdf_ocr_hotfolder.service.shutil.which",
|
||||
return_value="/usr/bin/fake"):
|
||||
assert main() == 0
|
||||
captured = capsys.readouterr()
|
||||
assert "Config sauber" in captured.out
|
||||
assert captured.err == ""
|
||||
|
||||
|
||||
def test_check_config_keeps_errors_on_stderr(tmp_path: Path, monkeypatch,
|
||||
capsys) -> None:
|
||||
cfg = tmp_path / "cfg.toml"
|
||||
cfg.write_text('[ocr]\nlanguages = "deu"\n')
|
||||
monkeypatch.setattr(sys, "argv",
|
||||
["pdf-ocr-hotfolder", "--config", str(cfg),
|
||||
"--check-config"])
|
||||
assert main() == 2
|
||||
captured = capsys.readouterr()
|
||||
assert "FEHLER" in captured.err
|
||||
assert "FEHLER" not in captured.out
|
||||
Reference in New Issue
Block a user