using Microsoft.Extensions.Logging.Abstractions; namespace Halo.Metadata.Tests; /// Womit sich Halo bei TMDB ausweist. public class ZugangTests { private const string LeseToken = "eyJhbGciOiJIUzI1NiJ9.beispiel.unterschrift"; private const string AelterSchluessel = "0123456789abcdef0123456789abcdef"; private static (TmdbClient Client, Attrappe Attrappe) Mit(string zugang) { var attrappe = Attrappe.MitJson("""{"results":[{"id":1,"title":"Egal"}]}"""); return (new TmdbClient( new HttpClient(attrappe) { BaseAddress = new Uri(TmdbClient.BaseAddress) }, new FesteEinstellungen(new TmdbSettings(zugang, "de-DE")), NullLogger.Instance), attrappe); } /// Ein Lese-Token gehört in die Kopfzeile, nicht in die Adresse. /// /// /// Eine Adresse steht in jedem Zugriffsprotokoll. In jedem Reverse /// Proxy, in jeder Fehlermeldung, die eine URL zitiert. Eine Kopfzeile tut /// das nicht — und bei einem Schlüssel, der in einem offenen Projekt /// mitgeliefert wird, zählt jede Stelle, an der er nicht auftaucht. /// [Test] public async Task Der_Lese_Token_geht_als_Bearer_und_nicht_in_die_Adresse() { var (client, attrappe) = Mit(LeseToken); await client.FindMovieAsync("Egal", null); // **Jede** Anfrage, nicht nur die erste: seit der IMDb-Nachschlag // dazukam, gehen zwei hinaus, und beide müssen es richtig machen. Assert.That(attrappe.Kopfzeilen, Is.All.EqualTo($"Bearer {LeseToken}")); Assert.That(attrappe.Aufrufe, Is.All.Not.Contains("api_key")); Assert.That(attrappe.Aufrufe, Is.All.Not.Contains(LeseToken)); } /// Der ältere Schlüssel kann nur in die Adresse — also tut er das. [Test] public async Task Der_aeltere_Schluessel_geht_weiterhin_als_Abfrageteil() { var (client, attrappe) = Mit(AelterSchluessel); await client.FindMovieAsync("Egal", null); Assert.That(attrappe.Kopfzeilen, Is.All.Null); Assert.That(attrappe.Aufrufe, Is.All.Contains($"api_key={AelterSchluessel}")); } [Test] public async Task Auch_bei_Folgen_wird_der_Bearer_mitgeschickt() { var (client, attrappe) = Mit(LeseToken); await client.FindEpisodeAsync(70523, 1, 1); Assert.That(attrappe.Kopfzeilen, Is.All.StartsWith("Bearer ")); Assert.That(attrappe.Aufrufe, Is.All.Not.Contains("api_key")); } } /// Welcher Schlüssel gilt, wenn mehrere in Frage kommen. public class SchluesselwahlTests { [Test] public void Ein_eigener_Schluessel_sticht_den_mitgelieferten() { var gewählt = TmdbSettings.Resolve("mein-eigener", "de-DE"); Assert.That(gewählt.ApiKey, Is.EqualTo("mein-eigener")); Assert.That(gewählt.UsingProjectKey, Is.False); } [Test] public void Ohne_eigenen_greift_der_mitgelieferte() { var gewählt = TmdbSettings.Resolve(null, null); if (TmdbSettings.ProjectApiKey.Length == 0) { Assert.That(gewählt.ApiKey, Is.Null, "Ohne mitgelieferten Schlüssel bleibt es dabei"); Assert.That(gewählt.UsingProjectKey, Is.False); return; } Assert.That(gewählt.ApiKey, Is.EqualTo(TmdbSettings.ProjectApiKey)); Assert.That(gewählt.UsingProjectKey, Is.True); } [TestCase("")] [TestCase(" ")] public void Ein_leerer_eigener_Schluessel_zaehlt_als_keiner(string leer) { Assert.That(TmdbSettings.Resolve(leer, null).UsingProjectKey, Is.EqualTo(TmdbSettings.ProjectApiKey.Length > 0)); } [Test] public void Leerraum_um_den_Schluessel_wird_abgeschnitten() { Assert.That(TmdbSettings.Resolve(" meiner ", " de-DE ").ApiKey, Is.EqualTo("meiner")); Assert.That(TmdbSettings.Resolve("meiner", " de-DE ").Language, Is.EqualTo("de-DE")); } [Test] public void Ohne_Sprachangabe_gilt_Englisch() { Assert.That(TmdbSettings.Resolve("meiner", null).Language, Is.EqualTo("en-US")); } /// Der mitgelieferte Schlüssel darf nur lesen dürfen. /// /// /// Er steht öffentlich im Quellcode. Ein Lese-Token kann nichts ändern — /// ein älterer API-Schlüssel schon. Diese Prüfung hält fest, dass hier /// keiner der falschen Art hineingerät. /// [Test] public void Der_mitgelieferte_Schluessel_ist_ein_Lese_Token() { if (TmdbSettings.ProjectApiKey.Length == 0) Assert.Pass("Kein Schlüssel mitgeliefert."); Assert.That(TmdbSettings.ProjectApiKey, Does.StartWith("eyJ"), "Erwartet wird ein API Read Access Token, kein älterer API-Schlüssel"); } }