shoc-backend/SeaHavenIndustries.Tests/WorkOrderBoardSearchTests.cs
Alexandre Brandizzi 146fe6fdb0 style: satisfy dotnet format whitespace gate for region board files
Break collection initializer entries onto one line each in
WorkOrderBoardRegions and WorkOrderBoardSearchTests so the changed-file
formatting gate (dotnet format --verify-no-changes) passes.
2026-09-17 02:26:19 -03:00

1532 lines
57 KiB
C#

using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Helpers;
using SeaHaven.DataServices.Implementation;
namespace SeaHavenIndustries.Tests;
public class WorkOrderBoardSearchFilterTests
{
private static ApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
[Theory]
[InlineData("BK5", 1)]
[InlineData("10000000001", 1)]
[InlineData("HVAC Tech", 1)]
[InlineData("Acme HVAC", 1)]
[InlineData("sched", 1)]
[InlineData("Leak repair", 1)]
[InlineData("Reactive", 1)]
[InlineData("6/23", 1)]
public async Task Apply_MatchesExpectedField(string search, int expectedId)
{
await using var context = CreateContext();
await SeedSearchFixtures(context);
var ids = await WorkOrderBoardSearchFilter
.Apply(context.workOrders.AsQueryable(), search)
.Select(w => w.Id)
.ToListAsync();
Assert.Contains(expectedId, ids);
}
[Fact]
public void NormalizeSearch_IgnoresSingleCharacter()
{
Assert.Null(WorkOrderBoardSearchFilter.NormalizeSearch("a"));
Assert.Null(WorkOrderBoardSearchFilter.NormalizeSearch(" "));
Assert.Equal("ab", WorkOrderBoardSearchFilter.NormalizeSearch(" ab "));
}
[Fact]
public async Task Apply_LifecycleLabel_MatchesScheduled()
{
await using var context = CreateContext();
await SeedSearchFixtures(context);
var ids = await WorkOrderBoardSearchFilter
.Apply(context.workOrders.AsQueryable(), "scheduled")
.Select(w => w.Id)
.ToListAsync();
Assert.Single(ids);
Assert.Equal(1, ids[0]);
}
private static async Task SeedSearchFixtures(ApplicationDbContext context)
{
context.Vendors.Add(new Vendor
{
Id = 1,
CompanyName = "Acme HVAC",
ContactName = "HVAC Tech",
Phone = "555-0100"
});
context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1 });
context.workOrders.Add(new WorkOrder
{
Id = 1,
SiteCode = "BK5",
InternalWONumber = "10000000001",
WorkerOrderTitle = "Leak repair",
WorkOrderType = WorkOrderType.Reactive,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
PrimaryDispatchId = 10
});
await context.SaveChangesAsync();
}
}
public class WorkOrderBoardSearchCountsTests
{
private static ApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
[Fact]
public async Task GetBoardRows_SearchReducesScheduledAndKeepsTotal()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
SiteCode = "BK5",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 2,
SiteCode = "BK6",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 3,
SiteCode = "BK5",
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete
});
await context.SaveChangesAsync();
var service = new WorkOrderBoardDataService(context);
var result = await service.GetBoardRowsAsync(new SeaHaven.DataServices.Interfaces.WorkOrderBoardQuery(
weekStart,
weekStart.AddDays(4),
null,
false,
null,
false,
"BK5",
null));
Assert.Equal(2, result.ScheduledTotalBeforeSearch);
Assert.Single(result.ScheduledRows);
Assert.Equal("BK5", result.ScheduledRows[0].SiteCode);
Assert.Empty(result.UnscheduledRows);
}
}
public class WorkOrderSearchDateRangeResolverTests
{
[Fact]
public void Resolve_ThisWeek_UsesIsoMonday()
{
var reference = new DateOnly(2026, 6, 24);
var range = SeaHaven.Services.Helpers.WorkOrderSearchDateRangeResolver.Resolve(
SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
null,
null,
reference);
Assert.Equal(new DateOnly(2026, 6, 22), range.From);
Assert.Equal(new DateOnly(2026, 6, 26), range.To);
}
[Fact]
public void Resolve_Custom_RequiresDates()
{
var ex = Assert.Throws<ArgumentException>(() =>
SeaHaven.Services.Helpers.WorkOrderSearchDateRangeResolver.Resolve(
SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
null,
null));
Assert.Contains("dateFrom", ex.Message);
}
}
public class WorkOrderAdvancedSearchServiceTests
{
private static ApplicationDbContext CreateContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
[Fact]
public async Task SearchAsync_FiltersBySiteAndPaginates()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
SiteCode = "BK5",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
SiteCode = "BK6",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(6),
Sites = new List<string> { "BK5" },
Page = 0,
PageSize = 50
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Single(result.Items);
Assert.Equal("BK5", result.Items.First().SiteCode);
}
[Fact]
public async Task SearchAsync_FiltersByCanonicalRegionAndUnmappedBucket()
{
await using var context = CreateContext();
var date = new DateTime(2026, 6, 23);
context.Locations.AddRange(
new Locations { Id = 1, State = "WA" },
new Locations { Id = 2, State = "CALIFORNIA" },
new Locations { Id = 3, State = "Atlantis" },
new Locations { Id = 4, State = null });
context.workOrders.AddRange(
new WorkOrder { Id = 1, LocationId = 1, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
new WorkOrder { Id = 2, LocationId = 2, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
new WorkOrder { Id = 3, LocationId = 3, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
new WorkOrder { Id = 4, LocationId = 4, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled });
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService, WorkOrderAccountTestHelpers.Resolver(context));
var query = new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2026, 6, 22),
DateTo = new DateOnly(2026, 6, 30),
PageSize = 50
};
query.Regions = new List<string> { "West" };
var west = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(new[] { 1 }, west.Items.Select(item => item.Id));
query.Regions = new List<string> { "California" };
var california = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(new[] { 2 }, california.Items.Select(item => item.Id));
query.Regions = new List<string> { "Unmapped/Other" };
var unmapped = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(new[] { 3, 4 }, unmapped.Items.Select(item => item.Id));
}
[Fact]
public async Task SearchAsync_ClampsPageSizeToMax()
{
await using var context = CreateContext();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
PageSize = 500
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(200, result.PageSize);
}
[Fact]
public async Task SearchAsync_AcceptsPageSize200()
{
await using var context = CreateContext();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
PageSize = 200
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(200, result.PageSize);
}
[Theory]
[InlineData(25)]
[InlineData(50)]
[InlineData(200)]
public async Task SearchAsync_UnassignedDispatcher_AcceptsBoardPagerPageSizes(int pageSize)
{
await using var context = CreateContext();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2000, 1, 1),
DateTo = new DateOnly(2099, 12, 31),
Dispatchers = new List<string> { "__unassigned__" },
Page = 0,
PageSize = pageSize
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(pageSize, result.PageSize);
Assert.Equal(0, result.Page);
Assert.Equal(0, result.PageBase);
}
[Fact]
public async Task SearchAsync_UnassignedDispatcher_WideCustomRange_IncludesDatedOutsideWeekAndUndated()
{
await using var context = CreateContext();
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
AssignTo = null,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 2,
InternalWONumber = "10000000002",
AssignTo = null,
ScheduledDate = new DateTime(2026, 8, 4),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 3,
InternalWONumber = "10000000003",
AssignTo = "",
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete
},
new WorkOrder
{
Id = 4,
InternalWONumber = "10000000004",
AssignTo = "user-a",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2000, 1, 1),
DateTo = new DateOnly(2099, 12, 31),
Dispatchers = new List<string> { "__unassigned__" },
Page = 0,
PageSize = 50
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(3, result.TotalCount);
Assert.Equal(50, result.PageSize);
Assert.Contains(result.Items, i => i.Id == 1 && i.DispatcherId == null);
Assert.Contains(result.Items, i => i.Id == 2 && i.DispatcherId == null);
Assert.Contains(result.Items, i => i.Id == 3);
Assert.DoesNotContain(result.Items, i => i.Id == 4);
}
[Fact]
public async Task SearchAsync_UnassignedDispatcher_PaginatesWideCustomRange()
{
await using var context = CreateContext();
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
AssignTo = null,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 2,
InternalWONumber = "10000000002",
AssignTo = null,
ScheduledDate = new DateTime(2026, 8, 4),
LifecycleStatus = LifecycleStatus.Scheduled
},
new WorkOrder
{
Id = 3,
InternalWONumber = "10000000003",
AssignTo = null,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var page0 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2000, 1, 1),
DateTo = new DateOnly(2099, 12, 31),
Dispatchers = new List<string> { "__unassigned__" },
Page = 0,
PageSize = 2
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
var page1 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2000, 1, 1),
DateTo = new DateOnly(2099, 12, 31),
Dispatchers = new List<string> { "__unassigned__" },
Page = 1,
PageSize = 2
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(3, page0.TotalCount);
Assert.Equal(2, page0.PageSize);
Assert.Equal(2, page0.Items.Count());
Assert.Equal(3, page1.TotalCount);
Assert.Single(page1.Items);
Assert.Empty(page0.Items.Select(i => i.Id).Intersect(page1.Items.Select(i => i.Id)));
}
[Fact]
public async Task SearchAsync_PageZero_ReturnsFirstPage()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
SiteCode = "BK5",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
SiteCode = "BK6",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(4),
Page = 0,
PageSize = 1
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(2, result.TotalCount);
Assert.Single(result.Items);
Assert.Equal(0, result.Page);
}
[Fact]
public async Task SearchAsync_TypesOther_ReturnsOtherRowsNotPastDueSentinel()
{
await using var context = CreateContext();
var today = DateTime.UtcNow.Date;
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
WorkOrderType = WorkOrderType.PM,
ScheduledDate = today.AddDays(-3),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
WorkOrderType = WorkOrderType.Other,
ScheduledDate = today.AddDays(3),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
Types = new List<WorkOrderType> { WorkOrderType.Other }
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(2, result.Items.First().Id);
Assert.Equal(WorkOrderType.Other, result.Items.First().WorkOrderType);
Assert.False(result.Items.First().IsPastDue);
}
[Fact]
public async Task SearchAsync_OverdueTrue_ReturnsPastDueRowsOnly()
{
await using var context = CreateContext();
var today = DateTime.UtcNow.Date;
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
WorkOrderType = WorkOrderType.PM,
DueDate = today.AddDays(-3),
ScheduledDate = today.AddDays(5),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
WorkOrderType = WorkOrderType.Other,
ScheduledDate = today.AddDays(3),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
},
new WorkOrder
{
Id = 3,
WorkOrderType = WorkOrderType.Reactive,
DueDate = today.AddDays(1),
ScheduledDate = today.AddDays(-5),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000003"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
Overdue = true
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(3, result.Items.First().Id);
Assert.True(result.Items.First().IsPastDue);
}
[Fact]
public async Task SearchAsync_OtherPlusOverdue_ReturnsUnion()
{
await using var context = CreateContext();
var today = DateTime.UtcNow.Date;
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
WorkOrderType = WorkOrderType.PM,
DueDate = today.AddDays(5),
ScheduledDate = today.AddDays(-3),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
WorkOrderType = WorkOrderType.Other,
ScheduledDate = today.AddDays(3),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
},
new WorkOrder
{
Id = 3,
WorkOrderType = WorkOrderType.Reactive,
DueDate = today.AddDays(1),
ScheduledDate = today.AddDays(1),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000003"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
Types = new List<WorkOrderType> { WorkOrderType.Other },
Overdue = true
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(2, result.TotalCount);
Assert.Contains(result.Items, i => i.Id == 1 && i.IsPastDue);
Assert.Contains(result.Items, i => i.Id == 2 && i.WorkOrderType == WorkOrderType.Other);
Assert.DoesNotContain(result.Items, i => i.Id == 3);
}
[Fact]
public async Task SearchAsync_OverdueDerivesFromDateAndStatus_NotWorkOrderType()
{
await using var context = CreateContext();
var today = DateTime.UtcNow.Date;
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
WorkOrderType = WorkOrderType.Other,
DueDate = today.AddDays(5),
ScheduledDate = today.AddDays(-5),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
WorkOrderType = WorkOrderType.PM,
DueDate = today.AddDays(-3),
ScheduledDate = today.AddDays(5),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
},
new WorkOrder
{
Id = 3,
WorkOrderType = WorkOrderType.Reactive,
DueDate = today.AddDays(-2),
ScheduledDate = today.AddDays(-2),
LifecycleStatus = LifecycleStatus.Completed,
InternalWONumber = "10000000003"
},
new WorkOrder
{
Id = 4,
WorkOrderType = WorkOrderType.Reactive,
DueDate = today.AddDays(-1),
ScheduledDate = today.AddDays(-1),
LifecycleStatus = LifecycleStatus.Canceled,
InternalWONumber = "10000000004"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
Overdue = true,
Page = 0,
PageSize = 50
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
var overdue = Assert.Single(result.Items);
Assert.Equal(0, result.Page);
Assert.Equal(1, overdue.Id);
Assert.True(overdue.IsPastDue);
Assert.DoesNotContain(result.Items, i => i.Id == 2);
Assert.DoesNotContain(result.Items, i => i.Id == 3);
Assert.DoesNotContain(result.Items, i => i.Id == 4);
}
[Fact]
public async Task SearchAsync_TreatsOtherAsRealTypeFilter_NotOverdueSentinel()
{
await using var context = CreateContext();
var today = DateTime.UtcNow.Date;
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
WorkOrderType = WorkOrderType.PM,
ScheduledDate = today.AddDays(2),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
WorkOrderType = WorkOrderType.Other,
ScheduledDate = today.AddDays(2),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
},
new WorkOrder
{
Id = 3,
WorkOrderType = WorkOrderType.Reactive,
ScheduledDate = today.AddDays(-3),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000003"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
Types = new List<WorkOrderType> { WorkOrderType.PM, WorkOrderType.Other },
Page = 0,
PageSize = 50
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(2, result.TotalCount);
Assert.Equal(0, result.Page);
Assert.Contains(result.Items, i => i.Id == 1 && i.WorkOrderType == WorkOrderType.PM);
Assert.Contains(result.Items, i => i.Id == 2 && i.WorkOrderType == WorkOrderType.Other);
Assert.DoesNotContain(result.Items, i => i.Id == 3);
}
[Fact]
public async Task SearchAsync_IncludesUnscheduledRows()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000002"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(4)
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(2, result.TotalCount);
Assert.Contains(result.Items, i => i.Id == 2);
}
[Fact]
public async Task SearchAsync_InvalidSortBy_Throws()
{
await using var context = CreateContext();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService, WorkOrderAccountTestHelpers.Resolver(context));
var ex = await Assert.ThrowsAsync<ArgumentException>(() => service.SearchAsync(
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.ThisWeek,
SortBy = "invalid"
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null));
Assert.Contains("sortBy", ex.Message);
}
[Fact]
public async Task SearchAsync_TypesAddOn_MatchesLegacyTypeOrIsAddOnFlag()
{
await using var context = CreateContext();
var today = DateTime.UtcNow.Date;
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
WorkOrderType = WorkOrderType.AddOn,
IsAddOn = false,
ScheduledDate = today.AddDays(1),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001"
},
new WorkOrder
{
Id = 2,
WorkOrderType = WorkOrderType.PM,
IsAddOn = true,
ScheduledDate = today.AddDays(2),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002"
},
new WorkOrder
{
Id = 3,
WorkOrderType = WorkOrderType.Reactive,
IsAddOn = false,
ScheduledDate = today.AddDays(3),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000003"
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
Types = new List<WorkOrderType> { WorkOrderType.AddOn }
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(2, result.TotalCount);
Assert.Contains(result.Items, i => i.Id == 1 && i.WorkOrderType == WorkOrderType.AddOn);
Assert.Contains(result.Items, i => i.Id == 2 && i.IsAddOn && i.WorkOrderType == WorkOrderType.PM);
Assert.DoesNotContain(result.Items, i => i.Id == 3);
}
[Fact]
public async Task SearchAsync_SeverityFilter_ReturnsEmergencyReactiveOnly()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
WorkOrderType = WorkOrderType.Emergency,
Severity = "1",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001",
},
new WorkOrder
{
Id = 2,
WorkOrderType = WorkOrderType.Reactive,
Severity = "2",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002",
},
new WorkOrder
{
Id = 3,
WorkOrderType = WorkOrderType.PM,
Severity = "1",
ScheduledDate = new DateTime(2026, 6, 25),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000003",
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(6),
Severities = new List<int> { 1 },
},
WorkOrderAccountTestHelpers.OrgWideAdmin(),
null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(1, result.Items.Single().Id);
}
[Fact]
public async Task SearchAsync_InternalOnly_ReturnsShPrefixRows()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
InternalWONumber = "SH-1001",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
},
new WorkOrder
{
Id = 2,
InternalWONumber = "10000000002",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(6),
InternalOnly = true,
},
WorkOrderAccountTestHelpers.OrgWideAdmin(),
null);
Assert.Equal(1, result.TotalCount);
Assert.Equal("SH-1001", result.Items.Single().WoNumber);
}
[Fact]
public async Task SearchAsync_HasUpliftWithPendingStatus_FiltersRows()
{
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.Dispatches.Add(new Dispatch { Id = 11, VendorId = 1, WorkOrderId = 2 });
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
PrimaryDispatchId = 10,
},
new WorkOrder
{
Id = 2,
InternalWONumber = "10000000002",
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
PrimaryDispatchId = 11,
});
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
{
Id = 100,
DispatchId = 10,
RequestedNTE = 1500m,
Status = "Pending",
RequiredTier = 1,
NotificationStatus = "Pending",
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(6),
HasUplift = true,
UpliftStatuses = new List<string> { "pending" },
},
WorkOrderAccountTestHelpers.OrgWideAdmin(),
null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(1, result.Items.Single().Id);
}
[Fact]
public async Task SearchAsync_AvetaOnly_ReturnsFlaggedRows()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
AvetaRequired = true,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001",
},
new WorkOrder
{
Id = 2,
AvetaRequired = false,
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002",
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(6),
AvetaOnly = true,
},
WorkOrderAccountTestHelpers.OrgWideAdmin(),
null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(1, result.Items.Single().Id);
Assert.True(result.Items.Single().AvetaRequired);
}
[Fact]
public async Task SearchAsync_UnscheduledOnly_ReturnsOnlyUndatedRows()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001",
istemplate = false
},
new WorkOrder
{
Id = 2,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000002",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(4),
UnscheduledOnly = true
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Single(result.Items);
Assert.Equal(2, result.Items.Single().Id);
Assert.Null(result.Items.Single().ScheduledDate);
}
[Fact]
public async Task SearchAsync_UnscheduledOnlyFalse_MatchesDefaultRangeSearch()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001",
istemplate = false
},
new WorkOrder
{
Id = 2,
ScheduledDate = new DateTime(2026, 8, 4),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002",
istemplate = false
},
new WorkOrder
{
Id = 3,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000003",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var withFlagFalse = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(4),
UnscheduledOnly = false
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
var withFlagAbsent = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(4)
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(withFlagAbsent.TotalCount, withFlagFalse.TotalCount);
Assert.Equal(
withFlagAbsent.Items.Select(i => i.Id).ToList(),
withFlagFalse.Items.Select(i => i.Id).ToList());
Assert.Equal(2, withFlagFalse.TotalCount);
Assert.Contains(withFlagFalse.Items, i => i.Id == 1);
Assert.Contains(withFlagFalse.Items, i => i.Id == 3);
Assert.DoesNotContain(withFlagFalse.Items, i => i.Id == 2);
}
[Fact]
public async Task SearchAsync_UnscheduledOnly_IgnoresScheduledDateRange()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001",
istemplate = false
},
new WorkOrder
{
Id = 2,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000002",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(4),
UnscheduledOnly = true
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Single(result.Items);
Assert.Equal(2, result.Items.Single().Id);
}
[Fact]
public async Task SearchAsync_UnscheduledOnly_CustomPresetWithoutDates_DoesNotThrow()
{
await using var context = CreateContext();
context.workOrders.Add(new WorkOrder
{
Id = 1,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000001",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
UnscheduledOnly = true
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(1, result.Items.Single().Id);
}
[Fact]
public async Task SearchAsync_UnscheduledOnly_ComposesWithAccountScopeAndSiteFacet()
{
await using var context = CreateContext();
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
AccountId = 1,
SiteCode = "BK5",
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000001",
istemplate = false
},
new WorkOrder
{
Id = 2,
AccountId = 2,
SiteCode = "BK5",
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000002",
istemplate = false
},
new WorkOrder
{
Id = 3,
AccountId = 1,
SiteCode = "BK6",
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000003",
istemplate = false
},
new WorkOrder
{
Id = 4,
AccountId = 1,
SiteCode = "BK5",
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000004",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2026, 6, 22),
DateTo = new DateOnly(2026, 6, 26),
UnscheduledOnly = true,
Sites = new List<string> { "BK5" }
}, WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"), "disp-1");
Assert.Equal(1, result.TotalCount);
Assert.Single(result.Items);
Assert.Equal(1, result.Items.Single().Id);
}
[Fact]
public async Task SearchAsync_UnscheduledOnly_NoUndatedRows_ReturnsEmptyPage()
{
await using var context = CreateContext();
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001",
istemplate = false
},
new WorkOrder
{
Id = 2,
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2000, 1, 1),
DateTo = new DateOnly(2099, 12, 31),
UnscheduledOnly = true,
Page = 0,
PageSize = 25
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(0, result.TotalCount);
Assert.Empty(result.Items);
Assert.Equal(0, result.Page);
Assert.Equal(25, result.PageSize);
}
[Fact]
public async Task SearchAsync_UnscheduledOnly_PaginatesStableOrderWithoutDuplicates()
{
await using var context = CreateContext();
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000003",
istemplate = false
},
new WorkOrder
{
Id = 2,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000001",
istemplate = false
},
new WorkOrder
{
Id = 3,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000002",
istemplate = false
},
new WorkOrder
{
Id = 4,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000000",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var page0 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2026, 6, 22),
DateTo = new DateOnly(2026, 6, 26),
UnscheduledOnly = true,
Page = 0,
PageSize = 2
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
var page1 = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2026, 6, 22),
DateTo = new DateOnly(2026, 6, 26),
UnscheduledOnly = true,
Page = 1,
PageSize = 2
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(3, page0.TotalCount);
Assert.Equal(2, page0.Items.Count());
Assert.Equal(new[] { 2, 3 }, page0.Items.Select(i => i.Id).ToArray());
Assert.Equal(3, page1.TotalCount);
Assert.Single(page1.Items);
Assert.Equal(1, page1.Items.Single().Id);
Assert.Empty(page0.Items.Select(i => i.Id).Intersect(page1.Items.Select(i => i.Id)));
Assert.Equal(new[] { 1, 2, 3 }, page0.Items.Concat(page1.Items).Select(i => i.Id).OrderBy(id => id).ToArray());
}
[Fact]
public async Task SearchAsync_UnscheduledOnly_ExcludesCompletedAndCanceledUndatedRows()
{
await using var context = CreateContext();
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Incomplete,
InternalWONumber = "10000000001",
istemplate = false
},
new WorkOrder
{
Id = 2,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Completed,
InternalWONumber = "10000000002",
istemplate = false
},
new WorkOrder
{
Id = 3,
ScheduledDate = null,
LifecycleStatus = LifecycleStatus.Canceled,
InternalWONumber = "10000000003",
istemplate = false
},
new WorkOrder
{
Id = 4,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Completed,
InternalWONumber = "10000000004",
istemplate = false
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = new DateOnly(2026, 6, 22),
DateTo = new DateOnly(2026, 6, 26),
UnscheduledOnly = true
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Equal(1, result.TotalCount);
Assert.Single(result.Items);
Assert.Equal(1, result.Items.Single().Id);
Assert.Null(result.Items.Single().ScheduledDate);
Assert.DoesNotContain(result.Items, i => i.Id == 2);
Assert.DoesNotContain(result.Items, i => i.Id == 3);
Assert.DoesNotContain(result.Items, i => i.Id == 4);
}
}