Vanguard media stack: ingest, copyparty, Syncthing, normalize sweep, Authentik, Jellyfin + Vanguard Metadata plugin, MariaDB, Gitea, bot runtime
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
|
||||
<PackageReference Include="xunit" Version="2.*" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Jellyfin.Plugin.Vanguard.Core/Jellyfin.Plugin.Vanguard.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,115 @@
|
||||
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<int, string> { [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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user