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>
117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
"""
|
|
Integration test for the /compliance/agent/migration/* endpoints.
|
|
|
|
Simulates a finished compliance-check run by persisting cmp_vendors +
|
|
extracted_profile via the sidecar audit log, then exercises the FastAPI
|
|
TestClient against banner-preview / document-preview / summary.
|
|
|
|
This is the M5 BMW-scenario in miniature: realistic ePaaS-shaped vendor
|
|
records (BMW INTERNAL + 2 third-party PROCESSOR) feed through to a
|
|
ready-to-apply banner config and pre-filled documents.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture()
|
|
def app_client(monkeypatch):
|
|
# Isolate the sidecar SQLite so this test never races with /data prod DB
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("COMPLIANCE_AUDIT_DB", os.path.join(tmp, "audit.db"))
|
|
# Build a minimal app — avoid importing the full main.py which pulls in
|
|
# smtp_sender / weasyprint / pydantic-v1 modules not relevant here.
|
|
from fastapi import FastAPI
|
|
from compliance.api.agent_migration_routes import router
|
|
app = FastAPI()
|
|
app.include_router(router, prefix="/api")
|
|
return TestClient(app)
|
|
|
|
|
|
@pytest.fixture()
|
|
def seeded_check_id():
|
|
"""Persist a fake compliance check so the migration routes have data."""
|
|
from compliance.services.compliance_audit_log import record_check_payload, record_check_run
|
|
cid = "bmw-test-check-001"
|
|
vendors = [
|
|
{
|
|
"name": "BMW AG", "category": "necessary",
|
|
"recipient_type": "INTERNAL",
|
|
"purpose": "Grundfunktionen + Login",
|
|
"cookies": [{"name": "JSESSIONID", "expiry": "Session"}],
|
|
},
|
|
{
|
|
"name": "Adobe Analytics", "category": "statistics",
|
|
"recipient_type": "PROCESSOR", "country": "US",
|
|
"purpose": "Reichweitenmessung",
|
|
"opt_out_url": "https://adobe.com/opt-out", "opt_out_ok": True,
|
|
"privacy_policy_url": "https://adobe.com/privacy",
|
|
"cookies": [{"name": "s_cc", "expiry": "1 Tag",
|
|
"is_third_party": True}],
|
|
},
|
|
{
|
|
"name": "YouTube", "category": "marketing",
|
|
"recipient_type": "PROCESSOR", "country": "US",
|
|
"purpose": "Videos",
|
|
"cookies": [{"name": "VISITOR_INFO1_LIVE", "expiry": "6 Monate",
|
|
"is_third_party": True}],
|
|
},
|
|
]
|
|
record_check_run(
|
|
check_id=cid, tenant_id="t1", site_name="bmw.de",
|
|
base_domain="bmw.de", doc_count=4,
|
|
scorecard={"totals": {"pct": 75, "passed": 30, "failed": 10,
|
|
"total": 40, "skipped": 0}},
|
|
)
|
|
record_check_payload(check_id=cid, vendors=vendors,
|
|
profile={"companyName": "BMW AG",
|
|
"headquartersStreet": "Petuelring 130",
|
|
"headquartersZip": "80809",
|
|
"headquartersCity": "Muenchen",
|
|
"dpoEmail": "datenschutz@bmw.de"})
|
|
return cid
|
|
|
|
|
|
def test_banner_preview_returns_valid_config(app_client, seeded_check_id):
|
|
r = app_client.get(f"/api/compliance/agent/migration/{seeded_check_id}/banner-preview")
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["summary"]["vendors_total"] == 3
|
|
cat_ids = {c["id"] for c in body["config"]["categories"]}
|
|
assert "ESSENTIAL" in cat_ids
|
|
assert "EXTERNAL_MEDIA" in cat_ids # YouTube
|
|
# BMW AG (INTERNAL, cookies present) should not raise any flags
|
|
assert not any(f.get("vendor") == "BMW AG" for f in body["flags"])
|
|
|
|
|
|
def test_document_preview_includes_all_three_templates(app_client, seeded_check_id):
|
|
r = app_client.get(f"/api/compliance/agent/migration/{seeded_check_id}/document-preview")
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["vendor_count"] == 3
|
|
assert set(body["templates"].keys()) == {
|
|
"cookie_policy", "vvt_register", "privacy_policy",
|
|
}
|
|
assert "BMW AG" in body["templates"]["cookie_policy"]["initialContent"]
|
|
assert "Petuelring" in body["templates"]["cookie_policy"]["initialContent"]
|
|
|
|
|
|
def test_summary_returns_overview(app_client, seeded_check_id):
|
|
r = app_client.get(f"/api/compliance/agent/migration/{seeded_check_id}/summary")
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["company_name"] == "BMW AG"
|
|
assert body["vendor_count"] == 3
|
|
assert body["site_name"] == "bmw.de"
|
|
assert "cookie_policy" in body["available_templates"]
|
|
|
|
|
|
def test_unknown_check_id_returns_404(app_client):
|
|
r = app_client.get("/api/compliance/agent/migration/nope-not-there/banner-preview")
|
|
assert r.status_code == 404
|