Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt). Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e): Properties, Record-Parameter, Enum-Member sowie die Parameter der Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
4.1 KiB
C#
110 lines
4.1 KiB
C#
using ClawdDotNet.Core.Tasks;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Core.Tests.Tasks;
|
|
|
|
/// <summary>
|
|
/// Checking schedules, next run and runs per day (#594, #595, #597) as well as the hint
|
|
/// about growing context (#598).
|
|
/// </summary>
|
|
public sealed class TaskboardExtensionTests
|
|
{
|
|
private static DateTime Utc(int y, int mo, int d, int h, int mi) => new(y, mo, d, h, mi, 0, DateTimeKind.Utc);
|
|
|
|
private static TaskWhen Cron(string value, string tz = "Europe/Berlin")
|
|
=> new() { Kind = TaskWhenKind.Cron, Value = value, TimeZone = tz };
|
|
|
|
// ─── #597 checking schedules ───
|
|
|
|
[Fact]
|
|
public void Valid_schedule_with_range_and_step_passes_the_check()
|
|
=> TaskSchedule.ValidateWhen(Cron("0 7-22/3 * * *")).ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void Broken_cron_expression_is_named()
|
|
=> TaskSchedule.ValidateWhen(Cron("0 7-22/x * * *")).ShouldNotBeNull().ShouldContain("'x' ist keine Zahl");
|
|
|
|
[Fact]
|
|
public void Unknown_time_zone_is_named()
|
|
=> TaskSchedule.ValidateWhen(Cron("0 9 * * *", "Phantasie/Ort")).ShouldNotBeNull().ShouldContain("Phantasie/Ort");
|
|
|
|
[Theory]
|
|
[InlineData(TaskWhenKind.Every, "30m", true)]
|
|
[InlineData(TaskWhenKind.Every, "halbstündlich", false)]
|
|
[InlineData(TaskWhenKind.At, "2026-09-15T08:00", true)]
|
|
[InlineData(TaskWhenKind.At, "morgen früh", false)]
|
|
public void Interval_and_time_are_checked(TaskWhenKind kind, string value, bool valid)
|
|
=> (TaskSchedule.ValidateWhen(new TaskWhen { Kind = kind, Value = value, TimeZone = "Europe/Berlin" }) is null)
|
|
.ShouldBe(valid);
|
|
|
|
// ─── #594 next run ───
|
|
|
|
[Fact]
|
|
public void Next_run_of_a_cron_schedule_in_Berlin_time()
|
|
{
|
|
var task = new TaskItem { Id = "t-7e0bbe", When = Cron("0 7-22/3 * * *") };
|
|
|
|
// 12:30 Berlin (CEST) -> next occurrence 13:00 Berlin = 11:00 UTC
|
|
TaskSchedule.NextOccurrenceUtc(task, Utc(2026, 9, 14, 10, 30)).ShouldBe(Utc(2026, 9, 14, 11, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void Next_run_of_an_interval_counts_from_the_last_run()
|
|
{
|
|
var task = new TaskItem
|
|
{
|
|
Id = "t-1",
|
|
When = new TaskWhen { Kind = TaskWhenKind.Every, Value = "2h", TimeZone = "" },
|
|
LastOccurrence = "2026-09-14T09:00:00.0000000Z"
|
|
};
|
|
|
|
TaskSchedule.NextOccurrenceUtc(task, Utc(2026, 9, 14, 10, 0)).ShouldBe(Utc(2026, 9, 14, 11, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void One_off_task_is_pending_until_it_ran()
|
|
{
|
|
var now = Utc(2026, 9, 14, 10, 0);
|
|
TaskSchedule.NextOccurrenceUtc(new TaskItem { Id = "t-1" }, now).ShouldBe(now);
|
|
TaskSchedule.NextOccurrenceUtc(new TaskItem { Id = "t-1", LastOccurrence = "x" }, now).ShouldBeNull();
|
|
}
|
|
|
|
// ─── #595 runs per day ───
|
|
|
|
[Fact]
|
|
public void Runs_per_day_from_cron_and_interval()
|
|
{
|
|
var now = Utc(2026, 9, 14, 10, 0);
|
|
|
|
TaskSchedule.RunsPerDay(new TaskItem { When = Cron("0 7-22/3 * * *") }, now).ShouldBe(6);
|
|
TaskSchedule.RunsPerDay(new TaskItem { When = Cron("5 * * * *") }, now).ShouldBe(24);
|
|
TaskSchedule.RunsPerDay(new TaskItem { When = new TaskWhen { Kind = TaskWhenKind.Every, Value = "30m" } }, now).ShouldBe(48);
|
|
TaskSchedule.RunsPerDay(new TaskItem(), now).ShouldBe(0);
|
|
}
|
|
|
|
// ─── #598 hint ───
|
|
|
|
[Fact]
|
|
public void Recurring_in_chat_history_gets_the_hint()
|
|
=> TaskHints.GrowingContext(new TaskItem { Assignee = "@nachrichten", When = Cron("0 7-22/3 * * *") })
|
|
.ShouldNotBeNull().ShouldContain("@new:nachrichten");
|
|
|
|
[Theory]
|
|
[InlineData("@new:nachrichten", true)]
|
|
[InlineData("@nachrichten", false)]
|
|
[InlineData("@human", true)]
|
|
public void No_hint_for_fresh_runs_one_off_and_human_tasks(string assignee, bool recurring)
|
|
=> TaskHints.GrowingContext(new TaskItem
|
|
{
|
|
Assignee = assignee,
|
|
When = recurring ? Cron("5 * * * *") : null
|
|
}).ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void No_hint_for_tool_jobs()
|
|
=> TaskHints.GrowingContext(new TaskItem
|
|
{
|
|
Assignee = "@telegram", Type = TaskItemType.ToolJob, When = Cron("* * * * *")
|
|
}).ShouldBeNull();
|
|
}
|