using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.DTOs; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Implementation; namespace SeaHavenIndustries.Tests; public class WorkOrderBoardUpdateServiceTests { private static (ApplicationDbContext Context, WorkOrderBoardUpdateService Service) CreateSut() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var context = new ApplicationDbContext(options); var boardData = new WorkOrderBoardDataService(context); var fieldLocks = new WorkOrderFieldLockService(context); var audit = new WorkOrderAuditService(context, fieldLocks); var service = new WorkOrderBoardUpdateService(context, boardData, audit); return (context, service); } private static string ToVersion(WorkOrder workOrder) => Convert.ToBase64String(workOrder.RowVersion ?? new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }); private static string ToVersion(Dispatch dispatch) => Convert.ToBase64String(dispatch.RowVersion ?? new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }); [Fact] public async Task PatchField_AutoSchedulesIncompleteToScheduled() { var (context, service) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Incomplete, AssignTo = "user-a", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.ScheduledDate, Value = "2026-06-25", WorkOrderVersion = ToVersion(wo) }, "actor-1"); Assert.Equal(LifecycleStatus.Scheduled, result.LifecycleStatus); Assert.Equal(2, await context.WorkOrderAuditLogs.CountAsync()); } [Fact] public async Task PatchField_IncrementsRescheduleCount() { var (context, service) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Scheduled, ScheduledDate = new DateTime(2026, 6, 20), RescheduleCount = 1, RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.ScheduledDate, Value = "2026-06-25", WorkOrderVersion = ToVersion(wo) }, "actor-1"); Assert.Equal(2, result.RescheduleCount); } [Fact] public async Task PatchField_BlocksStatusChangeWhenPastDue() { var (context, service) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Scheduled, ScheduledDate = DateTime.UtcNow.Date.AddDays(-3), RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.LifecycleStatus, Value = "Complete", WorkOrderVersion = ToVersion(wo) }, "actor-1")); Assert.Equal("PastDueStatusBlocked", ex.Code); } [Fact] public async Task PatchField_BlocksEditWhenCanceled() { var (context, service) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Canceled, RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.SiteCode, Value = "BK5", WorkOrderVersion = ToVersion(wo) }, "actor-1")); Assert.Equal("CanceledReadOnly", ex.Code); } [Fact] public async Task PatchField_VendorCreatesPrimaryDispatch() { var (context, service) = CreateSut(); context.Vendors.Add(new Vendor { Id = 5, CompanyName = "Vendor Co" }); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Incomplete, RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.VendorId, Value = "5", WorkOrderVersion = ToVersion(wo) }, "actor-1"); Assert.NotNull(result.PrimaryDispatchId); Assert.Equal(5, result.VendorId); Assert.Equal(1, await context.Dispatches.CountAsync()); } [Fact] public async Task PatchField_CreatesFieldLockAndAuditPerField() { var (context, service) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Scheduled, SiteCode = "OLD", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.SiteCode, Value = "BK5", WorkOrderVersion = ToVersion(wo) }, "actor-1"); Assert.Single(await context.WorkOrderAuditLogs.Where(a => a.Action == AuditActionType.FieldChanged.ToString()).ToListAsync()); Assert.True(await context.WorkOrderFieldLocks.AnyAsync(l => l.FieldName == "SiteCode")); } [Fact] public async Task PatchField_NormalizesWoNumber() { var (context, service) = CreateSut(); var wo = new WorkOrder { Id = 1, InternalWONumber = "00000009999", LifecycleStatus = LifecycleStatus.Incomplete, RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.WoNumber, Value = "12345", WorkOrderVersion = ToVersion(wo) }, "actor-1"); Assert.Equal("00000012345", result.WoNumber); } [Fact] public async Task PatchField_RejectsDuplicateWoNumber() { var (context, service) = CreateSut(); context.workOrders.AddRange( new WorkOrder { Id = 1, InternalWONumber = "00000011111", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }, new WorkOrder { Id = 2, InternalWONumber = "00000022222", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 } }); await context.SaveChangesAsync(); var wo = await context.workOrders.FindAsync(1); var ex = await Assert.ThrowsAsync(() => service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.WoNumber, Value = "22222", WorkOrderVersion = ToVersion(wo!) }, "actor-1")); Assert.Equal("DuplicateWoNumber", ex.Code); } }