diff options
| author | Cody Robibero <cody@robibe.ro> | 2026-07-20 20:17:20 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-20 20:17:20 -0400 |
| commit | c222d370cedcc4452a7246baff3494b29875a708 (patch) | |
| tree | 54c814bb5bd002748d4ed2ba47c84f2d3157ab4f /tests | |
| parent | 23fa02eb59519cdc4c2b15b9778ef006b5e85341 (diff) | |
| parent | e1d63c0ea09f542f3e0432a641153591b579cb37 (diff) | |
Merge pull request #16933 from WizardOfYendor1/fix/livetv-guide-image-optimization
Feat (fix) - Skip reprocessing program information when importing XMLTV EPG data
Diffstat (limited to 'tests')
10 files changed, 253 insertions, 0 deletions
diff --git a/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs b/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs index b71dc15201..f698edc637 100644 --- a/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs +++ b/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; using Jellyfin.LiveTv.Listings; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.LiveTv; using Moq; using Moq.Protected; @@ -66,6 +67,7 @@ public class XmlTvListingsProviderTests Assert.True(program.HasImage); Assert.Equal("https://domain.tld/image.png", program.ImageUrl); Assert.Equal("3297", program.ChannelId); + AssertXmlTvEtag(program.Etag); } [Theory] @@ -85,5 +87,60 @@ public class XmlTvListingsProviderTests var program = programsList[0]; Assert.DoesNotContain(program.Genres, g => string.IsNullOrEmpty(g)); Assert.Equal("3297", program.ChannelId); + AssertXmlTvEtag(program.Etag); + } + + [Fact] + public async Task GetProgramsAsync_Etag_SameContentIsStable() + { + var first = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml"); + var second = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml"); + + Assert.Equal(first.Etag, second.Etag); + } + + [Theory] + [InlineData("Test Data/LiveTv/Listings/XmlTv/etag-title-change.xml")] + [InlineData("Test Data/LiveTv/Listings/XmlTv/etag-description-change.xml")] + [InlineData("Test Data/LiveTv/Listings/XmlTv/etag-icon-change.xml")] + [InlineData("Test Data/LiveTv/Listings/XmlTv/etag-category-change.xml")] + [InlineData("Test Data/LiveTv/Listings/XmlTv/etag-progid-change.xml")] + public async Task GetProgramsAsync_Etag_ChangesWhenMappedContentChanges(string changedPath) + { + var original = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml"); + var changed = await GetSingleProgramAsync(changedPath); + + Assert.NotEqual(original.Etag, changed.Etag); + } + + [Theory] + [InlineData("Test Data/LiveTv/Listings/XmlTv/etag-reordered.xml")] + [InlineData("Test Data/LiveTv/Listings/XmlTv/etag-unknown-field.xml")] + public async Task GetProgramsAsync_Etag_DoesNotChangeWhenMappedContentIsEquivalent(string equivalentPath) + { + var original = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml"); + var equivalent = await GetSingleProgramAsync(equivalentPath); + + Assert.Equal(original.Etag, equivalent.Etag); + } + + private async Task<ProgramInfo> GetSingleProgramAsync(string path) + { + var info = new ListingsProviderInfo() + { + Id = Path.GetFileNameWithoutExtension(path), + Path = path + }; + + var startDate = new DateTime(2022, 11, 4, 0, 0, 0, DateTimeKind.Utc); + var programs = await _xmlTvListingsProvider.GetProgramsAsync(info, "3297", startDate, startDate.AddDays(1), CancellationToken.None); + + return Assert.Single(programs.ToList()); + } + + private static void AssertXmlTvEtag(string? etag) + { + Assert.NotNull(etag); + Assert.StartsWith("xmltv-sha256-v1:", etag!, StringComparison.Ordinal); } } diff --git a/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvProgramEtagTests.cs b/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvProgramEtagTests.cs new file mode 100644 index 0000000000..b8d1c60e1a --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvProgramEtagTests.cs @@ -0,0 +1,59 @@ +using System; +using Jellyfin.LiveTv.Listings; +using MediaBrowser.Controller.LiveTv; +using Xunit; + +namespace Jellyfin.LiveTv.Tests.Listings; + +public class XmlTvProgramEtagTests +{ + [Fact] + public void TryCreate_GenreOrderIsSignificant() + { + // GuideManager assigns item.Genres = info.Genres.ToArray() preserving order, + // so the same genres in a different order is a real mapped-content change. + var first = NewProgram(); + first.Genres = new() { "Drama", "Action" }; + + var second = NewProgram(); + second.Genres = new() { "Action", "Drama" }; + + Assert.True(XmlTvProgramEtag.TryCreate(first, out var firstEtag, out _)); + Assert.True(XmlTvProgramEtag.TryCreate(second, out var secondEtag, out _)); + Assert.NotEqual(firstEtag, secondEtag); + } + + [Fact] + public void MatchesStored_EqualXmlTvEtags_ReturnsTrue() + { + const string Etag = XmlTvProgramEtag.Prefix + "ABCDEF0123456789"; + Assert.True(XmlTvProgramEtag.MatchesStored(Etag, Etag)); + } + + [Fact] + public void MatchesStored_DifferentXmlTvEtags_ReturnsFalse() + { + Assert.False(XmlTvProgramEtag.MatchesStored( + XmlTvProgramEtag.Prefix + "AAAA", + XmlTvProgramEtag.Prefix + "BBBB")); + } + + [Fact] + public void MatchesStored_EqualNonXmlTvEtags_ReturnsFalse() + { + // Other providers (e.g. Schedules Direct) use their own etag schemes. + // The IsXmlTvEtag gate must keep them on the field-by-field update path + // even when their incoming and stored values happen to match exactly. + const string Etag = "sd-abc123"; + Assert.False(XmlTvProgramEtag.MatchesStored(Etag, Etag)); + } + + private static ProgramInfo NewProgram() => new() + { + Id = "program-id", + ChannelId = "channel-id", + Name = "Program Name", + StartDate = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc), + EndDate = new DateTime(2026, 1, 1, 13, 0, 0, DateTimeKind.Utc), + }; +} diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-base.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-base.xml new file mode 100644 index 0000000000..15f85f57e6 --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-base.xml @@ -0,0 +1,17 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000"> + <title lang="en">Base Program</title> + <sub-title lang="en">Base Episode</sub-title> + <desc lang="en">Base description.</desc> + <category lang="en">series</category> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789012</episode-num> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <star-rating> + <value>3/5</value> + </star-rating> + <icon src="https://domain.tld/base.png"/> + </programme> +</tv> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-category-change.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-category-change.xml new file mode 100644 index 0000000000..2b49c3bccd --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-category-change.xml @@ -0,0 +1,17 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000"> + <title lang="en">Base Program</title> + <sub-title lang="en">Base Episode</sub-title> + <desc lang="en">Base description.</desc> + <category lang="en">sports</category> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789012</episode-num> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <star-rating> + <value>3/5</value> + </star-rating> + <icon src="https://domain.tld/base.png"/> + </programme> +</tv> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-description-change.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-description-change.xml new file mode 100644 index 0000000000..090273ac98 --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-description-change.xml @@ -0,0 +1,17 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000"> + <title lang="en">Base Program</title> + <sub-title lang="en">Base Episode</sub-title> + <desc lang="en">Changed description.</desc> + <category lang="en">series</category> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789012</episode-num> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <star-rating> + <value>3/5</value> + </star-rating> + <icon src="https://domain.tld/base.png"/> + </programme> +</tv> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-icon-change.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-icon-change.xml new file mode 100644 index 0000000000..532b91da20 --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-icon-change.xml @@ -0,0 +1,17 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000"> + <title lang="en">Base Program</title> + <sub-title lang="en">Base Episode</sub-title> + <desc lang="en">Base description.</desc> + <category lang="en">series</category> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789012</episode-num> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <star-rating> + <value>3/5</value> + </star-rating> + <icon src="https://domain.tld/changed.png"/> + </programme> +</tv> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-progid-change.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-progid-change.xml new file mode 100644 index 0000000000..db0d5e86de --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-progid-change.xml @@ -0,0 +1,17 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000"> + <title lang="en">Base Program</title> + <sub-title lang="en">Base Episode</sub-title> + <desc lang="en">Base description.</desc> + <category lang="en">series</category> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789013</episode-num> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <star-rating> + <value>3/5</value> + </star-rating> + <icon src="https://domain.tld/base.png"/> + </programme> +</tv> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-reordered.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-reordered.xml new file mode 100644 index 0000000000..168c0a643b --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-reordered.xml @@ -0,0 +1,17 @@ +<tv date="20221104"> + <programme channel="3297" stop="20221104140000 +0000" start="20221104130000 +0000"> + <icon src="https://domain.tld/base.png"/> + <star-rating> + <value>3/5</value> + </star-rating> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789012</episode-num> + <category lang="en">series</category> + <desc lang="en">Base description.</desc> + <sub-title lang="en">Base Episode</sub-title> + <title lang="en">Base Program</title> + </programme> +</tv> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-title-change.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-title-change.xml new file mode 100644 index 0000000000..73288e7c57 --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-title-change.xml @@ -0,0 +1,17 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000"> + <title lang="en">Changed Program</title> + <sub-title lang="en">Base Episode</sub-title> + <desc lang="en">Base description.</desc> + <category lang="en">series</category> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789012</episode-num> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <star-rating> + <value>3/5</value> + </star-rating> + <icon src="https://domain.tld/base.png"/> + </programme> +</tv> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-unknown-field.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-unknown-field.xml new file mode 100644 index 0000000000..d0ff1b82f5 --- /dev/null +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/etag-unknown-field.xml @@ -0,0 +1,18 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000"> + <title lang="en">Base Program</title> + <sub-title lang="en">Base Episode</sub-title> + <desc lang="en">Base description.</desc> + <category lang="en">series</category> + <episode-num system="xmltv_ns">0 . 1 . </episode-num> + <episode-num system="dd_progid">EP123456789012</episode-num> + <rating system="VCHIP"> + <value>TV-G</value> + </rating> + <star-rating> + <value>3/5</value> + </star-rating> + <previously-unknown-field>Ignored by Jellyfin XMLTV mapping.</previously-unknown-field> + <icon src="https://domain.tld/base.png"/> + </programme> +</tv> |
