feat(worker): add NextDueCalculator with workday + catch-up logic
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
tests/ClaudeDo.Worker.Tests/Prime/NextDueCalculatorTests.cs
Normal file
91
tests/ClaudeDo.Worker.Tests/Prime/NextDueCalculatorTests.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using ClaudeDo.Worker.Prime;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.Prime;
|
||||
|
||||
public class NextDueCalculatorTests
|
||||
{
|
||||
private static PrimeScheduleDto Schedule(
|
||||
DateOnly start, DateOnly end, TimeSpan time,
|
||||
bool workdaysOnly = true, bool enabled = true, DateTimeOffset? lastRun = null) =>
|
||||
new(Guid.NewGuid(), start, end, time, workdaysOnly, enabled, lastRun, null);
|
||||
|
||||
[Fact]
|
||||
public void Disabled_Schedule_Returns_Null()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||
var s = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0), enabled: false);
|
||||
Assert.Null(NextDueCalculator.Compute(new[]{s}, now, TimeSpan.FromMinutes(30)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Future_Same_Day_Returns_Today_At_Target()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||
var s = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0));
|
||||
var r = NextDueCalculator.Compute(new[]{s}, now, TimeSpan.FromMinutes(30));
|
||||
Assert.NotNull(r);
|
||||
Assert.Equal(new DateTimeOffset(2026,5,5,7,0,0, now.Offset), r!.At);
|
||||
Assert.False(r.FireImmediately);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Within_CatchUp_Window_Fires_Immediately()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 5, 5, 7, 15, 0, TimeSpan.FromHours(2));
|
||||
var s = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0));
|
||||
var r = NextDueCalculator.Compute(new[]{s}, now, TimeSpan.FromMinutes(30));
|
||||
Assert.NotNull(r);
|
||||
Assert.True(r!.FireImmediately);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Past_CatchUp_Window_Skips_To_Next_Eligible_Day()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 5, 5, 9, 0, 0, TimeSpan.FromHours(2));
|
||||
var s = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0));
|
||||
var r = NextDueCalculator.Compute(new[]{s}, now, TimeSpan.FromMinutes(30));
|
||||
Assert.NotNull(r);
|
||||
Assert.Equal(new DateOnly(2026,5,6), DateOnly.FromDateTime(r!.At.LocalDateTime));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkdaysOnly_Skips_Weekend()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 5, 8, 8, 0, 0, TimeSpan.FromHours(2));
|
||||
var s = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0), workdaysOnly: true);
|
||||
var r = NextDueCalculator.Compute(new[]{s}, now, TimeSpan.FromMinutes(30));
|
||||
Assert.NotNull(r);
|
||||
Assert.Equal(DayOfWeek.Monday, r!.At.LocalDateTime.DayOfWeek);
|
||||
Assert.Equal(new DateOnly(2026,5,11), DateOnly.FromDateTime(r.At.LocalDateTime));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Already_Fired_Today_Skips_To_Tomorrow()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||
var lastRun = new DateTimeOffset(2026, 5, 5, 7, 1, 0, TimeSpan.FromHours(2));
|
||||
var s = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0), lastRun: lastRun);
|
||||
var r = NextDueCalculator.Compute(new[]{s}, now, TimeSpan.FromMinutes(30));
|
||||
Assert.NotNull(r);
|
||||
Assert.Equal(new DateOnly(2026,5,6), DateOnly.FromDateTime(r!.At.LocalDateTime));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Past_EndDate_Returns_Null()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 6, 1, 6, 0, 0, TimeSpan.FromHours(2));
|
||||
var s = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0));
|
||||
Assert.Null(NextDueCalculator.Compute(new[]{s}, now, TimeSpan.FromMinutes(30)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Multiple_Schedules_Returns_Earliest()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
|
||||
var early = Schedule(new(2026,5,1), new(2026,5,31), new(7,0,0));
|
||||
var late = Schedule(new(2026,5,1), new(2026,5,31), new(9,0,0));
|
||||
var r = NextDueCalculator.Compute(new[]{late, early}, now, TimeSpan.FromMinutes(30));
|
||||
Assert.NotNull(r);
|
||||
Assert.Equal(early.Id, r!.Schedule.Id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user