- Engine: leere Antworten nachfassen, Aufgaben enden mit Ergebniszeile (EmptyResponse/Incomplete), gleiche Fehlschlaege erkennen (RepeatedFailure), Zeitzeile vor Chatnachrichten, Werkzeugergebnisse gerahmt, Vorpruefung vor dem Staging, Belegkosten wie RunUsage - Staging: gescheiterte Freigaben als Failed; AgentEditor-Aenderungen freigabepflichtig, Selbstbearbeitung gesperrt (allowSelfEdit) - Taskboard: Termine beim Anlegen pruefen, naechster/letzter Lauf, pausieren, Sofortlauf, Kostenprognose, Hinweis auf wachsenden Kontext; Jobs-Reiter und /api/jobs entfernt - Release: Instanzmanager samt Weboberflaeche im Paket, setup.json fragt manager.json ab, Token nicht mehr ins systemd-Journal Version 0.1.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
4.3 KiB
C#
110 lines
4.3 KiB
C#
using ClawdDotNet.Core.Tasks;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Core.Tests.Tasks;
|
|
|
|
/// <summary>
|
|
/// Termine pruefen, naechster Lauf und Laeufe je Tag (#594, #595, #597) sowie der Hinweis
|
|
/// auf wachsenden Kontext (#598).
|
|
/// </summary>
|
|
public sealed class TaskboardAusbauTests
|
|
{
|
|
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 Termin pruefen ───
|
|
|
|
[Fact]
|
|
public void Gueltiger_Termin_mit_Bereich_und_Schritt_besteht_die_Pruefung()
|
|
=> TaskSchedule.ValidateWhen(Cron("0 7-22/3 * * *")).ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void Kaputter_Cron_Ausdruck_wird_benannt()
|
|
=> TaskSchedule.ValidateWhen(Cron("0 7-22/x * * *")).ShouldNotBeNull().ShouldContain("'x' ist keine Zahl");
|
|
|
|
[Fact]
|
|
public void Unbekannte_Zeitzone_wird_benannt()
|
|
=> 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 Intervall_und_Zeitpunkt_werden_geprueft(TaskWhenKind kind, string value, bool gueltig)
|
|
=> (TaskSchedule.ValidateWhen(new TaskWhen { Kind = kind, Value = value, TimeZone = "Europe/Berlin" }) is null)
|
|
.ShouldBe(gueltig);
|
|
|
|
// ─── #594 naechster Lauf ───
|
|
|
|
[Fact]
|
|
public void Naechster_Lauf_eines_Cron_Termins_in_Berliner_Zeit()
|
|
{
|
|
var task = new TaskItem { Id = "t-7e0bbe", When = Cron("0 7-22/3 * * *") };
|
|
|
|
// 12:30 Berlin (MESZ) -> naechster Termin 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 Naechster_Lauf_eines_Intervalls_zaehlt_ab_dem_letzten_Lauf()
|
|
{
|
|
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 Einmalige_Aufgabe_steht_an_bis_sie_lief()
|
|
{
|
|
var jetzt = Utc(2026, 9, 14, 10, 0);
|
|
TaskSchedule.NextOccurrenceUtc(new TaskItem { Id = "t-1" }, jetzt).ShouldBe(jetzt);
|
|
TaskSchedule.NextOccurrenceUtc(new TaskItem { Id = "t-1", LastOccurrence = "x" }, jetzt).ShouldBeNull();
|
|
}
|
|
|
|
// ─── #595 Laeufe je Tag ───
|
|
|
|
[Fact]
|
|
public void Laeufe_je_Tag_aus_Cron_und_Intervall()
|
|
{
|
|
var jetzt = Utc(2026, 9, 14, 10, 0);
|
|
|
|
TaskSchedule.RunsPerDay(new TaskItem { When = Cron("0 7-22/3 * * *") }, jetzt).ShouldBe(6);
|
|
TaskSchedule.RunsPerDay(new TaskItem { When = Cron("5 * * * *") }, jetzt).ShouldBe(24);
|
|
TaskSchedule.RunsPerDay(new TaskItem { When = new TaskWhen { Kind = TaskWhenKind.Every, Value = "30m" } }, jetzt).ShouldBe(48);
|
|
TaskSchedule.RunsPerDay(new TaskItem(), jetzt).ShouldBe(0);
|
|
}
|
|
|
|
// ─── #598 Hinweis ───
|
|
|
|
[Fact]
|
|
public void Wiederkehrend_im_Chatverlauf_bekommt_den_Hinweis()
|
|
=> TaskHinweise.WachsenderKontext(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 Kein_Hinweis_fuer_frische_Laeufe_einmalige_und_menschliche_Aufgaben(string assignee, bool wiederkehrend)
|
|
=> TaskHinweise.WachsenderKontext(new TaskItem
|
|
{
|
|
Assignee = assignee,
|
|
When = wiederkehrend ? Cron("5 * * * *") : null
|
|
}).ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void Kein_Hinweis_fuer_Werkzeugjobs()
|
|
=> TaskHinweise.WachsenderKontext(new TaskItem
|
|
{
|
|
Assignee = "@telegram", Type = TaskItemType.ToolJob, When = Cron("* * * * *")
|
|
}).ShouldBeNull();
|
|
}
|