using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Implementation; namespace SeaHavenIndustries.Tests; public class WorkOrderBoardCancelServiceTests { private static (ApplicationDbContext Context, WorkOrderBoardCancelService Cancel, WorkOrderBoardUpdateService Update) CreateSut() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var context = new ApplicationDbContext(options); var boardData = new WorkOrderBoardDataService(context); var mutationData = new WorkOrderBoardMutationDataService(context); var boardService = new WorkOrderBoardService(boardData, WorkOrderAccountTestHelpers.Resolver(context)); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var cancel = new WorkOrderBoardCancelService(mutationData, boardService, audit); var update = new WorkOrderBoardUpdateService(boardData, mutationData, audit); return (context, cancel, update); } private static string ToVersion(WorkOrder workOrder) => Convert.ToBase64String(workOrder.RowVersion ?? new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }); [Fact] public async Task Cancel_SetsLifecycleStatusCanceled() { var (context, cancel, _) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Incomplete, Status = "Incomplete", SiteCode = "BK5", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var result = await cancel.CancelAsync(1, WorkOrderAccountTestHelpers.OrgWideAdmin(), "actor-1"); Assert.Equal(LifecycleStatus.Canceled, result.LifecycleStatus); Assert.Equal("Canceled", result.LifecycleStatusLabel); } [Fact] public async Task Cancel_IsIdempotentWhenAlreadyCanceled() { var (context, cancel, _) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Canceled, Status = "Canceled", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); var before = await context.WorkOrderAuditLogs.CountAsync(); var result = await cancel.CancelAsync(1, WorkOrderAccountTestHelpers.OrgWideAdmin(), "actor-1"); var after = await context.WorkOrderAuditLogs.CountAsync(); Assert.Equal(LifecycleStatus.Canceled, result.LifecycleStatus); Assert.Equal(before, after); } [Fact] public async Task Cancel_BlocksWhenCompleted() { var (context, cancel, _) = CreateSut(); context.workOrders.Add(new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Completed, Status = "Completed" }); await context.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => cancel.CancelAsync(1, WorkOrderAccountTestHelpers.OrgWideAdmin(), "actor-1")); Assert.Equal("CancelNotAllowed", ex.Code); } [Fact] public async Task Cancel_BlocksWhenClosedLabel() { var (context, cancel, _) = CreateSut(); context.workOrders.Add(new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Completed, Status = "Closed" }); await context.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => cancel.CancelAsync(1, WorkOrderAccountTestHelpers.OrgWideAdmin(), "actor-1")); Assert.Equal("CancelNotAllowed", ex.Code); } [Fact] public async Task Cancel_WritesStatusChangedAudit() { var (context, cancel, _) = CreateSut(); context.workOrders.Add(new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Scheduled, Status = "Scheduled", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }); await context.SaveChangesAsync(); await cancel.CancelAsync(1, WorkOrderAccountTestHelpers.OrgWideAdmin(), "actor-1"); var log = await context.WorkOrderAuditLogs.SingleAsync(l => l.Action == "StatusChanged"); Assert.Equal("Canceled", log.NewValue); } [Fact] public async Task Patch_AfterCancel_IsBlocked() { var (context, cancel, update) = CreateSut(); var wo = new WorkOrder { Id = 1, LifecycleStatus = LifecycleStatus.Incomplete, Status = "Incomplete", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); await context.SaveChangesAsync(); await cancel.CancelAsync(1, WorkOrderAccountTestHelpers.OrgWideAdmin(), "actor-1"); var ex = await Assert.ThrowsAsync(() => update.PatchFieldAsync(1, new SeaHaven.Services.DTOs.WorkOrderBoardPatchRequestDto { Field = WorkOrderBoardFieldNames.SiteCode, Value = "NEW", WorkOrderVersion = ToVersion(wo) }, "actor-1")); Assert.Equal("CanceledReadOnly", ex.Code); } }