using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Helpers; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.DTOs; using SeaHaven.Services.Helpers; using SeaHaven.Services.Implementation; namespace SeaHavenIndustries.Tests; public class WorkOrderTechnicianAssignmentRelationalTests { [Fact] public async Task ClearingTechnicianOnOneWorkOrder_PreservesSharedDispatchAndSiblingSelectionAfterReload() { await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; byte[] workOrderVersion = { 1, 0, 0, 0, 0, 0, 0, 1 }; byte[] dispatchVersion = { 1, 0, 0, 0, 0, 0, 0, 2 }; await using (var seed = new SqliteBoardTestDbContext(options)) { await seed.Database.EnsureCreatedAsync(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(seed); seed.Users.Add(new ApplicationUser { Id = "actor-1", UserName = "actor-1", NormalizedUserName = "ACTOR-1", Email = "actor@test.local", NormalizedEmail = "ACTOR@TEST.LOCAL" }); seed.Vendors.Add(new Vendor { Id = 5, CompanyName = "Acme HVAC", ContactName = "Technician One", CompanyPhone = "312-555-0100" }); await seed.SaveChangesAsync(); var dispatch = new Dispatch { Id = 10, VendorId = 5, Status = "Sent", TechPhone = "312-555-0199", RowVersion = dispatchVersion }; seed.Dispatches.Add(dispatch); seed.workOrders.AddRange( new WorkOrder { Id = 1, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, ScheduledDate = new DateTime(2026, 9, 30), PrimaryDispatchId = 10, TechnicianAssigned = true, RowVersion = workOrderVersion }, new WorkOrder { Id = 2, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, ScheduledDate = new DateTime(2026, 9, 30), PrimaryDispatchId = 10, RowVersion = new byte[] { 2, 0, 0, 0, 0, 0, 0, 1 } }); await seed.SaveChangesAsync(); dispatch.WorkOrderId = 1; seed.DispatchWorkOrders.Add(new DispatchWorkOrder { Id = 20, DispatchId = 10, WorkOrderId = 2 }); await seed.SaveChangesAsync(); } await using (var context = new SqliteBoardTestDbContext(options)) { var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var service = WorkOrderAccountTestHelpers.BoardUpdateService(context, audit); var updated = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.VendorId, Value = "5", TechnicianAssigned = false, WorkOrderVersion = Convert.ToBase64String(workOrderVersion), DispatchVersion = Convert.ToBase64String(dispatchVersion), PrimaryDispatchId = 10 }, "actor-1"); Assert.False(updated.TechnicianAssigned); Assert.Equal(10, updated.PrimaryDispatchId); } await using var verify = new SqliteBoardTestDbContext(options); var data = new WorkOrderBoardDataService(verify); var workOrderA = Assert.IsType( await data.GetBoardRowByIdAsync(1)); var workOrderB = Assert.IsType( await data.GetBoardRowByIdAsync(2)); Assert.False(workOrderA.TechnicianAssigned); Assert.Null(workOrderA.TechName); Assert.Null(workOrderA.TechPhone); Assert.Equal("Technician One", workOrderB.TechName); Assert.Equal("312-555-0199", workOrderB.TechPhone); Assert.Equal(10, workOrderA.PrimaryDispatchId); Assert.Equal(10, workOrderB.PrimaryDispatchId); Assert.Equal(5, workOrderA.VendorId); Assert.Equal(5, workOrderB.VendorId); } [Fact] public async Task TechnicianNameSearch_MatchesLegacyAndAssignedRows_ButNotExplicitlyClearedRow() { await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using (var seed = new SqliteBoardTestDbContext(options)) { await seed.Database.EnsureCreatedAsync(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(seed); seed.Vendors.Add(new Vendor { Id = 5, CompanyName = "Acme HVAC", ContactName = "Technician One", CompanyPhone = "312-555-0100" }); seed.Dispatches.AddRange( new Dispatch { Id = 10, VendorId = 5, Status = "Sent", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 } }, new Dispatch { Id = 11, VendorId = 5, Status = "Sent", RowVersion = new byte[] { 2, 0, 0, 0, 0, 0, 0, 2 } }, new Dispatch { Id = 12, VendorId = 5, Status = "Sent", RowVersion = new byte[] { 3, 0, 0, 0, 0, 0, 0, 2 } }); seed.workOrders.AddRange( new WorkOrder { Id = 1, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 10, TechnicianAssigned = null, RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }, new WorkOrder { Id = 2, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 11, TechnicianAssigned = true, RowVersion = new byte[] { 2, 0, 0, 0, 0, 0, 0, 1 } }, new WorkOrder { Id = 3, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 12, TechnicianAssigned = false, RowVersion = new byte[] { 3, 0, 0, 0, 0, 0, 0, 1 } }); await seed.SaveChangesAsync(); } await using var context = new SqliteBoardTestDbContext(options); var technicianMatches = await WorkOrderBoardSearchFilter .Apply(context.workOrders.AsQueryable(), "Technician One") .Select(w => w.Id) .OrderBy(id => id) .ToListAsync(); Assert.Equal(new[] { 1, 2 }, technicianMatches); var companyMatches = await WorkOrderBoardSearchFilter .Apply(context.workOrders.AsQueryable(), "Acme HVAC") .Select(w => w.Id) .OrderBy(id => id) .ToListAsync(); Assert.Equal(new[] { 1, 2, 3 }, companyMatches); } private sealed class SqliteBoardTestDbContext : ApplicationDbContext { public SqliteBoardTestDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); foreach (var index in builder.Model.GetEntityTypes().SelectMany(entity => entity.GetIndexes())) { if (index.GetFilter() != null) index.SetFilter(null); } foreach (var entityType in new[] { typeof(WorkOrder), typeof(Dispatch) }) { var property = builder.Entity(entityType).Property("RowVersion").Metadata; property.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never; property.IsConcurrencyToken = false; } } } }