Files
pdf-ocr-hotfolder/tests/test_ghostscript_version.py
T
techadmin 465ff8873f fix: Ghostscript-Angebot log nicht mehr, fehlende Voraussetzungen dokumentiert (v0.7.1)
Befunde aus dem ersten echten Erstinstallations-Test auf frischen Debian-12-
und Debian-13-Containern. Der Weg selbst hat getragen (Basis-Install,
Instanz-Anlage, zweite Instanz, Update mit Rauchtest, Rollback) — diese
Stellen haben gelogen oder gefehlt:

- Das Ghostscript-Backports-Angebot auf Debian 12 war ein garantierter
  Leerlauf, der "aktualisiert ✓" meldete: bookworm-backports enthaelt gar
  kein ghostscript (am Paketindex verifiziert). Die Routine sucht jetzt den
  echten Kandidaten, vergleicht vorher/nachher und raeumt eine nur zur Probe
  angelegte Quelle wieder weg.
- Derselbe untaugliche Rat stand in der Preflight-Meldung, der
  pdfa_level-Warnung, config.example.toml und vier Doku-Dateien — ueberall
  ersetzt durch die echten Optionen.
- Mehrzeilige Log-Hinweise waren durch "echo -e" zerrissen und nicht
  kopierbar; log_* nutzt jetzt printf mit %s.
- pip-freeze.txt landete beim Rollback als /pip-freeze.txt im
  Wurzelverzeichnis, liegt jetzt unter opt/pdf-ocr-hotfolder/.
- git und sudo fehlen auf dem Proxmox-Debian-Template; "sudo ./install.sh"
  scheitert dort. Beide Wege dokumentiert, git als Voraussetzung ergaenzt,
  HTTPS-Clone als Normalfall.
- Rollback: systemctl start kann kein Glob. journald-Reparatur: Instanzen
  danach neu starten, sonst bleibt das Journal leer.

254 Tests gruen.

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

228 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests für Issue #3: Ghostscript 10.0.0–10.02.0 Bug-Erkennung.
Seit v0.6.1 bildet der Preflight die reale ocrmypdf-Bedingung ab:
betroffene GS-Version UND skip_text UND (pdfa_level ODER ocrmypdf < 17)
Der letzte Teil ist der Fall, der in v0.6.0 durchrutschte: mit ocrmypdf 16.x
greift die Ghostscript-Prüfung auch ohne PDF/A, und der Dienst meldete
trotzdem "Preflight ok", während jede einzelne PDF in error/ landete.
"""
from __future__ import annotations
from contextlib import contextmanager
from unittest.mock import patch
import pytest
from pdf_ocr_hotfolder.service import (
PreflightError,
check_preflight,
is_ghostscript_broken,
ocrmypdf_checks_gs_always,
)
@contextmanager
def _env(gs_version: str, ocrmypdf_version: str):
"""Binaries vorhanden, Ghostscript- und ocrmypdf-Version vorgegeben."""
with patch("pdf_ocr_hotfolder.service.shutil.which", return_value="/usr/bin/fake"), \
patch("pdf_ocr_hotfolder.service.detect_ghostscript_version",
return_value=gs_version), \
patch("pdf_ocr_hotfolder.service.detect_ocrmypdf_version",
return_value=ocrmypdf_version):
yield
@pytest.mark.parametrize("version,expected", [
# Betroffene Versionen
("10.0.0", True),
("10.00.0", True),
("10.01.0", True),
("10.01.1", True),
("10.01.2", True),
("10.02.0", True),
# Sichere Versionen
("10.02.1", False),
("10.03.0", False),
("10.04.0", False),
("11.0.0", False),
("9.56.1", False), # Debian 11 / Ubuntu 22.04
("9.55.0", False),
# Edge cases
("", False),
(None, False),
("garbage", False),
])
def test_is_ghostscript_broken(version, expected) -> None:
assert is_ghostscript_broken(version) is expected
@pytest.mark.parametrize("version,expected", [
("16.13.0", True), # der Pin aus v0.6.0, der den Ausfall ausgeloest hat
("16.0.0", True),
("15.4.4", True),
("17.0.0", False), # ab hier steckt die Pruefung hinter output_type
("17.4.1", False), # unser Pin
("17.12.1", False),
("18.0.0", False),
(None, False), # unbekannt -> kein Fehlalarm
("", False),
("garbage", False),
])
def test_ocrmypdf_checks_gs_always(version, expected) -> None:
assert ocrmypdf_checks_gs_always(version) is expected
# ---------------- Preflight: ocrmypdf 17.x (unser Pin) ----------------
def test_broken_gs_skip_text_without_pdfa_passes_on_ocrmypdf_17() -> None:
"""Der Debian-12-Standardfall: ohne PDF/A fasst ocrmypdf 17 gs nicht an.
Das ist die Default-Config (skip_text=true, pdfa_level="") auf Debian 12 —
sie muss laufen, sonst startet keine einzige Bestandsinstanz mehr.
"""
with _env("10.0.0", "17.4.1"):
check_preflight(pdfa_level="", skip_text=True) # darf nicht werfen
def test_broken_gs_with_pdfa_and_skip_text_fails() -> None:
"""Mit pdfa_level + skip_text + kaputtem GS → PreflightError."""
with _env("10.0.0", "17.4.1"):
with pytest.raises(PreflightError, match="Ghostscript 10.0.0"):
check_preflight(pdfa_level="2", skip_text=True)
def test_broken_gs_with_pdfa_without_skip_text_passes() -> None:
"""Ohne skip_text greift die ocrmypdf-Bedingung nicht — kein Abbruch."""
with _env("10.0.0", "17.4.1"):
check_preflight(pdfa_level="2", skip_text=False) # darf nicht werfen
def test_healthy_gs_with_pdfa_and_skip_text_passes() -> None:
"""Nicht betroffene GS-Version → nie ein Abbruch."""
with _env("10.02.1", "17.4.1"):
check_preflight(pdfa_level="2", skip_text=True) # darf nicht werfen
# ---------------- Preflight: ocrmypdf 16.x (der Ausfall aus v0.6.0) ----------------
def test_broken_gs_skip_text_without_pdfa_fails_on_ocrmypdf_16() -> None:
"""DER Fall, der in v0.6.0 durchrutschte.
ocrmypdf 16.13.0 + Ghostscript 10.0.0 + skip_text=true + pdfa_level="":
"Preflight ok", Dienst active — und jede PDF landete in error/.
Jetzt muss der Dienst beim START abbrechen.
"""
with _env("10.0.0", "16.13.0"):
with pytest.raises(PreflightError) as exc_info:
check_preflight(pdfa_level="", skip_text=True)
msg = str(exc_info.value)
assert "10.0.0" in msg
assert "16.13.0" in msg
def test_broken_gs_without_skip_text_passes_on_ocrmypdf_16() -> None:
"""skip_text=false → auch 16.x prüft Ghostscript nicht."""
with _env("10.0.0", "16.13.0"):
check_preflight(pdfa_level="", skip_text=False) # darf nicht werfen
def test_healthy_gs_passes_on_ocrmypdf_16() -> None:
"""Nicht betroffene GS-Version → auch mit 16.x kein Abbruch."""
with _env("10.02.1", "16.13.0"):
check_preflight(pdfa_level="", skip_text=True) # darf nicht werfen
# ---------------- Meldungstext ----------------
def test_error_message_names_both_remedies() -> None:
"""Der Admin muss aus der Meldung heraus handeln können.
Die Wege sind die **echten**: `pdfa_level = ""` (Default), `skip_text =
false` oder eine Distribution mit neuerem Ghostscript (Debian 13: 10.05.1).
Bis v0.7.0 stand hier „Ghostscript aus bookworm-backports" als Weg 1 — das
konnte nie funktionieren, denn bookworm-backports führt gar kein
Ghostscript-Paket (am echten Paketindex verifiziert: 2606 Pakete,
`ghostscript` nicht darunter). Der Rat darf nicht zurückkommen.
"""
# Fall A: ocrmypdf 16.x ohne PDF/A — Weg 1 ist hier, ocrmypdf anzuheben.
with _env("10.0.0", "16.13.0"):
with pytest.raises(PreflightError) as exc_info:
check_preflight(pdfa_level="", skip_text=True)
msg_no_pdfa = str(exc_info.value)
assert "ocrmypdf >= 17" in msg_no_pdfa, "Weg 1: ocrmypdf anheben"
assert "skip_text = false" in msg_no_pdfa, "Weg 2: skip_text abschalten"
assert "Debian 13" in msg_no_pdfa, "Weg 3: neuere Distribution"
# Fall B: PDF/A gewünscht — Weg 1 ist hier, PDF/A aufzugeben.
with _env("10.0.0", "17.4.1"):
with pytest.raises(PreflightError) as exc_info:
check_preflight(pdfa_level="2", skip_text=True)
msg_pdfa = str(exc_info.value)
assert 'pdfa_level = ""' in msg_pdfa, "Weg 1: PDF/A abschalten"
assert "skip_text = false" in msg_pdfa, "Weg 2: skip_text abschalten"
assert "Debian 13" in msg_pdfa, "Weg 3: neuere Distribution"
# Und in keiner der beiden Meldungen der widerlegte Backports-Rat.
for msg in (msg_no_pdfa, msg_pdfa):
assert "apt install -t bookworm-backports" not in msg
assert "sources.list.d/bookworm-backports.list" not in msg
# Erwähnt werden darf es — aber nur als ausdrückliche Absage.
assert "NICHT" in msg and "kein Ghostscript-Paket" in msg
def test_default_config_pdfa_level_is_empty() -> None:
"""Default-Config der Beispiel-Datei soll pdfa_level='' enthalten (Issue #3)."""
from pathlib import Path
import tomllib
cfg_path = Path(__file__).parent.parent / "config.example.toml"
with cfg_path.open("rb") as f:
data = tomllib.load(f)
assert data["ocr"]["pdfa_level"] == "", \
"config.example.toml muss pdfa_level='' als sicheren Default haben"
# ---------------- Der Dienst muss beim START abbrechen ----------------
def test_run_once_aborts_on_ocrmypdf_16_with_broken_gs(tmp_config) -> None:
"""Abbruch beim Start statt Totalausfall bei der ersten Datei."""
from pdf_ocr_hotfolder.service import HotfolderService
assert tmp_config.ocr.skip_text is True
assert tmp_config.ocr.pdfa_level == ""
service = HotfolderService(tmp_config)
try:
with _env("10.0.0", "16.13.0"):
with pytest.raises(PreflightError):
service.run_once()
finally:
service._executor.shutdown(wait=False)
def test_check_config_returns_2_on_ocrmypdf_16_with_broken_gs(
tmp_path, tmp_config, monkeypatch, capsys) -> None:
"""--check-config meldet den Zustand als Fehler (Exit 2) — auch mitten im Update."""
import sys
from pdf_ocr_hotfolder.__main__ import CHECK_ERROR, main
cfg_file = tmp_path / "cfg.toml"
cfg_file.write_text(f"""
[paths]
incoming = "{tmp_config.paths.incoming}"
outgoing = "{tmp_config.paths.outgoing}"
working = "{tmp_config.paths.working}"
error = "{tmp_config.paths.error}"
[ocr]
skip_text = true
pdfa_level = ""
""")
monkeypatch.setattr(sys, "argv",
["pdf-ocr-hotfolder", "--config", str(cfg_file),
"--check-config"])
with _env("10.0.0", "16.13.0"):
assert main() == CHECK_ERROR
assert "Ghostscript" in capsys.readouterr().err