test(uplifts): cover queue contract fields (SH-208)

Behavior coverage for the new queue read fields so the contract cannot
regress silently: workOrderClosed follows terminal lifecycle statuses,
decidedByName resolves from the deciding user, attachmentCount counts
active dispatch documents, and pendingExposureTotal sums granted amounts
across all pending requests independent of paging.
This commit is contained in:
Alexandre Brandizzi 2026-09-16 22:38:23 -03:00
parent e0e45d5ed3
commit b5e8f038c1

View file

@ -1,4 +1,5 @@
using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
@ -343,6 +344,125 @@ public sealed class UpliftQueueReadTests
item.EvidenceSizeBytes.Should().Be(512);
}
// --- Queue contract fields (SH-208/SH-213) ---
[Fact]
public async Task List_MarksWorkOrderClosed_OnlyForTerminalLifecycle()
{
using var context = NewContext();
var (vendorA, workOrderA) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
var (vendorB, workOrderB) = await SeedWorkOrderAsync(context, "WO-B", "SITE-B", "Plumbing");
workOrderA.LifecycleStatus = LifecycleStatus.Completed;
workOrderB.LifecycleStatus = LifecycleStatus.Canceled;
await context.SaveChangesAsync();
var dispatchA = await SeedDispatchAsync(context, vendorA, workOrderA, "DIS-A");
var dispatchB = await SeedDispatchAsync(context, vendorB, workOrderB, "DIS-B");
var (vendorC, workOrderC) = await SeedWorkOrderAsync(context, "WO-C", "SITE-C", "Electrical");
var dispatchC = await SeedDispatchAsync(context, vendorC, workOrderC, "DIS-C");
context.DispatchUpliftRequests.AddRange(
Request(dispatchA, "Approved", new DateTime(2026, 3, 1), decided: new DateTime(2026, 3, 2)),
Request(dispatchB, "Approved", new DateTime(2026, 3, 3), decided: new DateTime(2026, 3, 4)),
Request(dispatchC, "Approved", new DateTime(2026, 3, 5), decided: new DateTime(2026, 3, 6)));
await context.SaveChangesAsync();
var service = NewService(context);
var result = await service.ListAsync(UserWithRoles("Admin"), "Approved", null, 1, 25, CancellationToken.None);
result.Items.Single(i => i.WorkOrderNumber == "WO-A").WorkOrderClosed.Should().BeTrue();
result.Items.Single(i => i.WorkOrderNumber == "WO-B").WorkOrderClosed.Should().BeTrue();
result.Items.Single(i => i.WorkOrderNumber == "WO-C").WorkOrderClosed.Should().BeFalse();
}
[Fact]
public async Task List_ApprovedRow_CarriesDecidedByNameFromDecidingUser()
{
using var context = NewContext();
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-A");
context.Users.Add(new ApplicationUser { Id = "user-9", FirstName = "Grace", LastName = "Hopper" });
await context.SaveChangesAsync();
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
{
DispatchId = dispatch.Id,
Status = "Approved",
CreatedDate = new DateTime(2026, 3, 1),
DecidedAt = new DateTime(2026, 3, 2),
DecidedByUserId = "user-9",
RequiredTier = 1,
RequestedNTE = 300m,
NotificationStatus = "Pending"
});
await context.SaveChangesAsync();
var service = NewService(context);
var result = await service.ListAsync(UserWithRoles("Approver"), "Approved", null, 1, 25, CancellationToken.None);
result.Items.Single().DecidedByName.Should().Be("Grace Hopper");
}
[Fact]
public async Task List_AttachmentCount_CountsActiveDocumentsOnTheDispatch()
{
using var context = NewContext();
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-A");
context.VendorCompletionDocuments.AddRange(
new VendorCompletionDocument
{
VendorId = vendor.Id, DispatchId = dispatch.Id, WorkOrderId = workOrder.Id,
OriginalFileName = "quote.pdf", StoredFileName = "a.bin", ContentType = "application/pdf",
SizeBytes = 512, ScanStatus = "Passed", ReviewStatus = "Approved", Purpose = "UpliftEvidence", Version = 1
},
new VendorCompletionDocument
{
VendorId = vendor.Id, DispatchId = dispatch.Id, WorkOrderId = workOrder.Id,
OriginalFileName = "photo.jpg", StoredFileName = "b.bin", ContentType = "image/jpeg",
SizeBytes = 2048, ScanStatus = "Passed", ReviewStatus = "Approved", Purpose = "Completion", Version = 1
},
new VendorCompletionDocument
{
VendorId = vendor.Id, DispatchId = dispatch.Id, WorkOrderId = workOrder.Id,
OriginalFileName = "old.pdf", StoredFileName = "c.bin", ContentType = "application/pdf",
SizeBytes = 128, ScanStatus = "Passed", ReviewStatus = "Approved", Purpose = "Completion", Version = 1,
IsDeleted = true
});
await context.SaveChangesAsync();
context.DispatchUpliftRequests.Add(Request(dispatch, "Pending", new DateTime(2026, 3, 2)));
await context.SaveChangesAsync();
var service = NewService(context);
var result = await service.ListAsync(UserWithRoles("Approver"), "Pending", null, 1, 25, CancellationToken.None);
result.Items.Single().AttachmentCount.Should().Be(2);
}
[Fact]
public async Task List_PendingExposureTotal_SumsGrantedAmountsAcrossAllPendingRequests()
{
using var context = NewContext();
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-A");
context.Users.Add(new ApplicationUser { Id = "user-7", FirstName = "Ada", LastName = "Smith" });
await context.SaveChangesAsync();
context.DispatchUpliftRequests.AddRange(
// Vendor-portal rows (createdby null) store the requested new NTE total,
// so the pending exposure is the delta: 400 - 100 = 300 and 600 - 250 = 350.
Request(dispatch, "Pending", new DateTime(2026, 3, 1), requested: 400m, currentNte: 100m),
Request(dispatch, "Pending", new DateTime(2026, 3, 2), requested: 600m, currentNte: 250m),
// Work-order-path rows (createdby set) store the requested increment: 90.
Request(dispatch, "Pending", new DateTime(2026, 3, 3), requested: 90m, currentNte: 600m, createdBy: "user-7"),
// Non-pending rows never add pending exposure.
Request(dispatch, "Approved", new DateTime(2026, 2, 1), decided: new DateTime(2026, 2, 2), requested: 500m, currentNte: 100m));
await context.SaveChangesAsync();
var service = NewService(context);
// Page 1 with a single row still reports the total across ALL pending rows.
var result = await service.ListAsync(UserWithRoles("Approver"), "Pending", null, 1, 1, CancellationToken.None);
result.Items.Should().HaveCount(1);
result.PendingExposureTotal.Should().Be(740m);
}
// --- Approved-on-WO exposure totals ---
[Fact]