diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs index fcd8721..d34c6fb 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs @@ -16,17 +16,23 @@ namespace SeaHaven.Services.Implementation private readonly IWorkOrderBoardDataService _boardDataService; private readonly IWorkOrderBoardMutationDataService _mutationData; private readonly IWorkOrderAuditService _auditService; + private readonly IWorkOrderUpliftService _upliftService; + private readonly IUpliftDataService _upliftData; private readonly IServicesRegistryService? _servicesRegistryService; public WorkOrderBoardUpdateService( IWorkOrderBoardDataService boardDataService, IWorkOrderBoardMutationDataService mutationData, IWorkOrderAuditService auditService, + IWorkOrderUpliftService upliftService, + IUpliftDataService upliftData, IServicesRegistryService? servicesRegistryService = null) { _boardDataService = boardDataService; _mutationData = mutationData; _auditService = auditService; + _upliftService = upliftService; + _upliftData = upliftData; _servicesRegistryService = servicesRegistryService; } @@ -35,7 +41,7 @@ namespace SeaHaven.Services.Implementation WorkOrderBoardPatchRequestDto request, string? actorId) { - await _mutationData.ExecuteTransactionalAsync(async ct => + Func patch = async ct => { if (string.IsNullOrWhiteSpace(request.Field)) throw new WorkOrderBoardValidationException("InvalidField", "Field is required."); @@ -127,7 +133,17 @@ namespace SeaHaven.Services.Implementation } var auditField = WorkOrderBoardFieldNames.ToAuditFieldName(canonicalField); + var lifecycleBefore = workOrder.LifecycleStatus; var changes = await ApplyFieldMutationAsync(canonicalField, workOrder, dispatch, request.Value, auditField, ct); + + // Cancelling the work order cancels its pending uplift in the same action: the + // withdrawal and its own audit entry are staged here and committed by the same + // save as the status change. + if (lifecycleBefore != LifecycleStatus.Canceled + && workOrder.LifecycleStatus == LifecycleStatus.Canceled) + { + await _upliftService.WithdrawPendingForWorkOrderAsync(workOrderId, actorId, ct); + } if (resolved is { Created: true, Dispatch: var createdDispatch } && canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase)) { @@ -173,12 +189,40 @@ namespace SeaHaven.Services.Implementation } await SaveTrackedOrThrowAsync(workOrderId, ct); - }, CancellationToken.None); + }; + + // A cancel runs under the per-work-order gate uplift create uses, so a create that + // is in flight cannot commit a new pending uplift onto the work order being cancelled. + if (IsCancelRequest(request)) + { + await _upliftData.ExecuteWorkOrderMutationAsync( + workOrderId, + async ct => + { + await patch(ct); + return true; + }, + CancellationToken.None); + } + else + { + await _mutationData.ExecuteTransactionalAsync(patch, CancellationToken.None); + } var row = await LoadBoardRowAsync(workOrderId); return row ?? throw new WorkOrderBoardValidationException("NotFound", "Work order not found."); } + private static bool IsCancelRequest(WorkOrderBoardPatchRequestDto request) + { + if (string.IsNullOrWhiteSpace(request.Field)) + return false; + + var canonicalField = WorkOrderBoardFieldNames.Canonicalize(request.Field.Trim()); + return string.Equals(canonicalField, WorkOrderBoardFieldNames.LifecycleStatus, StringComparison.OrdinalIgnoreCase) + && LifecycleStatusMapper.ParseLifecycleStatus(request.Value) == LifecycleStatus.Canceled; + } + private async Task> ApplyFieldMutationAsync( string field, WorkOrder workOrder, diff --git a/SeaHavenIndustries.Tests/WorkOrderAccountTestHelpers.cs b/SeaHavenIndustries.Tests/WorkOrderAccountTestHelpers.cs index cb7c9f9..040e235 100644 --- a/SeaHavenIndustries.Tests/WorkOrderAccountTestHelpers.cs +++ b/SeaHavenIndustries.Tests/WorkOrderAccountTestHelpers.cs @@ -15,6 +15,31 @@ internal static class WorkOrderAccountTestHelpers new AccountDataService(context), new LocationDataService(context)); + /// Board update service over one context, with the real uplift cascade wired. + public static WorkOrderBoardUpdateService BoardUpdateService( + ApplicationDbContext context, + IWorkOrderAuditService audit, + IServicesRegistryService? servicesRegistry = null) + => new( + new WorkOrderBoardDataService(context), + new WorkOrderBoardMutationDataService(context), + audit, + UpliftService(context), + new UpliftDataService(context), + servicesRegistry); + + public static WorkOrderUpliftService UpliftService(ApplicationDbContext context) + => new( + new UpliftDataService(context), + new DispatchDataService(context), + new WorkOrderDetailDataService(context), + Resolver(context), + new UserDataService(context), + new TeamPermissionOverrideDataService(context), + new TeamPermissionPolicy(), + TimeProvider.System, + Microsoft.Extensions.Options.Options.Create(new SeaHaven.Services.Configuration.ApprovalsOptions())); + public static ClaimsPrincipal AccountUser( string userId = "actor-1", int accountId = 1, diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardCancelServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardCancelServiceTests.cs index 46e86fc..01e93df 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardCancelServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardCancelServiceTests.cs @@ -28,7 +28,7 @@ public class WorkOrderBoardCancelServiceTests var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var cancel = new WorkOrderBoardCancelService(mutationData, boardService, audit, new NoOpUpliftService(), new PassThroughUpliftData()); - var update = new WorkOrderBoardUpdateService(boardData, mutationData, audit); + var update = WorkOrderAccountTestHelpers.BoardUpdateService(context, audit); return (context, cancel, update); } diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardConcurrencyTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardConcurrencyTests.cs index 0aa2a17..9dd9a2a 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardConcurrencyTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardConcurrencyTests.cs @@ -25,7 +25,7 @@ public class WorkOrderBoardConcurrencyTests var mutationData = new WorkOrderBoardMutationDataService(context); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); - return new WorkOrderBoardUpdateService(boardData, mutationData, audit); + return WorkOrderAccountTestHelpers.BoardUpdateService(context, audit); } [Fact] diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardPatchCancelUpliftTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardPatchCancelUpliftTests.cs new file mode 100644 index 0000000..e2ad1f3 --- /dev/null +++ b/SeaHavenIndustries.Tests/WorkOrderBoardPatchCancelUpliftTests.cs @@ -0,0 +1,254 @@ +using Data.SeaHavenIndustries; +using Data.SeaHavenIndustries.Enums; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using SeaHaven.DataServices.Implementation; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Helpers; +using SeaHaven.Services.Implementation; +using Xunit; + +namespace SeaHavenIndustries.Tests; + +/// +/// Cancelling a work order from the board (the lifecycle status PATCH the board and +/// slide-over use) cancels its pending uplift in the same action and commit, with an +/// audit entry of its own. +/// +public sealed class WorkOrderBoardPatchCancelUpliftTests +{ + private static readonly byte[] Version = { 1, 0, 0, 0, 0, 0, 0, 1 }; + + private static async Task CancelPatchAsync(ApplicationDbContext context) + { + var stored = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == 1); + return new WorkOrderBoardPatchRequestDto + { + Field = WorkOrderBoardFieldNames.LifecycleStatus, + Value = "Canceled", + WorkOrderVersion = Convert.ToBase64String(stored.RowVersion ?? Version), + }; + } + + private static WorkOrderBoardUpdateService NewUpdateService(ApplicationDbContext context) + { + var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); + var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); + return WorkOrderAccountTestHelpers.BoardUpdateService(context, audit); + } + + // Saved in stages: the work order and its primary dispatch reference each other, which a + // relational provider cannot insert in one statement batch. + private static async Task SeedWorkOrderAsync(ApplicationDbContext context) + { + context.Accounts.Add(new Accounts { Id = 1, Name = "Acme Corp", IsDeleted = false }); + context.Users.Add(new ApplicationUser { Id = "actor-1", UserName = "actor-1" }); + context.Users.Add(new ApplicationUser { Id = "dispatcher-1", UserName = "dispatcher-1" }); + context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); + var workOrder = new WorkOrder + { + Id = 1, + InternalWONumber = "10000000001", + LifecycleStatus = LifecycleStatus.Incomplete, + Status = "Incomplete", + AccountId = 1, + RowVersion = Version, + }; + context.workOrders.Add(workOrder); + await context.SaveChangesAsync(); + + context.Dispatches.Add(new Dispatch + { + Id = 10, + VendorId = 1, + WorkOrderId = 1, + NTEAmount = 1900m, + DispatchNumber = "DIS-10", + Status = "Scheduled", + }); + await context.SaveChangesAsync(); + + workOrder.PrimaryDispatchId = 10; + await context.SaveChangesAsync(); + } + + private static DispatchUpliftRequest Uplift(int id, string status, decimal amount) => new() + { + Id = id, + DispatchId = 10, + CurrentNTE = 1000m, + RequestedNTE = amount, + Status = status, + RequiredTier = status == "NoApprovalRequired" ? 0 : 1, + NotificationStatus = "Sent", + createdby = "dispatcher-1", + CreatedDate = DateTime.UtcNow.AddHours(-id), + }; + + [Fact] + public async Task PatchToCanceled_CancelsPendingUpliftWithItsOwnAuditAndLeavesDecidedUpliftsAlone() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + await using (var seed = new ApplicationDbContext(options)) + { + await SeedWorkOrderAsync(seed); + seed.DispatchUpliftRequests.AddRange( + Uplift(100, "Pending", 700m), + Uplift(101, "Approved", 600m), + Uplift(102, "NoApprovalRequired", 300m), + Uplift(103, "Rejected", 900m)); + await seed.SaveChangesAsync(); + } + + await using (var context = new ApplicationDbContext(options)) + { + var row = await NewUpdateService(context).PatchFieldAsync(1, await CancelPatchAsync(context), "actor-1"); + Assert.Equal(LifecycleStatus.Canceled, row.LifecycleStatus); + } + + await using var verify = new ApplicationDbContext(options); + Assert.Equal(LifecycleStatus.Canceled, verify.workOrders.Single().LifecycleStatus); + + var cancelled = verify.DispatchUpliftRequests.Single(u => u.Id == 100); + Assert.Equal(UpliftStatus.Withdrawn, cancelled.Status); + Assert.Equal("actor-1", cancelled.DecidedByUserId); + Assert.NotNull(cancelled.DecidedAt); + + Assert.Equal(UpliftStatus.Approved, verify.DispatchUpliftRequests.Single(u => u.Id == 101).Status); + Assert.Equal(UpliftStatus.NoApprovalRequired, verify.DispatchUpliftRequests.Single(u => u.Id == 102).Status); + Assert.Equal(UpliftStatus.Rejected, verify.DispatchUpliftRequests.Single(u => u.Id == 103).Status); + Assert.Equal(1900m, verify.Dispatches.Single().NTEAmount); + + var upliftAudit = Assert.Single(verify.WorkOrderAuditLogs, log => log.Action == "uplift_cancel"); + Assert.Equal(1, upliftAudit.WorkOrderId); + Assert.Equal("actor-1", upliftAudit.UserId); + Assert.Equal("Dispatch DIS-10 Uplift", upliftAudit.FieldName); + Assert.Equal(UpliftStatus.Pending, upliftAudit.OldValue); + Assert.Equal(UpliftStatus.Withdrawn, upliftAudit.NewValue); + + var statusAudit = Assert.Single(verify.WorkOrderAuditLogs, log => log.Action == "StatusChanged"); + Assert.Equal(LifecycleStatus.Canceled.ToString(), statusAudit.NewValue); + Assert.NotEqual(upliftAudit.Id, statusAudit.Id); + } + + [Fact] + public async Task PatchToOtherStatus_LeavesPendingUpliftPending() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + await using (var seed = new ApplicationDbContext(options)) + { + await SeedWorkOrderAsync(seed); + seed.DispatchUpliftRequests.Add(Uplift(100, "Pending", 700m)); + await seed.SaveChangesAsync(); + } + + await using (var context = new ApplicationDbContext(options)) + { + var patch = await CancelPatchAsync(context); + patch.Value = "Pending"; + await NewUpdateService(context).PatchFieldAsync(1, patch, "actor-1"); + } + + await using var verify = new ApplicationDbContext(options); + Assert.Equal(LifecycleStatus.Pending, verify.workOrders.Single().LifecycleStatus); + Assert.Equal(UpliftStatus.Pending, verify.DispatchUpliftRequests.Single().Status); + Assert.DoesNotContain(verify.WorkOrderAuditLogs, log => log.Action == "uplift_cancel"); + } + + [Fact] + public async Task PatchToCanceled_FailureAfterTheWrites_RollsBackWorkOrderAndUpliftTogether() + { + await using var connection = new SqliteConnection("DataSource=:memory:"); + await connection.OpenAsync(); + var failAfterSave = new FailAfterSaveInterceptor(); + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .AddInterceptors(failAfterSave) + .Options; + + await using (var seed = new SqliteRowVersionDbContext(options)) + { + await seed.Database.EnsureCreatedAsync(); + await SeedWorkOrderAsync(seed); + seed.DispatchUpliftRequests.Add(Uplift(100, "Pending", 700m)); + await seed.SaveChangesAsync(); + } + + failAfterSave.Armed = true; + await using (var context = new SqliteRowVersionDbContext(options)) + { + var patch = await CancelPatchAsync(context); + await Assert.ThrowsAsync( + () => NewUpdateService(context).PatchFieldAsync(1, patch, "actor-1")); + } + + // The failing save did write both the cancelled work order and the withdrawn + // uplift inside the transaction; the failure after it must undo both. + Assert.True(failAfterSave.SawWithdrawnUpliftInSave); + + failAfterSave.Armed = false; + await using var verify = new SqliteRowVersionDbContext(options); + Assert.Equal(LifecycleStatus.Incomplete, (await verify.workOrders.AsNoTracking().SingleAsync()).LifecycleStatus); + Assert.Equal(UpliftStatus.Pending, (await verify.DispatchUpliftRequests.AsNoTracking().SingleAsync()).Status); + Assert.False(await verify.WorkOrderAuditLogs.AnyAsync()); + } + + private sealed class FailAfterSaveInterceptor : SaveChangesInterceptor + { + public bool Armed { get; set; } + public bool SawWithdrawnUpliftInSave { get; private set; } + + public override ValueTask> SavingChangesAsync( + DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + if (Armed && eventData.Context is not null) + { + SawWithdrawnUpliftInSave = eventData.Context.ChangeTracker + .Entries() + .Any(entry => entry.State == EntityState.Modified + && entry.Entity.Status == UpliftStatus.Withdrawn); + } + + return base.SavingChangesAsync(eventData, result, cancellationToken); + } + + public override ValueTask SavedChangesAsync( + SaveChangesCompletedEventData eventData, + int result, + CancellationToken cancellationToken = default) + { + if (Armed) + throw new InvalidOperationException("Simulated failure after the rows were written"); + + return base.SavedChangesAsync(eventData, result, cancellationToken); + } + } + + private sealed class SqliteRowVersionDbContext : ApplicationDbContext + { + public SqliteRowVersionDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + // SQLite has no rowversion type; store the seeded blobs as plain values. + foreach (var entityType in new[] { typeof(WorkOrder), typeof(Dispatch), typeof(DispatchUpliftRequest) }) + { + var property = builder.Entity(entityType).Property("RowVersion").Metadata; + property.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never; + property.IsConcurrencyToken = false; + } + } + } +} diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardPatchLifecycleRulesTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardPatchLifecycleRulesTests.cs index 050b78b..34a4b9d 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardPatchLifecycleRulesTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardPatchLifecycleRulesTests.cs @@ -22,9 +22,8 @@ public class WorkOrderBoardPatchLifecycleRulesTests var context = new ApplicationDbContext(options); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); - var service = new WorkOrderBoardUpdateService( - new WorkOrderBoardDataService(context), - new WorkOrderBoardMutationDataService(context), + var service = WorkOrderAccountTestHelpers.BoardUpdateService( + context, audit); return (context, service); } diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs index fcf096e..ae19c33 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs @@ -23,7 +23,7 @@ public class WorkOrderBoardUpdateServiceTests var mutationData = new WorkOrderBoardMutationDataService(context); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); - var service = new WorkOrderBoardUpdateService(boardData, mutationData, audit); + var service = WorkOrderAccountTestHelpers.BoardUpdateService(context, audit); return (context, service); } diff --git a/SeaHavenIndustries.Tests/WorkOrderCompletedSelectiveLockTests.cs b/SeaHavenIndustries.Tests/WorkOrderCompletedSelectiveLockTests.cs index cc765ed..7cce8be 100644 --- a/SeaHavenIndustries.Tests/WorkOrderCompletedSelectiveLockTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderCompletedSelectiveLockTests.cs @@ -22,9 +22,8 @@ public class WorkOrderCompletedSelectiveLockTests var context = new ApplicationDbContext(options); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); - var service = new WorkOrderBoardUpdateService( - new WorkOrderBoardDataService(context), - new WorkOrderBoardMutationDataService(context), + var service = WorkOrderAccountTestHelpers.BoardUpdateService( + context, audit); return (context, service); } diff --git a/SeaHavenIndustries.Tests/WorkOrderCompletionFreezeTests.cs b/SeaHavenIndustries.Tests/WorkOrderCompletionFreezeTests.cs index 6589f9f..11f7ab4 100644 --- a/SeaHavenIndustries.Tests/WorkOrderCompletionFreezeTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderCompletionFreezeTests.cs @@ -186,9 +186,8 @@ public class WorkOrderCompletionFreezeTests private static WorkOrderBoardUpdateService CreateService(ApplicationDbContext context) { var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); - return new WorkOrderBoardUpdateService( - new WorkOrderBoardDataService(context), - new WorkOrderBoardMutationDataService(context), + return WorkOrderAccountTestHelpers.BoardUpdateService( + context, new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks)); } diff --git a/SeaHavenIndustries.Tests/WorkOrderOverdueTypeTests.cs b/SeaHavenIndustries.Tests/WorkOrderOverdueTypeTests.cs index d08ac91..ea30df3 100644 --- a/SeaHavenIndustries.Tests/WorkOrderOverdueTypeTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderOverdueTypeTests.cs @@ -68,9 +68,8 @@ public class WorkOrderOverdueTypeTests private static WorkOrderBoardUpdateService NewUpdateService(ApplicationDbContext context) { var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); - return new WorkOrderBoardUpdateService( - new WorkOrderBoardDataService(context), - new WorkOrderBoardMutationDataService(context), + return WorkOrderAccountTestHelpers.BoardUpdateService( + context, new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks), new ServicesRegistryService(new ServicesRegistryDataService(context))); } diff --git a/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs b/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs index ea77fb4..abca125 100644 --- a/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs @@ -28,7 +28,7 @@ public class WorkOrderDocStatusPatchTests var mutationData = new WorkOrderBoardMutationDataService(context); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); - var service = new WorkOrderBoardUpdateService(boardData, mutationData, audit); + var service = WorkOrderAccountTestHelpers.BoardUpdateService(context, audit); return (context, service); } @@ -357,7 +357,7 @@ public class WorkOrderDetailServiceTests var boardData = new WorkOrderBoardDataService(context); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); - var update = new WorkOrderBoardUpdateService(boardData, mutationData, audit); + var update = WorkOrderAccountTestHelpers.BoardUpdateService(context, audit); await update.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto { diff --git a/SeaHavenIndustries.Tests/WorkOrderPocServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderPocServiceTests.cs index ad1ffa2..9e40739 100644 --- a/SeaHavenIndustries.Tests/WorkOrderPocServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderPocServiceTests.cs @@ -595,9 +595,8 @@ public class WorkOrderPocServiceTests private static WorkOrderBoardUpdateService CreateUpdateService(ApplicationDbContext context) { var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); - return new WorkOrderBoardUpdateService( - new WorkOrderBoardDataService(context), - new WorkOrderBoardMutationDataService(context), + return WorkOrderAccountTestHelpers.BoardUpdateService( + context, new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks)); } diff --git a/SeaHavenIndustries.Tests/WorkOrderUpliftDispatchOwnershipTests.cs b/SeaHavenIndustries.Tests/WorkOrderUpliftDispatchOwnershipTests.cs index adba1a7..40ff0fd 100644 --- a/SeaHavenIndustries.Tests/WorkOrderUpliftDispatchOwnershipTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderUpliftDispatchOwnershipTests.cs @@ -59,9 +59,8 @@ public sealed class WorkOrderUpliftDispatchOwnershipTests private static WorkOrderBoardUpdateService NewBoardUpdateService(ApplicationDbContext context) { var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); - return new WorkOrderBoardUpdateService( - new WorkOrderBoardDataService(context), - new WorkOrderBoardMutationDataService(context), + return WorkOrderAccountTestHelpers.BoardUpdateService( + context, new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks)); }