df7d83134b
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>
139 lines
5.0 KiB
Python
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
|