using ErsatzTV.Application.Libraries; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Tests.Support; using LanguageExt; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using NUnit.Framework; using Shouldly; using Unit = LanguageExt.Unit; namespace ErsatzTV.Tests.Application.Libraries; [TestFixture] public class MoveLocalLibraryPathHandlerTests { 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_422_When_Target_Library_Is_Same_As_Source() { (int libraryId, int pathId) = await SeedLibraryWithPath("Movies", LibraryMediaKind.Movies); MoveLocalLibraryPathHandler handler = CreateHandler(); Either result = await handler.Handle(new MoveLocalLibraryPath(pathId, libraryId), CancellationToken.None); result.IsLeft.ShouldBeTrue(); result.IfLeft(error => error.Value.ShouldContain("different")); await using TvContext dbContext = _db.CreateContext(); (await dbContext.LibraryPaths.FindAsync(pathId))!.LibraryId.ShouldBe(libraryId); } [Test] public async Task Handle_Should_Return_422_When_Target_Library_Has_Different_MediaKind() { (int sourceLibraryId, int pathId) = await SeedLibraryWithPath("Movies", LibraryMediaKind.Movies); int targetLibraryId = await SeedLibrary("TV Shows", LibraryMediaKind.Shows); MoveLocalLibraryPathHandler handler = CreateHandler(); Either result = await handler.Handle(new MoveLocalLibraryPath(pathId, targetLibraryId), CancellationToken.None); result.IsLeft.ShouldBeTrue(); result.IfLeft(error => error.Value.ShouldContain("media kind")); await using TvContext dbContext = _db.CreateContext(); (await dbContext.LibraryPaths.FindAsync(pathId))!.LibraryId.ShouldBe(sourceLibraryId); } [Test] public async Task Handle_Should_Move_Path_When_Target_Library_Is_Different_And_Same_MediaKind() { (_, int pathId) = await SeedLibraryWithPath("Movies", LibraryMediaKind.Movies); int targetLibraryId = await SeedLibrary("More Movies", LibraryMediaKind.Movies); MoveLocalLibraryPathHandler handler = CreateHandler(); Either result = await handler.Handle(new MoveLocalLibraryPath(pathId, targetLibraryId), CancellationToken.None); result.IsRight.ShouldBeTrue(); await using TvContext dbContext = _db.CreateContext(); (await dbContext.LibraryPaths.FindAsync(pathId))!.LibraryId.ShouldBe(targetLibraryId); } [Test] public async Task Handle_Should_Return_422_When_Path_Does_Not_Exist() { int targetLibraryId = await SeedLibrary("Movies", LibraryMediaKind.Movies); MoveLocalLibraryPathHandler handler = CreateHandler(); Either result = await handler.Handle(new MoveLocalLibraryPath(9999, targetLibraryId), CancellationToken.None); result.IsLeft.ShouldBeTrue(); } private MoveLocalLibraryPathHandler CreateHandler() => new( Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For(), _db.Factory, NullLogger.Instance); private async Task SeedLibrary(string name, LibraryMediaKind mediaKind) { await using TvContext context = _db.CreateContext(); var source = new LocalMediaSource { Libraries = [ new LocalLibrary { Name = name, MediaKind = mediaKind, Paths = [] } ] }; await context.LocalMediaSources.AddAsync(source); await context.SaveChangesAsync(); return source.Libraries[0].Id; } private async Task<(int LibraryId, int PathId)> SeedLibraryWithPath(string name, LibraryMediaKind mediaKind) { await using TvContext context = _db.CreateContext(); var source = new LocalMediaSource { Libraries = [ new LocalLibrary { Name = name, MediaKind = mediaKind, Paths = [new LibraryPath { Path = "/media/one" }] } ] }; await context.LocalMediaSources.AddAsync(source); await context.SaveChangesAsync(); return (source.Libraries[0].Id, source.Libraries[0].Paths[0].Id); } }