shoc-backend/SeaHavenIndustries.Tests/WorkOrderAdvancedSearchDateRangeTests.cs

173 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Implementation;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Helpers;
using SeaHaven.Services.Implementation;
namespace SeaHavenIndustries.Tests;
/// <summary>
/// SH-391: an Advanced Filters date range narrows GET /board/search to rows
/// whose Schedule On (or week-only Target Week) is in the range. Rows with no
/// date come back only when the caller asks for them (no range selected).
/// </summary>
public class WorkOrderAdvancedSearchDateRangeTests
{
private static readonly DateOnly RangeFrom = new(2026, 9, 21);
private static readonly DateOnly RangeTo = new(2026, 9, 25);
private static ApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
private static WorkOrderAdvancedSearchService CreateService(ApplicationDbContext context)
=> new(new WorkOrderAdvancedSearchDataService(context), WorkOrderAccountTestHelpers.Resolver(context));
private static WorkOrder Wo(
int id,
DateTime? scheduled,
string? assignTo = null,
LifecycleStatus status = LifecycleStatus.Scheduled,
DateOnly? targetWeek = null,
int? accountId = null)
=> new()
{
Id = id,
InternalWONumber = $"1000000000{id}",
AssignTo = assignTo,
ScheduledDate = scheduled,
ScheduleWeekOnly = targetWeek.HasValue,
TargetWeek = targetWeek,
LifecycleStatus = status,
AccountId = accountId,
istemplate = false
};
private static WorkOrder NotWeekOnly(WorkOrder wo)
{
wo.ScheduleWeekOnly = false;
return wo;
}
private static WorkOrderAdvancedSearchQueryDto UnassignedInRange(bool includeDateless = false) => new()
{
DatePreset = WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = RangeFrom,
DateTo = RangeTo,
Dispatchers = new List<string> { "__unassigned__" },
IncludeDateless = includeDateless,
PageSize = 50
};
[Fact]
public async Task SearchAsync_UnassignedWithRange_ReturnsOnlyUnassignedRowsScheduledInRange()
{
await using var context = CreateContext();
context.workOrders.AddRange(
Wo(1, new DateTime(2026, 9, 23)),
Wo(2, new DateTime(2026, 8, 4)),
Wo(3, null, status: LifecycleStatus.Incomplete),
Wo(4, new DateTime(2026, 9, 24), assignTo: "user-a"),
Wo(5, new DateTime(2026, 10, 2)));
await context.SaveChangesAsync();
var result = await CreateService(context).SearchAsync(
UnassignedInRange(), WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(new[] { 1 }, result.Items.Select(i => i.Id).ToArray());
}
[Fact]
public async Task SearchAsync_RangeWithoutDispatcherFilter_ExcludesUndatedRows()
{
await using var context = CreateContext();
context.workOrders.AddRange(
Wo(1, new DateTime(2026, 9, 22), assignTo: "user-a"),
Wo(2, null, assignTo: "user-a", status: LifecycleStatus.Incomplete),
Wo(3, new DateTime(2026, 9, 14), assignTo: "user-a", status: LifecycleStatus.Incomplete));
await context.SaveChangesAsync();
var result = await CreateService(context).SearchAsync(new WorkOrderAdvancedSearchQueryDto
{
DatePreset = WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = RangeFrom,
DateTo = RangeTo
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(new[] { 1 }, result.Items.Select(i => i.Id).ToArray());
}
[Fact]
public async Task SearchAsync_WeekOnlyRow_MatchesRangeThatOverlapsItsTargetWeek()
{
await using var context = CreateContext();
context.workOrders.AddRange(
Wo(1, null, targetWeek: new DateOnly(2026, 9, 21)),
Wo(2, null, targetWeek: new DateOnly(2026, 9, 14)),
Wo(3, null, targetWeek: new DateOnly(2026, 9, 28)),
// Week-only with a leftover date outside the range: the week decides,
// as on the board page (SH-391).
Wo(4, new DateTime(2026, 10, 2), targetWeek: new DateOnly(2026, 9, 21)),
// Not week-only: its date decides, so a target week does not bring it in.
NotWeekOnly(Wo(5, new DateTime(2026, 10, 2), targetWeek: new DateOnly(2026, 9, 21))));
await context.SaveChangesAsync();
// Wed–Fri of the week of 9/21: the week-only row for that week overlaps it.
var result = await CreateService(context).SearchAsync(new WorkOrderAdvancedSearchQueryDto
{
DatePreset = WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2026, 9, 23),
DateTo = RangeTo,
Dispatchers = new List<string> { "__unassigned__" }
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(new[] { 1, 4 }, result.Items.Select(i => i.Id).OrderBy(id => id).ToArray());
}
[Fact]
public async Task SearchAsync_IncludeDateless_AddsOpenUndatedRowsToTheRange()
{
await using var context = CreateContext();
context.workOrders.AddRange(
Wo(1, new DateTime(2026, 9, 23)),
Wo(2, new DateTime(2026, 8, 4)),
Wo(3, null, status: LifecycleStatus.Incomplete),
Wo(4, null, status: LifecycleStatus.Completed),
Wo(5, null, status: LifecycleStatus.Canceled));
await context.SaveChangesAsync();
var result = await CreateService(context).SearchAsync(
UnassignedInRange(includeDateless: true), WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(new[] { 1, 3 }, result.Items.Select(i => i.Id).OrderBy(id => id).ToArray());
}
[Fact]
public async Task SearchAsync_RangeForAccountUser_NeverReturnsAnotherAccountsRows()
{
await using var context = CreateContext();
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
context.workOrders.AddRange(
Wo(1, new DateTime(2026, 9, 23), accountId: 1),
Wo(2, new DateTime(2026, 9, 23), accountId: 2),
Wo(3, null, status: LifecycleStatus.Incomplete, accountId: 2),
Wo(4, null, targetWeek: new DateOnly(2026, 9, 21), accountId: 2));
await context.SaveChangesAsync();
var user = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher");
var strict = await CreateService(context).SearchAsync(UnassignedInRange(), user, "disp-1");
var withDateless = await CreateService(context).SearchAsync(
UnassignedInRange(includeDateless: true), user, "disp-1");
Assert.Equal(new[] { 1 }, strict.Items.Select(i => i.Id).ToArray());
Assert.Equal(new[] { 1 }, withDateless.Items.Select(i => i.Id).ToArray());
}
}