Add integration and unit tests for End activity and update flowchart termination logic (#7109)
- Introduced `EndInFlowchartWorkflow` and `EndInSequenceWorkflow` to test `End` activity behavior in flowcharts and sequences. - Added `EndTests` integration and unit tests to verify correct termination behavior, including `ITerminalNode` implementation. - Updated flowchart logic to handle terminal nodes, ensuring immediate flowchart completion upon encountering `End`.
This commit is contained in:
parent
730c01d9b0
commit
87dc976b5f
|
|
@ -14,12 +14,20 @@ public partial class Flowchart
|
|||
var flowContext = ctx.TargetContext;
|
||||
var completedActivity = ctx.ChildContext.Activity;
|
||||
var flowGraph = flowContext.GetFlowGraph();
|
||||
var tokens = GetTokenList(flowContext);
|
||||
|
||||
// If the completed activity is a terminal node, complete the flowchart immediately.
|
||||
if (completedActivity is ITerminalNode)
|
||||
{
|
||||
tokens.Clear();
|
||||
await flowContext.CompleteActivityAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit tokens for active outcomes.
|
||||
var outcomes = (ctx.Result as Outcomes ?? Outcomes.Default).Names;
|
||||
var outboundConnections = flowGraph.GetOutboundConnections(completedActivity);
|
||||
var activeOutboundConnections = outboundConnections.Where(x => outcomes.Contains(x.Source.Port)).Distinct().ToList();
|
||||
var tokens = GetTokenList(flowContext);
|
||||
|
||||
foreach (var connection in activeOutboundConnections)
|
||||
tokens.Add(Token.Create(connection.Source.Activity, connection.Target.Activity, connection.Source.Port));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
using Elsa.Activities.IntegrationTests.Flow.Workflows;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Activities.IntegrationTests.Flow;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for the <see cref="End"/> activity.
|
||||
/// </summary>
|
||||
public class EndTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
private readonly WorkflowTestFixture _fixture = new WorkflowTestFixture(testOutputHelper)
|
||||
.AddWorkflow<EndInSequenceWorkflow>()
|
||||
.AddWorkflow<EndInFlowchartWorkflow>();
|
||||
|
||||
[Fact(DisplayName = "End terminates sequence execution")]
|
||||
public async Task End_TerminatesSequenceExecution()
|
||||
{
|
||||
// Act
|
||||
var workflowState = await _fixture.RunWorkflowAsync(EndInSequenceWorkflow.DefinitionId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
|
||||
var lines = _fixture.CapturingTextWriter.Lines.ToList();
|
||||
Assert.Contains("Before End", lines);
|
||||
Assert.DoesNotContain("After End", lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "End in flowchart terminates flowchart immediately")]
|
||||
public async Task End_InFlowchart_TerminatesFlowchartImmediately()
|
||||
{
|
||||
// Act
|
||||
var workflowState = await _fixture.RunWorkflowAsync(EndInFlowchartWorkflow.DefinitionId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
|
||||
var lines = _fixture.CapturingTextWriter.Lines.ToList();
|
||||
Assert.Contains("Start", lines);
|
||||
Assert.Contains("Path A executed", lines);
|
||||
// End is a terminal node - it terminates the flowchart immediately
|
||||
// Path B should not execute because End completes the flowchart
|
||||
Assert.DoesNotContain("Path B executed", lines);
|
||||
// The outer sequence continues after the flowchart completes
|
||||
Assert.Contains("After flowchart", lines);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Activities.Flowchart.Activities;
|
||||
using Elsa.Workflows.Activities.Flowchart.Models;
|
||||
|
||||
namespace Elsa.Activities.IntegrationTests.Flow.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Workflow demonstrating that End terminates a flowchart.
|
||||
/// </summary>
|
||||
public class EndInFlowchartWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow.WithDefinitionId(DefinitionId);
|
||||
|
||||
var start = new WriteLine("Start");
|
||||
var pathA = new WriteLine("Path A executed");
|
||||
var end = new End();
|
||||
var pathB = new WriteLine("Path B executed");
|
||||
var afterFlowchart = new WriteLine("After flowchart");
|
||||
|
||||
workflow.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new Flowchart
|
||||
{
|
||||
Start = start,
|
||||
Activities =
|
||||
{
|
||||
start,
|
||||
pathA,
|
||||
end,
|
||||
pathB
|
||||
},
|
||||
Connections =
|
||||
{
|
||||
new Connection(start, pathA),
|
||||
new Connection(pathA, end),
|
||||
new Connection(end, pathB)
|
||||
}
|
||||
},
|
||||
afterFlowchart
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
|
||||
namespace Elsa.Activities.IntegrationTests.Flow.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Workflow demonstrating that End activity completes successfully in a sequence.
|
||||
/// </summary>
|
||||
public class EndInSequenceWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow.WithDefinitionId(DefinitionId);
|
||||
workflow.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine("Before End"),
|
||||
new End(),
|
||||
new WriteLine("After End")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
35
test/unit/Elsa.Activities.UnitTests/Flow/EndTests.cs
Normal file
35
test/unit/Elsa.Activities.UnitTests/Flow/EndTests.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
|
||||
namespace Elsa.Activities.UnitTests.Flow;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="End"/> activity.
|
||||
/// </summary>
|
||||
public class EndTests
|
||||
{
|
||||
[Fact(DisplayName = "End implements ITerminalNode interface")]
|
||||
public void End_ImplementsITerminalNode()
|
||||
{
|
||||
// Arrange
|
||||
var endActivity = new End();
|
||||
|
||||
// Assert
|
||||
Assert.IsAssignableFrom<ITerminalNode>(endActivity);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "End completes execution")]
|
||||
public async Task End_CompletesExecution()
|
||||
{
|
||||
// Arrange
|
||||
var endActivity = new End();
|
||||
var fixture = new ActivityTestFixture(endActivity);
|
||||
|
||||
// Act
|
||||
var context = await fixture.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ActivityStatus.Completed, context.Status);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue