shoc-backend/SeaHavenIndustries.Tests/WorkOrderBoardServiceTests.cs
Arthur Bassi 05dd262e80 fix(work-orders): derive Past Due from DueDate instead of ScheduledDate
Past Due must track the deadline (Due Date), not Schedule On. Keep dueDate and scheduledDate PATCH mutations independent so rescheduling alone does not clear Past Due.
2026-08-13 13:28:25 -03:00

427 lines
13 KiB
C#

using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Implementation;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Implementation;
namespace SeaHavenIndustries.Tests;
public class WorkOrderBoardDataServiceTests
{
private static ApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
[Fact]
public async Task GetBoardRows_ReturnsScheduledInWeekAndUnscheduled()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC", ContactName = "Tech", Phone = "555-0100" });
context.Dispatches.Add(new Dispatch
{
Id = 10,
VendorId = 1,
ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0)
});
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
PrimaryDispatchId = 10,
SiteCode = "BK5"
},
new WorkOrder
{
Id = 2,
InternalWONumber = "10000000002",
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
SiteCode = "BK6"
},
new WorkOrder
{
Id = 3,
InternalWONumber = "10000000003",
ScheduledDate = new DateTime(2026, 7, 1),
LifecycleStatus = LifecycleStatus.Scheduled
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
null,
false,
null,
false,
null,
null));
Assert.Single(result.ScheduledRows);
Assert.Equal(1, result.ScheduledRows[0].Id);
Assert.Equal("Acme HVAC", result.ScheduledRows[0].VendorName);
Assert.Single(result.UnscheduledRows);
Assert.Equal(2, result.UnscheduledRows[0].Id);
Assert.Equal(1, result.ScheduledTotalBeforeSearch);
}
[Fact]
public async Task GetBoardRows_FiltersByDispatcher()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
AssignTo = "user-a",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 2,
AssignTo = "user-b",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
new[] { "user-a" },
false,
null,
false,
null,
null));
Assert.Single(result.ScheduledRows);
Assert.Equal("user-a", result.ScheduledRows[0].AssignTo);
}
[Fact]
public async Task GetBoardRows_MyWorkOrders_FiltersCurrentUser()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
AssignTo = "me",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 2,
AssignTo = "other",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
null,
true,
null,
false,
null,
"me"));
Assert.Single(result.ScheduledRows);
Assert.Equal(1, result.ScheduledRows[0].Id);
}
}
public class WorkOrderBoardPendingUpliftTests
{
private static ApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
[Fact]
public async Task GetBoardRows_PendingUpliftOnPrimaryDispatch_SurfacesCount()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" });
context.Dispatches.Add(new Dispatch
{
Id = 10,
VendorId = 1,
WorkOrderId = 1,
ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0)
});
context.workOrders.Add(new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
PrimaryDispatchId = 10,
SiteCode = "BK5"
});
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
{
Id = 100,
DispatchId = 10,
RequestedNTE = 1500m,
Status = "Pending",
RequiredTier = 1,
NotificationStatus = "Pending"
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
null,
false,
null,
false,
null,
null));
Assert.Single(result.ScheduledRows);
Assert.Equal(1, result.ScheduledRows[0].PendingUpliftCount);
var dto = WorkOrderBoardService.MapRawRow(result.ScheduledRows[0], DateTime.UtcNow);
Assert.Equal(1, dto.PendingUpliftCount);
}
[Fact]
public async Task GetBoardRows_ApprovedUplift_DoesNotBlock()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" });
context.Dispatches.Add(new Dispatch
{
Id = 10,
VendorId = 1,
WorkOrderId = 1
});
context.workOrders.Add(new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
PrimaryDispatchId = 10
});
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
{
Id = 100,
DispatchId = 10,
RequestedNTE = 1500m,
Status = "Approved",
RequiredTier = 1,
NotificationStatus = "Sent"
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
null,
false,
null,
false,
null,
null));
Assert.Single(result.ScheduledRows);
Assert.Equal(0, result.ScheduledRows[0].PendingUpliftCount);
}
[Fact]
public async Task GetBoardRows_PendingUpliftViaDispatchWorkOrderLink_SurfacesCount()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" });
// Secondary link only via DispatchWorkOrders (no WorkOrderId / PrimaryDispatch).
context.Dispatches.Add(new Dispatch
{
Id = 20,
VendorId = 1,
WorkOrderId = null
});
context.DispatchWorkOrders.Add(new DispatchWorkOrder
{
DispatchId = 20,
WorkOrderId = 2
});
context.workOrders.Add(new WorkOrder
{
Id = 2,
InternalWONumber = "10000000002",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled
});
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
{
Id = 200,
DispatchId = 20,
RequestedNTE = 900m,
Status = "Pending",
RequiredTier = 1,
NotificationStatus = "Pending"
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
null,
false,
null,
false,
null,
null));
Assert.Single(result.ScheduledRows);
Assert.Equal(2, result.ScheduledRows[0].Id);
Assert.Equal(1, result.ScheduledRows[0].PendingUpliftCount);
}
[Fact]
public async Task GetBoardRows_PendingUpliftOnSoftDeletedDispatch_DoesNotCount()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" });
context.Dispatches.Add(new Dispatch
{
Id = 10,
VendorId = 1,
WorkOrderId = 1,
ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0),
IsDeleted = true
});
context.workOrders.Add(new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
PrimaryDispatchId = 10,
SiteCode = "BK5"
});
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
{
Id = 100,
DispatchId = 10,
RequestedNTE = 1500m,
Status = "Pending",
RequiredTier = 1,
NotificationStatus = "Pending"
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
null,
false,
null,
false,
null,
null));
Assert.Single(result.ScheduledRows);
Assert.Equal(0, result.ScheduledRows[0].PendingUpliftCount);
}
}
public class WorkOrderBoardServiceTests
{
private static ApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
[Fact]
public async Task GetBoardAsync_ComputesCountsAndPastDue()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.Add(new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
DueDate = new DateTime(2026, 6, 20),
ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc),
LifecycleStatus = LifecycleStatus.Scheduled
});
await context.SaveChangesAsync();
var dataService = new WorkOrderBoardDataService(context);
var boardService = new WorkOrderBoardService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto
{
WeekStart = weekStart
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, response.Counts.Total);
Assert.Single(response.Scheduled);
Assert.True(response.Scheduled[0].IsPastDue);
Assert.Equal("wednesday", response.Scheduled[0].DayGroup);
}
[Fact]
public async Task GetBoardAsync_InvalidWeekWindow_Throws()
{
await using var context = CreateContext();
var boardService = new WorkOrderBoardService(new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context));
await Assert.ThrowsAsync<ArgumentException>(() => boardService.GetBoardAsync(new WorkOrderBoardQueryDto
{
WeekStart = new DateOnly(2026, 6, 22),
WeekEnd = new DateOnly(2026, 6, 15)
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null));
}
}