using w4c_workflows.Models; using w4c_workflows.Services.Triggers; using Xunit; namespace w4c_workflows.Tests; public class TriggerDueTests { private static readonly DateTimeOffset Now = new(2026, 8, 28, 14, 30, 0, TimeSpan.Zero); private static TriggerSpec Interval(string interval) => TriggerSpec.Parse($"{{\"type\":\"interval\",\"interval\":\"{interval}\"}}", out _)!; private static TriggerSpec Cron(string expr) => TriggerSpec.Parse($"{{\"type\":\"cron\",\"cron\":\"{expr}\"}}", out _)!; [Fact] public void Fires_on_first_discovery_when_no_last_fire() { Assert.True(TriggerDue.IsDue(Interval("30m"), null, Now)); Assert.True(TriggerDue.IsDue(Cron("0 9 * * *"), null, Now)); } [Fact] public void Interval_not_due_before_elapse() { var lastFire = Now.AddMinutes(-10); Assert.False(TriggerDue.IsDue(Interval("30m"), lastFire, Now)); } [Fact] public void Interval_due_once_elapsed() { var lastFire = Now.AddMinutes(-31); Assert.True(TriggerDue.IsDue(Interval("30m"), lastFire, Now)); } [Fact] public void Interval_zero_is_never_due() { var spec = new TriggerSpec(TriggerType.Interval, null, TimeSpan.Zero, null, null); Assert.False(TriggerDue.IsDue(spec, Now.AddHours(-1), Now)); } [Fact] public void Cron_due_when_next_occurrence_has_passed() { // 9:00 daily; last fired yesterday 09:00, now past today's 09:00. var lastFire = new DateTimeOffset(2026, 8, 27, 9, 0, 0, TimeSpan.Zero); Assert.True(TriggerDue.IsDue(Cron("0 9 * * *"), lastFire, Now)); } [Fact] public void Cron_not_due_when_next_occurrence_in_future() { var lastFire = Now.AddMinutes(-1); // fired 1 minute ago; next daily 09:00 is future Assert.False(TriggerDue.IsDue(Cron("0 9 * * *"), lastFire, Now)); } [Fact] public void Unknown_type_is_never_due() { var spec = new TriggerSpec("webhook", null, null, "/h/x", null); Assert.False(TriggerDue.IsDue(spec, Now.AddHours(-1), Now)); } }