38a347a82a
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 9s
CI / validate-canonical-controls (push) Successful in 12s
CI / loc-budget (push) Successful in 24s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m11s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 24s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
AGB v2 (decision_method routing, 71%FP->~0) + DSE v3 (4-layer, recovered from container) + Architektur-Tab into /sdk/agent live path. Incl CI robustness (detect-changes.sh + PR-head checkout) + security (hardcoded Qdrant key removed, gitleaks allowlist). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
84 lines
3.4 KiB
Python
84 lines
3.4 KiB
Python
"""Unit-Tests der Prüfer-Library. Embedding + LLM gemockt → kein Netzwerk."""
|
|
import asyncio
|
|
|
|
import compliance.services.llm_cascade as cascade_mod
|
|
import compliance.services.mc_embedding_matcher as emb_mod
|
|
from compliance.services.checkers.base import (
|
|
ControlSpec,
|
|
DecisionMethod,
|
|
DocContext,
|
|
VerificationMethod,
|
|
)
|
|
from compliance.services.checkers.embedding_checker import EmbeddingChecker
|
|
from compliance.services.checkers.llm_checker import LLMChecker
|
|
from compliance.services.checkers.reference_checker import ReferenceChecker
|
|
|
|
|
|
def _run(coro):
|
|
return asyncio.run(coro)
|
|
|
|
|
|
def test_reference_present_and_absent():
|
|
rc = ReferenceChecker()
|
|
spec = ControlSpec("data_protection", VerificationMethod.REFERENCE,
|
|
DecisionMethod.LINK_RESOLVER,
|
|
patterns=[r"datenschutz(erkl|bestimmung|hinweis)"])
|
|
r = _run(rc.check(spec, DocContext(
|
|
text="Details in unserer Datenschutzerklaerung: https://x.de/datenschutz")))
|
|
assert r.present is True
|
|
assert r.detail.get("link", "").startswith("https://")
|
|
r2 = _run(rc.check(spec, DocContext(text="Keine Angabe zum Datenschutz-Thema.")))
|
|
assert r2.present is False
|
|
|
|
|
|
def test_embedding_threshold(monkeypatch):
|
|
monkeypatch.setattr(emb_mod, "DIM", 3, raising=False)
|
|
monkeypatch.setattr(emb_mod, "_chunk_text", lambda t: [t], raising=False)
|
|
|
|
async def _embed(texts):
|
|
return [[1.0, 0.0, 0.0] for _ in texts]
|
|
|
|
monkeypatch.setattr(emb_mod, "_embed_texts", _embed, raising=False)
|
|
ec = EmbeddingChecker()
|
|
spec = ControlSpec("scope_t", VerificationMethod.CONTENT, DecisionMethod.EMBEDDING,
|
|
paraphrases=["Geltungsbereich"], embed_threshold=0.58)
|
|
monkeypatch.setattr(emb_mod, "_cosine", lambda a, b: 0.90, raising=False)
|
|
r = _run(ec.check(spec, DocContext(text="x" * 200)))
|
|
assert r.present is True and r.confidence >= 0.58
|
|
monkeypatch.setattr(emb_mod, "_cosine", lambda a, b: 0.20, raising=False)
|
|
r2 = _run(ec.check(spec, DocContext(text="x" * 200)))
|
|
assert r2.present is False
|
|
|
|
|
|
def test_embedding_offline_returns_none(monkeypatch):
|
|
async def _boom(texts):
|
|
raise ConnectionError("embedding-service down")
|
|
|
|
monkeypatch.setattr(emb_mod, "_embed_texts", _boom, raising=False)
|
|
ec = EmbeddingChecker()
|
|
spec = ControlSpec("scope_off", VerificationMethod.CONTENT, DecisionMethod.EMBEDDING,
|
|
paraphrases=["x"], embed_threshold=0.6)
|
|
r = _run(ec.check(spec, DocContext(text="y" * 200)))
|
|
assert r.present is None # fail-safe
|
|
|
|
|
|
def test_llm_present_and_absent(monkeypatch):
|
|
lc = LLMChecker()
|
|
spec = ControlSpec("delivery_timeframe", VerificationMethod.CONTENT, DecisionMethod.LLM,
|
|
topic_regex=r"liefer", question="Konkrete Lieferfrist?")
|
|
doc = DocContext(text=("1. Lieferung\nDie Ware wird innerhalb von 2 Werktagen "
|
|
"geliefert.\n") * 4)
|
|
|
|
async def _erfuellt(system, user, **kw):
|
|
return {"text": '{"verdict":"ERFUELLT","zitat":"2 Werktagen","begruendung":"x"}',
|
|
"source": "qwen", "confidence": 0.7}
|
|
|
|
monkeypatch.setattr(cascade_mod, "call_with_cascade", _erfuellt, raising=False)
|
|
assert _run(lc.check(spec, doc)).present is True
|
|
|
|
async def _fehlt(system, user, **kw):
|
|
return {"text": '{"verdict":"FEHLT"}', "source": "qwen"}
|
|
|
|
monkeypatch.setattr(cascade_mod, "call_with_cascade", _fehlt, raising=False)
|
|
assert _run(lc.check(spec, doc)).present is False
|