mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-02 01:33:23 +00:00
173 lines
7.1 KiB
C#
173 lines
7.1 KiB
C#
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());
|
||
}
|
||
}
|