using Jellyfin.Plugin.Vanguard.Seasons; using Xunit; namespace Jellyfin.Plugin.Vanguard.Tests; public class SeasonPolicyTests { private static readonly TimeZoneInfo NY = TimeZoneInfo.FindSystemTimeZoneById("America/New_York"); private static DateTimeOffset Ny(int y, int mo, int d, int h, int mi, int s = 0) { var local = new DateTime(y, mo, d, h, mi, s, DateTimeKind.Unspecified); return new DateTimeOffset(local, NY.GetUtcOffset(local)); } // "now" = Thursday 2026-10-30 12:00 Eastern (ISO week 44) private static readonly DateTimeOffset Now = Ny(2026, 10, 30, 12, 0); [Fact] public void SameWeek_IsDaySeason() { var slot = SeasonPolicy.Resolve(Ny(2026, 10, 29, 21, 3, 11), Now, NY, 4); Assert.Equal(SeasonKind.Day, slot.Kind); Assert.Equal(20261029, slot.SeasonNumber); Assert.Equal("Thursday, Oct 29", slot.SeasonName); Assert.Equal(1029210311, slot.EpisodeNumber); } [Fact] public void AfterMidnight_BelongsToPreviousBroadcastDay() { var slot = SeasonPolicy.Resolve(Ny(2026, 10, 30, 1, 15), Now, NY, 4); Assert.Equal(new DateOnly(2026, 10, 29), slot.BroadcastDay); Assert.Equal(20261029, slot.SeasonNumber); Assert.Equal(1030011500, slot.EpisodeNumber); // episode number keeps the real clock time } [Fact] public void EarlierInMonth_IsWeekSeason() { var slot = SeasonPolicy.Resolve(Ny(2026, 10, 14, 20, 0), Now, NY, 4); // ISO week 42 Assert.Equal(SeasonKind.Week, slot.Kind); Assert.Equal(1202642, slot.SeasonNumber); Assert.Equal("Week 42", slot.SeasonName); } [Fact] public void EarlierInYear_IsMonthSeason() { var slot = SeasonPolicy.Resolve(Ny(2026, 7, 4, 20, 0), Now, NY, 4); Assert.Equal(SeasonKind.Month, slot.Kind); Assert.Equal(202607, slot.SeasonNumber); Assert.Equal("July 2026", slot.SeasonName); } [Fact] public void PreviousYear_IsYearSeason() { var slot = SeasonPolicy.Resolve(Ny(2025, 12, 31, 23, 0), Now, NY, 4); Assert.Equal(SeasonKind.Year, slot.Kind); Assert.Equal(2025, slot.SeasonNumber); Assert.Equal("2025", slot.SeasonName); } [Fact] public void EpisodeNumber_IsStableAcrossRolls() { var ts = Ny(2026, 10, 29, 21, 3, 11); var e1 = SeasonPolicy.Resolve(ts, Now, NY, 4).EpisodeNumber; var e2 = SeasonPolicy.Resolve(ts, Ny(2026, 11, 20, 12, 0), NY, 4).EpisodeNumber; // weeks later: week bucket? no -> month bucket var e3 = SeasonPolicy.Resolve(ts, Ny(2027, 3, 1, 12, 0), NY, 4).EpisodeNumber; // next year -> year bucket Assert.Equal(e1, e2); Assert.Equal(e1, e3); Assert.Equal(202610, SeasonPolicy.Resolve(ts, Ny(2026, 11, 20, 12, 0), NY, 4).SeasonNumber); Assert.Equal(2026, SeasonPolicy.Resolve(ts, Ny(2027, 3, 1, 12, 0), NY, 4).SeasonNumber); } [Fact] public void Overrides_ReplaceGeneratedName() { var over = SeasonPolicy.ParseOverrides("# comment\n202610 = Fall Training Block\nbad line\n"); var slot = SeasonPolicy.Resolve(Ny(2026, 10, 14, 20, 0), Ny(2026, 12, 1, 12, 0), NY, 4, over); Assert.Equal("Fall Training Block", slot.SeasonName); } [Fact] public void DstTransition_UsesCorrectOffset() { // 2026-11-01 02:30 local does not exist twice in a way that breaks us; 01:30 is ambiguous: must not throw var slot = SeasonPolicy.Resolve(Ny(2026, 11, 1, 1, 30), Ny(2026, 11, 2, 12, 0), NY, 4); Assert.Equal(new DateOnly(2026, 10, 31), slot.BroadcastDay); } [Fact] public void Describe_RecoversKindAndName() { Assert.Equal((SeasonKind.Day, "Thursday, Oct 29"), SeasonPolicy.Describe(20261029)); Assert.Equal((SeasonKind.Week, "Week 42"), SeasonPolicy.Describe(1202642)); Assert.Equal((SeasonKind.Month, "October 2026"), SeasonPolicy.Describe(202610)); Assert.Equal((SeasonKind.Year, "2025"), SeasonPolicy.Describe(2025)); Assert.Null(SeasonPolicy.Describe(7)); Assert.Equal("Fall Block", SeasonPolicy.Describe(202610, new Dictionary { [202610] = "Fall Block" })!.Value.Name); } [Fact] public void Filename_ParsesMediaMtxPattern() { var ts = ItemTimestamp.TryParseFilename("2026-10-29_21-03-11", NY); Assert.NotNull(ts); Assert.Equal(Ny(2026, 10, 29, 21, 3, 11), ts!.Value); Assert.Null(ItemTimestamp.TryParseFilename("clip-final-v2", NY)); Assert.Equal(Ny(2026, 10, 29, 21, 3, 11), ItemTimestamp.TryParseFilename("Overwatch 2 2026.10.29 - 21.03.11.05.DVR", NY)); Assert.Equal(Ny(2026, 10, 29, 21, 3, 11), ItemTimestamp.TryParseFilename("Halo Infinite 2026-10-29 21-03-11", NY)); Assert.Equal(Ny(2026, 10, 29, 21, 3, 11), ItemTimestamp.TryParseFilename("clip_730_20261029_210311", NY)); } }