shoc-backend/Api.SeaHavenIndustries.Tests/VendorServiceTests.cs
Alexandre Brandizzi 1162c68596
feat(vendors): add directory filters and details API (#25)
* feat(vendors): add directory filters and details API

* fix(vendors): preserve omitted status

* fix(vendors): align facet filtering

* fix(vendors): address directory review findings
2026-07-23 15:55:47 +00:00

213 lines
7.2 KiB
C#

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<IVendorDataService> data) =>
new(data.Object, new CreateVendorValidation(), new UpdateVendorValidation());
[Fact]
public async Task CreateVendor_PersistsCompleteProfile()
{
var data = new Mock<IVendorDataService>();
Vendor? saved = null;
data.Setup(x => x.AddAsync(It.IsAny<Vendor>()))
.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<IVendorDataService>();
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<IVendorDataService>();
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<IVendorDataService>();
data.Setup(x => x.GetPagedAsync(
1,
12,
"chicago",
false,
It.IsAny<IReadOnlyCollection<string>?>(),
It.IsAny<IReadOnlyCollection<string>?>(),
It.IsAny<IReadOnlyCollection<string>?>(),
It.IsAny<IReadOnlyCollection<string>?>()))
.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<Dispatch>
{
new()
{
WorkOrderId = 100,
DispatchWorkOrders = new List<DispatchWorkOrder>
{
new() { WorkOrderId = 100 },
new() { WorkOrderId = 101 }
}
},
new() { WorkOrderId = 101 }
}
};
var data = new Mock<IVendorDataService>();
data.Setup(x => x.GetByIdWithDetailsAsync(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<IVendorDataService>();
data.Setup(x => x.GetPagedAsync(
1,
25,
"gateway",
true,
companies,
trades,
locations,
jobBuckets))
.ReturnsAsync((Enumerable.Empty<Vendor>(), 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));
}
}