Drei gemeldete Fälle, alle an TMDB nachgemessen statt vermutet: highschool musical 3 0 Treffer high school musical 3 2 Treffer, der richtige zuerst Harry Potter im Orden des Phoenix 0 Treffer Harry Potter 20 Treffer, der richtige darunter dr dolittle 13 Treffer, der richtige zuerst Der fehlende Punkt bei Dr. Dolittle war nie das Problem — diese Vermutung ist in Suchvarianten.cs festgehalten, damit sie nicht wiederkommt. Die Suche versucht jetzt der Reihe nach: den Titel wie er dasteht, dann das längste Wort an jeder Stelle getrennt (von der Mitte nach aussen, ohne Wörterbuch — „highschool" trifft nach 2 Versuchen, „jurassicpark" nach 5), dann von hinten gekürzt, bis zwei Worte übrig sind. Höchstens zehn Anfragen, und nur dann, wenn die erste gar nichts brachte. Und die Treffer werden nach Ähnlichkeit zum gesuchten Titel sortiert, nicht nach Beliebtheit. Über gemeinsame Worte ohne Akzente: so trennt „Harry Potter" den „Orden des Phönix" von „Harry Potter: Fireplace", und „Phoenix" findet „Phönix". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
297 lines
11 KiB
C#
297 lines
11 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");
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|