elsa-core/test/integration/Elsa.Activities.IntegrationTests/Branching/FlowJoinTests.cs
Sipke Schoorstra 4bbdd6f3a7
Configures Flowchart Execution via DI (#7141)
* Introduce `FlowchartExecutionMode` to streamline flowchart execution logic.

- Added `FlowchartExecutionMode` enum to represent execution modes (Default, TokenBased, CounterBased).
- Updated flowchart-related integration and unit tests to use the new execution mode.
- Removed the global `UseTokenFlow` flag in favor of execution-specific configuration via `RunWorkflowOptions`.
- Refactored flowchart-related APIs and test helpers for improved flexibility and modularity.

* Add support for configuring flowchart execution behavior via `FlowchartOptions` and DI.

- Introduced extensions for `FlowchartFeature` to simplify configuration.
- Added DI support for setting default execution modes.
- Refactored flowchart execution logic to prioritize configuration.

* Update default Flowchart execution settings to align with version 3.5.2 behavior

- Changed `DefaultExecutionMode` to `CounterBased`.
- Updated `UseTokenFlow` default to `false`.

* Update src/modules/Elsa.Workflows.Core/Activities/Flowchart/Models/FlowchartExecutionMode.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/Elsa.Workflows.Core/Activities/Flowchart/Extensions/FlowchartFeatureExtensions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update test/integration/Elsa.Workflows.IntegrationTests/Scenarios/FlowchartNextActivity/Tests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/Elsa.Workflows.Core/Activities/Flowchart/Extensions/RunWorkflowOptionsExtensions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor flowchart integration tests for improved formatting and consistency

* Refactor workflow tests and related services to improve reusability and align with updated Flowchart execution behavior

* Update src/modules/Elsa.Workflows.Core/Activities/Flowchart/Options/FlowchartOptions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor flowchart execution logic to use `FlowchartExecutionMode` enum, replacing boolean checks for improved clarity and extensibility.

* Refactor tests and workflow logic to replace boolean `useTokenFlow` with `FlowchartExecutionMode` enum for clarity and consistency.

* Refactor flowchart execution logic to centralize mode-based behavior handling and simplify implementation.

* Remove unnecessary whitespace in Flowchart.cs to improve code formatting

* Remove unnecessary whitespace in FlowJoinTests.cs to improve code formatting

* Update src/common/Elsa.Testing.Shared.Integration/WorkflowTestFixture.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-15 10:15:39 +01:00

218 lines
8.8 KiB
C#

using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Activities.Flowchart.Models;
using Xunit.Abstractions;
using static Elsa.Activities.IntegrationTests.Flow.FlowchartTestHelpers;
namespace Elsa.Activities.IntegrationTests.Branching;
/// <summary>
/// Integration tests for FlowJoin activity in complex flowchart scenarios.
/// </summary>
public class FlowJoinTests
{
private readonly IServiceProvider _services;
private readonly CapturingTextWriter _output;
public FlowJoinTests(ITestOutputHelper testOutputHelper)
{
_output = new();
_services = CreateServiceProvider(testOutputHelper, _output);
}
[Theory]
[InlineData(true, FlowJoinMode.WaitAny)]
[InlineData(true, FlowJoinMode.WaitAll)]
[InlineData(false, FlowJoinMode.WaitAny)]
[InlineData(false, FlowJoinMode.WaitAll)]
public async Task Should_Handle_Complex_Flowchart_Scenarios(bool useTokenFlow, FlowJoinMode joinMode)
{
// Test with a more complex flowchart that has multiple activities
// Arrange
var executionMode = useTokenFlow ? FlowchartExecutionMode.TokenBased : FlowchartExecutionMode.CounterBased;
var startActivity = new WriteLine("Start");
var flowJoin = new FlowJoin { Mode = new(joinMode) };
var afterJoin = new WriteLine("AfterJoin");
var flowchart = new Flowchart
{
Start = startActivity,
Activities = { startActivity, flowJoin, afterJoin },
Connections =
{
new() { Source = new(startActivity, "Done"), Target = new(flowJoin) },
new() { Source = new(flowJoin, "Done"), Target = new(afterJoin) }
}
};
// Act
await RunFlowchartAsync(_services, flowchart, executionMode);
// Assert
Assert.Contains("Start", _output.Lines);
// In token flow mode, both WaitAny and WaitAll should act as no-ops
// The flowchart should execute normally: Start -> FlowJoin -> AfterJoin
// In counter flow mode:
// - WaitAny should allow continuation after first inbound connection
// - WaitAll should wait for all inbound connections (in this case just one)
// Since there's only one inbound connection, both should behave similarly
Assert.Contains("AfterJoin", _output.Lines);
}
[Theory]
[InlineData(true, FlowJoinMode.WaitAny)]
[InlineData(true, FlowJoinMode.WaitAll)]
[InlineData(false, FlowJoinMode.WaitAny)]
[InlineData(false, FlowJoinMode.WaitAll)]
public async Task Should_Handle_Fork_Join_Scenarios(bool useTokenFlow, FlowJoinMode joinMode)
{
// Test FlowJoin with a Fork-Join pattern
// Arrange
var executionMode = useTokenFlow ? FlowchartExecutionMode.TokenBased : FlowchartExecutionMode.CounterBased;
var start = new WriteLine("Start");
var fork = new FlowFork { Branches = new(["Branch1", "Branch2"]) };
var branch1 = new WriteLine("Branch1");
var branch2 = new WriteLine("Branch2");
var join = new FlowJoin { Mode = new(joinMode) };
var afterJoin = new WriteLine("AfterJoin");
var flowchart = new Flowchart
{
Start = start,
Activities = { start, fork, branch1, branch2, join, afterJoin },
Connections =
{
new() { Source = new(start, "Done"), Target = new(fork) },
new() { Source = new(fork, "Branch1"), Target = new(branch1) },
new() { Source = new(fork, "Branch2"), Target = new(branch2) },
new() { Source = new(branch1, "Done"), Target = new(join) },
new() { Source = new(branch2, "Done"), Target = new(join) },
new() { Source = new(join, "Done"), Target = new(afterJoin) }
}
};
// Act
await RunFlowchartAsync(_services, flowchart, executionMode);
// Assert
Assert.Contains("Start", _output.Lines);
if (useTokenFlow)
{
// In token flow mode, FlowJoin acts as a no-op
// Both branches should execute and continue to AfterJoin
Assert.Contains("Branch1", _output.Lines);
Assert.Contains("Branch2", _output.Lines);
Assert.Contains("AfterJoin", _output.Lines);
}
else
{
// In counter flow mode, behavior depends on join mode
if (joinMode == FlowJoinMode.WaitAny)
{
// WaitAny allows continuation after first branch completes
Assert.Contains("AfterJoin", _output.Lines);
// At least one branch should execute
Assert.True(_output.Lines.Contains("Branch1") || _output.Lines.Contains("Branch2"));
}
else // WaitAll
{
// WaitAll waits for both branches to complete
Assert.Contains("Branch1", _output.Lines);
Assert.Contains("Branch2", _output.Lines);
Assert.Contains("AfterJoin", _output.Lines);
}
}
}
[Theory]
[InlineData(true, FlowJoinMode.WaitAny)]
[InlineData(true, FlowJoinMode.WaitAll)]
[InlineData(false, FlowJoinMode.WaitAny)]
[InlineData(false, FlowJoinMode.WaitAll)]
public async Task Should_Handle_Multiple_Join_Scenarios(bool useTokenFlow, FlowJoinMode joinMode)
{
// Test multiple FlowJoin activities in a complex flowchart
// Arrange
var executionMode = useTokenFlow ? FlowchartExecutionMode.TokenBased : FlowchartExecutionMode.CounterBased;
var start = new WriteLine("Start");
var fork1 = new FlowFork { Branches = new(["A", "B"]) };
var activityA = new WriteLine("A");
var activityB = new WriteLine("B");
var join1 = new FlowJoin { Mode = new(joinMode) };
var middle = new WriteLine("Middle");
var fork2 = new FlowFork { Branches = new(["C", "D"]) };
var activityC = new WriteLine("C");
var activityD = new WriteLine("D");
var join2 = new FlowJoin { Mode = new(joinMode) };
var end = new WriteLine("End");
var flowchart = new Flowchart
{
Start = start,
Activities = { start, fork1, activityA, activityB, join1, middle, fork2, activityC, activityD, join2, end },
Connections =
{
new() { Source = new(start, "Done"), Target = new(fork1) },
new() { Source = new(fork1, "A"), Target = new(activityA) },
new() { Source = new(fork1, "B"), Target = new(activityB) },
new() { Source = new(activityA, "Done"), Target = new(join1) },
new() { Source = new(activityB, "Done"), Target = new(join1) },
new() { Source = new(join1, "Done"), Target = new(middle) },
new() { Source = new(middle, "Done"), Target = new(fork2) },
new() { Source = new(fork2, "C"), Target = new(activityC) },
new() { Source = new(fork2, "D"), Target = new(activityD) },
new() { Source = new(activityC, "Done"), Target = new(join2) },
new() { Source = new(activityD, "Done"), Target = new(join2) },
new() { Source = new(join2, "Done"), Target = new(end) }
}
};
// Act
await RunFlowchartAsync(_services, flowchart, executionMode);
// Assert
Assert.Contains("Start", _output.Lines);
Assert.Contains("Middle", _output.Lines);
Assert.Contains("End", _output.Lines);
// Verify expected execution patterns based on flow mode and join mode
if (useTokenFlow)
{
// In token flow mode, all activities should execute
Assert.Contains("A", _output.Lines);
Assert.Contains("B", _output.Lines);
Assert.Contains("C", _output.Lines);
Assert.Contains("D", _output.Lines);
}
else
{
// In counter flow mode, execution depends on join mode
if (joinMode == FlowJoinMode.WaitAll)
{
// WaitAll ensures all parallel branches complete
Assert.Contains("A", _output.Lines);
Assert.Contains("B", _output.Lines);
Assert.Contains("C", _output.Lines);
Assert.Contains("D", _output.Lines);
}
else // WaitAny
{
// WaitAny allows continuation after first branch in each fork
// At least one from each pair should execute
Assert.True(_output.Lines.Contains("A") || _output.Lines.Contains("B"));
Assert.True(_output.Lines.Contains("C") || _output.Lines.Contains("D"));
}
}
}
}