Files
rippy/docker/api/test_clients.py
T
HitonabiandClaude Opus 5 878b24c43b
Ampel / ampel (push) Successful in 54s
fix: die Metadaten-Suche war seit V2-1 tot — auf Windows UND auf der VM
Der Commander: "TMDB und OMDB Key sind hinterlegt, das laufwerk wird aber
auch nicht korrekt ausgelesen. Eigentlich sollte er direkt bei TMDB oder OMDB
oder JIKAN anfragen nach metadaten."

Er hat das Symptom gemeldet. Darunter lagen VIER Fehler.

## 1. Ein Phantom-Modul (der schwerste)

    clients/tmdb.py:27     from db import get_settings
    clients/omdb.py:36     from db import get_settings
    clients/thetvdb.py:16  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 -- und `_auto_prescan` verschluckte das in seinem
`except Exception`. Die Disc blieb namenlos, und niemand sah warum.

**Das betrifft die VM genauso.** Sie steht auf 05ab655, also NACH dd1d0b7 --
dort laeuft seit V2-1 dieselbe tote Metadaten-Suche.

## 2. Redis war Pflicht statt Beschleunigung

`cache/cache.py` sprach `redis:6379` an -- den Dienstnamen aus
docker-compose.yml. Auf einem Windows-PC gibt es kein Redis:

    redis.exceptions.ConnectionError: Error 11001 connecting to redis:6379

Das riss den Pre-Scan mit. Ein Zwischenspeicher ist eine Beschleunigung,
keine Voraussetzung: Jetzt faengt jeder Zugriff den Verbindungsfehler ab und
meldet "nicht vorhanden" -- was der Wahrheit entspricht. Gemeldet wird der
Ausfall EINMAL, nicht bei jeder Anfrage.

## 3. Der Klartext-Titel war unter Windows nicht erreichbar

`read_disc_title_via_mount` machte fest `mount -t udf` -- also Linux. Unter
Windows haengt Windows die Disc selbst ein. An der echten Disc gemessen:

    Volume-Label (UDF)          BD_EVG_D2      <- damit findet keine API etwas
    BDMV/META/DL/bdmt_deu.xml   Evangelion 2.22

`disc_wurzel()` liefert jetzt je Plattform einen lesbaren Einstieg, und
`titel_aus_bdmt()` ist davon getrennt (und damit ohne Disc pruefbar).

## 4. Modell und Seriennummer fehlten

Im UI stand "Modell: unbekannt, Seriennummer: -". Der Linux-Treiber liest
beides aus /sys; der Windows-Treiber lieferte leere Felder. Die Entsprechung
ist IOCTL_STORAGE_QUERY_PROPERTY. An echter Hardware gemessen:

    hersteller     'HL-DT-ST'
    modell         'BD-RE BU40N'
    fassung        '1.03'
    seriennummer   '0025114C0149'

Gemerkt statt jedes Mal abgefragt: Die Disc-Wache ruft device_info alle drei
Sekunden, und die Angaben eines Laufwerks aendern sich nicht.

## Ergebnis, an der eingelegten Disc gemessen

    title        'Neon Genesis Evangelion'
    year         1995
    disc_type    'Blu-ray'
    confidence   0.8
    fingerprint  'BD_EVG_D2|48149364736'

(Jikan antwortete waehrend der Messung mit 504 -- deren Ausfall, nicht
unserer. Der Treffer kam von TMDB.)

Zwei Waechter, beide beim Zurueckdrehen rot gesehen: Die Clients muessen sich
OHNE Datenbank und OHNE Redis bauen lassen, und ein `from db import` in
clients/ faellt mechanisch auf.

Ampel lokal: 769 gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:59:47 +02:00

158 lines
5.5 KiB
Python

"""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)