All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 36s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s
5-Phasen-Migration: Go consent-service Proxies durch native Python/FastAPI ersetzt. Phase 1 — DSR (Betroffenenrechte): 6 Tabellen, 30 Endpoints, Frontend-API umgestellt Phase 2 — E-Mail-Templates: 5 Tabellen, 20 Endpoints, neues Frontend, SDK_STEPS erweitert Phase 3 — Legal Documents Extension: User Consents, Audit Log, Cookie-Kategorien Phase 4 — Banner Consent: Device-Consents, Site-Configs, Kategorien, Vendors Phase 5 — Cleanup: DSR-Proxy aus main.py entfernt, Frontend-URLs aktualisiert 148 neue Tests (50 + 47 + 26 + 25), alle bestanden. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
315 lines
11 KiB
Python
315 lines
11 KiB
Python
"""
|
|
Tests for Banner Consent routes — device-based cookie consents.
|
|
"""
|
|
|
|
import uuid
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
from classroom_engine.database import Base, get_db
|
|
from compliance.db.banner_models import (
|
|
BannerConsentDB, BannerConsentAuditLogDB,
|
|
BannerSiteConfigDB, BannerCategoryConfigDB, BannerVendorConfigDB,
|
|
)
|
|
from compliance.api.banner_routes import router as banner_router
|
|
|
|
SQLALCHEMY_DATABASE_URL = "sqlite:///./test_banner.db"
|
|
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
|
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
|
|
TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
|
|
HEADERS = {"X-Tenant-ID": TENANT_ID}
|
|
|
|
app = FastAPI()
|
|
app.include_router(banner_router, prefix="/api/compliance")
|
|
|
|
|
|
def override_get_db():
|
|
db = TestingSessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
app.dependency_overrides[get_db] = override_get_db
|
|
client = TestClient(app)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_db():
|
|
Base.metadata.create_all(bind=engine)
|
|
yield
|
|
Base.metadata.drop_all(bind=engine)
|
|
|
|
|
|
# =============================================================================
|
|
# Helpers
|
|
# =============================================================================
|
|
|
|
def _create_site(site_id="example.com"):
|
|
r = client.post("/api/compliance/banner/admin/sites", json={
|
|
"site_id": site_id,
|
|
"site_name": "Example",
|
|
"banner_title": "Cookies",
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 200, r.text
|
|
return r.json()
|
|
|
|
|
|
def _record_consent(site_id="example.com", fingerprint="fp-123", categories=None):
|
|
r = client.post("/api/compliance/banner/consent", json={
|
|
"site_id": site_id,
|
|
"device_fingerprint": fingerprint,
|
|
"categories": categories or ["necessary"],
|
|
"ip_address": "1.2.3.4",
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 200, r.text
|
|
return r.json()
|
|
|
|
|
|
# =============================================================================
|
|
# Public Consent Endpoints
|
|
# =============================================================================
|
|
|
|
class TestRecordConsent:
|
|
def test_record_consent(self):
|
|
c = _record_consent()
|
|
assert c["site_id"] == "example.com"
|
|
assert c["device_fingerprint"] == "fp-123"
|
|
assert c["categories"] == ["necessary"]
|
|
assert c["ip_hash"] is not None
|
|
assert c["expires_at"] is not None
|
|
|
|
def test_upsert_consent(self):
|
|
c1 = _record_consent(categories=["necessary"])
|
|
c2 = _record_consent(categories=["necessary", "analytics"])
|
|
assert c1["id"] == c2["id"]
|
|
assert c2["categories"] == ["necessary", "analytics"]
|
|
|
|
def test_different_devices(self):
|
|
c1 = _record_consent(fingerprint="device-A")
|
|
c2 = _record_consent(fingerprint="device-B")
|
|
assert c1["id"] != c2["id"]
|
|
|
|
|
|
class TestGetConsent:
|
|
def test_get_existing(self):
|
|
_record_consent()
|
|
r = client.get("/api/compliance/banner/consent?site_id=example.com&device_fingerprint=fp-123", headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["has_consent"] is True
|
|
|
|
def test_get_nonexistent(self):
|
|
r = client.get("/api/compliance/banner/consent?site_id=example.com&device_fingerprint=unknown", headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["has_consent"] is False
|
|
|
|
|
|
class TestWithdrawConsent:
|
|
def test_withdraw(self):
|
|
c = _record_consent()
|
|
r = client.delete(f"/api/compliance/banner/consent/{c['id']}", headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["success"] is True
|
|
|
|
# Verify gone
|
|
r2 = client.get("/api/compliance/banner/consent?site_id=example.com&device_fingerprint=fp-123", headers=HEADERS)
|
|
assert r2.json()["has_consent"] is False
|
|
|
|
def test_withdraw_not_found(self):
|
|
r = client.delete(f"/api/compliance/banner/consent/{uuid.uuid4()}", headers=HEADERS)
|
|
assert r.status_code == 404
|
|
|
|
|
|
class TestExportConsent:
|
|
def test_export(self):
|
|
_record_consent()
|
|
r = client.get(
|
|
"/api/compliance/banner/consent/export?site_id=example.com&device_fingerprint=fp-123",
|
|
headers=HEADERS,
|
|
)
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data["consents"]) == 1
|
|
assert len(data["audit_trail"]) >= 1
|
|
assert data["audit_trail"][0]["action"] == "consent_given"
|
|
|
|
|
|
# =============================================================================
|
|
# Site Config Admin
|
|
# =============================================================================
|
|
|
|
class TestSiteConfig:
|
|
def test_create_site(self):
|
|
s = _create_site()
|
|
assert s["site_id"] == "example.com"
|
|
assert s["banner_title"] == "Cookies"
|
|
|
|
def test_create_duplicate(self):
|
|
_create_site()
|
|
r = client.post("/api/compliance/banner/admin/sites", json={
|
|
"site_id": "example.com",
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 409
|
|
|
|
def test_list_sites(self):
|
|
_create_site("site-a.com")
|
|
_create_site("site-b.com")
|
|
r = client.get("/api/compliance/banner/admin/sites", headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert len(r.json()) == 2
|
|
|
|
def test_update_site(self):
|
|
_create_site()
|
|
r = client.put("/api/compliance/banner/admin/sites/example.com", json={
|
|
"banner_title": "Neue Cookies",
|
|
"dsb_name": "Max DSB",
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["banner_title"] == "Neue Cookies"
|
|
assert r.json()["dsb_name"] == "Max DSB"
|
|
|
|
def test_update_not_found(self):
|
|
r = client.put("/api/compliance/banner/admin/sites/nonexistent.com", json={
|
|
"banner_title": "X",
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 404
|
|
|
|
def test_delete_site(self):
|
|
_create_site()
|
|
r = client.delete("/api/compliance/banner/admin/sites/example.com", headers=HEADERS)
|
|
assert r.status_code == 204
|
|
|
|
def test_get_config_default(self):
|
|
r = client.get("/api/compliance/banner/config/unknown-site", headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["banner_title"] == "Cookie-Einstellungen"
|
|
assert r.json()["categories"] == []
|
|
|
|
def test_get_config_with_categories(self):
|
|
_create_site()
|
|
client.post("/api/compliance/banner/admin/sites/example.com/categories", json={
|
|
"category_key": "necessary",
|
|
"name_de": "Notwendig",
|
|
"is_required": True,
|
|
}, headers=HEADERS)
|
|
|
|
r = client.get("/api/compliance/banner/config/example.com", headers=HEADERS)
|
|
data = r.json()
|
|
assert len(data["categories"]) == 1
|
|
assert data["categories"][0]["category_key"] == "necessary"
|
|
|
|
|
|
# =============================================================================
|
|
# Categories Admin
|
|
# =============================================================================
|
|
|
|
class TestCategories:
|
|
def test_create_category(self):
|
|
_create_site()
|
|
r = client.post("/api/compliance/banner/admin/sites/example.com/categories", json={
|
|
"category_key": "analytics",
|
|
"name_de": "Analyse",
|
|
"name_en": "Analytics",
|
|
"sort_order": 20,
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["category_key"] == "analytics"
|
|
assert r.json()["name_de"] == "Analyse"
|
|
|
|
def test_list_categories(self):
|
|
_create_site()
|
|
client.post("/api/compliance/banner/admin/sites/example.com/categories", json={
|
|
"category_key": "marketing", "name_de": "Marketing", "sort_order": 30,
|
|
}, headers=HEADERS)
|
|
client.post("/api/compliance/banner/admin/sites/example.com/categories", json={
|
|
"category_key": "necessary", "name_de": "Notwendig", "sort_order": 0, "is_required": True,
|
|
}, headers=HEADERS)
|
|
|
|
r = client.get("/api/compliance/banner/admin/sites/example.com/categories", headers=HEADERS)
|
|
data = r.json()
|
|
assert len(data) == 2
|
|
assert data[0]["category_key"] == "necessary" # sorted by sort_order
|
|
|
|
def test_delete_category(self):
|
|
_create_site()
|
|
cr = client.post("/api/compliance/banner/admin/sites/example.com/categories", json={
|
|
"category_key": "temp", "name_de": "Temp",
|
|
}, headers=HEADERS)
|
|
cat_id = cr.json()["id"]
|
|
r = client.delete(f"/api/compliance/banner/admin/categories/{cat_id}")
|
|
assert r.status_code == 204
|
|
|
|
def test_site_not_found(self):
|
|
r = client.post("/api/compliance/banner/admin/sites/nonexistent/categories", json={
|
|
"category_key": "x", "name_de": "X",
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 404
|
|
|
|
|
|
# =============================================================================
|
|
# Vendors Admin
|
|
# =============================================================================
|
|
|
|
class TestVendors:
|
|
def test_create_vendor(self):
|
|
_create_site()
|
|
r = client.post("/api/compliance/banner/admin/sites/example.com/vendors", json={
|
|
"vendor_name": "Google Analytics",
|
|
"category_key": "analytics",
|
|
"cookie_names": ["_ga", "_gid"],
|
|
"retention_days": 730,
|
|
}, headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["vendor_name"] == "Google Analytics"
|
|
assert r.json()["cookie_names"] == ["_ga", "_gid"]
|
|
|
|
def test_list_vendors(self):
|
|
_create_site()
|
|
client.post("/api/compliance/banner/admin/sites/example.com/vendors", json={
|
|
"vendor_name": "GA", "category_key": "analytics",
|
|
}, headers=HEADERS)
|
|
r = client.get("/api/compliance/banner/admin/sites/example.com/vendors", headers=HEADERS)
|
|
assert len(r.json()) == 1
|
|
|
|
def test_delete_vendor(self):
|
|
_create_site()
|
|
cr = client.post("/api/compliance/banner/admin/sites/example.com/vendors", json={
|
|
"vendor_name": "Temp", "category_key": "analytics",
|
|
}, headers=HEADERS)
|
|
vid = cr.json()["id"]
|
|
r = client.delete(f"/api/compliance/banner/admin/vendors/{vid}")
|
|
assert r.status_code == 204
|
|
|
|
|
|
# =============================================================================
|
|
# Stats
|
|
# =============================================================================
|
|
|
|
class TestStats:
|
|
def test_stats_empty(self):
|
|
r = client.get("/api/compliance/banner/admin/stats/example.com", headers=HEADERS)
|
|
assert r.status_code == 200
|
|
assert r.json()["total_consents"] == 0
|
|
|
|
def test_stats_with_data(self):
|
|
_record_consent(fingerprint="d1", categories=["necessary", "analytics"])
|
|
_record_consent(fingerprint="d2", categories=["necessary"])
|
|
_record_consent(fingerprint="d3", categories=["necessary", "analytics", "marketing"])
|
|
|
|
r = client.get("/api/compliance/banner/admin/stats/example.com", headers=HEADERS)
|
|
data = r.json()
|
|
assert data["total_consents"] == 3
|
|
assert data["category_acceptance"]["necessary"]["count"] == 3
|
|
assert data["category_acceptance"]["analytics"]["count"] == 2
|
|
assert data["category_acceptance"]["marketing"]["count"] == 1
|