using System.Net; using Halo.Core.Metadata; using Microsoft.Extensions.Logging.Abstractions; namespace Halo.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.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); } /// Ein falscher Schlüssel muss auffallen. /// /// /// „Nichts gefunden" und „dein Schlüssel ist falsch" sehen in der /// Mediathek gleich aus. Ohne diese Unterscheidung sucht jemand /// stundenlang nach dem Grund, warum keine Bilder erscheinen. /// [TestCase(HttpStatusCode.Unauthorized)] [TestCase(HttpStatusCode.Forbidden)] public void Ein_zurueckgewiesener_Schluessel_wirft(HttpStatusCode code) { var client = Client(Attrappe.MitStatus(code)); var fehler = Assert.ThrowsAsync(() => client.FindMovieAsync("Matrix", null)); Assert.That(fehler!.Message, Does.Contain("Schlüssel")); } /// Alles andere ist kein Grund, den Scan abzubrechen. [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); } /// Ein Anmeldeportal im Hotel-WLAN antwortet mit HTML und Status 200. /// /// /// Darauf mit einer Ausnahme zu reagieren hiesse, den ganzen Scan an einem /// fremden Netz scheitern zu lassen. /// [Test] public async Task Unsinn_statt_JSON_wirft_nicht() { var attrappe = Attrappe.MitJson("Bitte anmelden"); 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")); } /// Eine Folge, die TMDB nicht kennt, ist kein Fehler. [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); } } /// Welcher Treffer gewinnt, wenn mehrere in Frage kommen. public class TrefferwahlTests { private static TmdbClient Client(Attrappe attrappe) => new(new HttpClient(attrappe) { BaseAddress = new Uri(TmdbClient.BaseAddress) }, FesteEinstellungen.MitSchluessel(), NullLogger.Instance); /// Am echten Dienst gemessen, nicht vermutet. /// /// /// Die Suche nach „Dark" lieferte „Dark Shadows" von 1966. 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. /// [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)); } /// Ohne genaue Entsprechung bleibt es beim beliebtesten. /// /// /// Eine eigene Ähnlichkeitsbewertung zu bauen hiesse, TMDBs Rangfolge /// nachzubauen — mit weniger Daten. /// [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)); } }