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:
parent
82489ee83c
commit
6aa4887f22
3 changed files with 105 additions and 11 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue