test(dse): adopt canonical v3 tests + criteria/GT/validation
CI / detect-changes (pull_request) Failing after 5s
CI / branch-name (pull_request) Successful in 2s
CI / guardrail-integrity (pull_request) Failing after 4s
CI / secret-scan (pull_request) Failing after 4s
CI / dep-audit (pull_request) Failing after 2s
CI / sbom-scan (pull_request) Failing after 2s
CI / build-sha-integrity (pull_request) Failing after 3s
CI / validate-canonical-controls (pull_request) Failing after 3s
CI / loc-budget (pull_request) Has been skipped
CI / go-lint (pull_request) Has been skipped
CI / python-lint (pull_request) Has been skipped
CI / nodejs-lint (pull_request) Has been skipped
CI / nodejs-build (pull_request) Has been skipped
CI / test-go (pull_request) Has been skipped
CI / iace-gt-coverage (pull_request) Has been skipped
CI / test-python-backend (pull_request) Has been skipped
CI / test-python-document-crawler (pull_request) Has been skipped
CI / test-python-dsms-gateway (pull_request) Has been skipped
CI / detect-changes (pull_request) Failing after 5s
CI / branch-name (pull_request) Successful in 2s
CI / guardrail-integrity (pull_request) Failing after 4s
CI / secret-scan (pull_request) Failing after 4s
CI / dep-audit (pull_request) Failing after 2s
CI / sbom-scan (pull_request) Failing after 2s
CI / build-sha-integrity (pull_request) Failing after 3s
CI / validate-canonical-controls (pull_request) Failing after 3s
CI / loc-budget (pull_request) Has been skipped
CI / go-lint (pull_request) Has been skipped
CI / python-lint (pull_request) Has been skipped
CI / nodejs-lint (pull_request) Has been skipped
CI / nodejs-build (pull_request) Has been skipped
CI / test-go (pull_request) Has been skipped
CI / iace-gt-coverage (pull_request) Has been skipped
CI / test-python-backend (pull_request) Has been skipped
CI / test-python-document-crawler (pull_request) Has been skipped
CI / test-python-dsms-gateway (pull_request) Has been skipped
Replace the reconstructed test_dse_agent.py with the canonical version and add the companion unit tests (classification_gate, embedding_recall) covering the recovered v3 modules. Include the curated DSE criteria backup + changelog (legal-note rationale per control), the v1 validation writeup, and the multi-company DSE ground-truth fulltexts (elli/eto/mercedes/safetykon) used for threshold calibration. 18 DSE tests green offline (DB/embedding/LLM stubbed). dev-only, no deploy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""DSE Embedding-Recall — deterministische semantische Schicht (gecacht).
|
||||
|
||||
Testet die reine Logik OHNE Embedding-Service: Cache-Treffer-Pfad,
|
||||
Schwellen-Filter, Kandidaten-Schnitt, Reachability-Guard. Das Einbetten selbst
|
||||
(Embedding-Service) ist Integration und wird auf macmini/Prod validiert.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
import compliance.services.specialist_agents.dse._embedding_recall as er
|
||||
|
||||
|
||||
_TEXT = ("Datenschutzerklaerung der Muster GmbH. " * 20) # > 100 Zeichen
|
||||
|
||||
|
||||
def _seed_cache(tmp_path, scores: dict[str, float]) -> str:
|
||||
p = tmp_path / "dse_embed_cache.json"
|
||||
p.write_text(json.dumps({er._doc_hash(_TEXT): scores}))
|
||||
return str(p)
|
||||
|
||||
|
||||
def test_doc_hash_deterministic():
|
||||
# feste Funktion: gleicher Text → gleicher Hash (Reproduzierbarkeit)
|
||||
assert er._doc_hash(_TEXT) == er._doc_hash(_TEXT)
|
||||
assert er._doc_hash("a") != er._doc_hash("b")
|
||||
|
||||
|
||||
def test_cache_hit_threshold_filter(tmp_path, monkeypatch):
|
||||
# Cache-Treffer: kein Embedding-Service nötig. Nur Scores >= Schwelle UND
|
||||
# in den Kandidaten werden zurückgegeben.
|
||||
scores = {"DATA-1": 0.71, "DATA-2": 0.60, "AUTH-3": 0.68, "SEC-4": 0.50}
|
||||
monkeypatch.setenv("DSE_EMBED_CACHE", _seed_cache(tmp_path, scores))
|
||||
monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "dse_embed_cache.json"))
|
||||
|
||||
cands = ["DATA-1", "DATA-2", "AUTH-3", "SEC-4"]
|
||||
out = asyncio.run(er.embedding_recall(_TEXT, cands, threshold=0.65))
|
||||
# >=0.65: DATA-1 (0.71), AUTH-3 (0.68). NICHT DATA-2 (0.60), SEC-4 (0.50).
|
||||
assert out == {"DATA-1", "AUTH-3"}
|
||||
|
||||
|
||||
def test_cache_hit_candidate_intersection(tmp_path, monkeypatch):
|
||||
# Nur Kandidaten (durchgefallene Controls) zählen — andere ignoriert.
|
||||
scores = {"DATA-1": 0.90, "DATA-2": 0.90}
|
||||
monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "c.json"))
|
||||
(tmp_path / "c.json").write_text(json.dumps({er._doc_hash(_TEXT): scores}))
|
||||
out = asyncio.run(er.embedding_recall(_TEXT, ["DATA-1"], threshold=0.65))
|
||||
assert out == {"DATA-1"} # DATA-2 nicht in Kandidaten
|
||||
|
||||
|
||||
def test_empty_inputs():
|
||||
assert asyncio.run(er.embedding_recall("zu kurz", ["X"])) == set()
|
||||
assert asyncio.run(er.embedding_recall(_TEXT, [])) == set()
|
||||
|
||||
|
||||
def test_service_down_returns_empty(tmp_path, monkeypatch):
|
||||
# Kein Cache + Service nicht erreichbar → leer (deterministischer Layer trägt),
|
||||
# KEIN Hang.
|
||||
monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "none.json"))
|
||||
|
||||
async def _unreachable(timeout=2.0):
|
||||
return False
|
||||
monkeypatch.setattr(er, "_embedding_reachable", _unreachable)
|
||||
out = asyncio.run(er.embedding_recall(_TEXT, ["DATA-1"]))
|
||||
assert out == set()
|
||||
Reference in New Issue
Block a user