Files
git.stella-ops.org/src/JobEngine/StellaOps.TaskRunner/StellaOps.TaskRunner.Tests/PackRunGateStateUpdaterTests.cs

155 lines
5.5 KiB
C#

using System;
using System.Collections.Generic;
using StellaOps.TaskRunner.Core.Execution;
using StellaOps.TaskRunner.Core.Planning;
using StellaOps.TestKit;
namespace StellaOps.TaskRunner.Tests;
public sealed class PackRunGateStateUpdaterTests
{
private static readonly DateTimeOffset RequestedAt = DateTimeOffset.UnixEpoch;
private static readonly DateTimeOffset UpdateTimestamp = DateTimeOffset.UnixEpoch.AddMinutes(5);
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Apply_ApprovedGate_ClearsReasonAndSucceeds()
{
var plan = BuildApprovalPlan();
var graph = new PackRunExecutionGraphBuilder().Build(plan);
var state = CreateInitialState(plan, graph);
var coordinator = PackRunApprovalCoordinator.Create(plan, RequestedAt);
coordinator.Approve("security-review", "approver-1", UpdateTimestamp);
var result = PackRunGateStateUpdater.Apply(state, graph, coordinator, UpdateTimestamp);
Assert.False(result.HasBlockingFailure);
Assert.Equal(UpdateTimestamp, result.State.UpdatedAt);
var gate = result.State.Steps["approval"];
Assert.Equal(PackRunStepExecutionStatus.Succeeded, gate.Status);
Assert.Null(gate.StatusReason);
Assert.Equal(UpdateTimestamp, gate.LastTransitionAt);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Apply_RejectedGate_FlagsFailure()
{
var plan = BuildApprovalPlan();
var graph = new PackRunExecutionGraphBuilder().Build(plan);
var state = CreateInitialState(plan, graph);
var coordinator = PackRunApprovalCoordinator.Create(plan, RequestedAt);
coordinator.Reject("security-review", "approver-1", UpdateTimestamp, "not-safe");
var result = PackRunGateStateUpdater.Apply(state, graph, coordinator, UpdateTimestamp);
Assert.True(result.HasBlockingFailure);
Assert.Equal(UpdateTimestamp, result.State.UpdatedAt);
var gate = result.State.Steps["approval"];
Assert.Equal(PackRunStepExecutionStatus.Failed, gate.Status);
Assert.StartsWith("approval-rejected", gate.StatusReason, StringComparison.Ordinal);
Assert.Equal(UpdateTimestamp, gate.LastTransitionAt);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Apply_PolicyGate_ClearsPendingReason()
{
var plan = BuildPolicyPlan();
var graph = new PackRunExecutionGraphBuilder().Build(plan);
var state = CreateInitialState(plan, graph);
var coordinator = PackRunApprovalCoordinator.Create(plan, RequestedAt);
var result = PackRunGateStateUpdater.Apply(state, graph, coordinator, UpdateTimestamp);
Assert.False(result.HasBlockingFailure);
var gate = result.State.Steps["policy-check"];
Assert.Equal(PackRunStepExecutionStatus.Succeeded, gate.Status);
Assert.Null(gate.StatusReason);
Assert.Equal(UpdateTimestamp, gate.LastTransitionAt);
var prepare = result.State.Steps["prepare"];
Assert.Equal(PackRunStepExecutionStatus.Pending, prepare.Status);
Assert.Null(prepare.StatusReason);
}
private static TaskPackPlan BuildApprovalPlan()
{
var manifest = TestManifests.Load(TestManifests.Sample);
var planner = new TaskPackPlanner();
var inputs = new Dictionary<string, System.Text.Json.Nodes.JsonNode?>
{
["dryRun"] = System.Text.Json.Nodes.JsonValue.Create(false)
};
return planner.Plan(manifest, inputs).Plan!;
}
private static TaskPackPlan BuildPolicyPlan()
{
var manifest = TestManifests.Load(TestManifests.PolicyGate);
var planner = new TaskPackPlanner();
return planner.Plan(manifest).Plan!;
}
private static PackRunState CreateInitialState(TaskPackPlan plan, PackRunExecutionGraph graph)
{
var steps = new Dictionary<string, PackRunStepStateRecord>(StringComparer.Ordinal);
foreach (var step in EnumerateSteps(graph.Steps))
{
var status = PackRunStepExecutionStatus.Pending;
string? reason = null;
if (!step.Enabled)
{
status = PackRunStepExecutionStatus.Skipped;
reason = "disabled";
}
else if (step.Kind == PackRunStepKind.GateApproval)
{
reason = "requires-approval";
}
else if (step.Kind == PackRunStepKind.GatePolicy)
{
reason = "requires-policy";
}
steps[step.Id] = new PackRunStepStateRecord(
step.Id,
step.Kind,
step.Enabled,
step.ContinueOnError,
step.MaxParallel,
step.ApprovalId,
step.GateMessage,
status,
Attempts: 0,
LastTransitionAt: null,
NextAttemptAt: null,
StatusReason: reason);
}
return PackRunState.Create("run-1", plan.Hash, plan, graph.FailurePolicy, RequestedAt, steps, RequestedAt);
}
private static IEnumerable<PackRunExecutionStep> EnumerateSteps(IReadOnlyList<PackRunExecutionStep> steps)
{
foreach (var step in steps)
{
yield return step;
if (step.Children.Count > 0)
{
foreach (var child in EnumerateSteps(step.Children))
{
yield return child;
}
}
}
}
}