"""Import-Smoke-Test: bricht, wenn main.py kaputte Imports oder Verdrahtung hat. Warum: Kein anderer Test importiert main.py — ein Tippfehler dort fiele sonst erst beim Container-Start auf (und die Ampel bliebe fälschlich grün). Läuft nur unter Linux (detection.py nutzt fcntl/ioctl), also genau dort, wo auch die Ampel läuft. """ import sys import pytest if sys.platform == "win32": # pragma: no cover pytest.skip("detection.py braucht fcntl (Linux)", allow_module_level=True) def test_main_importierbar_und_routen_verdrahtet(): from main import app routen = {route.path for route in app.routes} for pfad in ( "/health", "/jobs", "/devices", "/logs", "/settings", "/prescan", # KEYDB.cfg + AACS-Dumps: der Platz für die selbst mitgebrachte # Schlüsseldatei (Befund 25.07.2026 — MakeMKV holt UHD-Schlüssel nicht # mehr online nach). Ohne diese Routen ist die Seite im UI tot. "/system/keydb", "/system/aacs-dumps", "/system/aacs-dumps/{dateiname}", ): assert pfad in routen, f"Route {pfad} fehlt" def test_dateiname_validierung_blockt_pfad_tricks(): """Download-Endpoint: nur nackte Dateinamen — kein .., kein Slash, kein Dotfile.""" from main import _sicherer_dateiname assert _sicherer_dateiname("film.mkv") is True assert _sicherer_dateiname("../../etc/passwd") is False assert _sicherer_dateiname("a/b.mkv") is False assert _sicherer_dateiname("a\\b.mkv") is False assert _sicherer_dateiname(".versteckt") is False assert _sicherer_dateiname("") is False def test_worker_task_name_passt_zum_celery_client(): """API schickt an 'worker.tasks.rip_disc' — der Name ist Vertrag mit dem Worker.""" import inspect import celery_client quelle = inspect.getsource(celery_client.start_rip) assert '"worker.tasks.rip_disc"' in quelle def test_remount_blockiert_den_api_start_nicht(): """Regression (Vorfall 24.07.): ein haengender Netz-Mount (CIFS-Schreibtest kann im Kernel haengen, wait_for_response) darf den API-Start NICHT blockieren. remount muss als Hintergrund-Task laufen (create_task), nicht direkt awaited werden.""" import inspect import main quelle = inspect.getsource(main.startup_event) assert "create_task(asyncio.to_thread(remount))" in quelle, \ "remount muss als Hintergrund-Task laufen (nicht blockierend)" assert "await asyncio.to_thread(remount)" not in quelle, \ "remount darf nicht mehr direkt awaited werden (blockiert sonst den Start)"