"""Tests fuer _original_aufheben — der Pfad, der am 25.07.2026 die Platte fuellte. Der Kern: Es wird NICHT vorhergesagt, ob umgehaengt werden kann, sondern os.rename versucht. Die alte Fassung verglich st_dev und lag damit falsch — auf der Rippy-VM sind st_dev von /app/temp und /app/media identisch (2050), os.rename scheitert zwischen ihnen trotzdem mit EXDEV, weil der Kernel den Mount vergleicht und nicht das Geraet. Die Platzpruefung wurde deshalb uebersprungen und shutil.move kopierte 75 GB bei 37 GB frei. """ import errno import os import pytest import tasks class FakeDb: """Faengt nur die Log-Zeilen ab — mehr braucht _original_aufheben nicht.""" def __init__(self): self.logs = [] def add_log(self, level, source, message): self.logs.append((level, message)) def meldungen(self): return " ".join(m for _, m in self.logs) @pytest.fixture def fake_db(monkeypatch): ersatz = FakeDb() monkeypatch.setattr(tasks, "db", ersatz) return ersatz def _lege_rohdaten_an(tmp_path, groesse=2048): raw = tmp_path / "raw" / "job-1" raw.mkdir(parents=True) (raw / "title_t00.mkv").write_bytes(b"x" * groesse) final = tmp_path / "media" / "Film (2020)" final.mkdir(parents=True) return str(raw), str(final) def test_umhaengen_wenn_derselbe_mount(tmp_path, fake_db): """Der gute Fall: rename klappt, nichts wird kopiert, kein Platz noetig.""" raw, final = _lege_rohdaten_an(tmp_path) tasks._original_aufheben("job-1", raw, final) assert os.path.isdir(os.path.join(final, "original")) assert os.path.isfile(os.path.join(final, "original", "title_t00.mkv")) assert not os.path.exists(raw) assert "umgehängt" in fake_db.meldungen() def test_bei_exdev_und_zu_wenig_platz_wird_nur_gewarnt(tmp_path, fake_db, monkeypatch): """Der Fall, der die Platte fuellte: rename geht nicht, Platz reicht nicht. Vorher lief hier eine Vollkopie an, weil die st_dev-Pruefung „gleiches Dateisystem" meldete und die Platzpruefung deshalb ausblieb. """ raw, final = _lege_rohdaten_an(tmp_path) def kein_rename(*_a, **_k): raise OSError(errno.EXDEV, "Invalid cross-device link") monkeypatch.setattr(tasks.os, "rename", kein_rename) monkeypatch.setattr(tasks, "_frei_bytes", lambda _p: 1024) # weniger als die Rohdaten tasks._original_aufheben("job-1", raw, final) # Rohdaten bleiben unangetastet liegen, es wurde NICHTS kopiert assert os.path.isfile(os.path.join(raw, "title_t00.mkv")) assert not os.path.exists(os.path.join(final, "original")) meldungen = fake_db.meldungen() assert "NICHT aufgehoben" in meldungen assert "anderen Mount" in meldungen assert "Arbeitsverzeichnis" in meldungen # nennt die Abhilfe def test_bei_exdev_und_genug_platz_wird_kopiert(tmp_path, fake_db, monkeypatch): raw, final = _lege_rohdaten_an(tmp_path) echtes_rename = os.rename aufrufe = {"n": 0} def rename_erst_exdev(*args, **kwargs): # Nur der Versuch von _original_aufheben scheitert; shutil.move darf # intern weiter umbenennen (es kopiert selbst und benennt Teile um). aufrufe["n"] += 1 if aufrufe["n"] == 1: raise OSError(errno.EXDEV, "Invalid cross-device link") return echtes_rename(*args, **kwargs) monkeypatch.setattr(tasks.os, "rename", rename_erst_exdev) monkeypatch.setattr(tasks, "_frei_bytes", lambda _p: 10 * 1024**3) tasks._original_aufheben("job-1", raw, final) assert os.path.isfile(os.path.join(final, "original", "title_t00.mkv")) assert "Original behalten" in fake_db.meldungen() def test_anderer_fehler_wird_ehrlich_gemeldet_und_reisst_job_nicht_mit( tmp_path, fake_db, monkeypatch ): """Ein Fehler beim Aufheben darf den Job NIE scheitern lassen — die komprimierte Datei ist zu diesem Zeitpunkt fertig und in Ordnung.""" raw, final = _lege_rohdaten_an(tmp_path) def zugriff_verweigert(*_a, **_k): raise OSError(errno.EACCES, "Permission denied") monkeypatch.setattr(tasks.os, "rename", zugriff_verweigert) tasks._original_aufheben("job-1", raw, final) # darf nicht werfen meldungen = fake_db.meldungen() assert "konnte nicht aufgehoben werden" in meldungen assert raw in meldungen # sagt, WO die Rohdatei liegt