Files

91 lines
3.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ClawdDotNet.Core.Scheduling;
using Shouldly;
namespace ClawdDotNet.Core.Tests.Scheduling;
/// <summary>
/// Cron-Parsing und Terminberechnung (deckt Teile von B6/B7 ab). Der bisher ungetestete
/// Basis-Scheduler bekommt hier sein Netz: gültige Ausdrücke rechnen richtig, ungültige
/// werfen eine saubere <see cref="FormatException"/> statt einer rohen Parser-Exception
/// oder einer Endlosschleife.
/// </summary>
public sealed class CronExpressionTests
{
private static DateTime Local(int y, int mo, int d, int h, int mi)
=> new(y, mo, d, h, mi, 0, DateTimeKind.Local);
// ─── R1: korrekte nächste Zeitpunkte ───
[Fact]
public void Alle_30_Minuten()
{
var cron = CronExpression.Parse("*/30 * * * *");
cron.GetNextOccurrence(Local(2026, 7, 31, 10, 5)).ShouldBe(Local(2026, 7, 31, 10, 30));
cron.GetNextOccurrence(Local(2026, 7, 31, 10, 45)).ShouldBe(Local(2026, 7, 31, 11, 0));
}
[Fact]
public void Werktags_um_drei_Uhr_frueh()
{
var cron = CronExpression.Parse("0 3 * * 1-5"); // MoFr
// 2026-08-01 ist ein Samstag → nächster Lauf Montag, 2026-08-03 03:00
cron.GetNextOccurrence(Local(2026, 8, 1, 4, 0)).ShouldBe(Local(2026, 8, 3, 3, 0));
}
[Fact]
public void Listen_von_Minuten()
{
var cron = CronExpression.Parse("15,45 * * * *");
cron.GetNextOccurrence(Local(2026, 7, 31, 10, 20)).ShouldBe(Local(2026, 7, 31, 10, 45));
cron.GetNextOccurrence(Local(2026, 7, 31, 10, 50)).ShouldBe(Local(2026, 7, 31, 11, 15));
}
[Fact]
public void Schaltjahr_29_Februar()
{
var cron = CronExpression.Parse("0 0 29 2 *"); // nur am 29.02.
// 2028 ist das nächste Schaltjahr
cron.GetNextOccurrence(Local(2026, 3, 1, 0, 0)).ShouldBe(Local(2028, 2, 29, 0, 0));
}
// ─── R2: Invariante ───
[Theory]
[InlineData("*/5 * * * *")]
[InlineData("0 3 * * 1-5")]
[InlineData("15,45 9-17 * * *")]
[InlineData("0 0 1 * *")]
public void Der_naechste_Termin_liegt_echt_in_der_Zukunft_und_passt(string expr)
{
var cron = CronExpression.Parse(expr);
var now = Local(2026, 7, 31, 10, 23);
var next = cron.GetNextOccurrence(now);
next.ShouldNotBeNull();
next!.Value.ShouldBeGreaterThan(now);
cron.Matches(next.Value).ShouldBeTrue();
}
// ─── R3: ungültige Ausdrücke ───
[Theory]
[InlineData("* * *")] // zu wenige Felder
[InlineData("* * * * * *")] // zu viele Felder
[InlineData("99 * * * *")] // Minute außerhalb 0-59
[InlineData("* 25 * * *")] // Stunde außerhalb 0-23
[InlineData("a b c d e")] // keine Zahlen
[InlineData("*/0 * * * *")] // Schritt 0 (wäre Endlosschleife)
[InlineData("5-2 * * * *")] // rückwärtiger 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));
[Fact]
public void Ein_gueltiger_Ausdruck_mit_Schritt_bleibt_endlich()
{
// Regressionswächter: */0 hätte früher eine Endlosschleife erzeugt.
Should.CompleteIn(() => CronExpression.Parse("*/15 * * * *"), TimeSpan.FromSeconds(2));
}
}