aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Controller.Tests/Drawing/ImageHelperTests.cs64
-rw-r--r--tests/Jellyfin.Model.Tests/Drawing/DrawingUtilsTests.cs28
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs83
3 files changed, 175 insertions, 0 deletions
diff --git a/tests/Jellyfin.Controller.Tests/Drawing/ImageHelperTests.cs b/tests/Jellyfin.Controller.Tests/Drawing/ImageHelperTests.cs
new file mode 100644
index 0000000000..571cb7f0d4
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/Drawing/ImageHelperTests.cs
@@ -0,0 +1,64 @@
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Model.Drawing;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests.Drawing;
+
+public static class ImageHelperTests
+{
+ [Fact]
+ public static void GetNewImageSize_ExplicitSizeLargerThanSource_ClampsToSource()
+ {
+ // Regression test for https://github.com/jellyfin/jellyfin/issues/17056: the caller-supplied
+ // width/height were used verbatim, so a single request could ask for a 23100x23100 encode.
+ var options = new ImageProcessingOptions { Width = 23100, Height = 23100 };
+
+ var newSize = ImageHelper.GetNewImageSize(options, new ImageDimensions(600, 336));
+
+ Assert.Equal(336, newSize.Width);
+ Assert.Equal(336, newSize.Height);
+ }
+
+ [Fact]
+ public static void GetNewImageSize_WidthLargerThanSource_ClampsToSource()
+ {
+ var options = new ImageProcessingOptions { Width = 10000 };
+
+ var newSize = ImageHelper.GetNewImageSize(options, new ImageDimensions(600, 336));
+
+ Assert.Equal(600, newSize.Width);
+ Assert.Equal(336, newSize.Height);
+ }
+
+ [Fact]
+ public static void GetNewImageSize_FillLargerThanSource_ClampsToSource()
+ {
+ // ResizeFill already refused to upscale; this pins that behaviour.
+ var options = new ImageProcessingOptions { FillWidth = 23100, FillHeight = 23100 };
+
+ var newSize = ImageHelper.GetNewImageSize(options, new ImageDimensions(600, 336));
+
+ Assert.Equal(600, newSize.Width);
+ Assert.Equal(336, newSize.Height);
+ }
+
+ [Fact]
+ public static void GetNewImageSize_SmallerThanSource_StillDownscales()
+ {
+ var options = new ImageProcessingOptions { MaxWidth = 300 };
+
+ var newSize = ImageHelper.GetNewImageSize(options, new ImageDimensions(600, 336));
+
+ Assert.Equal(300, newSize.Width);
+ Assert.Equal(168, newSize.Height);
+ }
+
+ [Fact]
+ public static void GetNewImageSize_NoSizeRequested_ReturnsSource()
+ {
+ var newSize = ImageHelper.GetNewImageSize(new ImageProcessingOptions(), new ImageDimensions(600, 336));
+
+ Assert.Equal(600, newSize.Width);
+ Assert.Equal(336, newSize.Height);
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Drawing/DrawingUtilsTests.cs b/tests/Jellyfin.Model.Tests/Drawing/DrawingUtilsTests.cs
new file mode 100644
index 0000000000..473b07a8a1
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Drawing/DrawingUtilsTests.cs
@@ -0,0 +1,28 @@
+using MediaBrowser.Model.Drawing;
+using Xunit;
+
+namespace Jellyfin.Model.Drawing;
+
+public static class DrawingUtilsTests
+{
+ [Theory]
+ // Already inside the box, returned untouched.
+ [InlineData(600, 336, 1920, 1080, 600, 336)]
+ [InlineData(1920, 1080, 1920, 1080, 1920, 1080)]
+ // Scaled down uniformly, requested aspect ratio preserved.
+ [InlineData(23100, 23100, 1920, 1080, 1080, 1080)]
+ [InlineData(3840, 2160, 1920, 1080, 1920, 1080)]
+ [InlineData(1200, 400, 600, 336, 600, 200)]
+ // Extreme ratios still produce at least one pixel per axis.
+ [InlineData(10000, 1, 100, 100, 100, 1)]
+ // Degenerate inputs are passed through rather than dividing by zero.
+ [InlineData(600, 336, 0, 0, 600, 336)]
+ [InlineData(0, 0, 1920, 1080, 0, 0)]
+ public static void ScaleDownToFit_Bounds_WithoutUpscaling(int width, int height, int boxWidth, int boxHeight, int expectedWidth, int expectedHeight)
+ {
+ var scaled = DrawingUtils.ScaleDownToFit(new ImageDimensions(width, height), new ImageDimensions(boxWidth, boxHeight));
+
+ Assert.Equal(expectedWidth, scaled.Width);
+ Assert.Equal(expectedHeight, scaled.Height);
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs
index d18f8c6cff..6a3dcab57a 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
@@ -205,6 +206,84 @@ public class DtoServiceImageInheritanceTests
libraryManager.Verify(x => x.GetPeople(It.IsAny<BaseItem>()), Times.Never);
}
+ [Fact]
+ public void GetBaseItemDtos_Videos_ResolveMediaSourceCountFromBatch_WithoutPerItemLookup()
+ {
+ static Movie MakeMovie() => new Movie
+ {
+ Id = Guid.NewGuid(),
+ Name = "Movie",
+ ImageInfos = []
+ };
+
+ var movieOne = MakeMovie();
+ var movieTwo = MakeMovie();
+
+ var libraryManager = new Mock<ILibraryManager>();
+
+ // DtoService detects which videos own alternate versions in ONE batch
+ // (GetItemIdsWithAlternateVersions) before the per-item loop. Videos absent from that set have a
+ // single media source, so the per-item GetLinkedAlternateVersions/GetLocalAlternateVersionIds
+ // queries (the N+1) must be skipped entirely. Here neither movie has alternate versions.
+ libraryManager
+ .Setup(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>()))
+ .Returns(new HashSet<Guid>());
+
+ var dtoService = BuildDtoService(libraryManager);
+
+ var options = new DtoOptions(false) { Fields = [ItemFields.MediaSourceCount] };
+ var dtos = dtoService.GetBaseItemDtos([movieOne, movieTwo], options);
+
+ Assert.Equal(2, dtos.Count);
+
+ // A single media source is the default, so the count is left unset (the client treats null as one).
+ foreach (var dto in dtos)
+ {
+ Assert.Null(dto.MediaSourceCount);
+ }
+
+ // The alternate-version check is batched once for the whole set, and the per-item lookups are
+ // never reached because the batch already ruled out alternate versions.
+ libraryManager.Verify(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>()), Times.Once);
+ libraryManager.Verify(x => x.GetLinkedAlternateVersions(It.IsAny<Video>()), Times.Never);
+ libraryManager.Verify(x => x.GetLocalAlternateVersionIds(It.IsAny<Video>()), Times.Never);
+ }
+
+ [Fact]
+ public void GetBaseItemDtos_VideoInAlternateVersionBatch_ResolvesRealCount()
+ {
+ var movie = new Movie
+ {
+ Id = Guid.NewGuid(),
+ Name = "Movie",
+ ImageInfos = []
+ };
+
+ var libraryManager = new Mock<ILibraryManager>();
+
+ // This movie IS in the batch set, so the fast path must not short-circuit it: the per-item
+ // lookups still run and the count is computed exactly as it was before batching. Two linked
+ // alternate versions plus the movie itself is a count of three.
+ libraryManager
+ .Setup(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>()))
+ .Returns(new HashSet<Guid> { movie.Id });
+ libraryManager
+ .Setup(x => x.GetLinkedAlternateVersions(It.IsAny<Video>()))
+ .Returns([new Movie { Id = Guid.NewGuid() }, new Movie { Id = Guid.NewGuid() }]);
+ libraryManager
+ .Setup(x => x.GetLocalAlternateVersionIds(It.IsAny<Video>()))
+ .Returns([]);
+
+ var dtoService = BuildDtoService(libraryManager);
+
+ var options = new DtoOptions(false) { Fields = [ItemFields.MediaSourceCount] };
+ var dtos = dtoService.GetBaseItemDtos([movie], options);
+
+ Assert.Single(dtos);
+ Assert.Equal(3, dtos[0].MediaSourceCount);
+ libraryManager.Verify(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>()), Times.Once);
+ }
+
private static DtoService BuildDtoService(BaseItem displayParent)
{
var libraryManager = new Mock<ILibraryManager>();
@@ -231,6 +310,10 @@ public class DtoServiceImageInheritanceTests
.Setup(x => x.GetImageCacheTag(It.IsAny<BaseItem>(), It.IsAny<ItemImageInfo>()))
.Returns<BaseItem, ItemImageInfo>((_, image) => image.Path);
+ // Video.IsActiveRecording() dereferences this static during DTO building.
+ Video.RecordingsManager = recordingsManager.Object;
+ BaseItem.LibraryManager = libraryManager.Object;
+
return new DtoService(
logger.Object,
libraryManager.Object,