Vier Fehler, und bei zweien log der Kommentar daneben

**Der Einstellungsknopf lag hinter der Abspielleiste.** Gemessen: `.pl`
steht auf Stufe 150, `#chrome` auf gar keiner, und die kleine Leiste spannte
über die ganze Breite — genau über die unteren 72 Bildpunkte der
Seitenleiste, wo „Einstellungen" klebt. Weil der Spieler bei jedem Wechsel
der Adresse minimiert, stand sie zuverlässig im Weg, sobald man dorthin
wollte.

Die Leiste beginnt jetzt hinter der Seitenleiste. Nicht über die Stufen
gelöst, und das ist der Punkt: `#chrome` höher zu setzen hiesse, die
Seitenleiste läge auch über dem grossen Spieler — es ist dieselbe Hülle.
Dass diese Leiste Platz wegnimmt statt sich darüberzulegen, stand für
Inhalt, Hinweise und Auswahlleiste ohnehin schon da; die Seitenleiste war
die vergessene vierte. Gemessen, 1400 Bildpunkte breit: Leiste ab 240,
Knopf bei 0, keine Überschneidung, und ein Treffertest in der Knopfmitte
trifft den Knopf.

**Ein Klick auf eine Kategorie kam nie an.** Dort sprang der Code in die
Mediathek und suchte danach `.filterzeile select`, um den Wert zu setzen.
Die Filterzeile hat kein `<select>` — `auswahl()` baut einen Knopf, mit
Absicht und begründet. Der Zweig lief also nie, und man landete in der
ungefilterten Mediathek. Der Kommentar daneben behauptete das Gegenteil.

Das Genre steht jetzt in der Adresse: `#/m/{id}/mediathek/Horror`. Das
löst zugleich das zweite Problem — die Mediathek wird unter ihrem Weg
gemerkt, und stünde das Genre nur im Kopf, zeigte ein zweiter Besuch die
Liste des ersten. Geprüft: Klick setzt den Weg und die Pille, Neuladen
behält beides, Zurück führt auf die Kategorien, Vorwärts wieder hinein.

**Zurückgezogene Einladungen blieben für immer stehen.** Der Knopfblock
steckte ganz in `if (!e.revokedAt)`, eine erledigte Zeile hatte also gar
keinen Knopf. Die Serverbegründung — eine Zeile wegzuwerfen hiesse,
hinterher nicht mehr zu wissen, dass es den Link je gab — bleibt richtig,
sie gilt nur nicht ewig: eine Liste, aus der nichts verschwindet, wird
unlesbar, und dann sieht man die eine Zeile nicht mehr, um die es ging.

Deshalb kein umgedeutetes DELETE, sondern `?purge=true`, und das greift nur
bei einem Link, der ohnehin keine Tür mehr öffnet. Ein gültiger wird mit
einem deutschen Satz abgewiesen.

**Poster in der falschen Sprache.** „365 Tage" stand mit polnischem Plakat
da. Kein fehlender Anbieter: für den Schriftzug gab es die sprachgewichtete
Wahl längst, fürs Plakat wurde das blanke `poster_path` der Hauptantwort
genommen — bei TMDB das Bild der Originalsprache, wenn in der gewünschten
keines eingetragen ist. Die sortierte Liste lag im selben Abruf schon vor
und wurde ausschliesslich für den Schriftzug benutzt.

Plakat und Hintergrund nehmen jetzt dieselbe Rangfolge: eingestellte
Sprache, dann sprachlos, dann Englisch. Sprachlos vor Englisch ist keine
Verlegenheit — ein Bild ohne Sprachkennung trägt keinen Text, und ein
Plakat ohne Titel ist für einen deutschen Bestand besser als dasselbe mit
englischem. Im Bilderdialog stehen die Kandidaten jetzt ebenso sortiert.
Bestehende Titel ändern sich beim nächsten Auffrischen; von Hand gesetzte
Plakate bleiben unangetastet.

676 Tests grün, davon zehn neue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-08 17:46:46 +02:00
co-authored by Claude Opus 5
parent c6df9fdab0
commit 2809821c9b
9 changed files with 391 additions and 25 deletions
@@ -0,0 +1,144 @@
using Microsoft.Extensions.Logging.Abstractions;
namespace Flaro.Metadata.Tests;
/// <summary>Welches Plakat und welcher Hintergrund genommen werden.</summary>
///
/// <remarks>
/// <para>
/// <b>Der gemeldete Fehler.</b> „365 Tage" stand mit polnischem Plakat da,
/// obwohl in der eingestellten Sprache gesucht wurde. TMDB liefert im
/// Einzelabruf das Feld <c>poster_path</c> der Originalsprache, wenn in der
/// gewünschten keines eingetragen ist — und genau dieses Feld wurde
/// genommen. Die sprachsortierte Liste lag in derselben Antwort schon vor
/// (<c>append_to_response=…,images</c>) und wurde ausschliesslich für den
/// Schriftzug benutzt.
/// </para>
/// <para>
/// <b>Sprachlos vor Englisch.</b> Ein Bild ohne Sprachkennung trägt keinen
/// Text. Für einen deutschen Bestand ist ein Plakat ohne Titel besser als
/// dasselbe mit englischem Titel darüber.
/// </para>
/// </remarks>
public class Bildwahltests
{
private static TmdbClient Client(Attrappe attrappe, string sprache = "de-DE") =>
new(new HttpClient(attrappe) { BaseAddress = new Uri(TmdbClient.BaseAddress) },
FesteEinstellungen.MitSchluessel(sprache), NullLogger<TmdbClient>.Instance);
private const string Suche =
"""{"results":[{"id":500,"title":"Probe","release_date":"2020-02-07"}]}""";
/// <summary>Ein Einzelabruf mit Bilderliste und einem Rückfallfeld.</summary>
/// <summary>Ein Einzelabruf mit Bilderliste und einem Rückfallfeld.</summary>
private static Attrappe MitBildern(string posters, string backdrops = "[]") =>
Attrappe.NachPfad(
("search/movie", Suche),
("movie/500",
"{\"id\":500,\"title\":\"Probe\",\"release_date\":\"2020-02-07\","
+ "\"poster_path\":\"/original.jpg\",\"backdrop_path\":\"/grund.jpg\","
+ "\"images\":{\"posters\":" + posters
+ ",\"backdrops\":" + backdrops + "}}"));
private static Task<Core.Metadata.MovieMetadata?> Holen(Attrappe a, string sprache = "de-DE") =>
Client(a, sprache).FindMovieAsync("Probe", 2020);
[Test]
public async Task Das_deutsche_Plakat_sticht_das_der_Originalsprache()
{
var film = await Holen(MitBildern("""
[{"file_path":"/pl.jpg","iso_639_1":"pl","vote_average":9.0},
{"file_path":"/de.jpg","iso_639_1":"de","vote_average":1.0}]
"""));
Assert.That(film!.Poster!.Url, Does.EndWith("/de.jpg"),
"die eingestellte Sprache gewinnt, auch gegen die besseren Stimmen");
}
[Test]
public async Task Ohne_deutsches_Plakat_gewinnt_das_sprachlose()
{
var film = await Holen(MitBildern("""
[{"file_path":"/en.jpg","iso_639_1":"en","vote_average":9.0},
{"file_path":"/ohne.jpg","iso_639_1":null,"vote_average":1.0},
{"file_path":"/pl.jpg","iso_639_1":"pl","vote_average":8.0}]
"""));
Assert.That(film!.Poster!.Url, Does.EndWith("/ohne.jpg"),
"ein Plakat ohne Text ist besser als eines mit englischem");
}
[Test]
public async Task Gibt_es_nur_Englisch_und_Fremdes_gewinnt_Englisch()
{
var film = await Holen(MitBildern("""
[{"file_path":"/pl.jpg","iso_639_1":"pl","vote_average":9.0},
{"file_path":"/en.jpg","iso_639_1":"en","vote_average":1.0}]
"""));
Assert.That(film!.Poster!.Url, Does.EndWith("/en.jpg"));
}
/// <summary>Bei gleicher Sprache entscheiden die Stimmen.</summary>
[Test]
public async Task Unter_Gleichsprachigen_gewinnen_die_Stimmen()
{
var film = await Holen(MitBildern("""
[{"file_path":"/de-schwach.jpg","iso_639_1":"de","vote_average":2.0},
{"file_path":"/de-stark.jpg","iso_639_1":"de","vote_average":8.0}]
"""));
Assert.That(film!.Poster!.Url, Does.EndWith("/de-stark.jpg"));
}
/// <summary>Ohne Liste bleibt es beim Feld der Hauptantwort.</summary>
///
/// <remarks>
/// Ein alter Zwischenspeicher oder eine abgespeckte Antwort darf nicht
/// dazu führen, dass gar kein Plakat mehr herauskommt.
/// </remarks>
[Test]
public async Task Ohne_Bilderliste_bleibt_das_Feld_der_Hauptantwort()
{
var attrappe = Attrappe.NachPfad(
("search/movie", Suche),
("movie/500", """
{"id":500,"title":"Probe","release_date":"2020-02-07",
"poster_path":"/original.jpg","backdrop_path":"/grund.jpg"}
"""));
var film = await Holen(attrappe);
Assert.Multiple(() =>
{
Assert.That(film!.Poster!.Url, Does.EndWith("/original.jpg"));
Assert.That(film.Backdrop!.Url, Does.EndWith("/grund.jpg"));
});
}
/// <summary>Der Hintergrund folgt derselben Regel.</summary>
[Test]
public async Task Der_Hintergrund_bevorzugt_das_Bild_ohne_Schrift()
{
var film = await Holen(MitBildern("[]", """
[{"file_path":"/mit-titel.jpg","iso_639_1":"en","vote_average":9.0},
{"file_path":"/ohne-titel.jpg","iso_639_1":null,"vote_average":1.0}]
"""));
Assert.That(film!.Backdrop!.Url, Does.EndWith("/ohne-titel.jpg"));
}
/// <summary>Eine andere eingestellte Sprache verschiebt die Rangfolge mit.</summary>
[Test]
public async Task Die_Rangfolge_haengt_an_der_eingestellten_Sprache()
{
var bilder = """
[{"file_path":"/de.jpg","iso_639_1":"de","vote_average":9.0},
{"file_path":"/fr.jpg","iso_639_1":"fr","vote_average":1.0}]
""";
var film = await Holen(MitBildern(bilder), "fr-FR");
Assert.That(film!.Poster!.Url, Does.EndWith("/fr.jpg"));
}
}