using Flaro.Core.Playback;
namespace Flaro.Core.Tests;
/// Die Regel, um derentwillen dieser Server existiert.
[TestFixture]
public sealed class ProgressRuleTests
{
private static readonly TimeSpan Zweistünder = TimeSpan.FromHours(2);
/// Der Fall, der den Ausschlag gab.
///
///
/// Jellyfins MinResumePct steht auf 5. Bei zwei Stunden sind das
/// sechs Minuten, und wer nach fünf abbricht, findet beim nächsten Mal
/// nichts vor. Hier ist genau diese Stelle abgelegt.
///
[Test]
public void Die_ersten_Minuten_zaehlen_auch()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromMinutes(5), Zweistünder);
Assert.Multiple(() =>
{
Assert.That(ergebnis.Position, Is.EqualTo(TimeSpan.FromMinutes(5)));
Assert.That(ergebnis.Watched, Is.False);
});
}
[Test]
public void Eine_einzige_Sekunde_zaehlt_auch()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromSeconds(1), Zweistünder);
Assert.That(ergebnis.Position, Is.EqualTo(TimeSpan.FromSeconds(1)));
}
/// Jellyfins MinResumeDurationSeconds steht auf 300.
///
///
/// Dort gilt eine vierminütige Folge nach dreissig Sekunden als
/// vollständig gesehen. Hier ist sie das, was sie ist: angefangen.
///
[Test]
public void Ein_kurzer_Titel_gilt_nicht_sofort_als_gesehen()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromSeconds(30), TimeSpan.FromMinutes(4));
Assert.Multiple(() =>
{
Assert.That(ergebnis.Watched, Is.False);
Assert.That(ergebnis.Position, Is.EqualTo(TimeSpan.FromSeconds(30)));
});
}
[Test]
public void Kurz_vor_Schluss_gilt_er_als_gesehen()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromMinutes(115), Zweistünder);
Assert.That(ergebnis.Watched, Is.True);
}
/// Und die Stelle bleibt trotzdem stehen.
///
///
/// Jellyfin setzt sie an dieser Stelle auf null. Wer den Abspann sehen
/// oder die letzte Szene nochmal will, fängt dort von vorn an.
///
[Test]
public void Gesehen_wirft_die_Stelle_nicht_weg()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromMinutes(118), Zweistünder);
Assert.Multiple(() =>
{
Assert.That(ergebnis.Watched, Is.True);
Assert.That(ergebnis.Position, Is.EqualTo(TimeSpan.FromMinutes(118)));
});
}
[Test]
public void Genau_an_der_Grenze_gilt_er_als_gesehen()
{
var ergebnis = ProgressRule.Apply(Zweistünder * ProgressRule.WatchedFraction, Zweistünder);
Assert.That(ergebnis.Watched, Is.True);
}
[Test]
public void Eine_Sekunde_davor_noch_nicht()
{
var ergebnis = ProgressRule.Apply(
Zweistünder * ProgressRule.WatchedFraction - TimeSpan.FromSeconds(1), Zweistünder);
Assert.That(ergebnis.Watched, Is.False);
}
/// Ohne Laufzeit gibt es keinen Anteil — also kein Urteil.
///
///
/// Jellyfin nimmt in diesem Fall an, der Titel sei vollständig gesehen
/// („we'll just have to assume it was fully played"). Das ist die
/// Annahme, die man nicht zurücknehmen kann, ohne sie zu bemerken.
///
[Test]
public void Ohne_Laufzeit_wird_nichts_vermutet()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromMinutes(90), null);
Assert.Multiple(() =>
{
Assert.That(ergebnis.Watched, Is.False);
Assert.That(ergebnis.Position, Is.EqualTo(TimeSpan.FromMinutes(90)));
});
}
[Test]
public void Eine_Laufzeit_von_null_ist_keine()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromMinutes(3), TimeSpan.Zero);
Assert.That(ergebnis.Watched, Is.False);
}
/// Spieler melden beim Spulen und beim Beenden Unsinn.
[Test]
public void Eine_negative_Stelle_wird_zu_null()
{
var ergebnis = ProgressRule.Apply(TimeSpan.FromSeconds(-5), Zweistünder);
Assert.That(ergebnis.Position, Is.EqualTo(TimeSpan.Zero));
}
[Test]
public void Jenseits_des_Endes_wird_auf_das_Ende_gekuerzt()
{
var ergebnis = ProgressRule.Apply(Zweistünder + TimeSpan.FromSeconds(3), Zweistünder);
Assert.Multiple(() =>
{
Assert.That(ergebnis.Position, Is.EqualTo(Zweistünder));
Assert.That(ergebnis.Watched, Is.True);
});
}
// --- Weiterschauen ---------------------------------------------------
[Test]
public void Angefangen_gehoert_in_Weiterschauen()
{
Assert.That(ProgressRule.BelongsInContinueWatching(
Zustand(TimeSpan.FromMinutes(20), Zweistünder)), Is.True);
}
[Test]
public void Nie_angefangen_gehoert_nicht_hinein()
{
Assert.That(ProgressRule.BelongsInContinueWatching(
Zustand(TimeSpan.Zero, Zweistünder)), Is.False);
}
[Test]
public void Durchgeschaut_gehoert_nicht_hinein()
{
Assert.That(ProgressRule.BelongsInContinueWatching(
Zustand(TimeSpan.FromMinutes(118), Zweistünder)), Is.False);
}
[Test]
public void Weggelegtes_gehoert_nicht_hinein()
{
var zustand = Zustand(TimeSpan.FromMinutes(20), Zweistünder);
zustand.Dismissed = true;
Assert.That(ProgressRule.BelongsInContinueWatching(zustand), Is.False);
}
/// Ein zweites Mal angefangen — dann steht er wieder da.
///
///
/// Das Häkchen bleibt: gesehen hat man ihn ja. Beides nebeneinander ist
/// genau das, was ohne weggeworfene Position möglich wird.
///
[Test]
public void Ein_gesehener_Titel_kommt_beim_zweiten_Mal_zurueck()
{
var zustand = Zustand(TimeSpan.FromMinutes(10), Zweistünder);
zustand.Watched = true;
Assert.That(ProgressRule.BelongsInContinueWatching(zustand), Is.True);
}
private static UserItemState Zustand(TimeSpan stelle, TimeSpan? laenge) =>
new() { UserId = Guid.CreateVersion7(), MovieId = Guid.CreateVersion7(), Position = stelle, Runtime = laenge };
}