Add unit and integration tests for Complete activity to validate composite workflows execution (#7108)

* Add unit and integration tests for `Complete` activity to validate composite workflows execution

- Introduced integration and unit tests covering `Complete` activity behavior in nested and simple composite workflows.
- Added `CompleteTests` for edge cases such as immediate parent completion and composite termination.
- Developed new composite activities (`InnerComposite`, `OuterComposite`, `SimpleComposite`) for testing scenarios.

* Remove unnecessary `Complete` constructors tests.
This commit is contained in:
Sipke Schoorstra 2025-11-26 12:26:31 +01:00 committed by GitHub
parent 87dc976b5f
commit 8700036123
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 209 additions and 0 deletions

View file

@ -0,0 +1,32 @@
using Elsa.Workflows;
using Elsa.Workflows.Activities;
namespace Elsa.Activities.IntegrationTests.Composition.Activities;
/// <summary>
/// Inner composite activity that uses Complete.
/// </summary>
public class InnerComposite : Composite
{
private readonly WriteLine _completedMessage = new("Inner composite completed");
public InnerComposite()
{
Root = new Sequence
{
Activities =
{
new WriteLine("Inner composite started"),
new Complete(),
new WriteLine("This should not execute in inner"),
_completedMessage
}
};
}
protected override async ValueTask OnCompletedAsync(ActivityCompletedContext context)
{
// Schedule the completion message
await context.TargetContext.ScheduleActivityAsync(_completedMessage);
}
}

View file

@ -0,0 +1,21 @@
using Elsa.Workflows.Activities;
namespace Elsa.Activities.IntegrationTests.Composition.Activities;
/// <summary>
/// Outer composite activity for nested composite testing.
/// </summary>
public class OuterComposite : Composite
{
public OuterComposite()
{
Root = new Sequence
{
Activities =
{
new InnerComposite(),
new WriteLine("After inner composite")
}
};
}
}

View file

@ -0,0 +1,22 @@
using Elsa.Workflows.Activities;
namespace Elsa.Activities.IntegrationTests.Composition.Activities;
/// <summary>
/// Simple composite activity that uses Complete to terminate early.
/// </summary>
public class SimpleComposite : Composite
{
public SimpleComposite()
{
Root = new Sequence
{
Activities =
{
new WriteLine("Before Complete"),
new Complete(),
new WriteLine("This should not execute")
}
};
}
}

View file

@ -0,0 +1,47 @@
using Elsa.Activities.IntegrationTests.Composition.Workflows;
using Elsa.Testing.Shared;
using Elsa.Workflows;
using Elsa.Workflows.Activities;
using Xunit.Abstractions;
namespace Elsa.Activities.IntegrationTests.Composition;
/// <summary>
/// Integration tests for the <see cref="Complete"/> activity.
/// </summary>
public class CompleteTests(ITestOutputHelper testOutputHelper)
{
private readonly WorkflowTestFixture _fixture = new WorkflowTestFixture(testOutputHelper)
.AddWorkflow<CompleteTerminatesCompositeWorkflow>()
.AddWorkflow<CompleteInNestedCompositeWorkflow>();
[Fact(DisplayName = "Complete terminates composite execution immediately")]
public async Task Complete_TerminatesCompositeExecutionImmediately()
{
// Act
var workflowState = await _fixture.RunWorkflowAsync(CompleteTerminatesCompositeWorkflow.DefinitionId);
// Assert
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
var lines = _fixture.CapturingTextWriter.Lines.ToList();
Assert.Contains("Before Complete", lines);
Assert.DoesNotContain("This should not execute", lines);
Assert.Contains("After composite", lines);
}
[Fact(DisplayName = "Complete in nested composite completes immediate parent only")]
public async Task Complete_InNestedComposite_CompletesImmediateParentOnly()
{
// Act
var workflowState = await _fixture.RunWorkflowAsync(CompleteInNestedCompositeWorkflow.DefinitionId);
// Assert
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
var lines = _fixture.CapturingTextWriter.Lines.ToList();
Assert.Contains("Outer composite started", lines);
Assert.Contains("Inner composite started", lines);
Assert.Contains("Inner composite completed", lines);
Assert.Contains("Outer composite completed", lines);
Assert.DoesNotContain("This should not execute in inner", lines);
}
}

View file

@ -0,0 +1,27 @@
using Elsa.Activities.IntegrationTests.Composition.Activities;
using Elsa.Workflows;
using Elsa.Workflows.Activities;
namespace Elsa.Activities.IntegrationTests.Composition.Workflows;
/// <summary>
/// Workflow with nested composite activities to verify Complete only affects immediate parent.
/// </summary>
public class CompleteInNestedCompositeWorkflow : 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("Outer composite started"),
new OuterComposite(),
new WriteLine("Outer composite completed")
}
};
}
}

View file

@ -0,0 +1,26 @@
using Elsa.Activities.IntegrationTests.Composition.Activities;
using Elsa.Workflows;
using Elsa.Workflows.Activities;
namespace Elsa.Activities.IntegrationTests.Composition.Workflows;
/// <summary>
/// Workflow demonstrating that Complete terminates composite execution immediately.
/// </summary>
public class CompleteTerminatesCompositeWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder workflow)
{
workflow.WithDefinitionId(DefinitionId);
workflow.Root = new Sequence
{
Activities =
{
new SimpleComposite(),
new WriteLine("After composite")
}
};
}
}

View file

@ -0,0 +1,34 @@
using Elsa.Testing.Shared;
using Elsa.Workflows;
namespace Elsa.Activities.UnitTests.Composition;
/// <summary>
/// Unit tests for the <see cref="Complete"/> activity.
/// </summary>
public class CompleteTests
{
[Fact(DisplayName = "Complete implements ITerminalNode interface")]
public void Complete_ImplementsITerminalNode()
{
// Arrange
var completeActivity = new Complete();
// Assert
Assert.IsAssignableFrom<ITerminalNode>(completeActivity);
}
[Fact(DisplayName = "Complete completes execution")]
public async Task Complete_CompletesExecution()
{
// Arrange
var completeActivity = new Complete();
var fixture = new ActivityTestFixture(completeActivity);
// Act
var context = await fixture.ExecuteAsync();
// Assert
Assert.Equal(ActivityStatus.Completed, context.Status);
}
}