Files
Benjamin Admin df7d83134b feat(agent): migrate compliance-check results to banner + documents (M1-M5)
After a compliance-check run finishes, the user can now apply the
extracted vendor inventory directly to their own:

  - CookieBanner config (admin /sdk/einwilligungen)
  - Cookie-Policy / VVT-Register / Privacy-Policy templates
    (admin /sdk/document-generator)

Backend:
  - migration_to_banner.py: vendor list -> CookieBannerConfig with
    ESSENTIAL/PERFORMANCE/PERSONALIZATION/EXTERNAL_MEDIA buckets +
    review flags (broken opt-out URLs, missing expiry, no cookies listed)
  - migration_to_document.py: vendor list -> pre-fills for 3 doc
    templates, recipient-type aware (INTERNAL/GROUP/PROCESSOR/CONTROLLER)
  - agent_migration_routes.py: GET /banner-preview, /document-preview,
    /summary keyed on check_id
  - compliance_audit_log: new check_payloads table persists cmp_vendors +
    extracted_profile so the preview survives an app restart
  - tests: 9 mapper units + 4 endpoint integration tests

Frontend:
  - MigrationPanel.tsx: modal showing banner-config diff + document
    pre-fills, plus links into the existing editors
  - ComplianceCheckTab.tsx: replaces standalone audit link with the
    panel; net -3 lines, stays at the 500-cap

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:06:28 +02:00

139 lines
5.0 KiB
Python

"""
Unit tests for vendor → CookieBannerConfig and vendor → Document pre-fill
mappers (M1 + M2 of the customer-banner migration feature).
"""
from __future__ import annotations
import pytest
from compliance.services.migration_to_banner import (
build_banner_config, map_category,
)
from compliance.services.migration_to_document import build_document_prefills
@pytest.fixture()
def sample_vendors() -> list[dict]:
return [
{
"name": "BMW AG",
"category": "necessary",
"recipient_type": "INTERNAL",
"purpose": "Sicherstellung der Grundfunktionen",
"cookies": [{"name": "JSESSIONID", "expiry": "Session"}],
},
{
"name": "Google Analytics",
"category": "statistics",
"recipient_type": "PROCESSOR",
"purpose": "Reichweitenmessung",
"country": "US",
"opt_out_url": "https://tools.google.com/dlpage/gaoptout",
"opt_out_ok": True,
"privacy_policy_url": "https://policies.google.com/privacy",
"cookies": [
{"name": "_ga", "expiry": "2 Jahre", "is_third_party": True},
{"name": "_gid", "expiry": "1 Tag", "is_third_party": True},
],
},
{
"name": "YouTube",
"category": "marketing",
"recipient_type": "PROCESSOR",
"purpose": "Eingebettete Videos",
"cookies": [],
},
{
"name": "Broken Pixel",
"category": "marketing",
"recipient_type": "PROCESSOR",
"purpose": "Werbung",
"opt_out_url": "https://example.com/optout",
"opt_out_ok": False,
"opt_out_status": 404,
"cookies": [{"name": "_pix", "expiry": ""}],
},
]
def test_map_category_youtube_routes_to_external_media():
assert map_category("marketing", "YouTube Player") == "EXTERNAL_MEDIA"
def test_map_category_unknown_falls_back_to_personalization():
assert map_category("weird-cat", "Some Vendor") == "PERSONALIZATION"
def test_map_category_necessary_is_essential():
assert map_category("necessary", "JSESSIONID") == "ESSENTIAL"
def test_build_banner_config_buckets_categories(sample_vendors):
out = build_banner_config(sample_vendors, site_name="bmw.de")
cats = {c["id"]: c for c in out["config"]["categories"]}
assert "ESSENTIAL" in cats
assert "PERFORMANCE" in cats
assert any(c["name"] == "_ga" for c in cats["PERFORMANCE"]["cookies"])
# YouTube vendor had no cookies → it should not pollute EXTERNAL_MEDIA
# but should produce a WARNING flag for the missing list
assert any(f["vendor"] == "YouTube" and f["issue"] == "no_cookies_listed"
for f in out["flags"])
def test_build_banner_config_flags_broken_opt_out(sample_vendors):
out = build_banner_config(sample_vendors, site_name="bmw.de")
errors = [f for f in out["flags"] if f["level"] == "ERROR"]
assert any(f["issue"] == "broken_opt_out" and f["vendor"] == "Broken Pixel"
for f in errors)
def test_build_banner_config_summary_counts(sample_vendors):
out = build_banner_config(sample_vendors, site_name="bmw.de")
s = out["summary"]
assert s["vendors_total"] == 4
assert s["vendors_with_no_cookies"] == 1
assert s["cookies_total"] == 4 # JSESSIONID + _ga + _gid + _pix
def test_build_document_prefills_emits_all_three_templates(sample_vendors):
out = build_document_prefills(
sample_vendors,
extracted_profile={
"company_profile": {
"companyName": "BMW AG",
"headquartersStreet": "Petuelring 130",
"headquartersZip": "80809",
"headquartersCity": "Muenchen",
"dpoEmail": "datenschutz@bmw.de",
},
},
site_name="bmw.de",
)
assert set(out.keys()) == {"cookie_policy", "vvt_register", "privacy_policy"}
cp = out["cookie_policy"]
assert cp["templateType"] == "cookie_policy"
assert "BMW AG" in cp["initialContent"]
assert "Google Analytics" in cp["initialContent"]
assert "Petuelring 130" in cp["initialContent"]
def test_vvt_register_marks_third_country_for_us_processor(sample_vendors):
out = build_document_prefills(sample_vendors, site_name="bmw.de")
acts = out["vvt_register"]["activities"]
ga = next(a for a in acts if a["name"] == "Google Analytics")
rcat = ga["recipientCategories"][0]
assert rcat["type"] == "PROCESSOR"
assert rcat["country"] == "US"
assert rcat["isThirdCountry"] is True
def test_privacy_policy_section_groups_by_recipient_type(sample_vendors):
out = build_document_prefills(sample_vendors, site_name="bmw.de")
body = out["privacy_policy"]["initialContent"]
assert "Eigene Verarbeitung" in body
assert "Auftragsverarbeiter" in body
# BMW AG (INTERNAL) must appear under Eigene, not under Auftragsverarbeiter
internal_block = body.split("### Auftragsverarbeiter")[0]
assert "BMW AG" in internal_block