""" 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 '' 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']*class="[^"]*sidebar[^"]*"[^>]*>.*?' 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'])