cd803a3dfe
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>
135 lines
4.9 KiB
Python
135 lines
4.9 KiB
Python
"""Tests für upload_folder() — Kopie per shutil.copyfile statt read_bytes()."""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from pdf_ocr_hotfolder.config import FolderUpload
|
|
from pdf_ocr_hotfolder.uploaders import upload_folder
|
|
|
|
|
|
def test_upload_folder_copies_file(tmp_path: Path) -> None:
|
|
src = tmp_path / "out" / "OCR_scan.pdf"
|
|
src.parent.mkdir()
|
|
src.write_bytes(b"%PDF-1.4 inhalt\n")
|
|
target = tmp_path / "ziel"
|
|
|
|
assert upload_folder(src, FolderUpload(enabled=True, target=str(target)),
|
|
tmp_path / "out") is True
|
|
assert (target / "OCR_scan.pdf").read_bytes() == b"%PDF-1.4 inhalt\n"
|
|
# Quelle bleibt liegen (Kopie, kein Move)
|
|
assert src.exists()
|
|
|
|
|
|
def test_upload_folder_uses_copyfile_not_read_bytes(tmp_path: Path) -> None:
|
|
"""Große PDFs dürfen nicht komplett in den Speicher gelesen werden."""
|
|
src = tmp_path / "out" / "OCR_scan.pdf"
|
|
src.parent.mkdir()
|
|
src.write_bytes(b"%PDF-1.4\n")
|
|
target = tmp_path / "ziel"
|
|
|
|
with patch("pdf_ocr_hotfolder.uploaders.shutil.copyfile") as copyfile:
|
|
upload_folder(src, FolderUpload(enabled=True, target=str(target)),
|
|
tmp_path / "out")
|
|
copyfile.assert_called_once()
|
|
|
|
|
|
def test_upload_folder_skips_self_target(tmp_path: Path) -> None:
|
|
"""Ist das Ziel = outgoing, wird nicht auf sich selbst kopiert."""
|
|
out = tmp_path / "out"
|
|
out.mkdir()
|
|
src = out / "OCR_scan.pdf"
|
|
src.write_bytes(b"%PDF-1.4\n")
|
|
|
|
with patch("pdf_ocr_hotfolder.uploaders.shutil.copyfile") as copyfile:
|
|
assert upload_folder(src, FolderUpload(enabled=True, target=""), out) is True
|
|
copyfile.assert_not_called()
|
|
assert src.read_bytes() == b"%PDF-1.4\n"
|
|
|
|
|
|
def test_upload_folder_disabled_returns_true(tmp_path: Path) -> None:
|
|
src = tmp_path / "OCR_scan.pdf"
|
|
src.write_bytes(b"%PDF-1.4\n")
|
|
assert upload_folder(src, FolderUpload(enabled=False), tmp_path) is True
|
|
|
|
|
|
def test_upload_folder_reports_failure(tmp_path: Path) -> None:
|
|
"""OSError beim Kopieren → False (wird vom Service als Fehler gezählt)."""
|
|
src = tmp_path / "out" / "OCR_scan.pdf"
|
|
src.parent.mkdir()
|
|
src.write_bytes(b"%PDF-1.4\n")
|
|
|
|
with patch("pdf_ocr_hotfolder.uploaders.shutil.copyfile",
|
|
side_effect=OSError("disk full")):
|
|
assert upload_folder(src, FolderUpload(enabled=True,
|
|
target=str(tmp_path / "ziel")),
|
|
tmp_path / "out") is False
|
|
|
|
|
|
# ---------------- Punkt 3: kein stilles Überschreiben im Ziel ----------------
|
|
|
|
def test_upload_folder_does_not_overwrite_existing(tmp_path: Path) -> None:
|
|
"""Gleichnamige Datei im abweichenden target wurde bisher still ersetzt."""
|
|
src = tmp_path / "out" / "OCR_scan.pdf"
|
|
src.parent.mkdir()
|
|
src.write_bytes(b"%PDF-1.4 neu\n")
|
|
target = tmp_path / "ziel"
|
|
target.mkdir()
|
|
(target / "OCR_scan.pdf").write_bytes(b"%PDF-1.4 alt\n")
|
|
|
|
assert upload_folder(src, FolderUpload(enabled=True, target=str(target)),
|
|
tmp_path / "out") is True
|
|
|
|
assert (target / "OCR_scan.pdf").read_bytes() == b"%PDF-1.4 alt\n"
|
|
neu = list(target.glob("OCR_scan_*.pdf"))
|
|
assert len(neu) == 1
|
|
assert neu[0].read_bytes() == b"%PDF-1.4 neu\n"
|
|
|
|
|
|
def test_upload_folder_logs_warning_on_collision(tmp_path: Path, caplog) -> None:
|
|
import logging
|
|
|
|
src = tmp_path / "out" / "OCR_scan.pdf"
|
|
src.parent.mkdir()
|
|
src.write_bytes(b"%PDF-1.4 neu\n")
|
|
target = tmp_path / "ziel"
|
|
target.mkdir()
|
|
(target / "OCR_scan.pdf").write_bytes(b"%PDF-1.4 alt\n")
|
|
|
|
with caplog.at_level(logging.WARNING, logger="pdf_ocr_hotfolder.uploaders"):
|
|
upload_folder(src, FolderUpload(enabled=True, target=str(target)),
|
|
tmp_path / "out")
|
|
|
|
assert "OCR_scan.pdf" in caplog.text
|
|
assert "überschrieben" in caplog.text
|
|
|
|
|
|
def test_upload_folder_without_collision_logs_nothing(tmp_path: Path, caplog) -> None:
|
|
import logging
|
|
|
|
src = tmp_path / "out" / "OCR_scan.pdf"
|
|
src.parent.mkdir()
|
|
src.write_bytes(b"%PDF-1.4\n")
|
|
|
|
with caplog.at_level(logging.WARNING, logger="pdf_ocr_hotfolder.uploaders"):
|
|
upload_folder(src, FolderUpload(enabled=True, target=str(tmp_path / "ziel")),
|
|
tmp_path / "out")
|
|
|
|
assert "überschrieben" not in caplog.text
|
|
assert (tmp_path / "ziel" / "OCR_scan.pdf").exists()
|
|
|
|
|
|
def test_upload_folder_self_target_is_not_renamed(tmp_path: Path) -> None:
|
|
"""Default (leeres target -> outgoing/): der resolve()-Kurzschluss greift.
|
|
|
|
Ohne ihn würde die Datei hier gegen sich selbst kollidieren und eine
|
|
Zeitstempel-Kopie neben sich selbst erzeugen.
|
|
"""
|
|
out = tmp_path / "out"
|
|
out.mkdir()
|
|
src = out / "OCR_scan.pdf"
|
|
src.write_bytes(b"%PDF-1.4\n")
|
|
|
|
assert upload_folder(src, FolderUpload(enabled=True, target=""), out) is True
|
|
assert list(out.iterdir()) == [src]
|