using System.Net; using System.Net.Http.Json; using Flaro.Api.Endpoints; namespace Flaro.Api.Tests; /// Melden darf jeder, lesen nur die Verwaltung — und die eigenen sieht man selbst. /// /// /// Der Test, der hier wirklich zählt, ist der mit dem ungeprüften Titel. /// Die Rechteprüfung beim Melden steht nirgends als `Where` — sie fällt aus dem /// Freigabefilter heraus. Das ist elegant und genau deshalb leicht zu /// zerstören: wer in den Endpunkt einmal `IgnoreQueryFilters` schreibt, um /// „auch zurückgezogene Titel zu finden", hat sie weggenommen, ohne dass eine /// Zeile danach aussieht. /// public class MeldungTests { private string _bestand = ""; [SetUp] public void Anlegen() => _bestand = Directory.CreateDirectory( Path.Combine(Path.GetTempPath(), "flaro-meldung-" + Guid.CreateVersion7().ToString("N"))).FullName; [TearDown] public void Aufraeumen() { try { Directory.Delete(_bestand, recursive: true); } catch (IOException) { } } private void Datei(string relativ) { var voll = Path.Combine(_bestand, relativ); Directory.CreateDirectory(Path.GetDirectoryName(voll)!); File.WriteAllBytes(voll, new byte[2048]); } [Test] public async Task Eine_Meldung_zu_einem_Film_landet_beim_Administrator() { Datei("Heat (1995).mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Filme", "Movies"); var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswortlang"); var film = (await Filme(gast)).Single(); var gemeldet = await gast.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("ton", "Der deutsche Ton setzt aus.", "1:02:03")); var liste = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); Assert.That(gemeldet.StatusCode, Is.EqualTo(HttpStatusCode.OK)); Assert.That(liste, Has.Count.EqualTo(1)); Assert.Multiple(() => { Assert.That(liste![0].Target, Is.EqualTo("movie")); Assert.That(liste[0].ItemId, Is.EqualTo(film.Id)); Assert.That(liste[0].Title, Is.EqualTo("Heat")); Assert.That(liste[0].Kind, Is.EqualTo("ton")); Assert.That(liste[0].PositionSeconds, Is.EqualTo(3723)); Assert.That(liste[0].ReportedBy, Is.EqualTo("gast")); Assert.That(liste[0].ResolvedAt, Is.Null); }); } [Test] public async Task Melden_darf_jeder_die_Verwaltung_bleibt_zu() { Datei("Heat (1995).mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Filme", "Movies"); var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswortlang"); var film = (await Filme(gast)).Single(); var gemeldet = await gast.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("bild", "Das Plakat gehört zu einem anderen Film.")); var meine = await gast.GetAsync("/api/v1/reports/mine"); var fremd = await gast.GetAsync("/api/v1/reports"); var id = (await alsAdmin.GetFromJsonAsync>("/api/v1/reports"))![0].Id; var abhaken = await gast.PostAsync($"/api/v1/reports/{id}/resolve", null); Assert.Multiple(() => { Assert.That(gemeldet.StatusCode, Is.EqualTo(HttpStatusCode.OK)); Assert.That(meine.StatusCode, Is.EqualTo(HttpStatusCode.OK)); Assert.That(fremd.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden), "Die Liste aller Meldungen ist Verwaltung."); Assert.That(abhaken.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); }); } [Test] public async Task Jeder_sieht_nur_die_eigenen_Meldungen() { Datei("Heat (1995).mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Filme", "Movies"); var eine = await server.NeuerBenutzer(alsAdmin, "eine", "einepasswortlang"); var andere = await server.NeuerBenutzer(alsAdmin, "andere", "anderepasswortlang"); var film = (await Filme(eine)).Single(); await eine.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("ton", "Von mir.")); await andere.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("bild", "Von jemand anderem.")); var meine = await eine.GetFromJsonAsync>("/api/v1/reports/mine"); var alle = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); Assert.Multiple(() => { Assert.That(meine, Has.Count.EqualTo(1)); Assert.That(meine![0].Comment, Is.EqualTo("Von mir.")); Assert.That(alle, Has.Count.EqualTo(2), "Der Administrator sieht beide."); }); } [Test] public async Task Erledigt_heisst_abgehakt_und_nicht_geloescht() { Datei("Heat (1995).mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Filme", "Movies"); var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswortlang"); var film = (await Filme(gast)).Single(); await gast.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("untertitel", "Die deutsche Spur ist leer.")); var id = (await alsAdmin.GetFromJsonAsync>("/api/v1/reports"))![0].Id; (await alsAdmin.PostAsync($"/api/v1/reports/{id}/resolve", null)).EnsureSuccessStatusCode(); var offen = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); var erledigt = await alsAdmin.GetFromJsonAsync>("/api/v1/reports?erledigt=true"); var beimMelder = await gast.GetFromJsonAsync>("/api/v1/reports/mine"); Assert.Multiple(() => { Assert.That(offen, Is.Empty); Assert.That(erledigt, Has.Count.EqualTo(1)); Assert.That(erledigt![0].ResolvedAt, Is.Not.Null); Assert.That(beimMelder, Has.Count.EqualTo(1), "Der Melder sieht seine Meldung weiterhin — mitsamt dem Stand."); Assert.That(beimMelder![0].ResolvedAt, Is.Not.Null); }); // Ein Fehlgriff beim Abhaken darf kein Dauerzustand sein. (await alsAdmin.PostAsync($"/api/v1/reports/{id}/reopen", null)).EnsureSuccessStatusCode(); var wiederOffen = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); Assert.That(wiederOffen, Has.Count.EqualTo(1)); } [Test] public async Task Was_man_nicht_sieht_kann_man_nicht_melden() { Datei("Geheimsache (2024).mkv"); using var server = new FlaroServer(); var admin = await server.SetUpAndSignIn(); var alsAdmin = server.AlsAngemeldeter(admin.Token); var angelegt = await alsAdmin.PostAsJsonAsync("/api/v1/libraries", new CreateLibraryRequest("Prüfbestand", "Movies", _bestand, ReviewNewItems: true)); var bibliothek = (await angelegt.Content.ReadFromJsonAsync())!; await Scannen(alsAdmin, bibliothek.Id); var wartend = await alsAdmin.GetFromJsonAsync>("/api/v1/review"); var id = wartend![0].Id; var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswortlang"); var abgelehnt = await gast.PostAsJsonAsync($"/api/v1/items/{id}/reports", new MeldungRequest("bild", "Sieht komisch aus.")); var vomAdmin = await alsAdmin.PostAsJsonAsync($"/api/v1/items/{id}/reports", new MeldungRequest("bild", "Sieht komisch aus.")); Assert.Multiple(() => { Assert.That(abgelehnt.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); Assert.That(vomAdmin.StatusCode, Is.EqualTo(HttpStatusCode.OK), "Der Administrator sieht den Titel und darf ihn deshalb melden."); }); } [Test] public async Task Eine_Staffel_wird_ueber_Serie_und_Nummer_gemeldet() { Datei("Dark/Season 01/Dark - S01E01.mkv"); Datei("Dark/Season 02/Dark - S02E01.mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Serien", "Shows"); var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswortlang"); var serien = await gast.GetFromJsonAsync>("/api/v1/series"); var serie = serien!.Items.Single(); var gut = await gast.PostAsJsonAsync($"/api/v1/items/{serie.Id}/reports", new MeldungRequest("ton", "Ab Folge zwei kein Ton.", null, SeasonNumber: 2)); var falscheStaffel = await gast.PostAsJsonAsync($"/api/v1/items/{serie.Id}/reports", new MeldungRequest("ton", "Gibt es nicht.", null, SeasonNumber: 9)); var liste = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); Assert.Multiple(() => { Assert.That(gut.StatusCode, Is.EqualTo(HttpStatusCode.OK)); Assert.That(falscheStaffel.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); Assert.That(liste, Has.Count.EqualTo(1)); Assert.That(liste![0].Target, Is.EqualTo("season")); Assert.That(liste[0].ItemId, Is.EqualTo(serie.Id)); Assert.That(liste[0].SubTitle, Is.EqualTo("Staffel 2")); }); } [Test] public async Task Eine_Folge_meldet_sich_unter_ihrer_eigenen_Kennung() { Datei("Dark/Season 01/Dark - S01E01.mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Serien", "Shows"); var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswortlang"); var serie = (await gast.GetFromJsonAsync>("/api/v1/series"))!.Items.Single(); var folgen = await gast.GetFromJsonAsync>( $"/api/v1/series/{serie.Id}/episodes"); var folge = folgen!.Items.Single(); await gast.PostAsJsonAsync($"/api/v1/items/{folge.Id}/reports", new MeldungRequest("bild", "Bei 0:34 zwei Minuten schwarz.", "0:34:00")); var liste = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); Assert.That(liste, Has.Count.EqualTo(1)); Assert.Multiple(() => { Assert.That(liste![0].Target, Is.EqualTo("episode")); Assert.That(liste[0].ItemId, Is.EqualTo(serie.Id), "Die Detailseite gehört der Serie, nicht der Folge."); Assert.That(liste[0].SubTitle, Does.StartWith("S1 · F1")); Assert.That(liste[0].PositionSeconds, Is.EqualTo(2040)); }); } [Test] public async Task Ohne_Beschreibung_gibt_es_nichts_nachzusehen() { Datei("Heat (1995).mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Filme", "Movies"); var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswortlang"); var film = (await Filme(gast)).Single(); var leer = await gast.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("ton", " ")); var zuLang = await gast.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("ton", new string('a', 2001))); var unbekannt = await gast.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("geruch", "Riecht komisch.")); Assert.Multiple(() => { Assert.That(leer.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); Assert.That(zuLang.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); Assert.That(unbekannt.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); }); } /// „2:03" sind zwei Minuten, nicht zwei Stunden. /// /// /// TimeSpan.Parse läse es als zwei Stunden und drei Minuten. Wer /// neben einem Film „2:03" schreibt, meint das, was unter dem /// Fortschrittsbalken steht. /// [TestCase("1:02:03", 3723)] [TestCase("2:03", 123)] [TestCase("90", 90)] public async Task Die_Stelle_kommt_als_Zeit_zurueck(string geschrieben, double sekunden) { Datei("Heat (1995).mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Filme", "Movies"); var film = (await Filme(alsAdmin)).Single(); await alsAdmin.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("bild", "Hier.", geschrieben)); var liste = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); Assert.That(liste![0].PositionSeconds, Is.EqualTo(sekunden)); } [Test] public async Task Eine_unleserliche_Stelle_wird_abgewiesen() { Datei("Heat (1995).mkv"); using var server = new FlaroServer(); var alsAdmin = await Aufsetzen(server, "Filme", "Movies"); var film = (await Filme(alsAdmin)).Single(); var krumm = await alsAdmin.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("bild", "Hier.", "Mitte")); var ohne = await alsAdmin.PostAsJsonAsync($"/api/v1/items/{film.Id}/reports", new MeldungRequest("bild", "Betrifft den ganzen Film.", "")); var liste = await alsAdmin.GetFromJsonAsync>("/api/v1/reports"); Assert.Multiple(() => { Assert.That(krumm.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); Assert.That(ohne.StatusCode, Is.EqualTo(HttpStatusCode.OK)); Assert.That(liste, Has.Count.EqualTo(1)); Assert.That(liste![0].PositionSeconds, Is.Null); }); } /* ---------- Gerüst ---------- */ private async Task Aufsetzen(FlaroServer server, string name, string art) { var admin = await server.SetUpAndSignIn(); var alsAdmin = server.AlsAngemeldeter(admin.Token); var angelegt = await alsAdmin.PostAsJsonAsync("/api/v1/libraries", new CreateLibraryRequest(name, art, _bestand, ReviewNewItems: false)); angelegt.EnsureSuccessStatusCode(); var bibliothek = (await angelegt.Content.ReadFromJsonAsync())!; await Scannen(alsAdmin, bibliothek.Id); return alsAdmin; } private static async Task> Filme(HttpClient client) => (await client.GetFromJsonAsync>("/api/v1/movies"))!.Items; private static async Task Scannen(HttpClient client, Guid bibliothek) { (await client.PostAsync($"/api/v1/libraries/{bibliothek}/scan", null)) .EnsureSuccessStatusCode(); for (var versuch = 0; versuch < 600; versuch++) { var stand = await client.GetFromJsonAsync($"/api/v1/libraries/{bibliothek}/scan"); if (stand!.State is "finished") return; if (stand.State is "failed") Assert.Fail($"Der Scan ist gescheitert: {stand.Error}"); await Task.Delay(50); } Assert.Fail("Der Scan wurde nicht fertig."); } }