using Data.SeaHavenIndustries; using FluentAssertions; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.DTOs; using SeaHaven.Services.Implementation; using Xunit; namespace Api.SeaHavenIndustries.Tests; public class DropdownOptionsServiceTests { private static ApplicationDbContext NewContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } private static DropdownOptionsService NewService(ApplicationDbContext ctx) => new(new DropdownOptionsDataService(ctx)); private static DropdownOption Option(string category, string value, string? parent = null, int sort = 0, bool active = true) => new() { Category = category, Value = value, ParentValue = parent, SortOrder = sort, IsActive = active }; [Fact] public async Task GetAllAsync_FiltersActiveAndOrdersBySortThenValue() { using var ctx = NewContext(); ctx.DropdownOptions.AddRange( Option("Color", "Red", sort: 2), Option("Color", "Blue", sort: 1), Option("Color", "Green", sort: 1, active: false), Option("Shape", "Circle") ); ctx.SaveChanges(); var result = (await NewService(ctx).GetAllAsync(null, CancellationToken.None)).ToList(); result.Should().HaveCount(3); result.Select(d => d.Value).Should().ContainInOrder("Blue", "Red", "Circle"); } [Fact] public async Task GetByCategoryAsync_FiltersByCategoryAndParent() { using var ctx = NewContext(); ctx.DropdownOptions.AddRange( Option("State", "TX", parent: "US"), Option("State", "CA", parent: "US"), Option("State", "ON", parent: "CA") ); ctx.SaveChanges(); var result = (await NewService(ctx).GetByCategoryAsync("State", "US", CancellationToken.None)).ToList(); result.Select(d => d.Value).Should().BeEquivalentTo(new[] { "TX", "CA" }); } [Fact] public async Task CreateAsync_WhenCategoryOrValueMissing_Throws() { using var ctx = NewContext(); var act = () => NewService(ctx).CreateAsync(new DropdownOptionDTO { Category = "", Value = "X" }, CancellationToken.None); await act.Should().ThrowAsync(); } [Fact] public async Task CreateAsync_WhenDuplicate_Throws() { using var ctx = NewContext(); ctx.DropdownOptions.Add(Option("Color", "Red", parent: "Primary")); ctx.SaveChanges(); var act = () => NewService(ctx).CreateAsync(new DropdownOptionDTO { Category = "Color", Value = "Red", ParentValue = "Primary" }, CancellationToken.None); await act.Should().ThrowAsync(); } [Fact] public async Task CreateAsync_ForcesActiveTrueAndReturnsDtoWithId() { using var ctx = NewContext(); var dto = await NewService(ctx).CreateAsync( new DropdownOptionDTO { Category = "Color", Value = "Red", IsActive = false }, CancellationToken.None); dto.Id.Should().BeGreaterThan(0); dto.IsActive.Should().BeTrue(); ctx.DropdownOptions.Single().IsActive.Should().BeTrue(); } [Fact] public async Task UpdateAsync_WhenNotFound_ThrowsKeyNotFound() { using var ctx = NewContext(); var act = () => NewService(ctx).UpdateAsync(404, new DropdownOptionDTO { Category = "Color", Value = "Red" }, CancellationToken.None); await act.Should().ThrowAsync(); } [Fact] public async Task UpdateAsync_UpdatesValueParentSortActiveButNotCategory() { using var ctx = NewContext(); var existing = Option("Color", "Red"); ctx.DropdownOptions.Add(existing); ctx.SaveChanges(); await NewService(ctx).UpdateAsync(existing.Id, new DropdownOptionDTO { Category = "Ignored", Value = "Crimson", ParentValue = "Warm", SortOrder = 5, IsActive = false }, CancellationToken.None); var row = ctx.DropdownOptions.Single(); row.Category.Should().Be("Color"); row.Value.Should().Be("Crimson"); row.ParentValue.Should().Be("Warm"); row.SortOrder.Should().Be(5); row.IsActive.Should().BeFalse(); } [Fact] public async Task DeleteAsync_WhenNotFound_ThrowsKeyNotFound() { using var ctx = NewContext(); var act = () => NewService(ctx).DeleteAsync(404, CancellationToken.None); await act.Should().ThrowAsync(); } [Fact] public async Task DeleteAsync_RemovesOption() { using var ctx = NewContext(); var existing = Option("Color", "Red"); ctx.DropdownOptions.Add(existing); ctx.SaveChanges(); await NewService(ctx).DeleteAsync(existing.Id, CancellationToken.None); ctx.DropdownOptions.Should().BeEmpty(); } }