Im Aufmacher der Startseite stand „Abspielen", und wer draufdrückte, landete auf der Detailseite. Gemessen am laufenden Server: beide Knöpfe riefen dieselbe Funktion — `oeffnen` —, der eine hiess nur anders. Drei Dinge waren falsch, alle an derselben Stelle: **Der Knopf log.** Jetzt startet er den Spieler, mit Fortsetzen ab der Stelle, an der man war. Ohne Datei — eine ganze Serie — heisst er „Ansehen" und führt dorthin, wo man wählen kann. **Eine angefangene Folge war kein Film.** Sie lief durch `alsHero` mit `art: 'filme'` und ihrer eigenen Kennung; der Klick ging auf `#/filme/<Folgenkennung>` und endete in „Diesen Film gibt es nicht mehr". Nachgemessen im Browser, dann `angefangenAlsHero` dafür gebaut: die Detailseite gehört der Serie, der Fortschritt der Folge. **Ein geplanter Titel sah aus wie jeder andere.** Jetzt steht statt des Knopfes „Bald ist es soweit" mit dem Termin, wie auf der Detailseite. Dafür nennt `/home/continue` und `/home/next` nun Datei, Länge und Termin, und die Vorschläge bringen ihre Dateien mit — ohne die konnte der Aufmacher gar nicht abspielen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
791 lines
31 KiB
C#
791 lines
31 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using Flaro.Api.Endpoints;
|
|
using Flaro.Core.Playback;
|
|
|
|
namespace Flaro.Api.Tests;
|
|
|
|
/// <summary>Fortschritt, Gesehen-Status und die Startseite — je Benutzer.</summary>
|
|
public class PlaybackTests
|
|
{
|
|
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]);
|
|
}
|
|
|
|
/// <summary>Legt eine Bibliothek an, scannt sie und meldet den Client.</summary>
|
|
private async Task<HttpClient> Eingelesen(FlaroServer server, string art = "Movies")
|
|
{
|
|
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<LibraryResponse>())!;
|
|
|
|
(await client.PostAsync($"/api/v1/libraries/{bibliothek.Id}/scan", null)).EnsureSuccessStatusCode();
|
|
for (var versuch = 0; versuch < 100; versuch++)
|
|
{
|
|
var stand = await client.GetFromJsonAsync<ScanStatus>($"/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> ErsterFilm(HttpClient client)
|
|
{
|
|
var seite = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
return seite!.Items[0].Id;
|
|
}
|
|
|
|
private static Task<HttpResponseMessage> Melden(HttpClient c, Guid id, double stelle, double? laenge) =>
|
|
c.PostAsJsonAsync($"/api/v1/items/{id}/progress", new ProgressRequest(stelle, laenge));
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
/// <summary>Der Fall, um dessentwillen der Server existiert.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Jellyfin verwirft bei zwei Stunden alles unterhalb von sechs Minuten —
|
|
/// <c>MinResumePct</c> steht auf 5. Hier kommt die Stelle zurück.
|
|
/// </remarks>
|
|
[Test]
|
|
public async Task Fuenf_Minuten_von_zwei_Stunden_bleiben_erhalten()
|
|
{
|
|
Datei("Der Untergang (2004)/Der Untergang (2004).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
var antwort = await Melden(client, film, 300, 7200);
|
|
var stand = (await antwort.Content.ReadFromJsonAsync<ItemStateResponse>())!;
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(stand.PositionSeconds, Is.EqualTo(300));
|
|
Assert.That(stand.Watched, Is.False);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Eine_Sekunde_wird_schon_gemerkt()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await Melden(client, film, 1, 7200);
|
|
|
|
var seite = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
Assert.That(seite!.Items[0].State!.PositionSeconds, Is.EqualTo(1));
|
|
}
|
|
|
|
[Test]
|
|
public async Task Kurz_vor_Schluss_gilt_er_als_gesehen_und_behaelt_die_Stelle()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
var antwort = await Melden(client, film, 7000, 7200);
|
|
var stand = (await antwort.Content.ReadFromJsonAsync<ItemStateResponse>())!;
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(stand.Watched, Is.True);
|
|
Assert.That(stand.PositionSeconds, Is.EqualTo(7000));
|
|
Assert.That(stand.PlayCount, Is.EqualTo(1));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Angefangenes_steht_in_Weiterschauen()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await Melden(client, film, 600, 7200);
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(reihe!.Items, Has.Count.EqualTo(1));
|
|
Assert.That(reihe.Items[0].Title, Is.EqualTo("Matrix"));
|
|
Assert.That(reihe.Items[0].Kind, Is.EqualTo("movie"));
|
|
Assert.That(reihe.Items[0].State.PositionSeconds, Is.EqualTo(600));
|
|
});
|
|
}
|
|
|
|
/// <summary>„Weiterschauen" muss sagen, was abgespielt würde.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Sonst kann der Aufmacher nur blättern.</b> Er bot „Abspielen" an
|
|
/// und öffnete die Detailseite — weil ihm die Datei fehlte. Und bei einer
|
|
/// Folge führte er auf <c>#/filme/<Folgenkennung></c>, wo nichts
|
|
/// steht: die Detailseite gehört der Serie, der Fortschritt der Folge.
|
|
/// </remarks>
|
|
[Test]
|
|
public async Task Weiterschauen_nennt_Datei_und_Serie()
|
|
{
|
|
Datei("Dark (2017)/Season 01/Dark - S01E01 - Geheimnisse.mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server, "Shows");
|
|
|
|
var serien = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items.Single();
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serie.Id}/episodes");
|
|
var folge = folgen!.Items.Single();
|
|
|
|
await Melden(client, folge.Id, 120, 1500);
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
var zeile = reihe!.Items.Single();
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(zeile.Kind, Is.EqualTo("episode"));
|
|
Assert.That(zeile.SeriesId, Is.EqualTo(serie.Id),
|
|
"ohne die Serie landet der Aufmacher auf einer Folge, die als Film nicht existiert");
|
|
Assert.That(zeile.FileId, Is.EqualTo(folge.Files.Single().Id),
|
|
"ohne die Datei kann „Abspielen\u201c nicht abspielen");
|
|
// Dieselbe Länge wie die Datei selbst. Im Prüfbestand liegen
|
|
// Attrappen ohne Spuren, also steht dort beidemal nichts — der
|
|
// Punkt ist, dass es derselbe Wert ist.
|
|
Assert.That(zeile.DurationSeconds, Is.EqualTo(folge.Files.Single().DurationSeconds));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Durchgeschautes_verlaesst_Weiterschauen()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await Melden(client, film, 600, 7200);
|
|
await Melden(client, film, 7000, 7200);
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
Assert.That(reihe!.Items, Is.Empty);
|
|
}
|
|
|
|
/// <summary>Ein zweites Mal angefangen — mit Häkchen und Stelle zugleich.</summary>
|
|
[Test]
|
|
public async Task Ein_gesehener_Film_kommt_beim_zweiten_Anlauf_zurueck()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await Melden(client, film, 7000, 7200);
|
|
await Melden(client, film, 120, 7200);
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(reihe!.Items, Has.Count.EqualTo(1));
|
|
Assert.That(reihe.Items[0].State.Watched, Is.True, "gesehen hat er ihn ja");
|
|
Assert.That(reihe.Items[0].State.PositionSeconds, Is.EqualTo(120));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Weglegen_nimmt_aus_der_Reihe_ohne_gesehen_zu_behaupten()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await Melden(client, film, 1800, 7200);
|
|
(await client.PostAsync($"/api/v1/items/{film}/dismiss", null)).EnsureSuccessStatusCode();
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
var seite = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(reihe!.Items, Is.Empty);
|
|
Assert.That(seite!.Items[0].State!.Watched, Is.False, "weggelegt heisst nicht gesehen");
|
|
Assert.That(seite.Items[0].State!.PositionSeconds, Is.EqualTo(1800), "die Stelle bleibt");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Wer_doch_weiterschaut_holt_es_zurueck()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await Melden(client, film, 1800, 7200);
|
|
await client.PostAsync($"/api/v1/items/{film}/dismiss", null);
|
|
await Melden(client, film, 1900, 7200);
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
Assert.That(reihe!.Items, Has.Count.EqualTo(1));
|
|
}
|
|
|
|
[Test]
|
|
public async Task Von_Hand_als_gesehen_raeumt_die_Stelle_weg()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await Melden(client, film, 1800, 7200);
|
|
var antwort = await client.PutAsJsonAsync($"/api/v1/items/{film}/watched", new WatchedRequest(true));
|
|
var stand = (await antwort.Content.ReadFromJsonAsync<ItemStateResponse>())!;
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(stand.Watched, Is.True);
|
|
Assert.That(stand.PositionSeconds, Is.Zero);
|
|
Assert.That(reihe!.Items, Is.Empty);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Favorit_und_Bewertung_bleiben_stehen()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
await client.PutAsJsonAsync($"/api/v1/items/{film}/favourite", new FavouriteRequest(true));
|
|
await client.PutAsJsonAsync($"/api/v1/items/{film}/rating", new RatingRequest(9));
|
|
|
|
var einzeln = await client.GetFromJsonAsync<MovieResponse>($"/api/v1/movies/{film}");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(einzeln!.State!.Favourite, Is.True);
|
|
Assert.That(einzeln.State!.Rating, Is.EqualTo(9));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Eine_Bewertung_ausserhalb_von_eins_bis_zehn_wird_abgelehnt()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
var antwort = await client.PutAsJsonAsync($"/api/v1/items/{film}/rating", new RatingRequest(11));
|
|
Assert.That(antwort.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
|
}
|
|
|
|
/// <summary>Zwei Leute im selben Haushalt stehen an verschiedenen Stellen.</summary>
|
|
[Test]
|
|
public async Task Der_Stand_gehoert_dem_Benutzer_nicht_dem_Server()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var admin = await Eingelesen(server);
|
|
var film = await ErsterFilm(admin);
|
|
|
|
(await admin.PostAsJsonAsync("/api/v1/users",
|
|
new CreateUserRequest("mitbewohner", "ein-langes-passwort", false))).EnsureSuccessStatusCode();
|
|
var zweiter = server.AlsAngemeldeter(
|
|
(await server.SignIn("mitbewohner", "ein-langes-passwort", "iPad", "geraet-2")).Token);
|
|
|
|
await Melden(admin, film, 600, 7200);
|
|
await Melden(zweiter, film, 3000, 7200);
|
|
|
|
var seiteA = await admin.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
var seiteB = await zweiter.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(seiteA!.Items[0].State!.PositionSeconds, Is.EqualTo(600));
|
|
Assert.That(seiteB!.Items[0].State!.PositionSeconds, Is.EqualTo(3000));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Einen_Titel_den_es_nicht_gibt_kann_man_nicht_melden()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
|
|
var antwort = await Melden(client, Guid.CreateVersion7(), 60, 7200);
|
|
Assert.That(antwort.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
|
|
}
|
|
|
|
/// <summary>Ohne Angabe nimmt der Server die Laufzeit aus der Datei.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// Die Prüfdateien haben keine lesbare Laufzeit — ffprobe findet nichts in
|
|
/// zweitausend Nullbytes. Ohne Laufzeit darf nichts vermutet werden, und
|
|
/// genau das wird hier festgehalten.
|
|
/// </remarks>
|
|
[Test]
|
|
public async Task Ohne_Laufzeit_wird_nichts_als_gesehen_vermutet()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
var antwort = await Melden(client, film, 99999, null);
|
|
var stand = (await antwort.Content.ReadFromJsonAsync<ItemStateResponse>())!;
|
|
|
|
Assert.That(stand.Watched, Is.False);
|
|
}
|
|
|
|
// --- Serien --------------------------------------------------------
|
|
|
|
[Test]
|
|
public async Task Ungesehene_Folgen_werden_gezaehlt()
|
|
{
|
|
Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv");
|
|
Datei("Dark/Season 01/Dark - S01E02 - Luegen.mkv");
|
|
Datei("Dark/Season 01/Dark - S01E03 - Gestern und heute.mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server, "Shows");
|
|
|
|
var serien = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
Assert.That(serie.UnwatchedCount, Is.EqualTo(3));
|
|
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>($"/api/v1/series/{serie.Id}/episodes");
|
|
await client.PutAsJsonAsync($"/api/v1/items/{folgen!.Items[0].Id}/watched", new WatchedRequest(true));
|
|
|
|
var danach = await client.GetFromJsonAsync<SeriesResponse>($"/api/v1/series/{serie.Id}");
|
|
Assert.That(danach!.UnwatchedCount, Is.EqualTo(2));
|
|
}
|
|
|
|
/// <summary>Eine Serie als gesehen markieren hakt jede Folge ab.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Der Fall, der vorher stumm scheiterte.</b> Der Server kannte zu
|
|
/// einer Serienkennung keinen Zustand und antwortete mit 404; die
|
|
/// Oberfläche verschluckte die Ablehnung, und der Klick tat sichtbar
|
|
/// nichts.
|
|
/// </remarks>
|
|
[Test]
|
|
public async Task Eine_Serie_als_gesehen_markieren_hakt_alle_Folgen_ab()
|
|
{
|
|
Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv");
|
|
Datei("Dark/Season 01/Dark - S01E02 - Luegen.mkv");
|
|
Datei("Dark/Season 02/Dark - S02E01 - Beginnings.mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server, "Shows");
|
|
|
|
var serien = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
|
|
var antwort = await client.PutAsJsonAsync(
|
|
$"/api/v1/items/{serie.Id}/watched", new WatchedRequest(true));
|
|
|
|
Assert.That(antwort.StatusCode, Is.EqualTo(HttpStatusCode.OK),
|
|
"eine Serienkennung ist eine gültige Titelkennung");
|
|
|
|
var danach = await client.GetFromJsonAsync<SeriesResponse>($"/api/v1/series/{serie.Id}");
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serie.Id}/episodes");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(danach!.UnwatchedCount, Is.Zero, "keine Folge steht mehr aus");
|
|
Assert.That(danach.State?.Watched, Is.True, "und die Serie selbst trägt das Häkchen");
|
|
Assert.That(folgen!.Items.Select(f => f.State?.Watched), Is.All.True,
|
|
"jede einzelne Folge ist abgehakt, nicht nur die Serie");
|
|
});
|
|
}
|
|
|
|
/// <summary>Und wieder zurück — ohne Rest.</summary>
|
|
[Test]
|
|
public async Task Eine_Serie_als_ungesehen_markieren_nimmt_jedes_Haekchen_weg()
|
|
{
|
|
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<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
|
|
await client.PutAsJsonAsync($"/api/v1/items/{serie.Id}/watched", new WatchedRequest(true));
|
|
await client.PutAsJsonAsync($"/api/v1/items/{serie.Id}/watched", new WatchedRequest(false));
|
|
|
|
var danach = await client.GetFromJsonAsync<SeriesResponse>($"/api/v1/series/{serie.Id}");
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serie.Id}/episodes");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(danach!.UnwatchedCount, Is.EqualTo(2));
|
|
Assert.That(danach.State?.Watched, Is.False);
|
|
Assert.That(folgen!.Items.Select(f => f.State?.Watched), Is.All.False);
|
|
});
|
|
}
|
|
|
|
/// <summary>Favorit an einer Serie — vorher ebenfalls eine stille 404.</summary>
|
|
[Test]
|
|
public async Task Eine_Serie_laesst_sich_zu_den_Favoriten_legen()
|
|
{
|
|
Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server, "Shows");
|
|
|
|
var serien = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
|
|
var antwort = await client.PutAsJsonAsync(
|
|
$"/api/v1/items/{serie.Id}/favourite", new FavouriteRequest(true));
|
|
antwort.EnsureSuccessStatusCode();
|
|
|
|
var liste = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
|
Assert.That(liste!.Items[0].State?.Favourite, Is.True,
|
|
"auch die Liste führt den Zustand mit — sonst wüsste die Kachel es nicht");
|
|
}
|
|
|
|
/// <summary>Der Stand einer Serie gehört dem Benutzer, nicht dem Server.</summary>
|
|
[Test]
|
|
public async Task Der_Serienstand_gehoert_dem_Benutzer()
|
|
{
|
|
Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var ich = await Eingelesen(server, "Shows");
|
|
|
|
var serien = await ich.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
await ich.PutAsJsonAsync($"/api/v1/items/{serie.Id}/watched", new WatchedRequest(true));
|
|
|
|
(await ich.PostAsJsonAsync("/api/v1/users",
|
|
new CreateUserRequest("mitbewohner", "ein-langes-passwort", false))).EnsureSuccessStatusCode();
|
|
var andere = server.AlsAngemeldeter(
|
|
(await server.SignIn("mitbewohner", "ein-langes-passwort", "iPad", "geraet-2")).Token);
|
|
|
|
var seine = await andere.GetFromJsonAsync<SeriesResponse>($"/api/v1/series/{serie.Id}");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(seine!.State?.Watched, Is.Not.True);
|
|
Assert.That(seine.UnwatchedCount, Is.EqualTo(1));
|
|
});
|
|
}
|
|
|
|
/// <summary>Viele auf einmal — eine Anfrage, nicht zweihundert.</summary>
|
|
[Test]
|
|
public async Task Eine_Sammelmarkierung_hakt_alles_Gewaehlte_ab()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
Datei("Alien (1979)/Alien (1979).mkv");
|
|
Datei("Heat (1995)/Heat (1995).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
|
|
var seite = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
var zwei = seite!.Items.Take(2).Select(m => m.Id).ToList();
|
|
|
|
var antwort = await client.PutAsJsonAsync("/api/v1/items/watched",
|
|
new BulkWatchedRequest(zwei, true));
|
|
var ergebnis = await antwort.Content.ReadFromJsonAsync<BulkWatchedResponse>();
|
|
|
|
var danach = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(ergebnis!.Changed, Is.EqualTo(2));
|
|
Assert.That(ergebnis.Unknown, Is.Empty);
|
|
Assert.That(danach!.Items.Count(m => m.State?.Watched == true), Is.EqualTo(2),
|
|
"genau die zwei gewählten, nicht der dritte");
|
|
});
|
|
}
|
|
|
|
/// <summary>Was es nicht gibt, wird benannt statt verschwiegen.</summary>
|
|
[Test]
|
|
public async Task Eine_Sammelmarkierung_nennt_was_sie_nicht_gefunden_hat()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
var erfunden = Guid.CreateVersion7();
|
|
|
|
var antwort = await client.PutAsJsonAsync("/api/v1/items/watched",
|
|
new BulkWatchedRequest([film, erfunden], true));
|
|
var ergebnis = await antwort.Content.ReadFromJsonAsync<BulkWatchedResponse>();
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(ergebnis!.Changed, Is.EqualTo(1));
|
|
Assert.That(ergebnis.Unknown, Is.EqualTo(new[] { erfunden }));
|
|
});
|
|
}
|
|
|
|
/// <summary>Auch Serien gehören in eine Sammelmarkierung.</summary>
|
|
[Test]
|
|
public async Task Eine_Sammelmarkierung_zieht_die_Folgen_einer_Serie_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 serien = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
|
|
await client.PutAsJsonAsync("/api/v1/items/watched",
|
|
new BulkWatchedRequest([serie.Id], true));
|
|
|
|
var danach = await client.GetFromJsonAsync<SeriesResponse>($"/api/v1/series/{serie.Id}");
|
|
Assert.That(danach!.UnwatchedCount, Is.Zero);
|
|
}
|
|
|
|
[Test]
|
|
public async Task Eine_leere_Sammelmarkierung_aendert_nichts_und_scheitert_nicht()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
|
|
var antwort = await client.PutAsJsonAsync("/api/v1/items/watched",
|
|
new BulkWatchedRequest([], true));
|
|
var ergebnis = await antwort.Content.ReadFromJsonAsync<BulkWatchedResponse>();
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(antwort.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
|
Assert.That(ergebnis!.Changed, Is.Zero);
|
|
});
|
|
}
|
|
|
|
/// <summary>Eine Folge in „Weiterschauen" nennt ihre Serie.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <b>Ohne diese Angabe führte die Kachel ins Leere.</b> Sie trug die
|
|
/// Folgenkennung und öffnete damit eine Serienseite — die es unter dieser
|
|
/// Kennung nie gibt. Heraus kam „Diese Serie gibt es nicht mehr",
|
|
/// ausgerechnet unter „Nächste Folge", wo man gerade weiterschauen wollte.
|
|
/// Es lag nicht am Scan und nicht am Auffrischen: die Kachel hat von
|
|
/// Anfang an auf die falsche Kennung gezeigt.
|
|
/// </remarks>
|
|
[Test]
|
|
public async Task Eine_angefangene_Folge_nennt_ihre_Serie()
|
|
{
|
|
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<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serie.Id}/episodes");
|
|
|
|
// Mittendrin stehenbleiben — dann steht die Folge in „Weiterschauen".
|
|
await Melden(client, folgen!.Items[0].Id, 600, 7200);
|
|
|
|
var weiter = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
var eintrag = weiter!.Items.Single();
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(eintrag.Id, Is.EqualTo(folgen.Items[0].Id), "die Kennung bleibt die der Folge");
|
|
Assert.That(eintrag.SeriesId, Is.EqualTo(serie.Id), "und daneben steht, wohin sie gehört");
|
|
});
|
|
}
|
|
|
|
/// <summary>Dasselbe für „Nächste Folge".</summary>
|
|
[Test]
|
|
public async Task Die_naechste_Folge_nennt_ihre_Serie()
|
|
{
|
|
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<Page<SeriesResponse>>("/api/v1/series");
|
|
var serie = serien!.Items[0];
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serie.Id}/episodes");
|
|
|
|
// Die erste ganz gesehen — dann ist die zweite „die nächste".
|
|
await client.PutAsJsonAsync($"/api/v1/items/{folgen!.Items[0].Id}/watched", new WatchedRequest(true));
|
|
|
|
var naechste = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/next");
|
|
var eintrag = naechste!.Items.Single();
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(eintrag.Id, Is.EqualTo(folgen.Items[1].Id));
|
|
Assert.That(eintrag.SeriesId, Is.EqualTo(serie.Id));
|
|
});
|
|
}
|
|
|
|
/// <summary>Ein Film hat keine Serie — und behauptet es auch nicht.</summary>
|
|
[Test]
|
|
public async Task Ein_angefangener_Film_nennt_keine_Serie()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
await Melden(client, film, 600, 7200);
|
|
|
|
var weiter = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
Assert.That(weiter!.Items.Single().SeriesId, Is.Null);
|
|
}
|
|
|
|
[Test]
|
|
public async Task Die_naechste_Folge_richtet_sich_nach_Staffel_und_Nummer()
|
|
{
|
|
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<Page<SeriesResponse>>("/api/v1/series");
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serien!.Items[0].Id}/episodes");
|
|
|
|
var naechste = await client.GetFromJsonAsync<EpisodeResponse>(
|
|
$"/api/v1/episodes/{folgen!.Items[0].Id}/next");
|
|
var hinterDerLetzten = await client.GetAsync($"/api/v1/episodes/{folgen.Items[1].Id}/next");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(naechste!.Episode, Is.EqualTo(2));
|
|
Assert.That(hinterDerLetzten.StatusCode, Is.EqualTo(HttpStatusCode.NoContent),
|
|
"nach der letzten Folge kommt keine — das ist kein Fehler");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task Die_erste_ungesehene_Folge_steht_unter_Naechste_Folge()
|
|
{
|
|
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<Page<SeriesResponse>>("/api/v1/series");
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serien!.Items[0].Id}/episodes");
|
|
|
|
await client.PutAsJsonAsync($"/api/v1/items/{folgen!.Items[0].Id}/watched", new WatchedRequest(true));
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/next");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(reihe!.Items, Has.Count.EqualTo(1));
|
|
Assert.That(reihe.Items[0].Episode, Is.EqualTo(2));
|
|
Assert.That(reihe.Items[0].Title, Is.EqualTo("Dark"), "oben die Serie");
|
|
});
|
|
}
|
|
|
|
/// <summary>Was in „Weiterschauen" steht, steht nicht nochmal daneben.</summary>
|
|
[Test]
|
|
public async Task Eine_angefangene_Folge_erscheint_nur_in_einer_Reihe()
|
|
{
|
|
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<Page<SeriesResponse>>("/api/v1/series");
|
|
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
|
$"/api/v1/series/{serien!.Items[0].Id}/episodes");
|
|
|
|
await Melden(client, folgen!.Items[0].Id, 400, 2700);
|
|
|
|
var weiter = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
var naechste = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/next");
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(weiter!.Items, Has.Count.EqualTo(1));
|
|
Assert.That(weiter.Items[0].Episode, Is.EqualTo(1));
|
|
Assert.That(naechste!.Items, Is.Empty);
|
|
});
|
|
}
|
|
|
|
/// <summary>Die Spalte und die Regel dürfen nicht auseinanderlaufen.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <c>InProgress</c> ist das festgehaltene Ergebnis von
|
|
/// <see cref="ProgressRule.BelongsInContinueWatching"/>. Wer die Regel
|
|
/// ändert und das Nachführen vergisst, bekommt eine Startseite, die lügt —
|
|
/// und zwar lautlos. Deshalb hier gegeneinandergehalten.
|
|
/// </remarks>
|
|
[Test]
|
|
public async Task Die_Reihe_zeigt_genau_das_was_die_Regel_sagt()
|
|
{
|
|
Datei("Matrix (1999)/Matrix (1999).mkv");
|
|
using var server = new FlaroServer();
|
|
var client = await Eingelesen(server);
|
|
var film = await ErsterFilm(client);
|
|
|
|
foreach (var (stelle, laenge) in new (double, double?)[]
|
|
{ (1, 7200), (300, 7200), (7000, 7200), (120, 7200) })
|
|
{
|
|
await Melden(client, film, stelle, laenge);
|
|
|
|
var erwartet = ProgressRule.BelongsInContinueWatching(new UserItemState
|
|
{
|
|
UserId = Guid.Empty,
|
|
MovieId = film,
|
|
Position = TimeSpan.FromSeconds(stelle),
|
|
Runtime = laenge is { } l ? TimeSpan.FromSeconds(l) : null,
|
|
});
|
|
|
|
var reihe = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/continue");
|
|
Assert.That(reihe!.Items.Count == 1, Is.EqualTo(erwartet),
|
|
$"bei {stelle}s von {laenge}s");
|
|
}
|
|
}
|
|
}
|