fix: Restore all files lost during destructive rebase
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>
This commit is contained in:
254
backend/tests/test_studio_frontend.py
Normal file
254
backend/tests/test_studio_frontend.py
Normal file
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
Tests fuer das BreakPilot Studio Frontend (studio.py)
|
||||
|
||||
Testet CSS-Regeln und HTML-Struktur des Frontends.
|
||||
|
||||
Nach dem Refactoring (2024-12-16) werden CSS und JS aus separaten Dateien geladen:
|
||||
- CSS: frontend/static/css/studio.css
|
||||
- JS: frontend/static/js/studio.js
|
||||
- HTML: frontend/templates/studio.html
|
||||
"""
|
||||
import pytest
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
# Pfade zu den statischen Dateien
|
||||
FRONTEND_DIR = Path(__file__).parent.parent / "frontend"
|
||||
STATIC_CSS = FRONTEND_DIR / "static" / "css" / "studio.css"
|
||||
STATIC_JS = FRONTEND_DIR / "static" / "js" / "studio.js"
|
||||
TEMPLATE_HTML = FRONTEND_DIR / "templates" / "studio.html"
|
||||
|
||||
|
||||
class TestStudioRefactoringStructure:
|
||||
"""Tests fuer die refactored Dateistruktur."""
|
||||
|
||||
def test_css_file_exists(self):
|
||||
"""Testet, dass die CSS-Datei existiert."""
|
||||
assert STATIC_CSS.exists(), f"CSS-Datei nicht gefunden: {STATIC_CSS}"
|
||||
|
||||
def test_js_file_exists(self):
|
||||
"""Testet, dass die JS-Datei existiert."""
|
||||
assert STATIC_JS.exists(), f"JS-Datei nicht gefunden: {STATIC_JS}"
|
||||
|
||||
def test_html_template_exists(self):
|
||||
"""Testet, dass das HTML-Template existiert."""
|
||||
assert TEMPLATE_HTML.exists(), f"HTML-Template nicht gefunden: {TEMPLATE_HTML}"
|
||||
|
||||
def test_html_references_css(self):
|
||||
"""Testet, dass das HTML-Template die CSS-Datei referenziert."""
|
||||
html_content = TEMPLATE_HTML.read_text(encoding="utf-8")
|
||||
assert '/static/css/studio.css' in html_content, \
|
||||
"HTML-Template muss CSS-Datei referenzieren"
|
||||
|
||||
def test_html_references_js(self):
|
||||
"""Testet, dass das HTML-Template die JS-Datei referenziert."""
|
||||
html_content = TEMPLATE_HTML.read_text(encoding="utf-8")
|
||||
assert '/static/js/studio.js' in html_content, \
|
||||
"HTML-Template muss JS-Datei referenzieren"
|
||||
|
||||
def test_studio_py_loads_template(self):
|
||||
"""Testet, dass studio.py das Template laedt."""
|
||||
from frontend.studio import app_ui
|
||||
html = app_ui()
|
||||
# Nach Refactoring sollte HTML aus Template kommen
|
||||
assert '<!DOCTYPE html>' in html or 'BreakPilot' in html, \
|
||||
"studio.py muss HTML-Inhalt zurueckgeben"
|
||||
|
||||
|
||||
class TestStudioSidebarCSS:
|
||||
"""Tests fuer die Sidebar CSS-Eigenschaften."""
|
||||
|
||||
@pytest.fixture
|
||||
def studio_css(self):
|
||||
"""Laedt den CSS-Inhalt aus der separaten CSS-Datei.
|
||||
|
||||
Nach dem CSS-Refactoring sind die Styles modularisiert.
|
||||
Sidebar-Styles befinden sich in modules/admin/sidebar.css.
|
||||
"""
|
||||
# Primaer: Modularisierte Sidebar-Datei
|
||||
sidebar_css_path = FRONTEND_DIR / "static" / "css" / "modules" / "admin" / "sidebar.css"
|
||||
if sidebar_css_path.exists():
|
||||
return sidebar_css_path.read_text(encoding="utf-8")
|
||||
|
||||
# Fallback: Legacy studio.css
|
||||
return STATIC_CSS.read_text(encoding="utf-8")
|
||||
|
||||
@pytest.fixture
|
||||
def studio_html(self):
|
||||
"""Laedt den HTML-Inhalt aus dem Template."""
|
||||
return TEMPLATE_HTML.read_text(encoding="utf-8")
|
||||
|
||||
def test_sidebar_has_overflow_y_auto(self, studio_css):
|
||||
"""
|
||||
Testet, dass die Sidebar vertikal scrollbar ist.
|
||||
|
||||
Regression Test: Die Sidebar muss overflow-y: auto haben,
|
||||
damit GPU Start/Stop Buttons sichtbar sind wenn viele
|
||||
Kostenzeilen vorhanden sind.
|
||||
"""
|
||||
# Suche nach .sidebar CSS-Block
|
||||
sidebar_css_pattern = r'\.sidebar\s*\{[^}]*\}'
|
||||
sidebar_css_match = re.search(sidebar_css_pattern, studio_css, re.DOTALL)
|
||||
|
||||
assert sidebar_css_match is not None, "Sidebar CSS-Block nicht gefunden"
|
||||
|
||||
sidebar_css = sidebar_css_match.group(0)
|
||||
|
||||
# Pruefe dass overflow-y: auto vorhanden ist
|
||||
assert 'overflow-y: auto' in sidebar_css or 'overflow-y:auto' in sidebar_css, \
|
||||
"Sidebar muss overflow-y: auto haben fuer Scrollbarkeit"
|
||||
|
||||
def test_sidebar_has_overflow_x_hidden(self, studio_css):
|
||||
"""
|
||||
Testet, dass die Sidebar horizontal nicht scrollbar ist.
|
||||
"""
|
||||
sidebar_css_pattern = r'\.sidebar\s*\{[^}]*\}'
|
||||
sidebar_css_match = re.search(sidebar_css_pattern, studio_css, re.DOTALL)
|
||||
|
||||
assert sidebar_css_match is not None, "Sidebar CSS-Block nicht gefunden"
|
||||
|
||||
sidebar_css_block = sidebar_css_match.group(0)
|
||||
|
||||
# Pruefe dass overflow-x: hidden vorhanden ist
|
||||
assert 'overflow-x: hidden' in sidebar_css_block or 'overflow-x:hidden' in sidebar_css_block, \
|
||||
"Sidebar muss overflow-x: hidden haben"
|
||||
|
||||
def test_sidebar_does_not_have_overflow_hidden_only(self, studio_css):
|
||||
"""
|
||||
Testet, dass die Sidebar NICHT nur overflow: hidden hat.
|
||||
|
||||
Regression Test: overflow: hidden wuerde verhindern,
|
||||
dass Benutzer zu den GPU-Buttons scrollen koennen.
|
||||
"""
|
||||
sidebar_css_pattern = r'\.sidebar\s*\{[^}]*\}'
|
||||
sidebar_css_match = re.search(sidebar_css_pattern, studio_css, re.DOTALL)
|
||||
|
||||
assert sidebar_css_match is not None, "Sidebar CSS-Block nicht gefunden"
|
||||
|
||||
sidebar_css_block = sidebar_css_match.group(0)
|
||||
|
||||
# Pruefe dass nicht einfach "overflow: hidden" steht (ohne x/y Spezifikation)
|
||||
# Erlaubt sind: overflow-x: hidden, overflow-y: hidden, aber nicht nur "overflow: hidden"
|
||||
has_overflow_hidden_only = re.search(r'overflow\s*:\s*hidden', sidebar_css_block) and \
|
||||
not re.search(r'overflow-[xy]\s*:', sidebar_css_block)
|
||||
|
||||
assert not has_overflow_hidden_only, \
|
||||
"Sidebar darf nicht nur 'overflow: hidden' haben - GPU Buttons waeren nicht erreichbar"
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="vast.ai GPU UI not yet implemented in frontend template")
|
||||
class TestStudioGPUControls:
|
||||
"""Tests fuer die GPU-Kontrollelemente.
|
||||
|
||||
HINWEIS: Diese Tests sind fuer zukuenftige vast.ai GPU-UI-Elemente.
|
||||
Die Backend-API existiert bereits (infra/vast_power.py), aber die
|
||||
Frontend-UI-Elemente wurden noch nicht in studio.html implementiert.
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def studio_html(self):
|
||||
"""Laedt den HTML-Inhalt aus dem Template."""
|
||||
return TEMPLATE_HTML.read_text(encoding="utf-8")
|
||||
|
||||
def test_gpu_start_button_exists(self, studio_html):
|
||||
"""Testet, dass der GPU Start-Button existiert."""
|
||||
assert 'id="btn-vast-start"' in studio_html, \
|
||||
"GPU Start-Button (btn-vast-start) nicht gefunden"
|
||||
|
||||
def test_gpu_stop_button_exists(self, studio_html):
|
||||
"""Testet, dass der GPU Stop-Button existiert."""
|
||||
assert 'id="btn-vast-stop"' in studio_html, \
|
||||
"GPU Stop-Button (btn-vast-stop) nicht gefunden"
|
||||
|
||||
def test_gpu_status_badge_exists(self, studio_html):
|
||||
"""Testet, dass der GPU Status-Badge existiert."""
|
||||
assert 'id="vast-status-badge"' in studio_html, \
|
||||
"GPU Status-Badge nicht gefunden"
|
||||
|
||||
def test_gpu_buttons_are_in_sidebar(self, studio_html):
|
||||
"""
|
||||
Testet, dass die GPU-Buttons innerhalb der Sidebar sind.
|
||||
"""
|
||||
# Finde die Sidebar-Sektion
|
||||
sidebar_pattern = r'<aside[^>]*class="[^"]*sidebar[^"]*"[^>]*>.*?</aside>'
|
||||
sidebar_match = re.search(sidebar_pattern, studio_html, re.DOTALL)
|
||||
|
||||
assert sidebar_match is not None, "Sidebar nicht gefunden"
|
||||
|
||||
sidebar_content = sidebar_match.group(0)
|
||||
|
||||
# Pruefe dass GPU-Buttons in der Sidebar sind
|
||||
assert 'btn-vast-start' in sidebar_content, \
|
||||
"GPU Start-Button muss in der Sidebar sein"
|
||||
assert 'btn-vast-stop' in sidebar_content, \
|
||||
"GPU Stop-Button muss in der Sidebar sein"
|
||||
|
||||
def test_gpu_cost_elements_exist(self, studio_html):
|
||||
"""Testet, dass die Kostenanzeige-Elemente existieren."""
|
||||
required_elements = [
|
||||
'vast-cost-hour', # Kosten pro Stunde
|
||||
'vast-credit', # Budget/Credit
|
||||
'vast-session-cost', # Session-Kosten
|
||||
]
|
||||
|
||||
for element_id in required_elements:
|
||||
assert f'id="{element_id}"' in studio_html, \
|
||||
f"Kostenelement {element_id} nicht gefunden"
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="vast.ai GPU UI not yet implemented in frontend template")
|
||||
class TestStudioVastButtons:
|
||||
"""Tests fuer die vast.ai Button-Styles.
|
||||
|
||||
HINWEIS: vast.ai UI-Elemente wurden noch nicht implementiert.
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def studio_html(self):
|
||||
"""Laedt den HTML-Inhalt aus dem Template."""
|
||||
return TEMPLATE_HTML.read_text(encoding="utf-8")
|
||||
|
||||
@pytest.fixture
|
||||
def studio_css(self):
|
||||
"""Laedt den CSS-Inhalt aus der separaten CSS-Datei."""
|
||||
return STATIC_CSS.read_text(encoding="utf-8")
|
||||
|
||||
def test_vast_buttons_container_exists(self, studio_html):
|
||||
"""Testet, dass der vast-buttons Container existiert."""
|
||||
assert 'class="vast-buttons"' in studio_html, \
|
||||
"vast-buttons Container nicht gefunden"
|
||||
|
||||
def test_vast_buttons_css_exists(self, studio_css):
|
||||
"""Testet, dass CSS fuer .vast-buttons existiert."""
|
||||
assert '.vast-buttons' in studio_css, \
|
||||
"CSS fuer .vast-buttons nicht gefunden"
|
||||
|
||||
|
||||
class TestStudioStaticFilesIntegration:
|
||||
"""Integration Tests fuer Static Files Serving."""
|
||||
|
||||
def test_main_py_mounts_static_files(self):
|
||||
"""Testet, dass main.py die Static Files korrekt mountet."""
|
||||
main_py = Path(__file__).parent.parent / "main.py"
|
||||
main_content = main_py.read_text(encoding="utf-8")
|
||||
|
||||
assert 'StaticFiles' in main_content, \
|
||||
"main.py muss StaticFiles importieren"
|
||||
assert 'app.mount("/static"' in main_content or "app.mount('/static'" in main_content, \
|
||||
"main.py muss /static mounten"
|
||||
|
||||
def test_static_directory_structure(self):
|
||||
"""Testet, dass die Static-Verzeichnisstruktur korrekt ist."""
|
||||
static_dir = FRONTEND_DIR / "static"
|
||||
assert static_dir.exists(), "static/ Verzeichnis muss existieren"
|
||||
assert (static_dir / "css").exists(), "static/css/ Verzeichnis muss existieren"
|
||||
assert (static_dir / "js").exists(), "static/js/ Verzeichnis muss existieren"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
Reference in New Issue
Block a user