Files
runner/src/Test/L0/Worker/Dap/StepCommands/StepManipulatorL0.cs
Francesco Renzi 008594a3ee editing jobs
2026-01-21 23:19:25 +00:00

726 lines
21 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Dap.StepCommands;
using Moq;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker.Dap.StepCommands
{
public sealed class StepManipulatorL0 : IDisposable
{
private TestHostContext _hc;
private Mock<IExecutionContext> _ec;
private StepManipulator _manipulator;
private Queue<IStep> _jobSteps;
public StepManipulatorL0()
{
_hc = new TestHostContext(this);
_manipulator = new StepManipulator();
_manipulator.Initialize(_hc);
}
public void Dispose()
{
_hc?.Dispose();
}
private void SetupJobContext(int pendingStepCount = 3)
{
_jobSteps = new Queue<IStep>();
for (int i = 0; i < pendingStepCount; i++)
{
var step = CreateMockStep($"Step {i + 1}");
_jobSteps.Enqueue(step);
}
_ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.JobSteps).Returns(_jobSteps);
_manipulator.Initialize(_ec.Object, 0);
}
private IStep CreateMockStep(string displayName, bool isActionRunner = true)
{
if (isActionRunner)
{
var actionRunner = new Mock<IActionRunner>();
var actionStep = new ActionStep
{
Id = Guid.NewGuid(),
Name = $"_step_{Guid.NewGuid():N}",
DisplayName = displayName,
Reference = new ScriptReference(),
Inputs = CreateScriptInputs("echo hello"),
Condition = "success()"
};
actionRunner.Setup(x => x.DisplayName).Returns(displayName);
actionRunner.Setup(x => x.Action).Returns(actionStep);
actionRunner.Setup(x => x.Condition).Returns("success()");
return actionRunner.Object;
}
else
{
var step = new Mock<IStep>();
step.Setup(x => x.DisplayName).Returns(displayName);
step.Setup(x => x.Condition).Returns("success()");
return step.Object;
}
}
private MappingToken CreateScriptInputs(string script)
{
var inputs = new MappingToken(null, null, null);
inputs.Add(
new StringToken(null, null, null, "script"),
new StringToken(null, null, null, script));
return inputs;
}
#region Initialization Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Initialize_SetsJobContext()
{
// Arrange
SetupJobContext();
// Act & Assert - no exception means success
var steps = _manipulator.GetAllSteps();
Assert.Equal(3, steps.Count);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Initialize_ThrowsOnNullContext()
{
// Arrange
var manipulator = new StepManipulator();
manipulator.Initialize(_hc);
// Act & Assert
Assert.Throws<ArgumentNullException>(() => manipulator.Initialize(null, 0));
}
#endregion
#region GetAllSteps Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetAllSteps_ReturnsPendingSteps()
{
// Arrange
SetupJobContext(3);
// Act
var steps = _manipulator.GetAllSteps();
// Assert
Assert.Equal(3, steps.Count);
Assert.All(steps, s => Assert.Equal(StepStatus.Pending, s.Status));
Assert.Equal(1, steps[0].Index);
Assert.Equal(2, steps[1].Index);
Assert.Equal(3, steps[2].Index);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetAllSteps_IncludesCompletedSteps()
{
// Arrange
SetupJobContext(2);
var completedStep = CreateMockStep("Completed Step");
_manipulator.AddCompletedStep(completedStep);
// Act
var steps = _manipulator.GetAllSteps();
// Assert
Assert.Equal(3, steps.Count);
Assert.Equal(StepStatus.Completed, steps[0].Status);
Assert.Equal("Completed Step", steps[0].Name);
Assert.Equal(StepStatus.Pending, steps[1].Status);
Assert.Equal(StepStatus.Pending, steps[2].Status);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetAllSteps_IncludesCurrentStep()
{
// Arrange
SetupJobContext(2);
var currentStep = CreateMockStep("Current Step");
_manipulator.SetCurrentStep(currentStep);
// Act
var steps = _manipulator.GetAllSteps();
// Assert
Assert.Equal(3, steps.Count);
Assert.Equal(StepStatus.Current, steps[0].Status);
Assert.Equal("Current Step", steps[0].Name);
}
#endregion
#region GetStep Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetStep_ReturnsCorrectStep()
{
// Arrange
SetupJobContext(3);
// Act
var step = _manipulator.GetStep(2);
// Assert
Assert.NotNull(step);
Assert.Equal(2, step.Index);
Assert.Equal("Step 2", step.Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetStep_ReturnsNullForInvalidIndex()
{
// Arrange
SetupJobContext(3);
// Act & Assert
Assert.Null(_manipulator.GetStep(0));
Assert.Null(_manipulator.GetStep(4));
Assert.Null(_manipulator.GetStep(-1));
}
#endregion
#region GetPendingCount Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetPendingCount_ReturnsCorrectCount()
{
// Arrange
SetupJobContext(5);
// Act
var count = _manipulator.GetPendingCount();
// Assert
Assert.Equal(5, count);
}
#endregion
#region GetFirstPendingIndex Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetFirstPendingIndex_WithNoPriorSteps_ReturnsOne()
{
// Arrange
SetupJobContext(3);
// Act
var index = _manipulator.GetFirstPendingIndex();
// Assert
Assert.Equal(1, index);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetFirstPendingIndex_WithCompletedSteps_ReturnsCorrectIndex()
{
// Arrange
SetupJobContext(2);
_manipulator.AddCompletedStep(CreateMockStep("Completed 1"));
_manipulator.AddCompletedStep(CreateMockStep("Completed 2"));
// Act
var index = _manipulator.GetFirstPendingIndex();
// Assert
Assert.Equal(3, index);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetFirstPendingIndex_WithNoSteps_ReturnsNegativeOne()
{
// Arrange
SetupJobContext(0);
// Act
var index = _manipulator.GetFirstPendingIndex();
// Assert
Assert.Equal(-1, index);
}
#endregion
#region InsertStep Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InsertStep_AtLast_AppendsToQueue()
{
// Arrange
SetupJobContext(2);
var newStep = CreateMockStep("New Step");
// Act
var index = _manipulator.InsertStep(newStep, StepPosition.Last());
// Assert
Assert.Equal(3, index);
Assert.Equal(3, _jobSteps.Count);
var steps = _manipulator.GetAllSteps();
Assert.Equal("New Step", steps[2].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InsertStep_AtFirst_PrependsToQueue()
{
// Arrange
SetupJobContext(2);
var newStep = CreateMockStep("New Step");
// Act
var index = _manipulator.InsertStep(newStep, StepPosition.First());
// Assert
Assert.Equal(1, index);
var steps = _manipulator.GetAllSteps();
Assert.Equal("New Step", steps[0].Name);
Assert.Equal("Step 1", steps[1].Name);
Assert.Equal("Step 2", steps[2].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InsertStep_AtPosition_InsertsCorrectly()
{
// Arrange
SetupJobContext(3);
var newStep = CreateMockStep("New Step");
// Act
var index = _manipulator.InsertStep(newStep, StepPosition.At(2));
// Assert
Assert.Equal(2, index);
var steps = _manipulator.GetAllSteps();
Assert.Equal("Step 1", steps[0].Name);
Assert.Equal("New Step", steps[1].Name);
Assert.Equal("Step 2", steps[2].Name);
Assert.Equal("Step 3", steps[3].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InsertStep_AfterPosition_InsertsCorrectly()
{
// Arrange
SetupJobContext(3);
var newStep = CreateMockStep("New Step");
// Act
var index = _manipulator.InsertStep(newStep, StepPosition.After(1));
// Assert
Assert.Equal(2, index);
var steps = _manipulator.GetAllSteps();
Assert.Equal("Step 1", steps[0].Name);
Assert.Equal("New Step", steps[1].Name);
Assert.Equal("Step 2", steps[2].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InsertStep_BeforePosition_InsertsCorrectly()
{
// Arrange
SetupJobContext(3);
var newStep = CreateMockStep("New Step");
// Act
var index = _manipulator.InsertStep(newStep, StepPosition.Before(3));
// Assert
Assert.Equal(3, index);
var steps = _manipulator.GetAllSteps();
Assert.Equal("Step 1", steps[0].Name);
Assert.Equal("Step 2", steps[1].Name);
Assert.Equal("New Step", steps[2].Name);
Assert.Equal("Step 3", steps[3].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InsertStep_TracksChange()
{
// Arrange
SetupJobContext(2);
var newStep = CreateMockStep("New Step");
// Act
_manipulator.InsertStep(newStep, StepPosition.Last());
// Assert
var changes = _manipulator.GetChanges();
Assert.Single(changes);
Assert.Equal(ChangeType.Added, changes[0].Type);
Assert.Equal(3, changes[0].CurrentIndex);
}
#endregion
#region RemoveStep Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RemoveStep_RemovesFromQueue()
{
// Arrange
SetupJobContext(3);
// Act
_manipulator.RemoveStep(2);
// Assert
Assert.Equal(2, _jobSteps.Count);
var steps = _manipulator.GetAllSteps();
Assert.Equal("Step 1", steps[0].Name);
Assert.Equal("Step 3", steps[1].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RemoveStep_TracksChange()
{
// Arrange
SetupJobContext(3);
// Act
_manipulator.RemoveStep(2);
// Assert
var changes = _manipulator.GetChanges();
Assert.Single(changes);
Assert.Equal(ChangeType.Removed, changes[0].Type);
Assert.Equal(2, changes[0].OriginalIndex);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RemoveStep_ThrowsForCompletedStep()
{
// Arrange
SetupJobContext(2);
_manipulator.AddCompletedStep(CreateMockStep("Completed Step"));
// Act & Assert
var ex = Assert.Throws<StepCommandException>(() => _manipulator.RemoveStep(1));
Assert.Equal(StepCommandErrors.InvalidIndex, ex.ErrorCode);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RemoveStep_ThrowsForCurrentStep()
{
// Arrange
SetupJobContext(2);
_manipulator.SetCurrentStep(CreateMockStep("Current Step"));
// Act & Assert
var ex = Assert.Throws<StepCommandException>(() => _manipulator.RemoveStep(1));
Assert.Equal(StepCommandErrors.InvalidIndex, ex.ErrorCode);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RemoveStep_ThrowsForInvalidIndex()
{
// Arrange
SetupJobContext(3);
// Act & Assert
Assert.Throws<StepCommandException>(() => _manipulator.RemoveStep(0));
Assert.Throws<StepCommandException>(() => _manipulator.RemoveStep(4));
}
#endregion
#region MoveStep Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void MoveStep_ToLast_MovesCorrectly()
{
// Arrange
SetupJobContext(3);
// Act
var newIndex = _manipulator.MoveStep(1, StepPosition.Last());
// Assert
Assert.Equal(3, newIndex);
var steps = _manipulator.GetAllSteps();
Assert.Equal("Step 2", steps[0].Name);
Assert.Equal("Step 3", steps[1].Name);
Assert.Equal("Step 1", steps[2].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void MoveStep_ToFirst_MovesCorrectly()
{
// Arrange
SetupJobContext(3);
// Act
var newIndex = _manipulator.MoveStep(3, StepPosition.First());
// Assert
Assert.Equal(1, newIndex);
var steps = _manipulator.GetAllSteps();
Assert.Equal("Step 3", steps[0].Name);
Assert.Equal("Step 1", steps[1].Name);
Assert.Equal("Step 2", steps[2].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void MoveStep_ToMiddle_MovesCorrectly()
{
// Arrange
SetupJobContext(4);
// Act - move step 1 to after step 2 (which becomes position 2)
var newIndex = _manipulator.MoveStep(1, StepPosition.After(2));
// Assert
var steps = _manipulator.GetAllSteps();
Assert.Equal("Step 2", steps[0].Name);
Assert.Equal("Step 1", steps[1].Name);
Assert.Equal("Step 3", steps[2].Name);
Assert.Equal("Step 4", steps[3].Name);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void MoveStep_TracksChange()
{
// Arrange
SetupJobContext(3);
// Act
_manipulator.MoveStep(1, StepPosition.Last());
// Assert
var changes = _manipulator.GetChanges();
Assert.Single(changes);
Assert.Equal(ChangeType.Moved, changes[0].Type);
Assert.Equal(1, changes[0].OriginalIndex);
Assert.Equal(3, changes[0].CurrentIndex);
}
#endregion
#region EditStep Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EditStep_ModifiesActionStep()
{
// Arrange
SetupJobContext(3);
// Act
_manipulator.EditStep(2, step =>
{
step.DisplayName = "Modified Step";
});
// Assert
var steps = _manipulator.GetAllSteps();
var actionRunner = steps[1].Step as IActionRunner;
Assert.NotNull(actionRunner);
Assert.Equal("Modified Step", actionRunner.Action.DisplayName);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EditStep_TracksChange()
{
// Arrange
SetupJobContext(3);
// Act
_manipulator.EditStep(2, step =>
{
step.DisplayName = "Modified Step";
});
// Assert
var changes = _manipulator.GetChanges();
Assert.Single(changes);
Assert.Equal(ChangeType.Modified, changes[0].Type);
Assert.Equal(2, changes[0].CurrentIndex);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EditStep_ThrowsForCompletedStep()
{
// Arrange
SetupJobContext(2);
_manipulator.AddCompletedStep(CreateMockStep("Completed Step"));
// Act & Assert
var ex = Assert.Throws<StepCommandException>(() =>
_manipulator.EditStep(1, step => { }));
Assert.Equal(StepCommandErrors.InvalidIndex, ex.ErrorCode);
}
#endregion
#region Change Tracking Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RecordOriginalState_CapturesSteps()
{
// Arrange
SetupJobContext(3);
// Act
_manipulator.RecordOriginalState();
_manipulator.InsertStep(CreateMockStep("New Step"), StepPosition.Last());
// Assert - changes should be tracked
var changes = _manipulator.GetChanges();
Assert.Single(changes);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void ClearChanges_RemovesAllChanges()
{
// Arrange
SetupJobContext(3);
_manipulator.InsertStep(CreateMockStep("New Step"), StepPosition.Last());
// Act
_manipulator.ClearChanges();
// Assert
Assert.Empty(_manipulator.GetChanges());
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void MultipleOperations_TrackAllChanges()
{
// Arrange
SetupJobContext(3);
// Act
_manipulator.InsertStep(CreateMockStep("New Step"), StepPosition.Last());
_manipulator.RemoveStep(1);
_manipulator.MoveStep(2, StepPosition.First());
// Assert
var changes = _manipulator.GetChanges();
Assert.Equal(3, changes.Count);
Assert.Equal(ChangeType.Added, changes[0].Type);
Assert.Equal(ChangeType.Removed, changes[1].Type);
Assert.Equal(ChangeType.Moved, changes[2].Type);
}
#endregion
#region StepInfo Factory Tests
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void StepInfo_FromStep_ExtractsRunStepInfo()
{
// Arrange
var step = CreateMockStep("Test Run Step");
// Act
var info = StepInfo.FromStep(step, 1, StepStatus.Pending);
// Assert
Assert.Equal("Test Run Step", info.Name);
Assert.Equal("run", info.Type);
Assert.Equal(StepStatus.Pending, info.Status);
Assert.NotNull(info.Action);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void StepInfo_FromStep_HandlesNonActionRunner()
{
// Arrange
var step = CreateMockStep("Extension Step", isActionRunner: false);
// Act
var info = StepInfo.FromStep(step, 1, StepStatus.Pending);
// Assert
Assert.Equal("Extension Step", info.Name);
Assert.Equal("extension", info.Type);
Assert.Null(info.Action);
}
#endregion
}
}