diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-29 10:41:50 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-29 10:41:50 +0200 |
| commit | 5feb70f489670808be682e1f2f80c4780651c57b (patch) | |
| tree | 1acfb2c1e7f471c3d3b44293a288ad13f136bf68 /tests | |
| parent | 0beb07c40756aca5ab6a6ba4f8494bc5147e3c2b (diff) | |
Fix recently added episode links and posters
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceTests.cs | 131 |
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); + } +} |
