A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
253 lines
11 KiB
Python
253 lines
11 KiB
Python
"""
|
|
Tests fuer die GDPR UI-Funktionalitaet (Art. 15-21).
|
|
|
|
Testet:
|
|
- GDPR-Rechte im Legal Modal (Art. 15-21)
|
|
- JavaScript-Funktionen fuer GDPR-Anfragen
|
|
- Consent Manager Integration
|
|
"""
|
|
|
|
import pytest
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
class TestGDPRUIStructure:
|
|
"""Tests fuer GDPR UI-Struktur im Legal Modal."""
|
|
|
|
@pytest.fixture
|
|
def studio_html(self):
|
|
"""Laedt studio.html fuer Tests."""
|
|
html_path = Path(__file__).parent.parent / "frontend" / "templates" / "studio.html"
|
|
if html_path.exists():
|
|
return html_path.read_text()
|
|
return None
|
|
|
|
def test_gdpr_section_exists(self, studio_html):
|
|
"""Test: GDPR-Bereich existiert im Legal Modal."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert 'id="legal-gdpr"' in studio_html, "GDPR-Bereich sollte im Legal Modal existieren"
|
|
|
|
def test_gdpr_section_has_title(self, studio_html):
|
|
"""Test: GDPR-Bereich hat Titel mit Art. 15-21 Referenz."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 15-21" in studio_html, "GDPR-Bereich sollte Art. 15-21 im Titel erwaehnen"
|
|
|
|
def test_art15_auskunftsrecht_exists(self, studio_html):
|
|
"""Test: Art. 15 (Auskunftsrecht) ist vorhanden."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 15" in studio_html, "Art. 15 (Auskunftsrecht) sollte vorhanden sein"
|
|
assert "Auskunft" in studio_html, "Auskunftsrecht-Button sollte vorhanden sein"
|
|
|
|
def test_art16_berichtigung_exists(self, studio_html):
|
|
"""Test: Art. 16 (Berichtigung) ist vorhanden."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 16" in studio_html, "Art. 16 (Berichtigung) sollte vorhanden sein"
|
|
assert "Berichtigung" in studio_html, "Berichtigung-Button sollte vorhanden sein"
|
|
|
|
def test_art17_loeschung_exists(self, studio_html):
|
|
"""Test: Art. 17 (Loeschung) ist vorhanden."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 17" in studio_html, "Art. 17 (Loeschung) sollte vorhanden sein"
|
|
assert "Löschung" in studio_html, "Loeschung-Button sollte vorhanden sein"
|
|
|
|
def test_art18_einschraenkung_exists(self, studio_html):
|
|
"""Test: Art. 18 (Einschraenkung der Verarbeitung) ist vorhanden."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 18" in studio_html, "Art. 18 (Einschraenkung) sollte vorhanden sein"
|
|
assert "Einschränkung" in studio_html, "Einschraenkung-Button sollte vorhanden sein"
|
|
|
|
def test_art19_mitteilungspflicht_exists(self, studio_html):
|
|
"""Test: Art. 19 (Mitteilungspflicht) ist vorhanden."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 19" in studio_html, "Art. 19 (Mitteilungspflicht) sollte vorhanden sein"
|
|
assert "Mitteilung" in studio_html, "Mitteilungspflicht sollte erwaehnt werden"
|
|
|
|
def test_art20_datenuebertragbarkeit_exists(self, studio_html):
|
|
"""Test: Art. 20 (Datenuebertragbarkeit) ist vorhanden."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 20" in studio_html, "Art. 20 (Datenuebertragbarkeit) sollte vorhanden sein"
|
|
assert "Datenübertragbarkeit" in studio_html or "exportieren" in studio_html.lower(), \
|
|
"Datenexport-Button sollte vorhanden sein"
|
|
|
|
def test_art21_widerspruch_exists(self, studio_html):
|
|
"""Test: Art. 21 (Widerspruchsrecht) ist vorhanden."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Art. 21" in studio_html, "Art. 21 (Widerspruchsrecht) sollte vorhanden sein"
|
|
assert "Widerspruch" in studio_html, "Widerspruch-Button sollte vorhanden sein"
|
|
|
|
def test_consent_manager_section_exists(self, studio_html):
|
|
"""Test: Consent Manager Bereich existiert."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert "Einwilligungen verwalten" in studio_html, \
|
|
"Consent Manager Bereich sollte vorhanden sein"
|
|
|
|
|
|
class TestGDPRJavaScriptFunctions:
|
|
"""Tests fuer GDPR JavaScript-Funktionen."""
|
|
|
|
@pytest.fixture
|
|
def studio_js(self):
|
|
"""Laedt studio.js fuer Tests."""
|
|
js_path = Path(__file__).parent.parent / "frontend" / "static" / "js" / "studio.js"
|
|
if js_path.exists():
|
|
return js_path.read_text()
|
|
return None
|
|
|
|
def test_request_data_export_function_exists(self, studio_js):
|
|
"""Test: requestDataExport Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function requestDataExport" in studio_js or "async function requestDataExport" in studio_js, \
|
|
"requestDataExport Funktion sollte existieren"
|
|
|
|
def test_request_data_correction_function_exists(self, studio_js):
|
|
"""Test: requestDataCorrection Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function requestDataCorrection" in studio_js or "async function requestDataCorrection" in studio_js, \
|
|
"requestDataCorrection Funktion (Art. 16) sollte existieren"
|
|
|
|
def test_request_data_deletion_function_exists(self, studio_js):
|
|
"""Test: requestDataDeletion Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function requestDataDeletion" in studio_js or "async function requestDataDeletion" in studio_js, \
|
|
"requestDataDeletion Funktion (Art. 17) sollte existieren"
|
|
|
|
def test_request_processing_restriction_function_exists(self, studio_js):
|
|
"""Test: requestProcessingRestriction Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function requestProcessingRestriction" in studio_js or \
|
|
"async function requestProcessingRestriction" in studio_js, \
|
|
"requestProcessingRestriction Funktion (Art. 18) sollte existieren"
|
|
|
|
def test_request_data_download_function_exists(self, studio_js):
|
|
"""Test: requestDataDownload Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function requestDataDownload" in studio_js or "async function requestDataDownload" in studio_js, \
|
|
"requestDataDownload Funktion (Art. 20) sollte existieren"
|
|
|
|
def test_request_processing_objection_function_exists(self, studio_js):
|
|
"""Test: requestProcessingObjection Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function requestProcessingObjection" in studio_js or \
|
|
"async function requestProcessingObjection" in studio_js, \
|
|
"requestProcessingObjection Funktion (Art. 21) sollte existieren"
|
|
|
|
def test_show_consent_manager_function_exists(self, studio_js):
|
|
"""Test: showConsentManager Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function showConsentManager" in studio_js, \
|
|
"showConsentManager Funktion sollte existieren"
|
|
|
|
def test_open_settings_modal_function_exists(self, studio_js):
|
|
"""Test: openSettingsModal Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function openSettingsModal" in studio_js, \
|
|
"openSettingsModal Funktion sollte existieren"
|
|
|
|
def test_open_legal_modal_function_exists(self, studio_js):
|
|
"""Test: openLegalModal Funktion existiert."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
assert "function openLegalModal" in studio_js, \
|
|
"openLegalModal Funktion sollte existieren"
|
|
|
|
def test_gdpr_functions_have_user_feedback(self, studio_js):
|
|
"""Test: GDPR-Funktionen geben Benutzer-Feedback (alert/confirm)."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
# Suche nach GDPR Functions Block
|
|
gdpr_match = re.search(r'// GDPR FUNCTIONS.*?// Load saved cookie', studio_js, re.DOTALL)
|
|
if gdpr_match:
|
|
gdpr_content = gdpr_match.group(0)
|
|
assert "alert(" in gdpr_content or "confirm(" in gdpr_content, \
|
|
"GDPR-Funktionen sollten Benutzer-Feedback geben"
|
|
|
|
def test_deletion_requires_confirmation(self, studio_js):
|
|
"""Test: Loeschung erfordert Bestaetigung."""
|
|
if studio_js is None:
|
|
pytest.skip("studio.js nicht gefunden")
|
|
|
|
# Suche nach requestDataDeletion Funktion
|
|
deletion_match = re.search(r'function requestDataDeletion.*?\}', studio_js, re.DOTALL)
|
|
if deletion_match:
|
|
deletion_content = deletion_match.group(0)
|
|
assert "confirm(" in deletion_content, \
|
|
"Datenlöschung sollte Bestaetigung erfordern"
|
|
|
|
|
|
class TestGDPRActions:
|
|
"""Tests fuer GDPR-Action Buttons im HTML."""
|
|
|
|
@pytest.fixture
|
|
def studio_html(self):
|
|
"""Laedt studio.html fuer Tests."""
|
|
html_path = Path(__file__).parent.parent / "frontend" / "templates" / "studio.html"
|
|
if html_path.exists():
|
|
return html_path.read_text()
|
|
return None
|
|
|
|
def test_gdpr_actions_container_exists(self, studio_html):
|
|
"""Test: GDPR-Actions Container existiert."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
assert 'class="gdpr-actions"' in studio_html, "GDPR-Actions Container sollte existieren"
|
|
|
|
def test_gdpr_action_items_exist(self, studio_html):
|
|
"""Test: Mindestens 6 GDPR-Action Items existieren (Art. 15-21, ohne Art. 19 als Button)."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
gdpr_action_count = studio_html.count('class="gdpr-action"')
|
|
assert gdpr_action_count >= 6, \
|
|
f"Mindestens 6 GDPR-Action Items sollten existieren, gefunden: {gdpr_action_count}"
|
|
|
|
def test_deletion_button_has_danger_class(self, studio_html):
|
|
"""Test: Loeschung-Button hat btn-danger Klasse."""
|
|
if studio_html is None:
|
|
pytest.skip("studio.html nicht gefunden")
|
|
|
|
# Suche nach Loeschung-Button
|
|
assert 'onclick="requestDataDeletion()"' in studio_html, "Loeschung-Button sollte existieren"
|
|
# Der Button sollte btn-danger haben
|
|
assert 'btn-danger' in studio_html and 'requestDataDeletion' in studio_html, \
|
|
"Loeschung-Button sollte btn-danger Klasse haben"
|