Files
git.stella-ops.org/src/StellaOps.Scheduler.Models.Tests/RunStateMachineTests.cs
2025-10-20 14:19:25 +03:00

109 lines
3.7 KiB
C#

using StellaOps.Scheduler.Models;
namespace StellaOps.Scheduler.Models.Tests;
public sealed class RunStateMachineTests
{
[Fact]
public void EnsureTransition_FromQueuedToRunningSetsStartedAt()
{
var run = new Run(
id: "run-queued",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Queued,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"));
var transitionTime = DateTimeOffset.Parse("2025-10-18T03:05:00Z");
var updated = RunStateMachine.EnsureTransition(
run,
RunState.Running,
transitionTime,
mutateStats: builder => builder.SetQueued(1));
Assert.Equal(RunState.Running, updated.State);
Assert.Equal(transitionTime.ToUniversalTime(), updated.StartedAt);
Assert.Equal(1, updated.Stats.Queued);
Assert.Null(updated.Error);
}
[Fact]
public void EnsureTransition_ToCompletedPopulatesFinishedAt()
{
var run = new Run(
id: "run-running",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Running,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"),
startedAt: DateTimeOffset.Parse("2025-10-18T03:05:00Z"));
var completedAt = DateTimeOffset.Parse("2025-10-18T03:10:00Z");
var updated = RunStateMachine.EnsureTransition(
run,
RunState.Completed,
completedAt,
mutateStats: builder =>
{
builder.SetQueued(1);
builder.SetCompleted(1);
});
Assert.Equal(RunState.Completed, updated.State);
Assert.Equal(completedAt.ToUniversalTime(), updated.FinishedAt);
Assert.Equal(1, updated.Stats.Completed);
}
[Fact]
public void EnsureTransition_ErrorRequiresMessage()
{
var run = new Run(
id: "run-running",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Running,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"),
startedAt: DateTimeOffset.Parse("2025-10-18T03:05:00Z"));
var timestamp = DateTimeOffset.Parse("2025-10-18T03:06:00Z");
var ex = Assert.Throws<InvalidOperationException>(
() => RunStateMachine.EnsureTransition(run, RunState.Error, timestamp));
Assert.Contains("requires a non-empty error message", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void Validate_ThrowsWhenTerminalWithoutFinishedAt()
{
var run = new Run(
id: "run-bad",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Completed,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"),
startedAt: DateTimeOffset.Parse("2025-10-18T03:05:00Z"));
Assert.Throws<InvalidOperationException>(() => RunStateMachine.Validate(run));
}
[Fact]
public void RunReasonExtension_NormalizesImpactWindow()
{
var reason = new RunReason(manualReason: "delta");
var from = DateTimeOffset.Parse("2025-10-18T01:00:00+02:00");
var to = DateTimeOffset.Parse("2025-10-18T03:30:00+02:00");
var updated = reason.WithImpactWindow(from, to);
Assert.Equal(from.ToUniversalTime().ToString("O"), updated.ImpactWindowFrom);
Assert.Equal(to.ToUniversalTime().ToString("O"), updated.ImpactWindowTo);
}
}