This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/tests/test_studio_frontend.py
Benjamin Admin bfdaf63ba9 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>
2026-02-09 09:51:32 +01:00

255 lines
10 KiB
Python

"""
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'])