using System.Net; using System.Net.Http.Json; using Flaro.Api.Endpoints; using Flaro.Core.Library; using Microsoft.EntityFrameworkCore; namespace Flaro.Api.Tests; /// Vorspann und Abspann auf dem Weg zu den Spielern. /// /// /// Ohne Tonabgleich. Ob er findet, was er finden soll, steht in /// TonabgleichTests — mit erfundenen Abdrücken und ohne Mediendateien. /// Hier geht es um das Stück danach: dass eine gefundene Marke auch bei dem /// ankommt, der den Knopf zeichnen soll. /// public class MarkenTests { private string _bestand = ""; [SetUp] public void Anlegen() => _bestand = Directory.CreateDirectory( Path.Combine(Path.GetTempPath(), "flaro-medien-" + 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]); } private async Task Eingelesen(FlaroServer server, string art) { var admin = await server.SetUpAndSignIn(); var client = server.AlsAngemeldeter(admin.Token); var angelegt = await client.PostAsJsonAsync("/api/v1/libraries", new CreateLibraryRequest("Prüfbestand", art, _bestand)); angelegt.EnsureSuccessStatusCode(); var bibliothek = (await angelegt.Content.ReadFromJsonAsync())!; (await client.PostAsync($"/api/v1/libraries/{bibliothek.Id}/scan", null)).EnsureSuccessStatusCode(); for (var versuch = 0; versuch < 100; versuch++) { var stand = await client.GetFromJsonAsync($"/api/v1/libraries/{bibliothek.Id}/scan"); if (stand!.State is "finished") return client; if (stand.State is "failed") Assert.Fail($"Der Scan ist gescheitert: {stand.Error}"); await Task.Delay(50); } Assert.Fail("Der Scan wurde in fünf Sekunden nicht fertig."); return null!; } private static async Task<(Guid Serie, Guid Folge)> MitEinerFolge(HttpClient client) { var serien = await client.GetFromJsonAsync>("/api/v1/series"); var serie = serien!.Items[0].Id; var folgen = await client.GetFromJsonAsync>( $"/api/v1/series/{serie}/episodes"); return (serie, folgen!.Items[0].Id); } [Test] public async Task Eine_Marke_kommt_bei_der_Folge_an() { Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv"); using var server = new FlaroServer(); var client = await Eingelesen(server, "Shows"); var (_, folge) = await MitEinerFolge(client); await server.InDerDatenbank(async db => { db.Marken.Add(new Marke { EpisodeId = folge, Art = Markenart.Intro, VonSekunden = 30, BisSekunden = 65, ErkanntAm = DateTimeOffset.UtcNow, }); db.Marken.Add(new Marke { EpisodeId = folge, Art = Markenart.Abspann, VonSekunden = 2500, BisSekunden = 2600, ErkanntAm = DateTimeOffset.UtcNow, }); await db.SaveChangesAsync(); }); var einzeln = await client.GetFromJsonAsync($"/api/v1/episodes/{folge}"); var intro = einzeln!.Markers!.Single(m => m.Kind == "intro"); Assert.Multiple(() => { Assert.That(einzeln.Markers, Has.Count.EqualTo(2)); Assert.That(intro.StartSeconds, Is.EqualTo(30)); Assert.That(intro.EndSeconds, Is.EqualTo(65)); Assert.That(einzeln.Markers!.Any(m => m.Kind == "credits"), Is.True, "der Abspann heisst gegenüber den Apps credits"); }); } /// Auch in der Folgenliste — dort holt die App sie beim Blättern. [Test] public async Task Die_Folgenliste_bringt_die_Marken_mit() { Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv"); Datei("Dark/Season 01/Dark - S01E02 - Luegen.mkv"); using var server = new FlaroServer(); var client = await Eingelesen(server, "Shows"); var (serie, folge) = await MitEinerFolge(client); await server.InDerDatenbank(async db => { db.Marken.Add(new Marke { EpisodeId = folge, Art = Markenart.Intro, VonSekunden = 10, BisSekunden = 40, ErkanntAm = DateTimeOffset.UtcNow, }); await db.SaveChangesAsync(); }); var liste = await client.GetFromJsonAsync>( $"/api/v1/series/{serie}/episodes"); Assert.Multiple(() => { Assert.That(liste!.Items.Single(f => f.Id == folge).Markers, Has.Count.EqualTo(1)); Assert.That(liste.Items.Single(f => f.Id != folge).Markers, Is.Empty, "eine Folge ohne Marke bekommt eine leere Liste, nicht die der anderen"); }); } /// Ohne erkannte Marken bleibt die Liste leer — kein Knopf, kein Fehler. [Test] public async Task Ohne_Erkennung_ist_die_Liste_leer() { Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv"); using var server = new FlaroServer(); var client = await Eingelesen(server, "Shows"); var (_, folge) = await MitEinerFolge(client); var einzeln = await client.GetFromJsonAsync($"/api/v1/episodes/{folge}"); Assert.That(einzeln!.Markers, Is.Empty); } /// Den Lauf darf nur ein Verwalter anstossen. [Test] public async Task Die_Suche_laesst_sich_anstossen() { Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv"); using var server = new FlaroServer(); var client = await Eingelesen(server, "Shows"); var serien = await client.GetFromJsonAsync>("/api/v1/series"); var antwort = await client.PostAsync($"/api/v1/series/{serien!.Items[0].Id}/marken", null); var unbekannt = await client.PostAsync($"/api/v1/series/{Guid.NewGuid()}/marken", null); Assert.Multiple(() => { Assert.That(antwort.StatusCode, Is.EqualTo(HttpStatusCode.Accepted)); Assert.That(unbekannt.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); }); } /// Eine abgesuchte Staffel wird nicht noch einmal abgetastet. /// /// /// Der teuerste Fehler, den es hier gab. Gemerkt wurde nur, was /// gefunden wurde. Eine Staffel ohne Abspann — es gibt genug davon — /// sah damit bei jedem Scan aus wie eine, in der noch niemand /// nachgesehen hat: jede Tonspur jeder Folge noch einmal von der /// Platte, für immer, ohne Ertrag. /// /// Geprüft wird hier der Vermerk, nicht das Abtasten selbst — ohne /// fpcalc und ohne echten Ton gibt es nichts zu finden, und genau /// dieser Fall ist der, um den es geht. /// [Test] public async Task Ein_Lauf_ohne_Fund_merkt_sich_das() { Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv"); Datei("Dark/Season 01/Dark - S01E02 - Luegen.mkv"); using var server = new FlaroServer(); var client = await Eingelesen(server, "Shows"); var serien = await client.GetFromJsonAsync>("/api/v1/series"); var serie = serien!.Items[0].Id; (await client.PostAsync($"/api/v1/series/{serie}/marken", null)).EnsureSuccessStatusCode(); // Der Lauf hängt im Hintergrund; ohne fpcalc ist er sofort durch. List vermerke = []; for (var versuch = 0; versuch < 40; versuch++) { await server.InDerDatenbank(async db => vermerke = await db.Markensuchen.AsNoTracking() .Where(x => x.SeriesId == serie).ToListAsync()); if (vermerke.Count > 0) break; await Task.Delay(50); } // Ohne fpcalc gibt es keinen Vermerk — dann ist nichts gelaufen und // auch nichts zu merken. Mit fpcalc müssen beide Arten dastehen. Assert.That(vermerke.Count, Is.AnyOf(0, 2), "entweder gar nicht gelaufen oder beide Arten vermerkt"); if (vermerke.Count == 2) { Assert.That(vermerke.Select(v => v.Folgen), Is.All.EqualTo(2), "die Folgenzahl löst den nächsten Lauf aus"); } } /// Ein Film trägt seine Abspannmarke genauso. [Test] public async Task Ein_Film_bekommt_seine_Marke() { Datei("Matrix (1999)/Matrix (1999).mkv"); using var server = new FlaroServer(); var client = await Eingelesen(server, "Movies"); var filme = await client.GetFromJsonAsync>("/api/v1/movies"); var film = filme!.Items[0].Id; await server.InDerDatenbank(async db => { db.Marken.Add(new Marke { MovieId = film, Art = Markenart.Abspann, VonSekunden = 6900, BisSekunden = 7200, ErkanntAm = DateTimeOffset.UtcNow, }); await db.SaveChangesAsync(); }); var einzeln = await client.GetFromJsonAsync($"/api/v1/movies/{film}"); var liste = await client.GetFromJsonAsync>("/api/v1/movies"); Assert.Multiple(() => { Assert.That(einzeln!.Markers!.Single().Kind, Is.EqualTo("credits")); Assert.That(einzeln.Markers!.Single().StartSeconds, Is.EqualTo(6900)); Assert.That(liste!.Items[0].Markers, Has.Count.EqualTo(1), "auch in der Liste"); }); } /// Eine falsche Marke lässt sich wieder wegnehmen. /// /// /// Ohne diesen Weg wäre die Erkennung eine Blackbox, die manchmal recht /// hat: ein Fehlgriff bliebe stehen, und niemand könnte etwas dagegen /// tun ausser die Datenbank anzufassen. /// [Test] public async Task Eine_falsche_Filmmarke_laesst_sich_verwerfen() { Datei("Matrix (1999)/Matrix (1999).mkv"); using var server = new FlaroServer(); var client = await Eingelesen(server, "Movies"); var filme = await client.GetFromJsonAsync>("/api/v1/movies"); var film = filme!.Items[0].Id; await server.InDerDatenbank(async db => { db.Marken.Add(new Marke { MovieId = film, Art = Markenart.Abspann, VonSekunden = 60, BisSekunden = 7200, ErkanntAm = DateTimeOffset.UtcNow, }); db.Markensuchen.Add(new Markensuche { MovieId = film, Art = Markenart.Abspann, Folgen = 1, GelaufenAm = DateTimeOffset.UtcNow, }); await db.SaveChangesAsync(); }); var weg = await client.DeleteAsync($"/api/v1/movies/{film}/marken"); var danach = await client.GetFromJsonAsync($"/api/v1/movies/{film}"); var vermerke = 0; await server.InDerDatenbank(async db => vermerke = await db.Markensuchen.CountAsync(x => x.MovieId == film)); Assert.Multiple(() => { Assert.That(weg.StatusCode, Is.EqualTo(HttpStatusCode.NoContent)); Assert.That(danach!.Markers, Is.Empty); Assert.That(vermerke, Is.EqualTo(1), "der Vermerk bleibt, sonst legte der nächste Scan dieselbe Marke wieder an"); }); } }