mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 03:43:11 +00:00
Seed an organization-wide Area catalogue (East, Central, West, California) with stable ids, add a nullable AreaId to VendorCompany, allow only Admins to change it through the roster endpoints, expose areas facet metadata and an areas[n] company-directory filter with the __unassigned__ sentinel.
1114 lines
45 KiB
C#
1114 lines
45 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using FluentAssertions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Implementation;
|
|
using SeaHaven.DataServices.Models;
|
|
using System.Text.RegularExpressions;
|
|
using Xunit;
|
|
|
|
namespace Api.SeaHavenIndustries.Tests;
|
|
|
|
public class VendorDataServiceTests
|
|
{
|
|
private static ApplicationDbContext NewContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new ApplicationDbContext(options);
|
|
}
|
|
|
|
private static ApplicationDbContext NewSqlServerContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseSqlServer("Server=(local);Database=TranslationOnly;Trusted_Connection=True;TrustServerCertificate=True")
|
|
.Options;
|
|
return new ApplicationDbContext(options);
|
|
}
|
|
|
|
private static Vendor VendorWithJobs(
|
|
string company,
|
|
string city,
|
|
string state,
|
|
string trades,
|
|
int jobCount,
|
|
bool isActive = true)
|
|
{
|
|
return new Vendor
|
|
{
|
|
CompanyName = company,
|
|
ContactName = $"{company} Technician",
|
|
City = city,
|
|
State = state,
|
|
TradeSpecialties = trades,
|
|
IsActive = isActive,
|
|
IsDeleted = false,
|
|
Dispatches = Enumerable.Range(1, jobCount)
|
|
.Select(workOrderId => new Dispatch { WorkOrderId = workOrderId })
|
|
.ToList()
|
|
};
|
|
}
|
|
|
|
private static async Task SeedBoundaryVendors(ApplicationDbContext context)
|
|
{
|
|
context.Vendors.AddRange(
|
|
VendorWithJobs("Under Co", "Chicago", "IL", "Plumbing, HVAC", 49),
|
|
VendorWithJobs("Fifty Co", "Chicago", "IL", "Electrical", 50),
|
|
VendorWithJobs("Hundred Co", "Dallas", "TX", "Plumbing", 100),
|
|
VendorWithJobs("One Fifty Co", "Dallas", "TX", "Roofing", 150));
|
|
await context.SaveChangesAsync();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("under-50", "Under Co")]
|
|
[InlineData("50-99", "Fifty Co")]
|
|
[InlineData("100-149", "Hundred Co")]
|
|
[InlineData("150-plus", "One Fifty Co")]
|
|
public async Task GetPagedAsync_AppliesExactJobBucketBoundaries(
|
|
string bucket,
|
|
string expectedCompany)
|
|
{
|
|
await using var context = NewContext();
|
|
await SeedBoundaryVendors(context);
|
|
var service = new VendorDataService(context);
|
|
|
|
var (items, totalCount) = await service.GetPagedAsync(
|
|
1,
|
|
12,
|
|
isActive: true,
|
|
jobBuckets: new[] { bucket });
|
|
|
|
totalCount.Should().Be(1);
|
|
items.Single().CompanyName.Should().Be(expectedCompany);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPagedAsync_UsesOrWithinFacetAndAndAcrossFacets()
|
|
{
|
|
await using var context = NewContext();
|
|
await SeedBoundaryVendors(context);
|
|
var service = new VendorDataService(context);
|
|
|
|
var (items, totalCount) = await service.GetPagedAsync(
|
|
1,
|
|
12,
|
|
isActive: true,
|
|
companies: new[] { "Under Co", "Hundred Co" },
|
|
trades: new[] { "HVAC", "Plumbing" },
|
|
locations: new[] { "Chicago, IL" },
|
|
jobBuckets: new[] { "under-50", "100-149" });
|
|
|
|
totalCount.Should().Be(1);
|
|
items.Single().CompanyName.Should().Be("Under Co");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPagedAsync_StatusAndCountAreIndependentOfInactiveHistory()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Vendors.AddRange(
|
|
VendorWithJobs("Active Co", "Chicago", "IL", "Plumbing", 1),
|
|
VendorWithJobs("Inactive Co", "Chicago", "IL", "Plumbing", 1, false));
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
var active = await service.GetPagedAsync(1, 1, isActive: true);
|
|
var inactive = await service.GetPagedAsync(1, 1, isActive: false);
|
|
|
|
active.TotalCount.Should().Be(1);
|
|
inactive.TotalCount.Should().Be(1);
|
|
active.Items.Single().CompanyName.Should().Be("Active Co");
|
|
inactive.Items.Single().CompanyName.Should().Be("Inactive Co");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPagedAsync_LocationFacetMatchesCityStateAndFullLabels()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Vendors.AddRange(
|
|
new Vendor { CompanyName = "CityOnly", City = "Austin", State = null, TradeSpecialties = "Plumbing", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = "StateOnly", City = null, State = "TX", TradeSpecialties = "Plumbing", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = "FullLoc", City = "Austin", State = "TX", TradeSpecialties = "Plumbing", IsActive = true, IsDeleted = false });
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
var cityOnly = await service.GetPagedAsync(1, 12, isActive: true, locations: new[] { "Austin" });
|
|
cityOnly.TotalCount.Should().Be(1);
|
|
cityOnly.Items.Single().CompanyName.Should().Be("CityOnly");
|
|
|
|
var stateOnly = await service.GetPagedAsync(1, 12, isActive: true, locations: new[] { "TX" });
|
|
stateOnly.TotalCount.Should().Be(1);
|
|
stateOnly.Items.Single().CompanyName.Should().Be("StateOnly");
|
|
|
|
var full = await service.GetPagedAsync(1, 12, isActive: true, locations: new[] { "Austin, TX" });
|
|
full.TotalCount.Should().Be(1);
|
|
full.Items.Single().CompanyName.Should().Be("FullLoc");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPagedAsync_TradeFacetMatchesExactTokensNotSubstrings()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Vendors.AddRange(
|
|
new Vendor { CompanyName = "HvacOnly", City = "Chicago", State = "IL", TradeSpecialties = "HVAC", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = "HvacControls", City = "Chicago", State = "IL", TradeSpecialties = "HVAC Controls", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = "PlumbingCo", City = "Chicago", State = "IL", TradeSpecialties = "Plumbing", IsActive = true, IsDeleted = false });
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
var hvac = await service.GetPagedAsync(1, 12, isActive: true, trades: new[] { "HVAC" });
|
|
hvac.TotalCount.Should().Be(1);
|
|
hvac.Items.Single().CompanyName.Should().Be("HvacOnly");
|
|
|
|
var hvacControls = await service.GetPagedAsync(1, 12, isActive: true, trades: new[] { "HVAC Controls" });
|
|
hvacControls.TotalCount.Should().Be(1);
|
|
hvacControls.Items.Single().CompanyName.Should().Be("HvacControls");
|
|
|
|
var plumb = await service.GetPagedAsync(1, 12, isActive: true, trades: new[] { "Plumb" });
|
|
plumb.TotalCount.Should().Be(0);
|
|
plumb.Items.Should().BeEmpty();
|
|
|
|
var plumbing = await service.GetPagedAsync(1, 12, isActive: true, trades: new[] { "Plumbing" });
|
|
plumbing.TotalCount.Should().Be(1);
|
|
plumbing.Items.Single().CompanyName.Should().Be("PlumbingCo");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPagedAsync_TradeFacetMatchesTokenInCommaDelimitedList()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Vendors.AddRange(
|
|
new Vendor { CompanyName = "Multi", City = "Chicago", State = "IL", TradeSpecialties = "Plumbing, HVAC", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = "Middle", City = "Chicago", State = "IL", TradeSpecialties = "Electrical, Plumbing, Roofing", IsActive = true, IsDeleted = false });
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
var plumbing = await service.GetPagedAsync(1, 12, isActive: true, trades: new[] { "Plumbing" });
|
|
plumbing.TotalCount.Should().Be(2);
|
|
plumbing.Items.Select(v => v.CompanyName).Should().BeEquivalentTo("Multi", "Middle");
|
|
|
|
var roofing = await service.GetPagedAsync(1, 12, isActive: true, trades: new[] { "Roofing" });
|
|
roofing.TotalCount.Should().Be(1);
|
|
roofing.Items.Single().CompanyName.Should().Be("Middle");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPagedAsync_CountsDistinctDirectAndLinkedWorkOrders()
|
|
{
|
|
await using var context = NewContext();
|
|
var vendor = new Vendor
|
|
{
|
|
CompanyName = "History Co",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Dispatches = new List<Dispatch>
|
|
{
|
|
new()
|
|
{
|
|
WorkOrderId = 100,
|
|
DispatchWorkOrders = new List<DispatchWorkOrder>
|
|
{
|
|
new() { WorkOrderId = 100 },
|
|
new() { WorkOrderId = 101 }
|
|
}
|
|
},
|
|
new() { WorkOrderId = 101 }
|
|
}
|
|
};
|
|
context.Vendors.Add(vendor);
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var result = await service.GetPagedAsync(1, 12, isActive: true);
|
|
|
|
result.Items.Single().TotalJobs.Should().Be(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_ReturnsTrackedVendorWithoutLoadingDispatchGraph()
|
|
{
|
|
await using var context = NewContext();
|
|
var vendor = VendorWithJobs("Lean Co", "Chicago", "IL", "Plumbing", 2);
|
|
context.Vendors.Add(vendor);
|
|
await context.SaveChangesAsync();
|
|
context.ChangeTracker.Clear();
|
|
|
|
var service = new VendorDataService(context);
|
|
var result = await service.GetByIdAsync(vendor.Id);
|
|
|
|
result.Should().NotBeNull();
|
|
context.Entry(result!).State.Should().Be(EntityState.Unchanged);
|
|
context.Entry(result!).Collection(nameof(Vendor.Dispatches)).IsLoaded.Should().BeFalse();
|
|
result!.Dispatches.Should().BeNull();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("trade")]
|
|
[InlineData("location")]
|
|
[InlineData("address-location")]
|
|
[InlineData("job-bucket")]
|
|
public void BuildDirectoryQuery_TranslatesRepresentativeFiltersForSqlServer(string filter)
|
|
{
|
|
using var context = NewSqlServerContext();
|
|
var service = new VendorDataService(context);
|
|
|
|
var query = filter switch
|
|
{
|
|
"trade" => service.BuildDirectoryQuery(trades: new[] { "Plumbing" }),
|
|
"location" => service.BuildDirectoryQuery(locations: new[] { "Chicago, IL" }),
|
|
"address-location" => service.BuildDirectoryQuery(locations: new[] { "123 Main St" }),
|
|
_ => service.BuildDirectoryQuery(jobBuckets: new[] { "50-99" })
|
|
};
|
|
|
|
var sql = query.ToQueryString();
|
|
|
|
sql.Should().Contain("SELECT");
|
|
if (filter == "trade")
|
|
sql.Should().Contain("TradeSpecialties");
|
|
if (filter == "location")
|
|
sql.Should().Contain("City");
|
|
if (filter == "address-location")
|
|
sql.Should().Contain("Address");
|
|
if (filter == "job-bucket")
|
|
sql.Should().Contain("UNION");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildLinkedWorkOrdersQuery_TranslatesForSqlServerWithoutClientEvaluation()
|
|
{
|
|
using var context = NewSqlServerContext();
|
|
var service = new VendorDataService(context);
|
|
|
|
var sql = service.BuildLinkedWorkOrdersQuery(60).ToQueryString();
|
|
|
|
sql.Should().Contain("SELECT");
|
|
sql.Should().Contain("Dispatches");
|
|
sql.Should().Contain("DispatchWorkOrders");
|
|
sql.Should().Contain("workOrders");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetLinkedWorkOrdersAsync_DedupsToOneRecordPerWorkOrderAcrossDirectAndMultiLinks()
|
|
{
|
|
await using var context = NewContext();
|
|
var vendor = new Vendor { Id = 60, CompanyName = "Linked Co", IsActive = true };
|
|
context.Vendors.Add(vendor);
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 701,
|
|
WorkerOrderNumber = "WO-701",
|
|
WorkerOrderTitle = "Direct And Multi",
|
|
Status = "Scheduled",
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 702,
|
|
WorkerOrderNumber = "WO-702",
|
|
WorkerOrderTitle = "Multi Only",
|
|
Status = "Scheduled",
|
|
LifecycleStatus = LifecycleStatus.Scheduled
|
|
});
|
|
|
|
var directDispatch = new Dispatch { Id = 7001, VendorId = 60, WorkOrderId = 701, Vendor = vendor };
|
|
var multiDispatch = new Dispatch { Id = 7002, VendorId = 60, WorkOrderId = null, Vendor = vendor };
|
|
context.Dispatches.AddRange(directDispatch, multiDispatch);
|
|
context.DispatchWorkOrders.AddRange(
|
|
new DispatchWorkOrder { DispatchId = 7002, Dispatch = multiDispatch, WorkOrderId = 702 },
|
|
new DispatchWorkOrder { DispatchId = 7002, Dispatch = multiDispatch, WorkOrderId = 701 });
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var result = await service.GetLinkedWorkOrdersAsync(60);
|
|
|
|
result.Should().HaveCount(2);
|
|
result.Select(x => x.WorkOrderId).Should().BeEquivalentTo(new[] { 701, 702 });
|
|
result.Select(x => x.WorkOrderId).Should().OnlyHaveUniqueItems();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCompanyDirectoryPagedAsync_GroupsCompaniesAndKeepsLegacyRowsDistinct()
|
|
{
|
|
await using var context = NewContext();
|
|
var linkedCompany = new VendorCompany
|
|
{
|
|
Name = "Linked Co",
|
|
NormalizedName = "linked co",
|
|
Email = "dispatch@linked.example",
|
|
CompanyPhone = "555-0100",
|
|
Address = "100 Company Way",
|
|
City = "Chicago",
|
|
State = "IL"
|
|
};
|
|
var companyOnly = new VendorCompany { Name = "Company Only", NormalizedName = "company only" };
|
|
context.VendorCompanies.AddRange(linkedCompany, companyOnly);
|
|
context.Vendors.AddRange(
|
|
new Vendor
|
|
{
|
|
Company = linkedCompany,
|
|
CompanyName = "Stale Linked Name",
|
|
ContactName = "Older Tech",
|
|
Email = "older@linked.example",
|
|
Phone = "555-0101",
|
|
TradeSpecialties = "Plumbing",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Dispatches = new List<Dispatch> { new() { WorkOrderId = 101 } }
|
|
},
|
|
new Vendor
|
|
{
|
|
Company = linkedCompany,
|
|
CompanyName = "Another Stale Name",
|
|
ContactName = "Newer Tech",
|
|
Email = "newer@linked.example",
|
|
Phone = "555-0102",
|
|
TradeSpecialties = "HVAC, plumbing",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Dispatches = new List<Dispatch> { new() { WorkOrderId = 102 } }
|
|
},
|
|
new Vendor { CompanyName = " Legacy Co ", ContactName = "Legacy One", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = "legacy co", ContactName = "Legacy Two", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = " ", ContactName = "Unnamed One", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = null, ContactName = "Unnamed Two", IsActive = true, IsDeleted = false });
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var (items, totalCount) = await service.GetCompanyDirectoryPagedAsync(1, 50, isActive: true);
|
|
|
|
totalCount.Should().Be(5);
|
|
var linked = items.Single(item => item.CompanyId == linkedCompany.Id);
|
|
linked.CompanyName.Should().Be("Linked Co");
|
|
linked.Email.Should().Be("dispatch@linked.example");
|
|
linked.ContactName.Should().Be("Newer Tech");
|
|
linked.Technicians.Should().HaveCount(2);
|
|
linked.TotalJobs.Should().Be(2);
|
|
linked.TradeSpecialties.Should().Be("HVAC, plumbing");
|
|
|
|
var canonicalSearch = await service.GetCompanyDirectoryPagedAsync(
|
|
1, 50, search: "Linked Co", isActive: true);
|
|
canonicalSearch.Items.Should().ContainSingle(item => item.CompanyId == linkedCompany.Id);
|
|
|
|
var canonicalCompanyFilter = await service.GetCompanyDirectoryPagedAsync(
|
|
1, 50, isActive: true, companies: new[] { "Linked Co" });
|
|
canonicalCompanyFilter.Items.Should().ContainSingle(item => item.CompanyId == linkedCompany.Id);
|
|
|
|
var legacy = items.Single(item =>
|
|
item.Technicians.Any(tech => tech.ContactName == "Legacy One"));
|
|
legacy.Technicians.Select(tech => tech.ContactName)
|
|
.Should().BeEquivalentTo("Legacy One", "Legacy Two");
|
|
items.Count(item => item.Technicians.Count == 1 &&
|
|
item.Technicians.Single().ContactName?.StartsWith("Unnamed") == true).Should().Be(2);
|
|
items.Single(item => item.CompanyId == companyOnly.Id).Technicians.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCompanyDirectoryPagedAsync_FilterSelectsGroupAndSearchPromotesMatchingTechnician()
|
|
{
|
|
await using var context = NewContext();
|
|
var company = new VendorCompany { Name = "Search Co", NormalizedName = "search co" };
|
|
context.VendorCompanies.Add(company);
|
|
var older = new Vendor
|
|
{
|
|
Company = company,
|
|
CompanyName = company.Name,
|
|
ContactName = "Alice Plumber",
|
|
TradeSpecialties = "Plumbing",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Dispatches = new List<Dispatch> { new() { WorkOrderId = 201 } }
|
|
};
|
|
var newer = new Vendor
|
|
{
|
|
Company = company,
|
|
CompanyName = company.Name,
|
|
ContactName = "Zed HVAC",
|
|
TradeSpecialties = "HVAC",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Dispatches = new List<Dispatch> { new() { WorkOrderId = 202 } }
|
|
};
|
|
var inactive = new Vendor
|
|
{
|
|
Company = company,
|
|
CompanyName = company.Name,
|
|
ContactName = "Inactive Tech",
|
|
TradeSpecialties = "Roofing",
|
|
IsActive = false,
|
|
IsDeleted = false,
|
|
Dispatches = new List<Dispatch> { new() { WorkOrderId = 203 } }
|
|
};
|
|
context.Vendors.AddRange(older, newer, inactive);
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var search = await service.GetCompanyDirectoryPagedAsync(1, 10, search: "Alice", isActive: true);
|
|
var trade = await service.GetCompanyDirectoryPagedAsync(
|
|
1, 10, isActive: true, trades: new[] { "Plumbing" });
|
|
|
|
search.TotalCount.Should().Be(1);
|
|
search.Items.Single().Id.Should().Be(older.Id);
|
|
search.Items.Single().ContactName.Should().Be("Alice Plumber");
|
|
search.Items.Single().Technicians.Should().HaveCount(2);
|
|
search.Items.Single().TotalJobs.Should().Be(2);
|
|
|
|
trade.TotalCount.Should().Be(1);
|
|
trade.Items.Single().Id.Should().Be(newer.Id);
|
|
trade.Items.Single().Technicians.Should().HaveCount(2);
|
|
trade.Items.Single().Technicians.Should().NotContain(tech => tech.Id == inactive.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildCompanyDirectoryKeysQuery_TranslatesGroupingAndFacetFiltersToSqlServer()
|
|
{
|
|
using var context = NewSqlServerContext();
|
|
var service = new VendorDataService(context);
|
|
|
|
var sql = service.BuildCompanyDirectoryKeysQuery(
|
|
search: "alice",
|
|
trades: new[] { "Plumbing" },
|
|
locations: new[] { "Chicago, IL" },
|
|
jobBuckets: new[] { "under-50" },
|
|
areaFilter: new VendorAreaFilter { AreaIds = new[] { 1, 2 }, IncludeUnassigned = true }).ToQueryString();
|
|
|
|
sql.Should().Contain("GROUP BY");
|
|
sql.Should().Contain("LEFT JOIN");
|
|
sql.Should().Contain("TradeSpecialties");
|
|
sql.Should().Contain("TotalJobs");
|
|
sql.Should().Contain("AreaId");
|
|
Regex.IsMatch(sql, @"SUM\s*\(\s*\(\s*SELECT", RegexOptions.IgnoreCase)
|
|
.Should().BeFalse("company totals must sum pre-aggregated scalar job counts");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_IncludesCompanyOnlyRowsAndPreservesTechnicianRows()
|
|
{
|
|
await using var context = NewContext();
|
|
var soloCompany = new VendorCompany { Name = "Solo Co", NormalizedName = "solo co", Notes = "company notes" };
|
|
var techCompany = new VendorCompany { Name = "Tech Co", NormalizedName = "tech co" };
|
|
context.VendorCompanies.AddRange(soloCompany, techCompany);
|
|
context.Vendors.AddRange(
|
|
new Vendor
|
|
{
|
|
CompanyName = "Tech Co",
|
|
ContactName = "Pat",
|
|
City = "Austin",
|
|
State = "TX",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = techCompany
|
|
},
|
|
new Vendor
|
|
{
|
|
CompanyName = "Other",
|
|
ContactName = "No Company Tech",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
CompanyId = null
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var (items, totalCount) = await service.GetDirectoryPagedAsync(1, 50, isActive: true);
|
|
|
|
totalCount.Should().Be(3);
|
|
var companyOnly = items.Single(i => i.CompanyName == "Solo Co");
|
|
companyOnly.VendorId.Should().BeNull();
|
|
companyOnly.CompanyId.Should().Be(soloCompany.Id);
|
|
companyOnly.Notes.Should().Be("company notes");
|
|
|
|
items.Should().Contain(i => i.ContactName == "Pat" && i.VendorId != null && i.CompanyId == techCompany.Id);
|
|
items.Should().Contain(i => i.ContactName == "No Company Tech" && i.VendorId != null);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_UsesCanonicalCompanyLocationAndEmailFallback()
|
|
{
|
|
await using var context = NewContext();
|
|
var company = new VendorCompany
|
|
{
|
|
Name = "Canonical Co",
|
|
NormalizedName = "canonical co",
|
|
Email = "dispatch@canonical.example",
|
|
City = "Chicago",
|
|
State = "IL"
|
|
};
|
|
context.VendorCompanies.Add(company);
|
|
context.Vendors.AddRange(
|
|
new Vendor
|
|
{
|
|
CompanyName = "Canonical Co",
|
|
ContactName = "Company Fallback",
|
|
Email = " ",
|
|
City = "Stale City",
|
|
State = "TX",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = company
|
|
},
|
|
new Vendor
|
|
{
|
|
CompanyName = "Canonical Co",
|
|
ContactName = "Technician Email",
|
|
Email = "technician@canonical.example",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = company
|
|
},
|
|
new Vendor
|
|
{
|
|
CompanyName = "Legacy Co",
|
|
ContactName = "Unlinked Technician",
|
|
Email = "legacy@example.com",
|
|
City = "Austin",
|
|
State = "TX",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
CompanyId = null
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var (items, totalCount) = await service.GetDirectoryPagedAsync(1, 50, isActive: true);
|
|
|
|
totalCount.Should().Be(3);
|
|
var companyFallback = items.Single(i => i.ContactName == "Company Fallback");
|
|
companyFallback.Email.Should().Be("dispatch@canonical.example");
|
|
companyFallback.City.Should().Be("Chicago");
|
|
companyFallback.State.Should().Be("IL");
|
|
|
|
var technicianEmail = items.Single(i => i.ContactName == "Technician Email");
|
|
technicianEmail.Email.Should().Be("technician@canonical.example");
|
|
technicianEmail.City.Should().Be("Chicago");
|
|
technicianEmail.State.Should().Be("IL");
|
|
|
|
var legacy = items.Single(i => i.ContactName == "Unlinked Technician");
|
|
legacy.Email.Should().Be("legacy@example.com");
|
|
legacy.City.Should().Be("Austin");
|
|
legacy.State.Should().Be("TX");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_SearchAndLocationFilterUseCanonicalCompanyLocation()
|
|
{
|
|
await using var context = NewContext();
|
|
var company = new VendorCompany
|
|
{
|
|
Name = "Searchable Co",
|
|
NormalizedName = "searchable co",
|
|
City = "Chicago",
|
|
State = null
|
|
};
|
|
context.VendorCompanies.Add(company);
|
|
context.Vendors.Add(new Vendor
|
|
{
|
|
CompanyName = "Searchable Co",
|
|
ContactName = "Pat",
|
|
City = "Stale City",
|
|
State = "TX",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = company
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var search = await service.GetDirectoryPagedAsync(1, 50, search: "chicago", isActive: true);
|
|
var location = await service.GetDirectoryPagedAsync(
|
|
1,
|
|
50,
|
|
isActive: true,
|
|
locations: new[] { "Chicago" });
|
|
|
|
var searchRow = search.Items.Should().ContainSingle(i => i.ContactName == "Pat").Subject;
|
|
searchRow.City.Should().Be("Chicago");
|
|
searchRow.State.Should().BeNull();
|
|
|
|
var locationRow = location.Items.Should().ContainSingle(i => i.ContactName == "Pat").Subject;
|
|
locationRow.City.Should().Be("Chicago");
|
|
locationRow.State.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_UsesCanonicalCompanyAddressAndCompanyPhoneWithVendorFallback()
|
|
{
|
|
await using var context = NewContext();
|
|
var fullCompany = new VendorCompany
|
|
{
|
|
Name = "Full Co",
|
|
NormalizedName = "full co",
|
|
Address = "100 Company Way",
|
|
CompanyPhone = "(555) 111-2222"
|
|
};
|
|
var blankCompany = new VendorCompany
|
|
{
|
|
Name = "Blank Co",
|
|
NormalizedName = "blank co",
|
|
Address = " ",
|
|
CompanyPhone = ""
|
|
};
|
|
context.VendorCompanies.AddRange(fullCompany, blankCompany);
|
|
context.Vendors.AddRange(
|
|
new Vendor
|
|
{
|
|
CompanyName = "Full Co",
|
|
ContactName = "Full Tech",
|
|
Phone = "(555) 222-3333",
|
|
CompanyPhone = "(555) 999-0000",
|
|
Address = "9 Old Street",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = fullCompany
|
|
},
|
|
new Vendor
|
|
{
|
|
CompanyName = "Blank Co",
|
|
ContactName = "Blank Tech",
|
|
CompanyPhone = "(555) 333-4444",
|
|
Address = "200 Vendor Way",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = blankCompany
|
|
},
|
|
new Vendor
|
|
{
|
|
CompanyName = "Legacy Co",
|
|
ContactName = "Unlinked Tech",
|
|
CompanyPhone = "(555) 777-8888",
|
|
Address = "300 Legacy Way",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
CompanyId = null
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
var (items, totalCount) = await service.GetDirectoryPagedAsync(1, 50, isActive: true);
|
|
|
|
totalCount.Should().Be(3);
|
|
var fullTech = items.Single(i => i.ContactName == "Full Tech");
|
|
fullTech.Address.Should().Be("100 Company Way");
|
|
fullTech.CompanyPhone.Should().Be("(555) 111-2222");
|
|
fullTech.Phone.Should().Be("(555) 222-3333");
|
|
|
|
var blankTech = items.Single(i => i.ContactName == "Blank Tech");
|
|
blankTech.Address.Should().Be("200 Vendor Way");
|
|
blankTech.CompanyPhone.Should().Be("(555) 333-4444");
|
|
|
|
var legacy = items.Single(i => i.ContactName == "Unlinked Tech");
|
|
legacy.Address.Should().Be("300 Legacy Way");
|
|
legacy.CompanyPhone.Should().Be("(555) 777-8888");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_SearchIncludesProjectedAddress()
|
|
{
|
|
await using var context = NewContext();
|
|
var company = new VendorCompany
|
|
{
|
|
Name = "Addr Co",
|
|
NormalizedName = "addr co",
|
|
Address = "123 Main St"
|
|
};
|
|
context.VendorCompanies.Add(company);
|
|
context.Vendors.Add(new Vendor
|
|
{
|
|
CompanyName = "Addr Co",
|
|
ContactName = "Pat",
|
|
Address = "9 Old Street",
|
|
City = "Oldtown",
|
|
State = "OT",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = company
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
|
|
var search = await service.GetDirectoryPagedAsync(1, 50, search: "123 main", isActive: true);
|
|
search.TotalCount.Should().Be(1);
|
|
search.Items.Single().ContactName.Should().Be("Pat");
|
|
|
|
var stale = await service.GetDirectoryPagedAsync(1, 50, search: "9 old street", isActive: true);
|
|
stale.TotalCount.Should().Be(0);
|
|
|
|
var staleCity = await service.GetDirectoryPagedAsync(1, 50, search: "oldtown", isActive: true);
|
|
staleCity.TotalCount.Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_LocationFilterFallsBackToExactAddressWhenCityStateMissing()
|
|
{
|
|
await using var context = NewContext();
|
|
var addressOnlyCompany = new VendorCompany
|
|
{
|
|
Name = "Addr Only Co",
|
|
NormalizedName = "addr only co",
|
|
Address = " 123 Main St "
|
|
};
|
|
var soloCompany = new VendorCompany
|
|
{
|
|
Name = "Solo Co",
|
|
NormalizedName = "solo co",
|
|
Address = "500 Solo Way"
|
|
};
|
|
context.VendorCompanies.AddRange(addressOnlyCompany, soloCompany);
|
|
context.Vendors.Add(new Vendor
|
|
{
|
|
CompanyName = "Addr Only Co",
|
|
ContactName = "Pat",
|
|
City = "Oldtown",
|
|
State = "OT",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = addressOnlyCompany
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
|
|
var technicianRow = await service.GetDirectoryPagedAsync(
|
|
1, 50, isActive: true, locations: new[] { "123 Main St" });
|
|
technicianRow.TotalCount.Should().Be(1);
|
|
technicianRow.Items.Single().ContactName.Should().Be("Pat");
|
|
|
|
var companyOnlyRow = await service.GetDirectoryPagedAsync(
|
|
1, 50, isActive: true, locations: new[] { "500 Solo Way" });
|
|
companyOnlyRow.TotalCount.Should().Be(1);
|
|
companyOnlyRow.Items.Single().CompanyName.Should().Be("Solo Co");
|
|
|
|
var partial = await service.GetDirectoryPagedAsync(
|
|
1, 50, isActive: true, locations: new[] { "123 Main" });
|
|
partial.TotalCount.Should().Be(0);
|
|
|
|
var staleLocation = await service.GetDirectoryPagedAsync(
|
|
1, 50, isActive: true, locations: new[] { "Oldtown, OT" });
|
|
staleLocation.TotalCount.Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_LocationLabelPrefersCityStateOverAddress()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Vendors.AddRange(
|
|
new Vendor
|
|
{
|
|
CompanyName = "City Co",
|
|
ContactName = "City Tech",
|
|
City = "Austin",
|
|
Address = "999 Address Way",
|
|
IsActive = true,
|
|
IsDeleted = false
|
|
},
|
|
new Vendor
|
|
{
|
|
CompanyName = "State Co",
|
|
ContactName = "State Tech",
|
|
State = "TX",
|
|
Address = "888 Address Way",
|
|
IsActive = true,
|
|
IsDeleted = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
|
|
var byCity = await service.GetDirectoryPagedAsync(1, 50, isActive: true, locations: new[] { "Austin" });
|
|
byCity.TotalCount.Should().Be(1);
|
|
byCity.Items.Single().CompanyName.Should().Be("City Co");
|
|
|
|
var byState = await service.GetDirectoryPagedAsync(1, 50, isActive: true, locations: new[] { "TX" });
|
|
byState.TotalCount.Should().Be(1);
|
|
byState.Items.Single().CompanyName.Should().Be("State Co");
|
|
|
|
var bySuppressedAddress = await service.GetDirectoryPagedAsync(
|
|
1, 50, isActive: true, locations: new[] { "999 Address Way" });
|
|
bySuppressedAddress.TotalCount.Should().Be(0);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("trade")]
|
|
[InlineData("location")]
|
|
[InlineData("address-location")]
|
|
[InlineData("job-bucket")]
|
|
public void BuildDirectoryUnionQuery_TranslatesToSqlServerUnion(string filter)
|
|
{
|
|
using var context = NewSqlServerContext();
|
|
var service = new VendorDataService(context);
|
|
|
|
var query = filter switch
|
|
{
|
|
"trade" => service.BuildDirectoryUnionQuery(trades: new[] { "Plumbing" }),
|
|
"location" => service.BuildDirectoryUnionQuery(locations: new[] { "Chicago, IL" }),
|
|
"address-location" => service.BuildDirectoryUnionQuery(locations: new[] { "123 Main St" }),
|
|
_ => service.BuildDirectoryUnionQuery(jobBuckets: new[] { "under-50" })
|
|
};
|
|
|
|
var sql = query.ToQueryString();
|
|
|
|
sql.Should().Contain("SELECT");
|
|
sql.Should().Contain("UNION");
|
|
if (filter == "trade")
|
|
sql.Should().Contain("TradeSpecialties");
|
|
if (filter == "location")
|
|
sql.Should().Contain("City");
|
|
if (filter == "address-location")
|
|
sql.Should().Contain("Address");
|
|
if (filter == "job-bucket")
|
|
sql.Should().Contain("TotalJobs");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildDirectoryUnionQuery_TranslatesAddressSearchForSqlServer()
|
|
{
|
|
using var context = NewSqlServerContext();
|
|
var service = new VendorDataService(context);
|
|
|
|
var sql = service.BuildDirectoryUnionQuery(search: "123 main").ToQueryString();
|
|
|
|
sql.Should().Contain("SELECT");
|
|
sql.Should().Contain("UNION");
|
|
sql.Should().Contain("Address");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCompaniesForFacetsAsync_IncludesZeroTechnicianCompanyForActiveAndNullStatus()
|
|
{
|
|
await using var context = NewContext();
|
|
var rosterService = new VendorCompanyRosterDataService(context);
|
|
await rosterService.CreateRosterAsync(new VendorCompanyRosterWriteModel
|
|
{
|
|
Name = "Solo Co",
|
|
Email = "solo@example.com",
|
|
ActorUserId = "42",
|
|
Technicians = new List<RosterTechnicianWriteModel>()
|
|
}, CancellationToken.None);
|
|
|
|
var techCompany = new VendorCompany { Name = "Tech Co", NormalizedName = "tech co" };
|
|
context.VendorCompanies.Add(techCompany);
|
|
context.Vendors.Add(new Vendor
|
|
{
|
|
CompanyName = "Tech Co",
|
|
ContactName = "Pat",
|
|
IsActive = true,
|
|
IsDeleted = false,
|
|
Company = techCompany
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
|
|
var active = await service.GetCompaniesForFacetsAsync(true, CancellationToken.None);
|
|
active.Select(c => c.Name).Should().BeEquivalentTo(new[] { "Solo Co", "Tech Co" });
|
|
|
|
var noStatus = await service.GetCompaniesForFacetsAsync(null, CancellationToken.None);
|
|
noStatus.Select(c => c.Name).Should().BeEquivalentTo(new[] { "Solo Co", "Tech Co" });
|
|
|
|
var inactive = await service.GetCompaniesForFacetsAsync(false, CancellationToken.None);
|
|
inactive.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCompaniesForFacetsAsync_InactiveStatusReturnsOnlyCompaniesWithInactiveTechnicians()
|
|
{
|
|
await using var context = NewContext();
|
|
var activeOnlyCo = new VendorCompany { Name = "Active Only Co", NormalizedName = "active only co" };
|
|
var inactiveOnlyCo = new VendorCompany { Name = "Inactive Only Co", NormalizedName = "inactive only co" };
|
|
var mixedCo = new VendorCompany { Name = "Mixed Co", NormalizedName = "mixed co" };
|
|
context.VendorCompanies.AddRange(activeOnlyCo, inactiveOnlyCo, mixedCo);
|
|
context.Vendors.AddRange(
|
|
new Vendor { CompanyName = "Active Only Co", IsActive = true, IsDeleted = false, Company = activeOnlyCo },
|
|
new Vendor { CompanyName = "Inactive Only Co", IsActive = false, IsDeleted = false, Company = inactiveOnlyCo },
|
|
new Vendor { CompanyName = "Mixed Co", IsActive = true, IsDeleted = false, Company = mixedCo },
|
|
new Vendor { CompanyName = "Mixed Co", IsActive = false, IsDeleted = false, Company = mixedCo });
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
|
|
var inactive = await service.GetCompaniesForFacetsAsync(false, CancellationToken.None);
|
|
inactive.Select(c => c.Name).Should().BeEquivalentTo(new[] { "Inactive Only Co", "Mixed Co" });
|
|
|
|
var active = await service.GetCompaniesForFacetsAsync(true, CancellationToken.None);
|
|
active.Select(c => c.Name).Should().BeEquivalentTo(new[] { "Active Only Co", "Mixed Co" });
|
|
|
|
var noStatus = await service.GetCompaniesForFacetsAsync(null, CancellationToken.None);
|
|
noStatus.Select(c => c.Name).Should().BeEquivalentTo(new[] { "Active Only Co", "Inactive Only Co", "Mixed Co" });
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCompaniesForFacetsAsync_TreatsCompanyWithOnlyDeletedTechniciansAsZeroTechnician()
|
|
{
|
|
await using var context = NewContext();
|
|
var company = new VendorCompany { Name = "Ghost Co", NormalizedName = "ghost co" };
|
|
context.VendorCompanies.Add(company);
|
|
context.Vendors.Add(new Vendor
|
|
{
|
|
CompanyName = "Ghost Co",
|
|
IsActive = true,
|
|
IsDeleted = true,
|
|
Company = company
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new VendorDataService(context);
|
|
|
|
var active = await service.GetCompaniesForFacetsAsync(true, CancellationToken.None);
|
|
active.Single().Name.Should().Be("Ghost Co");
|
|
|
|
var inactive = await service.GetCompaniesForFacetsAsync(false, CancellationToken.None);
|
|
inactive.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetActiveTradesAsync_ReturnsEmptyOnEmptyDatabase()
|
|
{
|
|
await using var context = NewContext();
|
|
var service = new VendorDataService(context);
|
|
|
|
var trades = await service.GetActiveTradesAsync(CancellationToken.None);
|
|
|
|
trades.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetActiveTradesAsync_FiltersInactiveAndOrdersBySortOrderThenName()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Trades.AddRange(
|
|
new Trade { Name = "Janitorial", NormalizedName = "janitorial", SortOrder = 7 },
|
|
new Trade { Name = "Plumbing", NormalizedName = "plumbing", SortOrder = 1 },
|
|
new Trade { Name = "Electrical", NormalizedName = "electrical", SortOrder = 2 },
|
|
new Trade { Name = "Retired Trade", NormalizedName = "retired trade", SortOrder = 0, IsActive = false });
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
var trades = await service.GetActiveTradesAsync(CancellationToken.None);
|
|
|
|
trades.Select(t => t.Name).Should().Equal("Plumbing", "Electrical", "Janitorial");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPagedAsync_TradeFilterStillMatchesLegacyFreeTextValues()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Vendors.AddRange(
|
|
VendorWithJobs("Legacy Co", "Chicago", "IL", "Backflow Testing, Plumbing", 3),
|
|
VendorWithJobs("Canonical Co", "Chicago", "IL", "Plumbing", 5));
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
// SH-249: stored TradeSpecialties stay free text; filtering by a legacy
|
|
// value that is not in the canonical catalog must keep matching.
|
|
var (items, totalCount) = await service.GetPagedAsync(
|
|
1,
|
|
12,
|
|
isActive: true,
|
|
trades: new[] { "Backflow Testing" });
|
|
|
|
totalCount.Should().Be(1);
|
|
items.Single().CompanyName.Should().Be("Legacy Co");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDirectoryPagedAsync_TradeFilterStillMatchesLegacyFreeTextValues()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Vendors.AddRange(
|
|
VendorWithJobs("Legacy Co", "Chicago", "IL", "Backflow Testing, Plumbing", 3),
|
|
VendorWithJobs("Canonical Co", "Chicago", "IL", "Plumbing", 5));
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
var (items, totalCount) = await service.GetDirectoryPagedAsync(
|
|
1,
|
|
12,
|
|
isActive: true,
|
|
trades: new[] { "Backflow Testing" });
|
|
|
|
totalCount.Should().Be(1);
|
|
items.Single().CompanyName.Should().Be("Legacy Co");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
[InlineData(null)]
|
|
public void BuildCompaniesForFacetsQuery_TranslatesToSqlServerWithoutClientEvaluation(bool? isActive)
|
|
{
|
|
using var context = NewSqlServerContext();
|
|
var service = new VendorDataService(context);
|
|
|
|
var sql = service.BuildCompaniesForFacetsQuery(isActive).ToQueryString();
|
|
|
|
sql.Should().Contain("SELECT");
|
|
sql.Should().Contain("VendorCompanies");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetCompanyDirectoryPagedAsync_AreaFilterOrsWithinFacetAndUnassignedIncludesLegacyRows()
|
|
{
|
|
await using var context = NewContext();
|
|
var east = new Area { Id = 1, Name = "East", NormalizedName = "east" };
|
|
var west = new Area { Id = 3, Name = "West", NormalizedName = "west" };
|
|
context.Areas.AddRange(east, west);
|
|
var eastCo = new VendorCompany { Name = "East Co", NormalizedName = "east co", Area = east };
|
|
var westCo = new VendorCompany { Name = "West Co", NormalizedName = "west co", Area = west };
|
|
var unassignedCo = new VendorCompany { Name = "Unassigned Co", NormalizedName = "unassigned co" };
|
|
var eastOnly = new VendorCompany { Name = "East Only", NormalizedName = "east only", Area = east };
|
|
context.VendorCompanies.AddRange(eastCo, westCo, unassignedCo, eastOnly);
|
|
context.Vendors.AddRange(
|
|
new Vendor { Company = eastCo, CompanyName = "East Co", ContactName = "E", City = "Boston", State = "MA", IsActive = true, IsDeleted = false },
|
|
new Vendor { Company = westCo, CompanyName = "West Co", ContactName = "W", IsActive = true, IsDeleted = false },
|
|
new Vendor { Company = unassignedCo, CompanyName = "Unassigned Co", ContactName = "U", IsActive = true, IsDeleted = false },
|
|
new Vendor { CompanyName = "Legacy Co", ContactName = "L", IsActive = true, IsDeleted = false });
|
|
await context.SaveChangesAsync();
|
|
var service = new VendorDataService(context);
|
|
|
|
var eastResult = await service.GetCompanyDirectoryPagedAsync(
|
|
1, 50, isActive: true, areaFilter: new VendorAreaFilter { AreaIds = new[] { east.Id } });
|
|
eastResult.Items.Select(item => item.CompanyName)
|
|
.Should().BeEquivalentTo("East Co", "East Only");
|
|
eastResult.TotalCount.Should().Be(2);
|
|
var eastRow = eastResult.Items.Single(item => item.CompanyName == "East Co");
|
|
eastRow.AreaId.Should().Be(east.Id);
|
|
eastRow.AreaName.Should().Be("East");
|
|
eastResult.Items.Single(item => item.CompanyName == "East Only").AreaName.Should().Be("East");
|
|
|
|
var eastOrUnassigned = await service.GetCompanyDirectoryPagedAsync(
|
|
1, 50, isActive: true,
|
|
areaFilter: new VendorAreaFilter { AreaIds = new[] { east.Id }, IncludeUnassigned = true });
|
|
eastOrUnassigned.Items.Select(item => item.CompanyName)
|
|
.Should().BeEquivalentTo("East Co", "East Only", "Unassigned Co", "Legacy Co");
|
|
eastOrUnassigned.Items.Single(item => item.CompanyName == "Legacy Co").AreaId.Should().BeNull();
|
|
|
|
var eastAndBoston = await service.GetCompanyDirectoryPagedAsync(
|
|
1, 50, isActive: true, locations: new[] { "Boston, MA" },
|
|
areaFilter: new VendorAreaFilter { AreaIds = new[] { east.Id } });
|
|
eastAndBoston.Items.Should().ContainSingle(item => item.CompanyName == "East Co");
|
|
|
|
var matchesNobody = await service.GetCompanyDirectoryPagedAsync(
|
|
1, 50, isActive: true, areaFilter: new VendorAreaFilter());
|
|
matchesNobody.TotalCount.Should().Be(0);
|
|
matchesNobody.Items.Should().BeEmpty();
|
|
|
|
var unfiltered = await service.GetCompanyDirectoryPagedAsync(1, 50, isActive: true);
|
|
unfiltered.TotalCount.Should().Be(5);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetActiveAreasAsync_ExcludesArchivedAreas()
|
|
{
|
|
await using var context = NewContext();
|
|
context.Areas.AddRange(
|
|
new Area { Id = 1, Name = "East", NormalizedName = "east" },
|
|
new Area { Id = 4, Name = "California", NormalizedName = "california", IsActive = false });
|
|
await context.SaveChangesAsync();
|
|
|
|
var areas = await new VendorDataService(context).GetActiveAreasAsync(CancellationToken.None);
|
|
|
|
areas.Select(area => area.Name).Should().Equal("East");
|
|
}
|
|
}
|