Files
breakpilot-compliance/consent-tester/services/scan_matrix_summary.py
T
Benjamin Admin 881e9c28de feat(consent-tester): /scan-matrix echt — Profil je Engine + Per-Engine-Summary (Phase 1.2)
- _scanner_run reicht browser_profile an run_consent_test durch (statt Single-Chromium-Shim)
- neue scan_matrix_summary.matrix_scan_dict: ConsentTestResult -> schlanke
  Matrix-dict-Form (phases fuer _extract_dimensions + kompakter `summary`:
  cookies_before_consent/after_reject, reject_respected-Heuristik [keine
  Verstoesse UND kein neuer Tracker], surface, screenshot)
- multi_browser_scanner._run_one hebt summary + engine + is_mobile an die
  Zeile, verwirft die vollen Cookie-Listen (JSONB-Persistenz schlank)
- consent_scanner: _ctx_base mit Mobile-Device-Emulation (iPhone-Profil ->
  echtes Mobile-Viewport/Touch), alle 5 new_context auf **_ctx_base
- Tests: test_scan_matrix_summary (6) inkl. _extract_dimensions-Vertrag

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 22:46:42 +02:00

86 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Kompakte Per-Engine-Projektion eines ConsentTestResult für die Browser-Matrix.
Die Matrix braucht NICHT die volle `/scan`-Antwort — nur die Felder, die je
Browser-Zeile angezeigt + persistiert werden: Cookies vor Consent / nach
Ablehnen, ob „Ablehnen" respektiert wurde, Oberflächen-Signale, Screenshot.
Bewusst schlank gehalten, damit der in `banner_result.browser_matrix` (JSONB)
persistierte Block klein bleibt — 6 Engines × voller Cookie-Liste + Screenshot
würde sonst schnell mehrere MB groß (BMW: ~780 Cookies je Phase).
"""
from __future__ import annotations
from typing import Any
# Cookie-Namen je Phase deckeln — die Matrix zeigt Zahlen + Beispiele, nicht
# die volle Liste (die steckt im textbasierten Cookie-Modul).
_NAME_CAP = 40
_TRACK_CAP = 20
def _vdict(v: Any) -> dict:
"""Violation (dataclass/obj/dict) → serialisierbares dict."""
if isinstance(v, dict):
return v
return getattr(v, "__dict__", None) or {"text": str(v)}
def matrix_scan_dict(result: Any) -> dict:
"""`ConsentTestResult` → dict in der Form, die
`multi_browser_scanner._extract_dimensions` liest (phases/banner_checks)
plus ein kompakter `summary`-Block für Frontend + Persistenz.
Defensiv via getattr — funktioniert auch, falls der Scanner mal ein
bereits serialisiertes dict liefert (dann greifen die Defaults)."""
before = list(getattr(result, "before_cookies", []) or [])
after = list(getattr(result, "reject_cookies", []) or [])
before_violations = list(getattr(result, "before_violations", []) or [])
reject_violations = list(getattr(result, "reject_violations", []) or [])
reject_new_tracking = list(getattr(result, "reject_new_tracking", []) or [])
banner_text_violations = list(
getattr(result, "banner_text_violations", []) or [])
provider = getattr(result, "banner_provider", "") or ""
summary = {
"cookies_before_consent": len(before),
"cookies_after_reject": len(after),
"cookies_before_names": before[:_NAME_CAP],
"cookies_after_reject_names": after[:_NAME_CAP],
# „Ablehnen respektiert" = nach dem Klick auf „Ablehnen" keine Verstöße
# UND kein neuer Tracker. Verbleibende essentielle Cookies (z.B. die
# gespeicherte Consent-Entscheidung selbst) sind erlaubt → NICHT über
# die reine Cookie-Zahl bewerten (sonst False Positive).
"reject_respected": (len(reject_violations) == 0
and len(reject_new_tracking) == 0),
"reject_new_tracking": reject_new_tracking[:_TRACK_CAP],
"banner_detected": bool(getattr(result, "banner_detected", False)),
"banner_provider": provider,
"banner_screenshot_b64": getattr(result, "banner_screenshot_b64", "") or "",
"surface": {
"has_impressum_link": bool(
getattr(result, "banner_has_impressum_link", False)),
"has_dse_link": bool(
getattr(result, "banner_has_dse_link", False)),
"banner_text_issues": len(banner_text_violations),
},
"violations": {
"before_consent": len(before_violations),
"after_reject": len(reject_violations),
"banner_text": len(banner_text_violations),
},
}
return {
"banner_detected": bool(getattr(result, "banner_detected", False)),
"banner_provider": provider,
# Minimal-Form für _extract_dimensions (nur cookies-Listen + violations):
"phases": {
"before_consent": {"cookies": before},
"after_reject": {"cookies": after},
},
"banner_checks": {
"violations": [_vdict(v) for v in banner_text_violations],
},
"summary": summary,
}