aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2026-06-01 21:59:06 +0200
committerGitHub <noreply@github.com>2026-06-01 21:59:06 +0200
commit53eda14dccddbcbd8806150489ff8f4495814631 (patch)
treef2db2d10496c90a554a41a5366d191a75b7629d4 /tests
parent0d30a595642c647828c5e62bd64c067d5c33c02f (diff)
parent5feb70f489670808be682e1f2f80c4780651c57b (diff)
Merge pull request #16967 from Shadowghost/fix-recently-added-postersHEADmaster
Fix recently added episode links and posters
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs131
1 files changed, 131 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs
new file mode 100644
index 0000000000..a5de0a4416
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs
@@ -0,0 +1,131 @@
+using System;
+using Emby.Server.Implementations.Dto;
+using MediaBrowser.Common;
+using MediaBrowser.Controller.Chapters;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Trickplay;
+using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Dto;
+
+public class DtoServiceTests
+{
+ private readonly Mock<ILibraryManager> _libraryManagerMock;
+ private readonly DtoService _dtoService;
+
+ public DtoServiceTests()
+ {
+ _libraryManagerMock = new Mock<ILibraryManager>();
+
+ var imageProcessor = new Mock<IImageProcessor>();
+ // Deterministic tag derived from the image so each item gets a distinct, assertable tag.
+ imageProcessor
+ .Setup(x => x.GetImageCacheTag(It.IsAny<BaseItem>(), It.IsAny<ItemImageInfo>()))
+ .Returns((BaseItem _, ItemImageInfo image) => "tag:" + image.Path);
+
+ var appHost = new Mock<IApplicationHost>();
+ appHost.Setup(x => x.SystemId).Returns("test-server");
+
+ // Video.SourceType probes the active-recording manager; provide one so it doesn't NRE.
+ Video.RecordingsManager = new Mock<IRecordingsManager>().Object;
+
+ _dtoService = new DtoService(
+ NullLogger<DtoService>.Instance,
+ _libraryManagerMock.Object,
+ new Mock<IUserDataManager>().Object,
+ imageProcessor.Object,
+ new Mock<IProviderManager>().Object,
+ new Mock<IRecordingsManager>().Object,
+ appHost.Object,
+ new Mock<IMediaSourceManager>().Object,
+ new Lazy<ILiveTvManager>(() => new Mock<ILiveTvManager>().Object),
+ new Mock<ITrickplayManager>().Object,
+ new Mock<IChapterManager>().Object);
+
+ // Episode.Series / Episode.Season resolve through the static BaseItem.LibraryManager.
+ BaseItem.LibraryManager = _libraryManagerMock.Object;
+ }
+
+ [Fact]
+ public void GetBaseItemDto_PreferEpisodeParentPoster_PrefersSeasonPosterOverEpisodeAndSeries()
+ {
+ var (episode, season, series) = BuildEpisode(seasonHasPoster: true);
+ var options = new DtoOptions(false) { PreferEpisodeParentPoster = true };
+
+ var dto = _dtoService.GetBaseItemDto(episode, options);
+
+ // The episode's own 16:9 primary is dropped in favor of the season's portrait poster.
+ Assert.False(dto.ImageTags is not null && dto.ImageTags.ContainsKey(ImageType.Primary));
+ Assert.Null(dto.SeriesPrimaryImageTag);
+ Assert.Equal(season.Id, dto.ParentPrimaryImageItemId);
+ Assert.Equal("tag:" + season.GetImageInfo(ImageType.Primary, 0)!.Path, dto.ParentPrimaryImageTag);
+ // Aspect ratio follows the (portrait) poster, not the episode's 16:9 image.
+ Assert.Equal(season.GetDefaultPrimaryImageAspectRatio(), dto.PrimaryImageAspectRatio);
+ }
+
+ [Fact]
+ public void GetBaseItemDto_PreferEpisodeParentPoster_FallsBackToSeriesWhenSeasonHasNoPoster()
+ {
+ var (episode, _, series) = BuildEpisode(seasonHasPoster: false);
+ var options = new DtoOptions(false) { PreferEpisodeParentPoster = true };
+
+ var dto = _dtoService.GetBaseItemDto(episode, options);
+
+ Assert.False(dto.ImageTags is not null && dto.ImageTags.ContainsKey(ImageType.Primary));
+ Assert.Null(dto.SeriesPrimaryImageTag);
+ Assert.Equal(series.Id, dto.ParentPrimaryImageItemId);
+ Assert.Equal("tag:" + series.GetImageInfo(ImageType.Primary, 0)!.Path, dto.ParentPrimaryImageTag);
+ }
+
+ [Fact]
+ public void GetBaseItemDto_WithoutPreferEpisodeParentPoster_KeepsEpisodePrimary()
+ {
+ var (episode, _, _) = BuildEpisode(seasonHasPoster: true);
+ var options = new DtoOptions(false);
+
+ var dto = _dtoService.GetBaseItemDto(episode, options);
+
+ // Default behavior: the episode keeps its own primary and exposes the series poster as a tag.
+ Assert.NotNull(dto.ImageTags);
+ Assert.True(dto.ImageTags.ContainsKey(ImageType.Primary));
+ Assert.NotNull(dto.SeriesPrimaryImageTag);
+ Assert.Null(dto.ParentPrimaryImageItemId);
+ }
+
+ private (Episode Episode, Season Season, Series Series) BuildEpisode(bool seasonHasPoster)
+ {
+ // Non-local (http) paths keep aspect-ratio resolution off the image processor and on the
+ // item's default ratio, which is portrait (2/3) for Season/Series and 16:9 for Episode.
+ var series = new Series { Id = Guid.NewGuid(), Name = "Series" };
+ series.SetImage(new ItemImageInfo { Type = ImageType.Primary, Path = "http://test/series.jpg" }, 0);
+
+ var season = new Season { Id = Guid.NewGuid(), Name = "Season", SeriesId = series.Id };
+ if (seasonHasPoster)
+ {
+ season.SetImage(new ItemImageInfo { Type = ImageType.Primary, Path = "http://test/season.jpg" }, 0);
+ }
+
+ var episode = new Episode
+ {
+ Id = Guid.NewGuid(),
+ Name = "Episode",
+ SeasonId = season.Id,
+ SeriesId = series.Id
+ };
+ episode.SetImage(new ItemImageInfo { Type = ImageType.Primary, Path = "http://test/episode.jpg" }, 0);
+
+ _libraryManagerMock.Setup(x => x.GetItemById(season.Id)).Returns(season);
+ _libraryManagerMock.Setup(x => x.GetItemById(series.Id)).Returns(series);
+
+ return (episode, season, series);
+ }
+}