Ampel / ampel (push) Successful in 1m20s
Commander: „Bro, du musst alles was rippy jetzt im code hat für Windows
Bauen! Jeden pfad, alles wo die tools drauf zugreifen. Diese Rippy version
MUSS 100% Windows Kompatibel sein. Prüfe bitte den kompletten Quellcode nach
Docker Resten."
Systematisch gesucht statt Fundstelle fuer Fundstelle: feste POSIX-Pfade,
Linux-Programme, POSIX-eigene Aufrufe, `shutil.which`, `posixpath` auf echten
Pfaden, Container-Texte. Sechs echte Fehler dabei.
## 1. `/dev/{name}` in drei Endpunkten — der schwerste
Das UI ruft `/devices/{id}/eject`, `/scan-tracks` und `/tracks` mit der
Kennung aus der Geraeteliste auf, unter Windows also `G`. Gebaut wurde daraus
`/dev/G` — steht in keiner Laufwerksliste. **Auswerfen und „Disc scannen"
antworteten unter Windows IMMER mit 404**, ohne dass irgendwo stand, warum.
Hin- und Rueckweg gehoeren zusammen: Beide Treiber haben jetzt `kennung()`
und `pfad_zu_kennung()`. Wer die Kennung vergibt, loest sie auch auf.
## 2. `os.path.isdir("/app")` — zum zweiten Mal
Nach `caps.py` (heute frueh) auch in `ablauf.py`: Der eigenstaendige
Windows-Rippy hielt sich fuer einen FREMDEN Worker und haette sich selbst
vorgeworfen, Container-Pfade nicht zu erreichen — auf einer Maschine ohne
Container. Die Entscheidung ist jetzt einspritzbar; vorher hing der Test
daran, ob es einen Ordner `/app` gibt.
## 3. `shutil.which` in `schluessel.py`
Ausgerechnet im Modul, das es NUR unter Windows gibt: Es suchte makemkvcon im
PATH, wo unter Windows nie ein Programm aus „Programme" steht. Die
Schluessel-Automatik fuer 4K-UHD lief damit nie an.
## 4. `posixpath.join` auf echten Pfaden
`rohdaten.py` baute `C:\Roh/datei.mkv` — gemischte Trenner, die im UI falsch
aussehen und jeden Vergleich brechen.
## 5. Container-Pfad in einer Nutzermeldung
„Roh-Datei bleibt in /app/temp erhalten" nennt jetzt den echten Ordner. Wer
die Datei retten will, sucht sonst am falschen Ort.
## 6. Container-Pfade als UI-Vorbelegung
Rip-Dialog und `useBetrieb` starteten mit `/app/media`, bis die Antwort da
war. Leer ist ehrlicher: Es behauptet nichts.
## Und HandBrakes „Code 0"
Code 0 heisst ERFOLG. Rippy meldete trotzdem „fehlgeschlagen", weil die Datei
nicht am erwarteten Ort lag: **HandBrake bestimmt den Container aus dem
PRESET, nicht aus der Endung** — ein MP4-Preset schreibt `.mp4` neben das
verlangte `.mkv`. Jetzt erzwingt `--format` den Container passend zur Endung
(an HandBrake 1.11.2 gegengeprueft), und falls doch etwas daneben liegt, wird
es gefunden statt weggeworfen.
## Der Waechter
`test_keine_container_reste.py` prueft mechanisch, dass im Windows-Weg kein
Container-Pfad ohne Begruendung steht. Die Ausnahmen stehen namentlich mit
Grund da (Linux-Zweige, benannte Rueckfaelle) — und ein zweiter Test wirft
jede Ausnahme raus, die niemand mehr braucht.
Ueber den Tokenizer, nicht ueber „faengt mit Anfuehrungszeichen an": Der
erste Anlauf blieb prompt an seinem eigenen `r\"\"\"`-Docstring haengen.
887 Tests gruen, ruff sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
155 lines
6.2 KiB
Python
155 lines
6.2 KiB
Python
"""Tests für die Erreichbarkeits-Prüfung vor der Kompression.
|
|
|
|
Diese Prüfung hatte KEINEN einzigen Test — und genau deshalb ist sie am
|
|
26.07.2026 im ersten vollständigen Durchlauf umgefallen: Der Rip lief sauber
|
|
durch (40 GB Akira), die Kompression brach 0,2 s später ab mit „Dieser Worker
|
|
erreicht das Ziel nicht", obwohl die Freigabe erreichbar war. Es fehlten nur
|
|
zwei noch nie angelegte Ordner.
|
|
"""
|
|
|
|
import ablauf as tasks
|
|
|
|
|
|
# --- Der eigentliche Fehler: nur EINE fehlende Ebene war erlaubt -------------
|
|
|
|
def _ohne_schluss_trenner(p):
|
|
"""`\\\\NAS\\rippy\\` und `\\\\NAS\\rippy` sind derselbe Ordner.
|
|
|
|
Wichtig, weil `ntpath.dirname` für die UNC-Wurzel MIT abschließendem
|
|
Backslash zurückkommt. Das echte `os.path.isdir` stört das nicht — eine
|
|
Attrappe, die stur vergleicht, hätte hier einen Fehler erfunden, den es
|
|
nicht gibt.
|
|
"""
|
|
return p.rstrip("\\/") or p
|
|
|
|
|
|
def _fake_isdir(vorhandene):
|
|
"""isdir-Ersatz, der nur die genannten Pfade kennt."""
|
|
bekannt = {_ohne_schluss_trenner(p) for p in vorhandene}
|
|
return lambda p: _ohne_schluss_trenner(p) in bekannt
|
|
|
|
|
|
def test_findet_den_naechsten_vorhandenen_ordner():
|
|
da = {"\\\\NAS\\rippy"}
|
|
assert tasks.erster_vorhandener_ordner(
|
|
"\\\\NAS\\rippy\\movies\\Akira (1988)",
|
|
isdir=_fake_isdir(da), dirname=lambda p: p.rsplit("\\", 1)[0] if "\\" in p.strip("\\") else "",
|
|
) == "\\\\NAS\\rippy"
|
|
|
|
|
|
def test_zwei_fehlende_ebenen_sind_in_ordnung():
|
|
"""DER Vorfall: `movies` UND der Filmordner fehlten beide.
|
|
|
|
`os.makedirs` legt die ganze Kette an — es genügt also, dass irgendein
|
|
Vorfahre existiert. Die alte Prüfung sah nur nach `dirname` und erklärte
|
|
damit jeden ERSTEN Film in einer neuen Ablage für unerreichbar.
|
|
"""
|
|
da = {"/share"}
|
|
assert tasks.erster_vorhandener_ordner(
|
|
"/share/movies/Akira (1988)", isdir=_fake_isdir(da)) == "/share"
|
|
|
|
|
|
def test_gar_nichts_erreichbar_gibt_leer():
|
|
"""Ist die Freigabe wirklich weg, muss die Prüfung anschlagen."""
|
|
assert tasks.erster_vorhandener_ordner(
|
|
"/share/movies/Film", isdir=_fake_isdir(set())) == ""
|
|
|
|
|
|
def test_sucht_nicht_endlos_auf_einer_toten_freigabe():
|
|
"""Jeder Schritt kostet auf einer toten Freigabe einen Timeout."""
|
|
gefragt = []
|
|
|
|
def zaehlend(p):
|
|
gefragt.append(p)
|
|
return False
|
|
|
|
tiefer_pfad = "/" + "/".join(f"e{i}" for i in range(50))
|
|
assert tasks.erster_vorhandener_ordner(tiefer_pfad, isdir=zaehlend) == ""
|
|
assert len(gefragt) <= tasks.MAX_ELTERN_STUFEN
|
|
|
|
|
|
def test_leerer_pfad_stuerzt_nicht_ab():
|
|
assert tasks.erster_vorhandener_ordner("", isdir=_fake_isdir(set())) == ""
|
|
|
|
|
|
# --- Die Meldung: eine falsche Ursache ist teurer als gar keine -------------
|
|
|
|
def _pruefen(monkeypatch, vorhandene, mapping, im_container=False):
|
|
import ntpath
|
|
kennt = _fake_isdir(vorhandene)
|
|
monkeypatch.setattr(tasks.os.path, "isdir",
|
|
lambda p: kennt(p) or (im_container and p == "/app"))
|
|
monkeypatch.setattr(tasks.os.path, "dirname", ntpath.dirname)
|
|
monkeypatch.setenv("RIPPY_PATH_MAP", mapping)
|
|
return tasks._erreichbarkeit_pruefen(
|
|
"/app/media/rippy/job1", "\\\\NAS\\rippy\\job1",
|
|
"/app/media/rippy/movies/Akira (1988)", "\\\\NAS\\rippy\\movies\\Akira (1988)",
|
|
# „Fremd" heisst: Rippy laeuft woanders als dieser Worker. Bis zum
|
|
# 29.08.2026 wurde das an der Existenz von `/app` festgemacht — damit
|
|
# hing dieser Test am laufenden Rechner, und unter Windows hielt sich
|
|
# der eigenstaendige Rippy fuer einen fremden Worker.
|
|
fremd=not im_container,
|
|
)
|
|
|
|
|
|
def test_alles_da_meldet_nichts(monkeypatch):
|
|
assert _pruefen(monkeypatch, {"\\\\NAS\\rippy\\job1", "\\\\NAS\\rippy\\movies\\Akira (1988)"},
|
|
"/app/media/rippy=\\\\NAS\\rippy") == ""
|
|
|
|
|
|
def test_ziel_fehlt_ganz_ist_kein_fehler(monkeypatch):
|
|
"""Der Vorfall vom 26.07.2026 — jetzt geht er durch."""
|
|
assert _pruefen(monkeypatch, {"\\\\NAS\\rippy\\job1", "\\\\NAS\\rippy"},
|
|
"/app/media/rippy=\\\\NAS\\rippy") == ""
|
|
|
|
|
|
def test_fehlende_quelle_bleibt_ein_fehler(monkeypatch):
|
|
meldung = _pruefen(monkeypatch, {"\\\\NAS\\rippy"}, "/app/media/rippy=\\\\NAS\\rippy")
|
|
assert "Quelle" in meldung
|
|
assert "Rohdaten sind NICHT verloren" in meldung
|
|
|
|
|
|
def test_uebersetzter_aber_unerreichbarer_pfad_beschuldigt_nicht_die_karte(monkeypatch):
|
|
"""⚠️ Genau die Falschaussage aus dem Vorfall.
|
|
|
|
Die Meldung lautete „RIPPY_PATH_MAP … deckt diesen Pfad aber nicht ab",
|
|
während sie ihn im selben Satz korrekt übersetzt hatte. Wer dem folgte,
|
|
suchte in der Karte statt in der Freigabe.
|
|
"""
|
|
meldung = _pruefen(monkeypatch, set(), "/app/media/rippy=\\\\NAS\\rippy")
|
|
assert "deckt diesen Pfad aber nicht ab" not in meldung
|
|
assert "Übersetzt wurde er korrekt" in meldung
|
|
assert "Freigabe verbunden" in meldung
|
|
|
|
|
|
def test_nicht_uebersetzter_pfad_beschuldigt_sehr_wohl_die_karte(monkeypatch):
|
|
"""Hier ist die Karte wirklich schuld — der Pfad blieb unverändert."""
|
|
monkeypatch.setattr(tasks.os.path, "isdir", lambda p: False)
|
|
monkeypatch.setenv("RIPPY_PATH_MAP", "/app/temp=Y:\\temp")
|
|
meldung = tasks._erreichbarkeit_pruefen(
|
|
"/app/media/rippy/job1", "/app/media/rippy/job1",
|
|
"/app/media/movies/X", "/app/media/movies/X",
|
|
# Ausdruecklich ein FREMDER Worker — nur den betrifft die Pfad-Karte.
|
|
# Vorher ergab sich das aus „es gibt kein /app", und genau daran hing
|
|
# der Test am laufenden Rechner (Befund 29.08.2026).
|
|
fremd=True,
|
|
)
|
|
assert "deckt diesen Pfad aber nicht ab" in meldung
|
|
assert "von keinem Eintrag übersetzt" in meldung
|
|
|
|
|
|
def test_ohne_mapping_nennt_die_wahre_ursache(monkeypatch):
|
|
meldung = _pruefen(monkeypatch, set(), "")
|
|
assert "nicht gesetzt" in meldung
|
|
assert "RIPPY_PATH_MAP" in meldung
|
|
|
|
|
|
def test_artikel_stimmt_in_beiden_meldungen(monkeypatch):
|
|
"""„erreicht die Ziel" stand wörtlich im Log des Commanders."""
|
|
quelle = _pruefen(monkeypatch, set(), "")
|
|
assert "erreicht die Quelle" in quelle
|
|
# Quelle da, Ziel unerreichbar (kein einziger Vorfahre) → Ziel-Meldung
|
|
ziel = _pruefen(monkeypatch, {"\\\\NAS\\rippy\\job1"},
|
|
"/app/media/rippy=\\\\NAS\\rippy")
|
|
assert "erreicht das Ziel" in ziel
|