155 lines
5.5 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|