using ErsatzTV.Application.MediaCollections; using ErsatzTV.Core; using ErsatzTV.Core.Api.LibraryBrowse; using ErsatzTV.Core.Domain; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Tests.Support; using LanguageExt; using NUnit.Framework; using Shouldly; namespace ErsatzTV.Tests.Application.MediaCollections; [TestFixture] public class GetCollectionItemsHandlerTests { private InMemoryTvContext _db = null!; [SetUp] public async Task SetUp() => _db = await InMemoryTvContext.CreateAsync(); [TearDown] public async Task TearDown() => await _db.DisposeAsync(); [Test] public async Task Handle_Should_Return_NotFound_For_Missing_Collection() { var handler = new GetCollectionItemsHandler(_db.Factory); Either result = await handler.Handle(new GetCollectionItems(999, 0, 100), CancellationToken.None); result.IsLeft.ShouldBeTrue(); } [Test] public async Task Handle_Should_Order_By_Title_When_Custom_Order_Disabled() { await SeedCollectionGraph(useCustomPlaybackOrder: false); var handler = new GetCollectionItemsHandler(_db.Factory); Either result = await handler.Handle(new GetCollectionItems(10, 0, 100), CancellationToken.None); PagedLibraryBrowseItemsResponseModel page = result.RightToSeq().Single(); page.TotalCount.ShouldBe(3); page.Page.Select(i => i.Title).ShouldBe(["Alpha", "Beta", "Zeta"]); } [Test] public async Task Handle_Should_Order_By_CustomIndex_With_Nulls_Last_When_Custom_Order_Enabled() { await SeedCollectionGraph(useCustomPlaybackOrder: true); var handler = new GetCollectionItemsHandler(_db.Factory); Either result = await handler.Handle(new GetCollectionItems(10, 0, 100), CancellationToken.None); PagedLibraryBrowseItemsResponseModel page = result.RightToSeq().Single(); // Zeta has CustomIndex 0, Alpha has CustomIndex 1, Beta has no CustomIndex (sorts last). page.Page.Select(i => i.Title).ShouldBe(["Zeta", "Alpha", "Beta"]); } private async Task SeedCollectionGraph(bool useCustomPlaybackOrder) { await using TvContext context = _db.CreateContext(); var library = new LocalLibrary { Id = 1, Name = "Library", MediaKind = LibraryMediaKind.Movies, Paths = [] }; var path = new LibraryPath { Id = 1, Path = "/media", Library = library, LibraryFolders = [], MediaItems = [] }; library.Paths.Add(path); Movie alpha = MakeMovie(101, path, "Alpha"); Movie beta = MakeMovie(102, path, "Beta"); Movie zeta = MakeMovie(103, path, "Zeta"); var collection = new Collection { Id = 10, Name = "Manual", UseCustomPlaybackOrder = useCustomPlaybackOrder, MediaItems = [], CollectionItems = [ new CollectionItem { MediaItemId = 103, CustomIndex = 0 }, new CollectionItem { MediaItemId = 101, CustomIndex = 1 }, new CollectionItem { MediaItemId = 102, CustomIndex = null } ], MultiCollections = [], MultiCollectionItems = [] }; context.LocalLibraries.Add(library); context.Movies.AddRange(alpha, beta, zeta); context.Collections.Add(collection); await context.SaveChangesAsync(); } private static Movie MakeMovie(int id, LibraryPath path, string title) => new() { Id = id, LibraryPath = path, Collections = [], CollectionItems = [], TraktListItems = [], MovieMetadata = [ new MovieMetadata { Title = title, SortTitle = title, Artwork = [], Genres = [], Tags = [], Studios = [], Actors = [], Guids = [], Subtitles = [], Directors = [], Writers = [] } ], MediaVersions = [new MediaVersion { Duration = TimeSpan.FromMinutes(30) }] }; }