Cron: Bereich mit Schritt (7-22/3) lesen, ungueltige Ausdruecke im Scanner melden (#597)
Der Nachrichten-Digest im Dauerbetrieb der Test-VM lief nie: CronExpression verstand 'von-bis/schritt' nicht, TaskSchedule verschluckte den Fehler. Version 0.1.5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,24 @@ public sealed class CronExpressionTests
|
||||
cron.GetNextOccurrence(Local(2026, 3, 1, 0, 0)).ShouldBe(Local(2028, 2, 29, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bereich_mit_Schritt_alle_drei_Stunden_tagsueber()
|
||||
{
|
||||
// #597: "0 7-22/3 * * *" warf vorher eine FormatException, die Aufgabe lief nie.
|
||||
var cron = CronExpression.Parse("0 7-22/3 * * *"); // 7, 10, 13, 16, 19, 22
|
||||
cron.GetNextOccurrence(Local(2026, 9, 14, 12, 0)).ShouldBe(Local(2026, 9, 14, 13, 0));
|
||||
cron.GetNextOccurrence(Local(2026, 9, 14, 22, 0)).ShouldBe(Local(2026, 9, 15, 7, 0));
|
||||
cron.Matches(Local(2026, 9, 14, 8, 0)).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Zahl_mit_Schritt_laeuft_bis_zum_Feldende()
|
||||
{
|
||||
var cron = CronExpression.Parse("10/20 * * * *"); // 10, 30, 50
|
||||
cron.GetNextOccurrence(Local(2026, 9, 14, 12, 31)).ShouldBe(Local(2026, 9, 14, 12, 50));
|
||||
cron.GetNextOccurrence(Local(2026, 9, 14, 12, 50)).ShouldBe(Local(2026, 9, 14, 13, 10));
|
||||
}
|
||||
|
||||
// ─── R2: Invariante ───
|
||||
|
||||
[Theory]
|
||||
@@ -77,6 +95,9 @@ public sealed class CronExpressionTests
|
||||
[InlineData("a b c d e")] // keine Zahlen
|
||||
[InlineData("*/0 * * * *")] // Schritt 0 (wäre Endlosschleife)
|
||||
[InlineData("5-2 * * * *")] // rückwärtiger Bereich
|
||||
[InlineData("0 22-7/3 * * *")] // rückwärtiger Bereich mit Schritt
|
||||
[InlineData("0 7-25/3 * * *")] // Bereichsende außerhalb 0-23
|
||||
[InlineData("0 7-/3 * * *")] // halber Bereich
|
||||
[InlineData("* * * * ")] // ein leeres Feld nach Trim → 4 Felder
|
||||
public void Ungueltige_Ausdruecke_werfen_eine_saubere_FormatException(string expr)
|
||||
=> Should.Throw<FormatException>(() => CronExpression.Parse(expr));
|
||||
|
||||
@@ -13,6 +13,42 @@ public sealed class TaskScheduleTests
|
||||
private static DateTime Utc(int y, int mo, int d, int h, int mi)
|
||||
=> new(y, mo, d, h, mi, 0, DateTimeKind.Utc);
|
||||
|
||||
// ─── #597: ungültiger Cron-Ausdruck ───
|
||||
|
||||
[Fact]
|
||||
public void Ungueltiger_Cron_Ausdruck_wird_benannt_statt_verschwiegen()
|
||||
{
|
||||
var task = new TaskItem
|
||||
{
|
||||
Id = "t-1", CreatedAt = Utc(2026, 9, 14, 6, 0),
|
||||
When = new TaskWhen { Kind = TaskWhenKind.Cron, Value = "0 7-22/x * * *", TimeZone = "Europe/Berlin" }
|
||||
};
|
||||
|
||||
TaskSchedule.DueOccurrence(task, Utc(2026, 9, 14, 11, 0)).ShouldBeNull();
|
||||
TaskSchedule.InvalidCron(task).ShouldNotBeNull().ShouldContain("'x' ist keine Zahl");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Nachrichten_Digest_aus_dem_Testbetrieb_ist_um_13_Uhr_faellig()
|
||||
{
|
||||
var task = new TaskItem
|
||||
{
|
||||
Id = "t-7e0bbe", CreatedAt = Utc(2026, 9, 14, 10, 30),
|
||||
When = new TaskWhen { Kind = TaskWhenKind.Cron, Value = "0 7-22/3 * * *", TimeZone = "Europe/Berlin" }
|
||||
};
|
||||
|
||||
TaskSchedule.InvalidCron(task).ShouldBeNull();
|
||||
// 13:00 Berlin (MESZ) = 11:00 UTC
|
||||
TaskSchedule.DueOccurrence(task, Utc(2026, 9, 14, 11, 0)).ShouldBe("2026-09-14T11:00:00.0000000Z");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Andere_Terminarten_haben_keinen_Cron_Fehler()
|
||||
=> TaskSchedule.InvalidCron(new TaskItem
|
||||
{
|
||||
Id = "t-1", When = new TaskWhen { Kind = TaskWhenKind.Every, Value = "kaputt" }
|
||||
}).ShouldBeNull();
|
||||
|
||||
// ─── Einmalig, ohne Termin ───
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user