feat: Ollama und Modell vor dem Start prüfen, kurze Meldung statt Traceback

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-11 19:20:46 +02:00
commit 6aa4887f22
3 changed files with 105 additions and 11 deletions

View file

@ -1,4 +1,4 @@
"""Shared fixtures: yt-dlp is always replaced by fakes (no network)."""
"""Shared fixtures: yt-dlp and Ollama are always replaced by fakes (no network)."""
import json
import shutil
import subprocess
@ -7,6 +7,17 @@ from pathlib import Path
import pytest
@pytest.fixture(autouse=True)
def ollama_available(request, monkeypatch):
"""Skip the Ollama server check; tests marked real_ollama_check test it."""
if "real_ollama_check" in request.keywords:
return
import glossar_vorschlag
import rede_korrigieren
for module in (rede_korrigieren, glossar_vorschlag):
monkeypatch.setattr(module, "check_ollama", lambda model: None)
@pytest.fixture
def fake_yt_dlp():
"""Factory for a subprocess.run stand-in.

View file

@ -2,6 +2,7 @@
import io
import json
import sys
import urllib.request
import pytest
@ -63,7 +64,7 @@ def run_main(monkeypatch, *args):
def test_main_filters_and_formats(raw_file, glossary, monkeypatch, capsys):
model = fake_model(ENTRIES)
monkeypatch.setattr(gv.urllib.request, "urlopen", model)
monkeypatch.setattr(urllib.request, "urlopen", model)
run_main(monkeypatch, "-i", raw_file, "-g", glossary, "-m", "test:1b")
draft = raw_file.with_name("rede.glossar.entwurf.txt").read_text(encoding="utf-8")
@ -84,12 +85,12 @@ def test_main_filters_and_formats(raw_file, glossary, monkeypatch, capsys):
assert "Vermutete Hörfehler: 2 (1 sicher, 1 unsicher)" in out
# Second run uses the cache
monkeypatch.setattr(gv.urllib.request, "urlopen", no_model_calls)
monkeypatch.setattr(urllib.request, "urlopen", no_model_calls)
run_main(monkeypatch, "-i", raw_file, "-g", glossary, "-m", "test:1b")
def test_correction_changes_are_commented_out(raw_file, glossary, tmp_path, monkeypatch):
monkeypatch.setattr(gv.urllib.request, "urlopen", fake_model(ENTRIES[:2]))
monkeypatch.setattr(urllib.request, "urlopen", fake_model(ENTRIES[:2]))
corrected = tmp_path / "rede.korrigiert.txt"
# Corrected without glossary: "Kremmel" became "Kremml" (a model error)
corrected.write_text(
@ -110,7 +111,7 @@ def test_correction_changes_are_commented_out(raw_file, glossary, tmp_path, monk
def test_drafted_entries_are_valid_glossary(raw_file, monkeypatch, tmp_path):
monkeypatch.setattr(gv.urllib.request, "urlopen", fake_model(ENTRIES[:2]))
monkeypatch.setattr(urllib.request, "urlopen", fake_model(ENTRIES[:2]))
out = tmp_path / "entwurf.txt"
run_main(monkeypatch, "-i", raw_file, "-o", out)
assert rk.read_glossary(out) == ([], []) # nothing active
@ -126,7 +127,7 @@ def test_unreadable_model_answer(raw_file, monkeypatch, tmp_path, capsys):
def urlopen(request, timeout=None):
return io.BytesIO(json.dumps({"message": {"content": "kein JSON"}}).encode())
monkeypatch.setattr(gv.urllib.request, "urlopen", urlopen)
monkeypatch.setattr(urllib.request, "urlopen", urlopen)
run_main(monkeypatch, "-i", raw_file, "-o", tmp_path / "e.txt")
assert "unlesbar" in capsys.readouterr().out

View file

@ -566,3 +566,52 @@ def test_cli_time_errors(sample_copy, monkeypatch, capsys, args, message):
run_main(monkeypatch, *args)
assert exc.value.code == 2
assert message in capsys.readouterr().err
# === Ollama availability ===
def tags_urlopen(*names):
body = json.dumps({"models": [{"name": n} for n in names]}).encode("utf-8")
return fake_urlopen(body)
@pytest.mark.real_ollama_check
@pytest.mark.parametrize("model", ["qwen3:32b", "llama3"])
def test_check_ollama_model_installed(monkeypatch, model):
monkeypatch.setattr(rk.urllib.request, "urlopen", tags_urlopen("qwen3:32b", "llama3:latest"))
rk.check_ollama(model)
@pytest.mark.real_ollama_check
def test_check_ollama_model_missing(monkeypatch):
monkeypatch.setattr(rk.urllib.request, "urlopen", tags_urlopen("qwen3:8b"))
with pytest.raises(SystemExit, match="ollama pull qwen3:32b"):
rk.check_ollama("qwen3:32b")
def refused(request, timeout=None):
raise rk.urllib.error.URLError(ConnectionRefusedError(111, "Connection refused"))
@pytest.mark.real_ollama_check
def test_main_ollama_down_stops_before_download(fake_yt_dlp, yt_dlp_installed, monkeypatch):
run = fake_yt_dlp(VIDEO, vtt=VTT)
monkeypatch.setattr(subprocess, "run", run)
monkeypatch.setattr(rk.urllib.request, "urlopen", refused)
with pytest.raises(SystemExit, match="Ollama nicht erreichbar.*systemctl start ollama"):
run_main(monkeypatch, "-u", URL)
assert run.calls == []
def test_correct_chunk_ollama_gone_midway(monkeypatch):
monkeypatch.setattr(rk.urllib.request, "urlopen", refused)
with pytest.raises(SystemExit, match="Ollama nicht erreichbar"):
rk.correct_chunk("Text.", 1, "system")
def test_ollama_open_passes_http_errors(monkeypatch):
def not_found(request, timeout=None):
raise rk.urllib.error.HTTPError(rk.OLLAMA_URL, 404, "Not Found", {}, None)
monkeypatch.setattr(rk.urllib.request, "urlopen", not_found)
with pytest.raises(rk.urllib.error.HTTPError):
rk.ollama_open(rk.OLLAMA_URL)