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 }; }