"""Unit-Tests für die getierte 3-Status-Auswertung (_tiered_eval). Deckt ab: Status-Logik (inkl. kein-LM → ERFÜLLT, UNBESTIMMT bei nicht bewertbar), Empfehlungs-Sammlung, EMBEDDING/LLM-Routing (gemockt) und den Reproduzierbarkeits- Cache. Embedding/LLM werden gemockt — kein Netzwerk.""" import asyncio from compliance.services.specialist_agents.dse import _tiered_eval as te # ---- reine Status-Logik ------------------------------------------------- def test_status_no_lm_is_erfuellt(): assert te._status([]) == "ERFÜLLT" def test_status_all_met_erfuellt(): assert te._status([True, True]) == "ERFÜLLT" def test_status_none_met_fehlt(): assert te._status([False, False]) == "FEHLT" def test_status_partial_teilweise(): assert te._status([True, False]) == "TEILWEISE" def test_status_any_none_unbestimmt(): assert te._status([True, None]) == "UNBESTIMMT" # ---- evaluate_tiered (Embedding/LLM gemockt) ---------------------------- def _crit(text, tier, dm="EMBEDDING"): return {"criterion": text, "compliance_tier": tier, "decision_method": dm, "legal_basis": "x"} class _Doc: def __init__(self, text): self.text = text def test_evaluate_partial_with_recommendation(monkeypatch): crits = [_crit("Zwecke genannt", "LEGAL_MINIMUM"), _crit("Speicherdauer genannt", "LEGAL_MINIMUM"), _crit("tabellarisch ausgewiesen", "BEST_PRACTICE")] async def fake_embed(texts, ctx, thr): return {"Zwecke genannt": True, "Speicherdauer genannt": False, "tabellarisch ausgewiesen": False} monkeypatch.setattr(te, "_embed_present", fake_embed) out = asyncio.run(te.evaluate_tiered("C1", crits, {"hash": "h"}, _Doc("x" * 200))) assert out["status"] == "TEILWEISE" assert out["lm_met"] == 1 and out["lm_total"] == 2 assert len(out["recommendations"]) == 1 assert out["recommendations"][0]["tier"] == "BEST_PRACTICE" def test_evaluate_no_lm_is_erfuellt_with_recs(monkeypatch): crits = [_crit("Bildsymbole", "OPTIONAL"), _crit("Legende", "OPTIONAL")] async def fake_embed(texts, ctx, thr): return {t: False for t in texts} monkeypatch.setattr(te, "_embed_present", fake_embed) out = asyncio.run(te.evaluate_tiered("C2", crits, {"hash": "h"}, _Doc("x" * 200))) assert out["status"] == "ERFÜLLT" assert out["lm_total"] == 0 assert len(out["recommendations"]) == 2 def test_evaluate_llm_criterion_routed(monkeypatch): crits = [_crit("Speicherdauer hinreichend nachvollziehbar", "LEGAL_MINIMUM", dm="LLM")] async def fake_llm(cid, idx, crit, doc, dh): return True monkeypatch.setattr(te, "_llm_met", fake_llm) out = asyncio.run(te.evaluate_tiered("C3", crits, {"hash": "h"}, _Doc("x" * 200))) assert out["status"] == "ERFÜLLT" and out["lm_total"] == 1 def test_evaluate_unbestimmt_when_embed_unavailable(monkeypatch): crits = [_crit("Zwecke genannt", "LEGAL_MINIMUM")] async def fake_embed(texts, ctx, thr): return {t: None for t in texts} # Embedding-Service down monkeypatch.setattr(te, "_embed_present", fake_embed) out = asyncio.run(te.evaluate_tiered("C4", crits, {"hash": "h"}, _Doc("x" * 200))) assert out["status"] == "UNBESTIMMT" # ---- Reproduzierbarkeits-Cache ----------------------------------------- def test_cache_roundtrip(monkeypatch, tmp_path): monkeypatch.setattr(te, "_CACHE_DB", str(tmp_path / "cache.db")) assert te._cache_get("k1") is None te._cache_put("k1", True) te._cache_put("k2", False) assert te._cache_get("k1") is True assert te._cache_get("k2") is False