"""Die Metadaten-Clients muessen sich OHNE Datenbank und OHNE Redis bauen lassen. ## Warum es diese Tests gibt (Befund 28.08.2026) Der Commander meldete, dass eine eingelegte Blu-ray namenlos blieb und keine Metadaten-Abfrage lief. Zwei Ursachen, beide unsichtbar: **1. Ein Phantom-Modul.** `clients/tmdb.py`, `clients/omdb.py` und `clients/thetvdb.py` machten im Konstruktor `from db import get_settings`. `docker/api/db.py` gibt es seit der Zusammenlegung in V2-1 (dd1d0b7) nicht mehr — main.py schreibt seitdem `from rippy import store as db`, diese drei Module haben es nie mitbekommen. Damit starb JEDE Metadaten-Abfrage schon beim Erzeugen des Clients mit `ModuleNotFoundError`, auf Windows UND auf der VM. `_auto_prescan` verschluckte es in seinem `except Exception`. **2. Ein harter Redis-Zwang.** `cache/cache.py` sprach `redis:6379` an — den Dienstnamen aus `docker-compose.yml`. Auf einem Windows-PC gibt es kein Redis, und jeder Zugriff endete mit `ConnectionError`. Das riss den Pre-Scan mit. Beide Male war der SYMPTOM sichtbar (keine Metadaten) und die URSACHE nicht. Diese Tests machen die Ursache sichtbar: Sie bauen die Clients und benutzen den Zwischenspeicher, ohne dass irgendetwas davon erreichbar sein muss. """ import pytest def test_tmdb_client_baut_sich_ohne_datenbank(): """Der Test, der den Phantom-Import gefangen haette.""" from clients.tmdb import TMDBClient klient = TMDBClient() assert hasattr(klient, "api_key") def test_omdb_client_baut_sich_ohne_datenbank(): from clients.omdb import OMDbClient assert OMDbClient() is not None def test_thetvdb_client_baut_sich_ohne_datenbank(): from clients.thetvdb import TheTVDBClient assert TheTVDBClient() is not None def test_kein_client_importiert_das_verschwundene_db_modul(): """Der Waechter gegen genau diesen Fehler, mechanisch. Ein `from db import ...` faellt sonst erst auf, wenn jemand eine Disc einlegt — und dann als „keine Metadaten", nicht als Import-Fehler. """ import os hier = os.path.dirname(os.path.abspath(__file__)) fundstellen = [] for wurzel, ordner, dateien in os.walk(os.path.join(hier, "clients")): ordner[:] = [o for o in ordner if o != "__pycache__"] for name in dateien: if not name.endswith(".py"): continue pfad = os.path.join(wurzel, name) with open(pfad, encoding="utf-8") as f: for nr, zeile in enumerate(f, 1): if zeile.strip().startswith(("from db import", "import db")): fundstellen.append("%s:%d" % (name, nr)) assert not fundstellen, ( "docker/api/db.py gibt es seit V2-1 nicht mehr — benutzt " "rippy.store. Fundstellen: " + ", ".join(fundstellen)) # ── Der Zwischenspeicher ──────────────────────────────────────────────── def test_cache_ohne_redis_liefert_None_statt_zu_werfen(): """Ein Zwischenspeicher ist eine Beschleunigung, keine Voraussetzung. Auf einem Windows-PC gibt es kein Redis. Vorher riss jeder Zugriff den Pre-Scan mit — und die Disc blieb namenlos. """ from cache import cache class ToterRedis: def get(self, *a, **k): raise cache.RedisError("nicht erreichbar") def set(self, *a, **k): raise cache.RedisError("nicht erreichbar") def setex(self, *a, **k): raise cache.RedisError("nicht erreichbar") def delete(self, *a, **k): raise cache.RedisError("nicht erreichbar") def flushdb(self, *a, **k): raise cache.RedisError("nicht erreichbar") def ping(self, *a, **k): raise cache.RedisError("nicht erreichbar") echt = cache.redis_client cache.redis_client = ToterRedis() try: assert cache.get("egal") is None assert cache.set("egal", {"a": 1}) is False assert cache.delete("egal") is False assert cache.clear() is False cache.init_cache() # darf ebenfalls nicht werfen assert cache.zustand()["erreichbar"] is False finally: cache.redis_client = echt def test_cache_meldet_den_ausfall_nur_EINMAL(capsys): """Eine Meldung je Anfrage waere Laerm — und bei jedem Titel-Suchlauf kaeme sie mehrfach.""" from cache import cache class ToterRedis: def get(self, *a, **k): raise cache.RedisError("weg") echt, zustand = cache.redis_client, cache._erreichbar cache.redis_client = ToterRedis() cache._erreichbar = None try: for _ in range(5): cache.get("x") ausgabe = capsys.readouterr().out assert ausgabe.count("nicht erreichbar") == 1, ausgabe finally: cache.redis_client, cache._erreichbar = echt, zustand def test_ein_kaputter_eintrag_wirft_nicht(): """Ein unlesbarer Eintrag ist kein Grund zu scheitern — dann wird die Antwort eben frisch geholt.""" from cache import cache class KaputterRedis: def get(self, *a, **k): return "{kein json" echt = cache.redis_client cache.redis_client = KaputterRedis() try: assert cache.get("x") is None finally: cache.redis_client = echt @pytest.mark.parametrize("name", ["tmdb", "omdb", "jikan", "musicbrainz"]) def test_jeder_client_ist_importierbar(name): """Ein Tippfehler in einem Modulpfad faellt sonst erst auf, wenn jemand eine Disc einlegt.""" __import__("clients." + name)