a1aabd591d
Ampel / ampel (push) Successful in 28s
Meine eigene Zeile aus dem vorigen Commit. Im Quelltext stand "a\b.mkv" mit EINEM Backslash - Python liest \b als Backspace-Zeichen, der String enthaelt also gar keinen Backslash, und _sicherer_dateiname gab korrekt True zurueck. Der Test behauptete, den Backslash-Pfad zu pruefen, und tat es nicht. Jetzt ein Raw-String r"a\b.mkv". Warum das lokal nicht auffiel: test_api_smoke.py ueberspringt sich unter Windows selbst (main.py -> detection.py -> fcntl). Genau die Luecke, die im Savepoint als offener Punkt steht - hier hat sie sofort zugeschlagen. Lehre: Tests, die nur in der Ampel laufen, sind erst nach dem Push bewiesen, und Backslashes gehoeren in Raw-Strings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
99 lines
4.1 KiB
Python
99 lines
4.1 KiB
Python
"""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}",
|
|
# Der Hauptweg fuer 4K-UHD (25.07.2026): makemkvcon holt Disc-Schluessel
|
|
# unter Linux nie selbst, sie kommen von Hand ueber diesen Endpunkt.
|
|
"/system/keystore",
|
|
):
|
|
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)"
|
|
|
|
|
|
def test_unter_wurzel_faellt_nicht_auf_praefix_namen_herein():
|
|
"""Befund 25.07.2026: In main.py prueften neun Stellen mit nacktem
|
|
startswith(MEDIA_ROOT) — darunter /browse und /browse/mkdir, wo der Pfad
|
|
vom Nutzer kommt. „/app/media-boese/x" beginnt mit „/app/media", liegt
|
|
aber ausserhalb. Zwilling von tasks.unter_wurzel im Worker."""
|
|
from main import unter_wurzel
|
|
|
|
assert unter_wurzel("/app/media", "/app/media") is True
|
|
assert unter_wurzel("/app/media/movies", "/app/media") is True
|
|
assert unter_wurzel("/app/media-boese/x", "/app/media") is False
|
|
assert unter_wurzel("/app/mediaX", "/app/media") is False
|
|
assert unter_wurzel("/etc/passwd", "/app/media") is False
|
|
assert unter_wurzel("", "/app/media") is False
|
|
assert unter_wurzel("/app/media", "") is False
|
|
assert unter_wurzel("/app/media/movies", "/app/media/") is True
|
|
|
|
|
|
def test_sicherer_dateiname_blockt_pfad_tricks():
|
|
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
|
|
# Raw-String: "a\b.mkv" waere ein Backspace-Zeichen und gar kein Backslash —
|
|
# der Test war deshalb erst gruen, obwohl er nichts prueft (Ampel 19:10).
|
|
assert _sicherer_dateiname(r"a\b.mkv") is False
|
|
assert _sicherer_dateiname(".versteckt") is False
|
|
assert _sicherer_dateiname("") is False
|