fix(worker): erster Film in einer neuen Ablage war immer unerreichbar
Ampel / ampel (push) Failing after 30s
Ampel / ampel (push) Failing after 30s
Im ersten vollstaendigen Durchlauf aufgefallen: Der Rip lief sauber durch (40 GB Akira, Marke rip_fertig sprang korrekt auf true), die Kompression brach 0,2 s spaeter ab mit "Dieser Worker erreicht die Ziel (fertige Datei) nicht: \\192.168.178.62\rippy\movies\Akira (1988)". Die Freigabe war erreichbar. Es fehlten zwei noch nie angelegte Ordner - movies und der Filmordner darin. Die Pruefung liess aber nur EINE fehlende Ebene durch (sie sah nach dirname), obwohl gleich darauf os.makedirs die ganze Kette anlegt. Damit war jeder ERSTE Film in einer neuen Ablage systematisch unerreichbar. Jetzt zaehlt, ob irgendein Vorfahre existiert (erster_vorhandener_ordner, begrenzt auf 12 Stufen, damit auf einer toten Freigabe nicht endlos geklopft wird). Zweiter Fehler in derselben Meldung: Sie behauptete "RIPPY_PATH_MAP ist gesetzt, deckt diesen Pfad aber nicht ab" - und nannte im selben Satz den korrekt uebersetzten UNC-Pfad. Wer dem folgte, suchte in der Karte statt in der Freigabe. Die Karte wird nur noch beschuldigt, wenn sie den Pfad wirklich nicht angefasst hat; sonst steht da, was zutrifft: uebersetzt, aber gerade nicht erreichbar. Dazu der Artikel-Fehler "erreicht die Ziel" behoben. Diese Pruefung hatte KEINEN einzigen Test - deshalb kam beides durch. Jetzt 12 (301 gruen). Beim Schreiben ist noch ein dritter Fehler aufgefallen: `isdir=os.path.isdir` als Vorgabewert bindet die Funktion beim IMPORT, ein Ersetzen geht danach ins Leere. Aufloesung jetzt beim Aufruf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""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 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):
|
||||
kennt = _fake_isdir(vorhandene)
|
||||
monkeypatch.setattr(tasks.os.path, "isdir",
|
||||
lambda p: kennt(p) or (im_container and p == "/app"))
|
||||
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)",
|
||||
)
|
||||
|
||||
|
||||
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",
|
||||
)
|
||||
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
|
||||
Reference in New Issue
Block a user