using ErsatzTV.Application.Search.Queries; using ErsatzTV.Core.Api.Search; using ErsatzTV.Core.Domain; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Tests.Support; using LanguageExt; using NUnit.Framework; using Shouldly; namespace ErsatzTV.Tests.Application.Search; [TestFixture] public class GetSearchFieldValuesHandlerTests { 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 Returns_Distinct_Whole_Values_For_Genre_With_Prefix_And_Limit() { await using (TvContext context = _db.CreateContext()) { context.Set().AddRange( new Genre { Name = "Action" }, new Genre { Name = "Adventure" }, new Genre { Name = "Animation" }, new Genre { Name = "Comedy" }); await context.SaveChangesAsync(); } var handler = new GetSearchFieldValuesHandler(_db.Factory); Option result = await handler.Handle( new GetSearchFieldValues("genre", "A", 50), CancellationToken.None); result.IsSome.ShouldBeTrue(); result.IfSome(r => r.Values.ShouldBe(new List { "Action", "Adventure", "Animation" })); Option limited = await handler.Handle( new GetSearchFieldValues("genre", "A", 2), CancellationToken.None); limited.IsSome.ShouldBeTrue(); limited.IfSome(r => r.Values.ShouldBe(new List { "Action", "Adventure" })); } [Test] public async Task Splits_Content_Rating_On_Slash() { await using (TvContext context = _db.CreateContext()) { context.MovieMetadata.Add(new MovieMetadata { MetadataKind = MetadataKind.External, DateAdded = DateTime.UtcNow, DateUpdated = DateTime.UtcNow, ContentRating = "PG-13/TV-14" }); await context.SaveChangesAsync(); } var handler = new GetSearchFieldValuesHandler(_db.Factory); Option result = await handler.Handle( new GetSearchFieldValues("content_rating", string.Empty, 50), CancellationToken.None); result.IsSome.ShouldBeTrue(); result.IfSome(r => r.Values.ShouldBe(new List { "PG-13", "TV-14" })); } [Test] public async Task Returns_MediaItemState_Enum_Names_Without_Seeding() { var handler = new GetSearchFieldValuesHandler(_db.Factory); Option result = await handler.Handle( new GetSearchFieldValues("state", string.Empty, 50), CancellationToken.None); result.IsSome.ShouldBeTrue(); result.IfSome(r => r.Values.ShouldBe( new List { "FileNotFound", "Normal", "RemoteOnly", "Unavailable" })); } [Test] public async Task Returns_NotFound_For_Excluded_Text_Field_Title() { var handler = new GetSearchFieldValuesHandler(_db.Factory); Option result = await handler.Handle( new GetSearchFieldValues("title", string.Empty, 50), CancellationToken.None); result.IsNone.ShouldBeTrue(); } [Test] public async Task Returns_NotFound_For_NonText_Field() { var handler = new GetSearchFieldValuesHandler(_db.Factory); // "minutes" is a "number" field in SearchFieldCatalog, not "text" Option result = await handler.Handle( new GetSearchFieldValues("minutes", string.Empty, 50), CancellationToken.None); result.IsNone.ShouldBeTrue(); } [Test] public async Task Returns_NotFound_For_Unknown_Field() { var handler = new GetSearchFieldValuesHandler(_db.Factory); Option result = await handler.Handle( new GetSearchFieldValues("nope", string.Empty, 50), CancellationToken.None); result.IsNone.ShouldBeTrue(); } [Test] public async Task Matches_Case_Insensitive_Prefix() { await using (TvContext context = _db.CreateContext()) { context.Set().AddRange( new Genre { Name = "Action" }, new Genre { Name = "Comedy" }); await context.SaveChangesAsync(); } var handler = new GetSearchFieldValuesHandler(_db.Factory); Option result = await handler.Handle( new GetSearchFieldValues("genre", "a", 50), CancellationToken.None); result.IsSome.ShouldBeTrue(); result.IfSome(r => r.Values.ShouldBe(new List { "Action" })); } [Test] public async Task Excludes_Network_And_Country_Tags_From_Tag_Field_And_Routes_Network_Tags_To_Network_Field() { await using (TvContext context = _db.CreateContext()) { context.Set().AddRange( new Tag { Name = "PlainTag" }, new Tag { Name = "HBO", ExternalTypeId = Tag.PlexNetworkTypeId }, new Tag { Name = "USA", ExternalTypeId = Tag.NfoCountryTypeId }); await context.SaveChangesAsync(); } var handler = new GetSearchFieldValuesHandler(_db.Factory); Option tagResult = await handler.Handle( new GetSearchFieldValues("tag", string.Empty, 50), CancellationToken.None); tagResult.IsSome.ShouldBeTrue(); tagResult.IfSome(r => r.Values.ShouldBe(new List { "PlainTag" })); Option networkResult = await handler.Handle( new GetSearchFieldValues("network", string.Empty, 50), CancellationToken.None); networkResult.IsSome.ShouldBeTrue(); networkResult.IfSome(r => r.Values.ShouldBe(new List { "HBO" })); } [Test] public async Task Dedupes_Repeated_Values() { await using (TvContext context = _db.CreateContext()) { context.Set().AddRange( new Genre { Name = "Action" }, new Genre { Name = "Action" }); await context.SaveChangesAsync(); } var handler = new GetSearchFieldValuesHandler(_db.Factory); Option result = await handler.Handle( new GetSearchFieldValues("genre", string.Empty, 50), CancellationToken.None); result.IsSome.ShouldBeTrue(); result.IfSome(r => r.Values.ShouldBe(new List { "Action" })); } }