Files
breakpilot-compliance/backend-compliance/compliance/tests/test_agent_outputs_impressum.py
T
Benjamin Admin e21984e0ad feat(agent): strukturierte Ergebnis-Tabs — Impressum (Phase 1)
Der Compliance-Check legt zusätzlich einen strukturierten v3-AgentOutput
pro Thema in result.agent_outputs ab (additiv; B18-HTML + Firehose-Mail
bleiben unangetastet). Frontend: standardisiertes Ergebnis-Tab statt
Firehose — Impressum-Tab (AgentResultTab) + "Alle Checks (roh)" (ChecklistView).

- backend: _agent_outputs.py ruft den registrierten v3-ImpressumAgent,
  gewired in _orchestrator nach B18, surfaced via _phase_f_persist.
- frontend: AgentResultView (aus AgentSlotCard extrahiert, DRY),
  AgentResultTab, ComplianceResultTabs; ComplianceCheckTab 490->391 Zeilen.
- Tests: backend 2 passed, frontend 2 passed; tsc 0 neue Fehler; check-loc 0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 18:32:06 +02:00

81 lines
2.9 KiB
Python

"""Phase 1: der Compliance-Check legt einen strukturierten v3-AgentOutput
pro Thema in state['agent_outputs'][topic] ab (für die Ergebnis-Tabs).
Offline + deterministisch: die einzige LLM-Stelle im registrierten
ImpressumAgent ist `validate_present` (Semantic-Validator) — gemockt.
"""
from __future__ import annotations
import asyncio
import pytest
# Impressum mit Name+Anschrift+Geschäftsführer, aber OHNE Email, Telefon,
# Handelsregister, USt-IdNr → erzwingt Findings (alle mit norm + action).
IMPRESSUM_TEXT = (
"Angaben gemäß § 5 TMG\n\n"
"Musterfirma GmbH\n"
"Musterstraße 1\n"
"12345 Berlin\n\n"
"Vertreten durch den Geschäftsführer: Max Mustermann\n\n"
"Wir betreiben einen Online-Shop für Musterprodukte aller Art. "
"Weitere Informationen finden Sie auf unserer Website.\n"
)
@pytest.fixture(autouse=True)
def _llm_offline(monkeypatch):
"""Semantic-Validator (LLM) neutralisieren → rein deterministischer Lauf."""
async def _no_validate(*_a, **_kw):
return {}
monkeypatch.setattr(
"compliance.services.specialist_agents.impressum.agent.validate_present",
_no_validate,
raising=False,
)
def test_run_agent_outputs_populates_structured_impressum():
from compliance.api.agent_check._agent_outputs import run_agent_outputs
state = {
"doc_texts": {"impressum": IMPRESSUM_TEXT},
"profile_dict": {"has_online_shop": True},
"req": None,
"extracted_profile": {"company_name": "Musterfirma GmbH"},
"site_name": "musterfirma.de",
"domain": "musterfirma.de",
}
asyncio.run(run_agent_outputs(state))
out = (state.get("agent_outputs") or {}).get("impressum")
assert out is not None, "impressum AgentOutput muss im Ergebnis liegen"
assert out["agent"] == "impressum"
assert isinstance(out["findings"], list)
# Unvollständiges Impressum → mind. ein Finding, jedes mit Abstellmaßnahme
assert out["findings"], "erwarte Findings für ein unvollständiges Impressum"
assert all(f.get("action") for f in out["findings"]), \
"jedes Finding trägt eine Abstellmaßnahme (action)"
# Auditfest: Rechtsgrundlage + Quelle je Finding
assert all(f.get("norm") for f in out["findings"])
assert all(f.get("sources") for f in out["findings"])
# Aggregat-Felder fürs Speedometer vorberechnet
assert out["mc_total"] >= 1
# Linter-sauber: keine verbotenen Disclaimer-Begriffe im Output
blob = str(out).lower()
for term in ("rechtssicher", "garantiert", "gesetzeskonform"):
assert term not in blob
def test_run_agent_outputs_skips_short_text():
from compliance.api.agent_check._agent_outputs import run_agent_outputs
state = {
"doc_texts": {"impressum": "zu kurz"},
"profile_dict": {},
"req": None,
}
asyncio.run(run_agent_outputs(state))
assert not (state.get("agent_outputs") or {}).get("impressum")