elsa-core/test/integration/Elsa.Activities.IntegrationTests/ParallelTests.cs
Sipke Schoorstra 7a66926f16
Add unit and integration tests for ParallelForEach activity covering behavior with different item types, fault handling, and scenario-specific conditions (#7120)
* Add unit and integration tests for `ParallelForEach` activity covering behavior with different item types, fault handling, and scenario-specific conditions

* Replace `Array.Empty<string>()` with `[]` in `ParallelForEachTests` to simplify syntax.
2025-11-27 20:58:58 +01:00

189 lines
6.9 KiB
C#

using Elsa.Testing.Shared;
using Elsa.Workflows;
using Elsa.Workflows.Activities;
using Xunit.Abstractions;
using Parallel = Elsa.Workflows.Activities.Parallel;
namespace Elsa.Activities.IntegrationTests;
public class ParallelTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _serviceProvider;
public ParallelTests(ITestOutputHelper testOutputHelper)
{
_serviceProvider = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
}
[Fact(DisplayName = "Parallel executes all child activities and completes")]
public async Task Parallel_ExecutesAllChildren_AndCompletes()
{
// Arrange
var parallel = new Parallel(
new WriteLine("Activity 1"),
new WriteLine("Activity 2"),
new WriteLine("Activity 3")
);
// Act
var result = await _serviceProvider.RunActivityAsync(parallel);
// Assert
var journal = result.Journal;
var parallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity is Parallel);
Assert.NotNull(parallelContext);
Assert.Equal(ActivityStatus.Completed, parallelContext.Status);
Assert.Equal(3, _capturingTextWriter.Lines.Count);
Assert.Contains("Activity 1", _capturingTextWriter.Lines);
Assert.Contains("Activity 2", _capturingTextWriter.Lines);
Assert.Contains("Activity 3", _capturingTextWriter.Lines);
}
[Fact(DisplayName = "Parallel completes when empty")]
public async Task Parallel_Completes_WhenEmpty()
{
// Arrange
var parallel = new Parallel();
// Act
var result = await _serviceProvider.RunActivityAsync(parallel);
// Assert
var journal = result.Journal;
var parallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity is Parallel);
Assert.NotNull(parallelContext);
Assert.Equal(ActivityStatus.Completed, parallelContext.Status);
Assert.Empty(_capturingTextWriter.Lines);
}
[Fact(DisplayName = "Parallel executes single child activity and completes")]
public async Task Parallel_ExecutesSingleChild_AndCompletes()
{
// Arrange
var parallel = new Parallel(
new WriteLine("Single Activity")
);
// Act
var result = await _serviceProvider.RunActivityAsync(parallel);
// Assert
var journal = result.Journal;
var parallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity is Parallel);
Assert.NotNull(parallelContext);
Assert.Equal(ActivityStatus.Completed, parallelContext.Status);
Assert.Single(_capturingTextWriter.Lines);
Assert.Equal("Single Activity", _capturingTextWriter.Lines.Single());
}
[Fact(DisplayName = "Parallel executes multiple different activity types")]
public async Task Parallel_ExecutesMixedActivityTypes_AndCompletes()
{
// Arrange
var parallel = new Parallel(
new WriteLine("First"),
new SetName("TestName"),
new WriteLine("Second")
);
// Act
var result = await _serviceProvider.RunActivityAsync(parallel);
// Assert
var journal = result.Journal;
var parallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity is Parallel);
Assert.NotNull(parallelContext);
Assert.Equal(ActivityStatus.Completed, parallelContext.Status);
Assert.Equal(2, _capturingTextWriter.Lines.Count);
Assert.Contains("First", _capturingTextWriter.Lines);
Assert.Contains("Second", _capturingTextWriter.Lines);
}
[Fact(DisplayName = "Parallel completes only after all children complete")]
public async Task Parallel_CompletesOnlyAfterAllChildrenComplete()
{
// Arrange
var parallel = new Parallel(
new WriteLine("Child 1"),
new WriteLine("Child 2"),
new WriteLine("Child 3"),
new WriteLine("Child 4")
);
// Act
var result = await _serviceProvider.RunActivityAsync(parallel);
// Assert
var journal = result.Journal;
var parallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity is Parallel);
Assert.NotNull(parallelContext);
Assert.Equal(ActivityStatus.Completed, parallelContext.Status);
Assert.Equal(4, _capturingTextWriter.Lines.Count);
}
[Fact(DisplayName = "Parallel executes nested Parallel activities")]
public async Task Parallel_ExecutesNestedParallel_AndCompletes()
{
// Arrange
var innerParallel = new Parallel(
new WriteLine("Inner 1"),
new WriteLine("Inner 2")
);
var outerParallel = new Parallel(
new WriteLine("Outer 1"),
innerParallel,
new WriteLine("Outer 2")
);
// Act
var result = await _serviceProvider.RunActivityAsync(outerParallel);
// Assert
var journal = result.Journal;
var outerParallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity == outerParallel);
var innerParallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity == innerParallel);
Assert.NotNull(outerParallelContext);
Assert.NotNull(innerParallelContext);
Assert.Equal(ActivityStatus.Completed, outerParallelContext.Status);
Assert.Equal(ActivityStatus.Completed, innerParallelContext.Status);
Assert.Equal(4, _capturingTextWriter.Lines.Count);
Assert.Contains("Outer 1", _capturingTextWriter.Lines);
Assert.Contains("Outer 2", _capturingTextWriter.Lines);
Assert.Contains("Inner 1", _capturingTextWriter.Lines);
Assert.Contains("Inner 2", _capturingTextWriter.Lines);
}
[Fact(DisplayName = "Parallel remains in Running state when a child activity faults")]
public async Task Parallel_RemainsRunning_WhenChildFaults()
{
// Arrange
var parallel = new Parallel(
new WriteLine("Before Fault"),
new Fault { Message = new("Test fault") },
new WriteLine("After Fault")
);
// Act
var result = await _serviceProvider.RunActivityAsync(parallel);
// Assert
var journal = result.Journal;
var parallelContext = journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity is Parallel);
Assert.NotNull(parallelContext);
Assert.Equal(ActivityStatus.Running, parallelContext.Status);
Assert.Equal(1, parallelContext.AggregateFaultCount);
// The non-faulted activities should still execute
Assert.Contains("Before Fault", _capturingTextWriter.Lines);
Assert.Contains("After Fault", _capturingTextWriter.Lines);
}
}