Files
Flaro/server/tests/Flaro.Metadata.Tests/TmdbClientTests.cs
T
ScarriffleandClaude Opus 5 bfe263d495 Vierundzwanzig Bilder, und der Weg dorthin fehlte sowieso
Zwei Dinge, die zusammengehören.

**Den Bilderdialog gab es nur an der Kachel.** Wer auf der Seite eines
Films stand und sah, dass das Plakat falsch ist, stand genau dort, wo er
nichts dagegen tun konnte — der Weg führte zurück in die Liste und über
das Dreipunktmenü. Jetzt steht „Bearbeiten" in der Knopfreihe der
Detailseite, bei Filmen wie bei Serien, und nur im Kopf: dieselbe Reihe
steht auch über jeder Fassung und jeder Folge.

**Und es waren vierundzwanzig, weil dort vierundzwanzig stand.** Bei
„365 Tage" hat TMDB neunundsechzig Plakate. Dazu ein zweiter, leiserer
Filter: gefragt wurden nur drei Sprachen. Fürs automatische Holen ist
das richtig — dort wird eines genommen, und es soll passen. Wer von Hand
wählt, will sehen, was es gibt; sortiert wird ohnehin nach Sprache, also
steht das Passende weiterhin oben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-12 20:49:37 +02:00

341 lines
13 KiB
C#

using System.Net;
using Flaro.Core.Metadata;
using Microsoft.Extensions.Logging.Abstractions;
namespace Flaro.Metadata.Tests;
public class TmdbClientTests
{
private static TmdbClient Client(Attrappe attrappe, ITmdbSettingsSource? einstellungen = null) =>
new(new HttpClient(attrappe) { BaseAddress = new Uri(TmdbClient.BaseAddress) },
einstellungen ?? FesteEinstellungen.MitSchluessel(),
NullLogger<TmdbClient>.Instance);
private const string FilmTreffer = """
{"results":[{"id":603,"title":"Matrix","release_date":"1999-03-30",
"overview":"Neo zweifelt an der Wirklichkeit.",
"poster_path":"/abc.jpg","backdrop_path":"/def.jpg"}]}
""";
[Test]
public async Task Ohne_Schluessel_wird_nichts_gefragt()
{
var attrappe = Attrappe.MitJson(FilmTreffer);
var client = Client(attrappe, FesteEinstellungen.Ohne);
Assert.That(await client.IsConfiguredAsync(), Is.False);
Assert.That(await client.FindMovieAsync("Matrix", 1999), Is.Null);
Assert.That(attrappe.Aufrufe, Is.Empty, "Ohne Schlüssel darf keine Anfrage hinausgehen");
}
/// <summary>Die Bilderliste für die Auswahl von Hand.</summary>
///
/// <remarks>
/// <b>Zwei Grenzen, die beide störten.</b> Die Anfrage holte nur drei
/// Sprachen, und gezählt wurde bei vierundzwanzig je Art zu Ende — bei
/// „365 Tage" hat TMDB neunundsechzig Plakate, und die fehlenden
/// fünfundvierzig sah niemand. Wer von Hand wählt, soll sehen, was es
/// gibt.
/// </remarks>
[Test]
public async Task Die_Bilderliste_ist_nicht_bei_vierundzwanzig_zu_Ende()
{
var plakate = string.Join(",", Enumerable.Range(0, 69)
.Select(i => $$"""{"file_path":"/p{{i}}.jpg","iso_639_1":"en","vote_average":1.0}"""));
var attrappe = Attrappe.MitJson($$"""{"posters":[{{plakate}}],"backdrops":[],"logos":[]}""");
var bilder = await Client(attrappe).BilderAsync(603, serie: false);
Assert.Multiple(() =>
{
Assert.That(bilder.Count(b => b.Kind == "poster"), Is.EqualTo(69));
Assert.That(attrappe.Aufrufe[0], Does.Not.Contain("include_image_language"),
"Die Auswahl von Hand soll alle Sprachen sehen");
});
}
/// <summary>Die passende Sprache steht trotzdem oben.</summary>
[Test]
public async Task Die_eigene_Sprache_steht_vorn()
{
var attrappe = Attrappe.MitJson("""
{"posters":[
{"file_path":"/en.jpg","iso_639_1":"en","vote_average":9.0},
{"file_path":"/de.jpg","iso_639_1":"de","vote_average":1.0},
{"file_path":"/ja.jpg","iso_639_1":"ja","vote_average":8.0}],
"backdrops":[],"logos":[]}
""");
var bilder = await Client(attrappe, FesteEinstellungen.MitSchluessel("de-DE"))
.BilderAsync(603, serie: false);
Assert.That(bilder[0].Url, Does.Contain("/de.jpg"));
}
[Test]
public async Task Ein_Film_wird_gelesen()
{
var film = await Client(Attrappe.MitJson(FilmTreffer)).FindMovieAsync("Matrix", 1999);
Assert.That(film, Is.Not.Null);
Assert.That(film!.ProviderId, Is.EqualTo(603));
Assert.That(film.Title, Is.EqualTo("Matrix"));
Assert.That(film.Year, Is.EqualTo(1999));
Assert.That(film.Overview, Does.StartWith("Neo zweifelt"));
Assert.That(film.Poster!.Url, Is.EqualTo("https://image.tmdb.org/t/p/w500/abc.jpg"));
Assert.That(film.Backdrop!.Url, Is.EqualTo("https://image.tmdb.org/t/p/w1280/def.jpg"));
}
[Test]
public async Task Titel_Jahr_und_Sprache_landen_in_der_Anfrage()
{
var attrappe = Attrappe.MitJson(FilmTreffer);
await Client(attrappe, FesteEinstellungen.MitSchluessel("de-DE")).FindMovieAsync("Der Untergang", 2004);
// Die erste Anfrage ist die Suche; danach folgt der Nachschlag der
// IMDb-Kennung für die NFO-Datei.
var aufruf = attrappe.Aufrufe[0];
Assert.That(aufruf, Does.Contain("query=Der%20Untergang"));
Assert.That(aufruf, Does.Contain("year=2004"));
Assert.That(aufruf, Does.Contain("language=de-DE"));
}
[Test]
public async Task Ohne_Jahr_wird_keines_mitgeschickt()
{
var attrappe = Attrappe.MitJson(FilmTreffer);
await Client(attrappe).FindMovieAsync("Matrix", null);
Assert.That(attrappe.Aufrufe[0], Does.Not.Contain("year="));
}
[Test]
public async Task Eine_leere_Trefferliste_ergibt_nichts()
{
Assert.That(await Client(Attrappe.MitJson("""{"results":[]}""")).FindMovieAsync("Gibtesnicht", null), Is.Null);
}
/// <summary>Ein falscher Schlüssel muss auffallen.</summary>
///
/// <remarks>
/// <b>„Nichts gefunden" und „dein Schlüssel ist falsch" sehen in der
/// Mediathek gleich aus.</b> Ohne diese Unterscheidung sucht jemand
/// stundenlang nach dem Grund, warum keine Bilder erscheinen.
/// </remarks>
[TestCase(HttpStatusCode.Unauthorized)]
[TestCase(HttpStatusCode.Forbidden)]
public void Ein_zurueckgewiesener_Schluessel_wirft(HttpStatusCode code)
{
var client = Client(Attrappe.MitStatus(code));
var fehler = Assert.ThrowsAsync<MetadataCredentialsException>(() => client.FindMovieAsync("Matrix", null));
Assert.That(fehler!.Message, Does.Contain("Schlüssel"));
}
/// <summary>Alles andere ist kein Grund, den Scan abzubrechen.</summary>
[TestCase(HttpStatusCode.NotFound)]
[TestCase(HttpStatusCode.InternalServerError)]
[TestCase(HttpStatusCode.TooManyRequests)]
[TestCase(HttpStatusCode.ServiceUnavailable)]
public async Task Andere_Fehler_ergeben_still_nichts(HttpStatusCode code)
{
Assert.That(await Client(Attrappe.MitStatus(code)).FindMovieAsync("Matrix", null), Is.Null);
}
/// <summary>Ein Anmeldeportal im Hotel-WLAN antwortet mit HTML und Status 200.</summary>
///
/// <remarks>
/// Darauf mit einer Ausnahme zu reagieren hiesse, den ganzen Scan an einem
/// fremden Netz scheitern zu lassen.
/// </remarks>
[Test]
public async Task Unsinn_statt_JSON_wirft_nicht()
{
var attrappe = Attrappe.MitJson("<html><body>Bitte anmelden</body></html>");
Assert.That(await Client(attrappe).FindMovieAsync("Matrix", null), Is.Null);
}
[Test]
public async Task Kein_Netz_ergibt_nichts_statt_eines_Absturzes()
{
var attrappe = Attrappe.DieWirft(new HttpRequestException("Name or service not known"));
Assert.That(await Client(attrappe).FindMovieAsync("Matrix", null), Is.Null);
}
[Test]
public async Task Eine_Serie_wird_gelesen()
{
var json = """
{"results":[{"id":70523,"name":"Dark","first_air_date":"2017-12-01",
"overview":"Vier Familien.","poster_path":"/p.jpg","backdrop_path":null}]}
""";
var serie = await Client(Attrappe.MitJson(json)).FindSeriesAsync("Dark", null);
Assert.That(serie!.ProviderId, Is.EqualTo(70523));
Assert.That(serie.Title, Is.EqualTo("Dark"));
Assert.That(serie.Year, Is.EqualTo(2017));
Assert.That(serie.Poster!.Url, Does.EndWith("/p.jpg"));
Assert.That(serie.Backdrop, Is.Null, "Ein fehlendes Bild ist null, keine kaputte Adresse");
}
[Test]
public async Task Eine_Folge_wird_gelesen()
{
var attrappe = Attrappe.MitJson("""
{"name":"Geheimnisse","overview":"Ein Junge verschwindet.","still_path":"/s.jpg"}
""");
var folge = await Client(attrappe).FindEpisodeAsync(70523, 1, 1);
Assert.That(folge!.Title, Is.EqualTo("Geheimnisse"));
Assert.That(folge.Still!.Url, Is.EqualTo("https://image.tmdb.org/t/p/w300/s.jpg"));
Assert.That(attrappe.Aufrufe.Single(), Does.Contain("tv/70523/season/1/episode/1"));
}
/// <summary>Eine Folge, die TMDB nicht kennt, ist kein Fehler.</summary>
[Test]
public async Task Eine_unbekannte_Folge_ergibt_nichts()
{
Assert.That(await Client(Attrappe.MitStatus(HttpStatusCode.NotFound)).FindEpisodeAsync(1, 99, 99), Is.Null);
}
[Test]
public async Task Leere_Felder_werden_zu_null_statt_zu_Leerstrings()
{
var json = """{"results":[{"id":1,"title":"Ohne","release_date":"","overview":"","poster_path":""}]}""";
var film = await Client(Attrappe.MitJson(json)).FindMovieAsync("Ohne", null);
Assert.That(film!.Overview, Is.Null);
Assert.That(film.Year, Is.Null);
Assert.That(film.Poster, Is.Null);
}
}
/// <summary>Welcher Treffer gewinnt, wenn mehrere in Frage kommen.</summary>
public class TrefferwahlTests
{
private static TmdbClient Client(Attrappe attrappe) =>
new(new HttpClient(attrappe) { BaseAddress = new Uri(TmdbClient.BaseAddress) },
FesteEinstellungen.MitSchluessel(), NullLogger<TmdbClient>.Instance);
/// <summary>Am echten Dienst gemessen, nicht vermutet.</summary>
///
/// <remarks>
/// <b>Die Suche nach „Dark" lieferte „Dark Shadows" von 1966.</b> TMDB
/// sortiert nach Beliebtheit, und ohne Jahresangabe führt das in die Irre.
/// Ein Treffer mit genau diesem Namen ist gemeint, auch wenn er weiter
/// unten steht.
/// </remarks>
[Test]
public async Task Ein_genau_passender_Name_sticht_den_beliebteren()
{
var attrappe = Attrappe.MitJson("""
{"results":[
{"id":1,"name":"Dark Shadows","first_air_date":"1966-06-27"},
{"id":2,"name":"Dark","first_air_date":"2017-12-01"}
]}
""");
var serie = await Client(attrappe).FindSeriesAsync("Dark", null);
Assert.That(serie!.ProviderId, Is.EqualTo(2));
Assert.That(serie.Year, Is.EqualTo(2017));
}
[Test]
public async Task Gross_und_Kleinschreibung_spielt_dabei_keine_Rolle()
{
var attrappe = Attrappe.MitJson("""
{"results":[{"id":1,"title":"Irgendwas"},{"id":2,"title":"DER UNTERGANG"}]}
""");
var film = await Client(attrappe).FindMovieAsync("Der Untergang", null);
Assert.That(film!.ProviderId, Is.EqualTo(2));
}
/// <summary>Ohne genaue Entsprechung bleibt es beim beliebtesten.</summary>
///
/// <remarks>
/// Eine eigene Ähnlichkeitsbewertung zu bauen hiesse, TMDBs Rangfolge
/// nachzubauen — mit weniger Daten.
/// </remarks>
[Test]
public async Task Ohne_genaue_Entsprechung_gewinnt_der_erste()
{
var attrappe = Attrappe.MitJson("""
{"results":[{"id":1,"title":"Der Untergang des Hauses Usher"},{"id":2,"title":"Untergang"}]}
""");
var film = await Client(attrappe).FindMovieAsync("Der Untergang", null);
Assert.That(film!.ProviderId, Is.EqualTo(1));
}
}
/// <summary>Was versucht wird, wenn der Titel so nicht gefunden wird.</summary>
///
/// <remarks>
/// <b>Die Fälle stammen aus dem echten Bestand und sind an TMDB nachgemessen.</b>
/// „highschool musical 3" liefert dort null Treffer, „high school musical 3"
/// zwei. „Harry Potter im Orden des Phoenix" null, „Harry Potter" zwanzig.
/// Und „dr dolittle" dreizehn — der fehlende Punkt war nie das Problem.
/// </remarks>
public class SuchvariantenTests
{
[Test]
public void Der_Titel_selbst_kommt_zuerst()
{
Assert.That(Suchvarianten.Fuer("Dr. Dolittle").First(), Is.EqualTo("Dr. Dolittle"));
}
[Test]
public void Ein_zusammengeschriebenes_Wort_wird_getrennt()
{
Assert.That(Suchvarianten.Fuer("highschool musical 3"),
Does.Contain("high school musical 3"));
}
/// <summary>Von der Mitte nach aussen — der richtige Schnitt kommt früh.</summary>
[Test]
public void Der_richtige_Schnitt_kommt_in_den_ersten_Versuchen()
{
var alle = Suchvarianten.Fuer("highschool musical 3").ToList();
Assert.That(alle.IndexOf("high school musical 3"), Is.LessThanOrEqualTo(3));
}
[Test]
public void Hintere_Worte_fallen_weg()
{
Assert.That(Suchvarianten.Fuer("Harry Potter im Orden des Phoenix"),
Does.Contain("Harry Potter"));
}
/// <summary>Unter zwei Worten hört es auf — „Harry" allein taugt nicht.</summary>
[Test]
public void Ein_einzelnes_Wort_wird_nicht_gesucht()
{
Assert.That(Suchvarianten.Fuer("Harry Potter im Orden des Phoenix"),
Does.Not.Contain("Harry"));
}
[Test]
public void Es_wird_nicht_endlos_probiert()
{
Assert.That(Suchvarianten.Fuer("Ein ziemlich langer Titel mit vielen Worten darin"),
Has.Exactly(10).Items);
}
/// <summary>Phoenix findet Phönix: die Akzente fallen weg.</summary>
[Test]
public void Aehnlichkeit_sieht_ueber_Akzente_hinweg()
{
var richtig = Suchvarianten.Aehnlichkeit(
"Harry Potter im Orden des Phoenix", "Harry Potter und der Orden des Phönix");
var falsch = Suchvarianten.Aehnlichkeit(
"Harry Potter im Orden des Phoenix", "Harry Potter: Fireplace");
Assert.That(richtig, Is.GreaterThan(falsch));
}
[Test]
public void Nichts_Gemeinsames_ergibt_null()
{
Assert.That(Suchvarianten.Aehnlichkeit("Dune", "Alles steht Kopf"), Is.Zero);
}
}