using Data.SeaHavenIndustries; using FluentAssertions; using Moq; using SeaHaven.DataServices.Interfaces; using SeaHaven.Services.DTOs; using SeaHaven.Services.Implementation; using SeaHaven.Services.Validation; using Xunit; namespace Api.SeaHavenIndustries.Tests; public class VendorServiceTests { private static VendorService NewService(Mock data) => new(data.Object, new CreateVendorValidation(), new UpdateVendorValidation()); [Fact] public async Task CreateVendor_PersistsCompleteProfile() { var data = new Mock(); Vendor? saved = null; data.Setup(x => x.AddAsync(It.IsAny())) .ReturnsAsync((Vendor vendor) => { saved = vendor; vendor.Id = 7; return vendor; }); var result = await NewService(data).CreateVendorAsync(new CreateVendorDTO { Name = "Gateway Plumbing", ContactName = "Jordan Lee", Email = "dispatch@gateway.example", Phone = "555-0100", CompanyPhone = "555-0199", Address = "10 Lake St", City = "Chicago", State = "IL", Zipcode = "60601", TradeSpecialties = "Plumbing, Backflow", GoogleMapsUrl = "https://maps.google.com/example", Notes = "After-hours dispatch available", IsActive = true }, "42"); saved.Should().NotBeNull(); saved!.ContactName.Should().Be("Jordan Lee"); saved.TradeSpecialties.Should().Be("Plumbing, Backflow"); saved.CompanyPhone.Should().Be("555-0199"); saved.GoogleMapsUrl.Should().Be("https://maps.google.com/example"); saved.Notes.Should().Be("After-hours dispatch available"); saved.City.Should().Be("Chicago"); result.IsActive.Should().BeTrue(); result.ContactName.Should().Be("Jordan Lee"); } [Fact] public async Task UpdateVendor_UpdatesProfileAndActiveState() { var existing = new Vendor { Id = 8, CompanyName = "Old", IsActive = true }; var data = new Mock(); data.Setup(x => x.GetByIdAsync(8)).ReturnsAsync(existing); data.Setup(x => x.UpdateAsync(existing)).Returns(Task.CompletedTask); var result = await NewService(data).UpdateVendorAsync(8, new UpdateVendorDTO { Name = "New", ContactName = "Casey", TradeSpecialties = "HVAC", IsActive = false }, "42"); existing.CompanyName.Should().Be("New"); existing.ContactName.Should().Be("Casey"); existing.TradeSpecialties.Should().Be("HVAC"); existing.IsActive.Should().BeFalse(); existing.LastModifierUserId.Should().Be(42); result.IsActive.Should().BeFalse(); } [Fact] public async Task DeleteVendor_DeactivatesWithoutDeletingHistory() { var existing = new Vendor { Id = 9, CompanyName = "Keep History", IsActive = true }; var data = new Mock(); data.Setup(x => x.GetByIdAsync(9)).ReturnsAsync(existing); data.Setup(x => x.UpdateAsync(existing)).Returns(Task.CompletedTask); await NewService(data).DeleteVendorAsync(9, "42"); existing.IsActive.Should().BeFalse(); existing.IsDeleted.Should().NotBeTrue(); existing.DeletionTime.Should().BeNull(); existing.LastModificationTime.Should().NotBeNull(); } [Fact] public async Task GetVendorsPaged_RequestsSelectedStatusAndMapsDirectoryFields() { var data = new Mock(); data.Setup(x => x.GetPagedAsync( 1, 12, "chicago", false, It.IsAny?>(), It.IsAny?>(), It.IsAny?>(), It.IsAny?>())) .ReturnsAsync((new[] { new Vendor { Id = 10, CompanyName = "Inactive Vendor", ContactName = "Taylor", City = "Chicago", State = "IL", TradeSpecialties = "Electrical", IsActive = false } }.AsEnumerable(), 1)); var result = await NewService(data).GetVendorsPagedAsync(1, 12, "chicago", false); result.TotalCount.Should().Be(1); result.Items.Single().Should().BeEquivalentTo(new { Id = 10, Name = "Inactive Vendor", ContactName = "Taylor", City = "Chicago", State = "IL", TradeSpecialties = "Electrical", IsActive = false }, options => options.ExcludingMissingMembers()); } [Fact] public async Task GetVendorById_CountsDistinctHistoricalWorkOrders() { var vendor = new Vendor { Id = 11, CompanyName = "History Vendor", Dispatches = new List { new() { WorkOrderId = 100, DispatchWorkOrders = new List { new() { WorkOrderId = 100 }, new() { WorkOrderId = 101 } } }, new() { WorkOrderId = 101 } } }; var data = new Mock(); data.Setup(x => x.GetByIdAsync(11)).ReturnsAsync(vendor); var result = await NewService(data).GetVendorByIdAsync(11); result.Should().NotBeNull(); result!.TotalJobs.Should().Be(2); } [Fact] public async Task GetVendorsPaged_ForwardsFacetFilters() { var companies = new[] { "Gateway Plumbing", "Roto-Rooter Dallas" }; var trades = new[] { "Backflow Preventers" }; var locations = new[] { "Dallas, TX" }; var jobBuckets = new[] { "100-149" }; var data = new Mock(); data.Setup(x => x.GetPagedAsync( 1, 25, "gateway", true, companies, trades, locations, jobBuckets)) .ReturnsAsync((Enumerable.Empty(), 0)); await NewService(data).GetVendorsPagedAsync( 1, 25, "gateway", true, companies, trades, locations, jobBuckets); data.VerifyAll(); } [Fact] public void CreateVendor_RequiresTechnicianNameAndHttpsMapUrl() { var validator = new CreateVendorValidation(); var result = validator.Validate(new CreateVendorDTO { Name = "Gateway Plumbing", ContactName = "", GoogleMapsUrl = "http://maps.google.com/gateway" }); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(error => error.PropertyName == nameof(CreateVendorDTO.ContactName)); result.Errors.Should().Contain(error => error.PropertyName == nameof(CreateVendorDTO.GoogleMapsUrl)); } }