using Data.SeaHavenIndustries; using FluentAssertions; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.DTOs; using SeaHaven.Services.Implementation; using Xunit; namespace Api.SeaHavenIndustries.Tests; public class SettingsServiceTests { private static ApplicationDbContext NewContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } private static SettingsService NewService(ApplicationDbContext ctx) => new(new SettingsDataService(ctx), null!); private static (ApplicationDbContext ctx, UserManager userManager, RoleManager roleManager) NewIdentityContext() { var services = new ServiceCollection(); services.AddLogging(); services.AddDbContext(o => o.UseInMemoryDatabase(Guid.NewGuid().ToString())); services.AddIdentity().AddEntityFrameworkStores(); var provider = services.BuildServiceProvider(); var scope = provider.CreateScope(); var ctx = scope.ServiceProvider.GetRequiredService(); var userManager = scope.ServiceProvider.GetRequiredService>(); var roleManager = scope.ServiceProvider.GetRequiredService>(); return (ctx, userManager, roleManager); } [Fact] public async Task AddCategoryAsync_PersistsName() { using var ctx = NewContext(); await NewService(ctx).AddCategoryAsync("Plumbing", CancellationToken.None); ctx.Categories.Single().Name.Should().Be("Plumbing"); } [Fact] public async Task UpdateCategoryAsync_UpdatesNameAndReturnsFalseWhenMissing() { using var ctx = NewContext(); ctx.Categories.Add(new Category { Name = "Old" }); ctx.SaveChanges(); var service = NewService(ctx); (await service.UpdateCategoryAsync(1, "New", CancellationToken.None)).Should().BeTrue(); ctx.Categories.Single().Name.Should().Be("New"); (await service.UpdateCategoryAsync(999, "X", CancellationToken.None)).Should().BeFalse(); } [Fact] public async Task DeleteCategoryAsync_CascadesTemplatesAndWorkOrderCategories() { using var ctx = NewContext(); ctx.Categories.Add(new Category { Id = 1, Name = "C1" }); ctx.Templates.Add(new Template { CategoryId = 1, WorkerOrderTitle = "T" }); ctx.workOrderCategories.Add(new WorkOrderCategories { CategoryId = 1 }); ctx.SaveChanges(); var service = NewService(ctx); (await service.DeleteCategoryAsync(1, CancellationToken.None)).Should().BeTrue(); (await service.DeleteCategoryAsync(1, CancellationToken.None)).Should().BeFalse(); ctx.Categories.Should().BeEmpty(); ctx.Templates.Should().BeEmpty(); ctx.workOrderCategories.Should().BeEmpty(); } [Fact] public async Task GetCategoryListAsync_MapsToServiceDtoAndPages() { using var ctx = NewContext(); ctx.Categories.Add(new Category { Name = "A" }); ctx.Categories.Add(new Category { Name = "B" }); ctx.SaveChanges(); var (items, total) = await NewService(ctx).GetCategoryListAsync(1, 10, CancellationToken.None); total.Should().Be(2); items.Should().OnlyContain(c => c is SettingsCategoryDTO); items.Select(c => c.Name).Should().Contain(new[] { "A", "B" }); } [Fact] public async Task GetTemplatesAsync_MapsWorkOrderScalarsToTypedDto() { using var ctx = NewContext(); var wo = new WorkOrder { istemplate = true, InternalWONumber = "WO-100", WorkerOrderTitle = "Template A", Status = "Active", Priority = "High", Trade = "HVAC", VendorNTE = 1500m, Customer = "Cust" }; ctx.workOrders.Add(wo); ctx.SaveChanges(); var (items, total) = await NewService(ctx).GetTemplatesAsync(1, 10, CancellationToken.None); total.Should().Be(1); var dto = items.Single(); dto.Should().BeOfType(); dto.InternalWONumber.Should().Be("WO-100"); dto.WorkerOrderTitle.Should().Be("Template A"); dto.Status.Should().Be("Active"); dto.Priority.Should().Be("High"); dto.Trade.Should().Be("HVAC"); dto.VendorNTE.Should().Be(1500m); dto.Customer.Should().Be("Cust"); dto.istemplate.Should().BeTrue(); } [Fact] public async Task GetUsersAsync_ExcludesAdminsAndResolvesRole() { var (ctx, userManager, roleManager) = NewIdentityContext(); var t1 = roleManager.CreateAsync(new IdentityRole("Admin")); var t2 = roleManager.CreateAsync(new IdentityRole("User")); await Task.WhenAll(t1, t2); var admin = new ApplicationUser { UserName = "admin1", Email = "a@x.com", FirstName = "Boss" }; var worker = new ApplicationUser { UserName = "worker1", Email = "w@x.com", FirstName = "Hand" }; await userManager.CreateAsync(admin); await userManager.CreateAsync(worker); await userManager.AddToRoleAsync(admin, "Admin"); await userManager.AddToRoleAsync(worker, "User"); ctx.SaveChanges(); var service = new SettingsService(new SettingsDataService(ctx), userManager); var (users, total) = await service.GetUsersAsync("", 1, 10, CancellationToken.None); total.Should().BeGreaterThanOrEqualTo(2); users.Should().NotContain(u => u.RoleName == "Admin"); users.Should().Contain(u => u.UserName == "Hand" && u.RoleName == "User"); } [Fact] public async Task GetRolesAsync_MapsKnownRoleNamesToDescriptions() { using var ctx = NewContext(); ctx.Roles.Add(new IdentityRole("Admin")); ctx.Roles.Add(new IdentityRole("Manager")); ctx.Roles.Add(new IdentityRole("Unknown")); ctx.SaveChanges(); var roles = await NewService(ctx).GetRolesAsync(CancellationToken.None); var byName = roles.ToDictionary(r => r.Name!, r => r.Description); byName["Admin"].Should().Be("Full Access"); byName["Manager"].Should().Be("Full access accept quotes"); roles.Should().NotContain(r => r.Name == "Unknown"); } }