Files
rippy/docker/api/test_pfadgrenze.py
HitonabiandClaude Opus 5 35370fd552 fix(windows): Cover, Arbeitsverzeichnis und die zurueckgelassenen _MEI-Ordner
Drei Befunde des Commanders vom 28.08.2026, alle drei gemessen.

## 1. „Was ist mit dem Cover auf Windows Rippy?"

Es gab keins, weil es keinen Treffer gab. `_scan_video` verlangte woertliche
Gleichheit:

    if movie.get("title", "").lower() == kandidat.lower():

An seiner Disc gemessen:

    'Evangelion 2.22'    1 Treffer   Evangelion: 2.0 You Can (Not) Advance
    'Evangelion'        20 Treffer   irgendein Evangelion

Der EINZIGE Treffer auf den vollen Disc-Titel war der richtige Film, mit
Poster — und wurde verworfen. Jetzt zaehlt die SPEZIFITAET der Anfrage: Wer
auf den vollen Disc-Titel hoechstens drei Treffer bekommt, hat gefragt wie
jemand, der weiss was er sucht. Ergebnis an derselben Disc:

    Evangelion: 2.0 You Can (Not) Advance / 2009 / 80 % / Poster + dt. Text

## 2. „Warum heisst das hier noch container platte? … Waere es moeglich das
##     Arbeitsverzeichnis zu aendern? momentan geht das nicht."

Beide Haelften gehen auf EINE Zeile zurueck: `MEDIA_ROOT = "/app/media"` war
zugleich Vorgabe UND Pfadgrenze. Auf Windows gibt es den Ordner nicht:

* `/storage-targets` fing den OSError und gab still [] zurueck — die Auswahl
  hatte genau einen Eintrag. Das ist „momentan geht das nicht".
* Dessen Text war fest verdrahtet „(Container-Platte)".
* `/browse` antwortete auf jeden Pfad mit 422.

Die Grenze faellt nicht weg, sie wird betriebsabhaengig: Container und
Kopflos-Betrieb bedienen ein Netz, die native App den Menschen davor.
Gemessen auf seinem PC: 7 Laufwerke zur Auswahl, X/Y/Z mit je 2,2 TB frei.

## 3. „Failed to remove temporary directory: …_MEI0000b0882"

GEMESSEN: 20 zurueckgelassene _MEI-Ordner mit 1,1 GB. Ursache: `Popen` ohne
`env=` reicht PyInstallers Auspack-Zeiger an die Kinder weiter (--dienst und
--oeffnen). Beide laufen dann im Ordner des Elternprozesses, der sich zuerst
beendet und ihn loeschen will. Waere das TEILWEISE geglueckt, haetten Dienst
und Fenster mitten im Betrieb ihre Dateien verloren.

Nebenbefund: test_setup_fenster scheiterte in jeder Umgebung ohne pywebview.

794 Tests gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 16:43:41 +02:00

83 lines
3.6 KiB
Python

"""Welche Pfade darf die API anfassen — und wo ist „oben"?
## Warum diese Grenze verschiebbar sein muss (Befund 28.08.2026)
`MEDIA_ROOT = "/app/media"` war zugleich Vorgabe UND Pfadgrenze. Im Container
ist beides richtig. Auf dem Windows-PC des Commanders war beides falsch, und
die Folge war eine Oberflaeche, die stillschweigend nichts konnte:
* `/storage-targets` lieferte `[]` (der Ordner existiert dort nicht),
* `/browse` antwortete auf JEDEN Pfad mit 422,
* im Auswahlfeld fuer das Arbeitsverzeichnis stand genau ein Eintrag.
Sein Befund dazu: „Wäre es möglich das Arbeitsverzeichnis zu ändern?
momentan geht das nicht."
Die Grenze faellt aber NICHT einfach weg. Im Container haengt die API im
Netz — eine Weboberflaeche, die jeden Pfad des Wirts ausliefern kann, ist ein
Loch. Sie gilt nur dort nicht, wo Rippy den Menschen bedient, der vor dem
Rechner sitzt. Diese Tests halten beide Haelften fest.
"""
import main
# ── Die Grenze gilt, wo zugehoert wird ──────────────────────────────────
def test_im_container_gilt_die_wurzel_weiter():
"""Der Docker-Weg darf sich durch die Reparatur NICHT lockern."""
assert main.pfad_erlaubt("/app/media/movies", "/app/media", frei=False)
assert not main.pfad_erlaubt("/etc/passwd", "/app/media", frei=False)
# Der Klassiker: beginnt mit der Wurzel, liegt aber ausserhalb.
assert not main.pfad_erlaubt("/app/media-boese/x", "/app/media", frei=False)
def test_nativ_ist_auch_eine_freigabe_erlaubt():
"""Sein Ziel ist `\\\\192.168.179.62\\rippy\\movies` — das liegt unter gar
keiner lokalen Wurzel. Mit der alten Regel war es unerreichbar."""
assert main.pfad_erlaubt(r"\\192.168.179.62\rippy\movies", "/app/media", frei=True)
assert main.pfad_erlaubt(r"D:\Rippy-Arbeit", "/app/media", frei=True)
def test_ein_leerer_pfad_ist_nie_erlaubt():
"""Sonst wuerde aus einem vergessenen Feld ein Zugriff auf `/`."""
assert not main.pfad_erlaubt("", "/app/media", frei=True)
assert not main.pfad_erlaubt("", "/app/media", frei=False)
# ── Wo ist oben ─────────────────────────────────────────────────────────
def test_an_der_wurzel_ist_schluss():
"""Im Container. `None` heisst: kein Knopf „nach oben"."""
assert main.eltern_von("/app/media", "/app/media", frei=False) is None
assert main.eltern_von("/app/media/movies", "/app/media", frei=False) == "/app/media"
def test_ueber_der_laufwerkswurzel_steht_die_laufwerksliste():
"""Zwei Fallen auf einmal.
`os.path.dirname("C:\\\\")` ist wieder `"C:\\\\"` — ein Knopf „nach oben",
der auf denselben Ordner zeigt, sieht aus wie ein Fehler. Und ueber der
Laufwerkswurzel steht nicht *nichts*, sondern die Liste der Laufwerke:
Sonst kaeme man von `D:\\` nie zu `C:\\`, und genau dort ist Platz fuer
100 GB Rohdaten.
"""
import ntpath
import os
echt = os.path.dirname
os.path.dirname = ntpath.dirname # Windows-Pfade auch auf Linux
try:
assert main.eltern_von("C:\\", "D:\\Rippy", frei=True) == ""
assert main.eltern_von("C:\\Users\\Tobi", "D:\\Rippy", frei=True) == "C:\\Users"
finally:
os.path.dirname = echt
def test_die_wurzel_kommt_aus_dem_betrieb_und_ist_nie_leer():
"""Eine leere Wurzel wuerde jede Pruefung durchwinken."""
assert main.medien_wurzel()
def test_laufwerke_ohne_windows_sind_eine_leere_liste():
"""Auf dem Linux-Runner der Ampel darf das kein Fehler sein."""
assert isinstance(main.betrieb_laufwerke(), list)