"""Tests für Legacy-Warnungen und unbekannte Config-Keys. Zwei stille Fallen: - [ocr].timeout bedeutet seit 0.4.0 Sekunden pro SEITE (vorher Gesamtlauf) - unbekannte Keys (Tippfehler!) wurden beim Laden stumm verworfen """ from __future__ import annotations import logging import sys from pathlib import Path from unittest.mock import patch from pdf_ocr_hotfolder.config import ( config_warnings, legacy_warnings, load_config, unknown_key_warnings, ) _PATHS = """ [paths] incoming = "/tmp/in" outgoing = "/tmp/out" working = "/tmp/work" error = "/tmp/err" """ def _write(tmp_path: Path, extra: str = "") -> Path: cfg = tmp_path / "config.toml" cfg.write_text(_PATHS + extra) return cfg # ---------------- Legacy: [ocr].timeout ---------------- def test_legacy_timeout_warns(tmp_path: Path) -> None: """Ein Altwert (Gesamt-Timeout 1800) muss deutlich benannt werden.""" cfg = load_config(_write(tmp_path, "\n[ocr]\ntimeout = 1800\n")) warnings = legacy_warnings(cfg) assert len(warnings) == 1 assert "timeout" in warnings[0] assert "PRO SEITE" in warnings[0] assert "300" in warnings[0] def test_timeout_at_threshold_warns(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, "\n[ocr]\ntimeout = 900\n")) assert legacy_warnings(cfg) def test_sane_timeout_does_not_warn(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, "\n[ocr]\ntimeout = 300\n")) assert legacy_warnings(cfg) == [] def test_default_config_has_no_warnings(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path)) assert config_warnings(cfg) == [] # ---------------- Legacy: [ocr].pdfa_level ---------------- def test_pdfa_level_warns_about_ghostscript(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, '\n[ocr]\npdfa_level = "2"\n')) warnings = legacy_warnings(cfg) assert len(warnings) == 1 assert "pdfa_level" in warnings[0] assert "Ghostscript" in warnings[0] assert "10.02.0" in warnings[0] def test_empty_pdfa_level_does_not_warn(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, '\n[ocr]\npdfa_level = ""\n')) assert legacy_warnings(cfg) == [] def test_both_legacy_warnings_together(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, '\n[ocr]\ntimeout = 1800\npdfa_level = "1"\n')) assert len(legacy_warnings(cfg)) == 2 # ---------------- Unbekannte Keys ---------------- def test_typo_key_is_collected(tmp_path: Path) -> None: """`langauges` statt `languages` darf nicht mehr stumm verschwinden.""" cfg = load_config(_write(tmp_path, '\n[ocr]\nlangauges = "deu"\n')) assert cfg.unknown_keys == ["[ocr].langauges"] assert "[ocr].langauges" in unknown_key_warnings(cfg)[0] # Der Rest wird weiterhin normal geladen assert cfg.ocr.languages == "deu+eng" def test_known_keys_are_not_reported(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, '\n[ocr]\nlanguages = "deu"\njobs = 2\n')) assert cfg.unknown_keys == [] assert cfg.ocr.languages == "deu" def test_unknown_keys_in_all_sections(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, """ [ocr] foo = 1 [output] bar = "x" [verapdf] baz = true [upload.folder] qux = "" [upload.nextcloud] quux = "" [upload.sftp] corge = 0 [notify.email] grault = "" [logging] level = "INFO" garply = 1 """)) assert cfg.unknown_keys == [ "[ocr].foo", "[output].bar", "[verapdf].baz", "[upload.folder].qux", "[upload.nextcloud].quux", "[upload.sftp].corge", "[notify.email].grault", "[logging].garply", ] def test_unknown_top_level_key_is_reported(tmp_path: Path) -> None: """Ein Key ausserhalb jeder Sektion (z.B. vergessene Sektionszeile).""" cfg_file = tmp_path / "config.toml" cfg_file.write_text('log_level = "DEBUG"\n' + _PATHS) cfg = load_config(cfg_file) assert cfg.unknown_keys == ["log_level"] def test_unknown_section_is_reported(tmp_path: Path) -> None: cfg = load_config(_write(tmp_path, '\n[ocrr]\nlanguages = "deu"\n\n[upload.ftp]\nhost = "x"\n')) assert "[ocrr]" in cfg.unknown_keys assert "[upload.ftp]" in cfg.unknown_keys def test_unknown_key_inside_paths(tmp_path: Path) -> None: """Ein zusätzlicher Key direkt in [paths] wird ebenfalls gemeldet.""" cfg_file = tmp_path / "config.toml" cfg_file.write_text(_PATHS + 'archive = "/tmp/a"\n') cfg = load_config(cfg_file) assert cfg.unknown_keys == ["[paths].archive"] def test_load_config_works_without_logging(tmp_path: Path) -> None: """load_config() darf nichts loggen müssen — Tests rufen sie direkt auf.""" cfg_file = _write(tmp_path, '\n[ocr]\nlangauges = "deu"\n') with patch("logging.Logger.warning") as warn: cfg = load_config(cfg_file) warn.assert_not_called() assert cfg.unknown_keys # ---------------- Warnungen beim Dienststart ---------------- def _argv(monkeypatch, cfg_file: Path, *extra: str) -> None: monkeypatch.setattr(sys, "argv", ["pdf-ocr-hotfolder", "--config", str(cfg_file), *extra]) def test_warnings_are_logged_on_service_start(tmp_path: Path, tmp_config, monkeypatch, caplog) -> None: """Beim normalen Start landen die Warnungen im Log (nicht nur im Check).""" 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] timeout = 1800 langauges = "deu" """) _argv(monkeypatch, cfg_file, "--once") from pdf_ocr_hotfolder.__main__ import main with caplog.at_level(logging.WARNING, logger="pdf_ocr_hotfolder.__main__"), \ patch("pdf_ocr_hotfolder.service.shutil.which", return_value="/usr/bin/fake"): assert main() == 0 text = caplog.text assert "PRO SEITE" in text assert "[ocr].langauges" in text