Files
breakpilot-compliance/backend-compliance/tests/test_banner_routes.py
Benjamin Admin b7c1a5da1a
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
feat: Consent-Service Module nach Compliance migriert (DSR, E-Mail-Templates, Legal Docs, Banner)
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>
2026-03-05 00:36:24 +01:00

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