elsa-core/test/unit/Elsa.Activities.UnitTests/Flow/ForkTests.cs
Copilot 5b336254b8
fix: restore HashSet-backed Fork completion state on resumed workflows (#7431)
* Initial plan

* fix: handle restored fork completed state as list

Agent-Logs-Url: https://github.com/elsa-workflows/elsa-core/sessions/dbed3cbb-8d9c-4493-b692-ee24b6c90c7d

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* test: avoid reflection in fork regression coverage

Agent-Logs-Url: https://github.com/elsa-workflows/elsa-core/sessions/dbed3cbb-8d9c-4493-b692-ee24b6c90c7d

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* fix: preserve hashset state for resumed fork completion

Agent-Logs-Url: https://github.com/elsa-workflows/elsa-core/sessions/d1bdb2e8-916c-45a0-9508-3e06052d806a

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

* fix: guard reflected collection add lookup

Agent-Logs-Url: https://github.com/elsa-workflows/elsa-core/sessions/d1bdb2e8-916c-45a0-9508-3e06052d806a

Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
2026-05-02 11:23:17 +02:00

113 lines
3.9 KiB
C#

using Elsa.Testing.Shared;
using Elsa.Workflows;
using Elsa.Extensions;
namespace Elsa.Activities.UnitTests.Flow;
/// <summary>
/// Unit tests for the <see cref="Fork"/> activity.
/// </summary>
public class ForkTests
{
[Fact(DisplayName = "Fork schedules all branches")]
public async Task Fork_SchedulesAllBranches()
{
// Arrange
var branches = CreateBranches(3);
var fork = new Fork
{
JoinMode = ForkJoinMode.WaitAll,
Branches = branches.Cast<IActivity>().ToList()
};
// Act
var context = await ExecuteForkAsync(fork);
// Assert
foreach (var branch in branches)
{
Assert.True(context.HasScheduledActivity(branch), $"{branch.Id} should be scheduled");
}
}
[Fact(DisplayName = "Fork with no branches completes immediately")]
public async Task Fork_WithNoBranchesCompletesImmediately()
{
// Arrange
var fork = new Fork { JoinMode = ForkJoinMode.WaitAll };
// Act
var context = await ExecuteForkAsync(fork);
// Assert
Assert.Equal(ActivityStatus.Completed, context.Status);
}
[Theory(DisplayName = "Fork respects join mode")]
[InlineData(ForkJoinMode.WaitAll)]
[InlineData(ForkJoinMode.WaitAny)]
public async Task Fork_RespectsJoinMode(ForkJoinMode joinMode)
{
// Arrange
var branch = new WriteLine("Test Branch");
var fork = new Fork
{
JoinMode = joinMode,
Branches = { branch }
};
// Act
var context = await ExecuteForkAsync(fork);
// Assert
Assert.Equal(joinMode, fork.JoinMode);
Assert.True(context.HasScheduledActivity(branch));
}
[Fact(DisplayName = "Fork resumes when completed activity IDs are restored as a list")]
public async Task Fork_Resumes_WhenCompletedActivityIdsAreRestoredAsList()
{
// Arrange
var branches = CreateBranches(2);
var fork = new Fork
{
JoinMode = ForkJoinMode.WaitAll,
Branches = branches.Cast<IActivity>().ToList()
};
var fixture = new ActivityTestFixture(fork);
var targetContext = await fixture.BuildAsync();
await fixture.ExecuteAsync(targetContext);
targetContext.SetProperty("Completed", new List<string> { branches[0].Id });
var childContext = await targetContext.WorkflowExecutionContext.CreateActivityExecutionContextAsync(branches[1]);
// Act
await CompleteChildAsync(fork, targetContext, childContext);
// Assert
Assert.Equal(ActivityStatus.Completed, targetContext.Status);
var completedActivityIds = targetContext.GetProperty<HashSet<string>>("Completed");
Assert.NotNull(completedActivityIds);
Assert.True(completedActivityIds.SetEquals([branches[0].Id, branches[1].Id]));
}
private static Task<ActivityExecutionContext> ExecuteForkAsync(Fork fork) =>
new ActivityTestFixture(fork).ExecuteAsync();
private static async Task CompleteChildAsync(Fork fork, ActivityExecutionContext targetContext, ActivityExecutionContext childContext)
{
var childNode = targetContext.WorkflowExecutionContext.FindNodeByActivity(childContext.Activity);
Assert.NotNull(childNode);
targetContext.WorkflowExecutionContext.AddCompletionCallback(targetContext, childNode!, fork.GetActivityCompletionCallback("CompleteChildAsync"));
var callback = targetContext.WorkflowExecutionContext.PopCompletionCallback(targetContext, childNode!);
Assert.NotNull(callback?.CompletionCallback);
await callback!.CompletionCallback!(new ActivityCompletedContext(targetContext, childContext));
}
private static WriteLine[] CreateBranches(int count) =>
Enumerable.Range(1, count)
.Select(i => new WriteLine($"Branch {i}") { Id = $"branch-{i}" })
.ToArray();
}