Files
ersatztv/ErsatzTV.Tests/Integration/CollectionLifecycleIntegrationTests.cs
T
timothy fb3f2856da
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m9s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
feat(api): add collections REST endpoints
Refs #35
2026-06-27 21:43:11 +02:00

132 lines
5.3 KiB
C#

using ErsatzTV.Application;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.Search;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Tests.Support;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using NUnit.Framework;
using Shouldly;
using Unit = LanguageExt.Unit;
namespace ErsatzTV.Tests.Integration;
/// <summary>
/// End-to-end create -> read -> add item -> remove item -> delete against the in-memory SQLite harness,
/// exercising the real EF Core handlers and collection item persistence.
/// </summary>
[TestFixture]
public class CollectionLifecycleIntegrationTests : MediaCollectionHandlerTestBase
{
[Test]
public async Task Create_Read_AddItem_RemoveItem_Delete()
{
int movieId = await SeedMovie();
IMediaCollectionRepository mediaCollectionRepository = Substitute.For<IMediaCollectionRepository>();
mediaCollectionRepository.PlayoutIdsUsingCollection(Arg.Any<int>()).Returns([]);
IMovieRepository movieRepository = Substitute.For<IMovieRepository>();
movieRepository.AllMoviesExist(Arg.Any<List<int>>()).Returns(true);
ITelevisionRepository televisionRepository = Substitute.For<ITelevisionRepository>();
televisionRepository.AllShowsExist(Arg.Any<List<int>>()).Returns(true);
televisionRepository.AllSeasonsExist(Arg.Any<List<int>>()).Returns(true);
televisionRepository.AllEpisodesExist(Arg.Any<List<int>>()).Returns(true);
var createHandler = new CreateCollectionHandler(Db.Factory, SearchTargets);
Either<BaseError, MediaCollectionViewModel> created =
await createHandler.Handle(new CreateCollection("Integration"), CancellationToken.None);
int collectionId = created.Match(Left: _ => throw new AssertionException("create failed"), Right: r => r.Id);
collectionId.ShouldBeGreaterThan(0);
var getHandler = new GetCollectionByIdHandler(Db.Factory);
Option<MediaCollectionViewModel> afterCreate =
await getHandler.Handle(new GetCollectionById(collectionId), CancellationToken.None);
afterCreate.IsSome.ShouldBeTrue();
afterCreate.Match(
Some: vm => vm.Name.ShouldBe("Integration"),
None: () => throw new AssertionException("expected collection to exist"));
var addHandler = new AddItemsToCollectionHandler(
Db.Factory,
mediaCollectionRepository,
movieRepository,
televisionRepository,
Worker,
System.Threading.Channels.Channel.CreateUnbounded<ISearchIndexBackgroundServiceRequest>().Writer);
Either<BaseError, Unit> added =
await addHandler.Handle(MakeAddItems(collectionId, movieId), CancellationToken.None);
added.IsRight.ShouldBeTrue();
await using (TvContext context = Db.CreateContext())
{
bool itemExists = await context.CollectionItems
.AnyAsync(ci => ci.CollectionId == collectionId && ci.MediaItemId == movieId);
itemExists.ShouldBeTrue();
}
var removeHandler = new RemoveItemsFromCollectionHandler(
Db.Factory,
mediaCollectionRepository,
Worker,
System.Threading.Channels.Channel.CreateUnbounded<ISearchIndexBackgroundServiceRequest>().Writer);
Either<BaseError, Unit> removed =
await removeHandler.Handle(
new RemoveItemsFromCollection(collectionId) { MediaItemIds = [movieId] },
CancellationToken.None);
removed.IsRight.ShouldBeTrue();
await using (TvContext context = Db.CreateContext())
{
bool itemExists = await context.CollectionItems
.AnyAsync(ci => ci.CollectionId == collectionId && ci.MediaItemId == movieId);
itemExists.ShouldBeFalse();
}
var deleteHandler = new DeleteCollectionHandler(Db.Factory, SearchTargets);
Either<BaseError, Unit> deleted =
await deleteHandler.Handle(new DeleteCollection(collectionId), CancellationToken.None);
deleted.IsRight.ShouldBeTrue();
Option<MediaCollectionViewModel> afterDelete =
await getHandler.Handle(new GetCollectionById(collectionId), CancellationToken.None);
afterDelete.IsNone.ShouldBeTrue();
}
private async Task<int> SeedMovie()
{
await using TvContext context = Db.CreateContext();
var mediaSource = new LocalMediaSource();
var library = new LocalLibrary
{
Name = "Movies",
MediaKind = LibraryMediaKind.Movies,
MediaSource = mediaSource,
Paths = []
};
var libraryPath = new LibraryPath
{
Path = "/media/movies",
Library = library,
LibraryFolders = [],
MediaItems = []
};
var movie = new Movie
{
LibraryPath = libraryPath,
MovieMetadata = [],
MediaVersions = [],
Collections = []
};
context.Movies.Add(movie);
await context.SaveChangesAsync();
return movie.Id;
}
private static AddItemsToCollection MakeAddItems(int collectionId, int movieId) =>
new(collectionId, [movieId], [], [], [], [], [], [], [], [], []);
}