diff --git a/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/InnerComposite.cs b/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/InnerComposite.cs new file mode 100644 index 000000000..3f3e737df --- /dev/null +++ b/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/InnerComposite.cs @@ -0,0 +1,32 @@ +using Elsa.Workflows; +using Elsa.Workflows.Activities; + +namespace Elsa.Activities.IntegrationTests.Composition.Activities; + +/// +/// Inner composite activity that uses Complete. +/// +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); + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/OuterComposite.cs b/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/OuterComposite.cs new file mode 100644 index 000000000..9de2de220 --- /dev/null +++ b/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/OuterComposite.cs @@ -0,0 +1,21 @@ +using Elsa.Workflows.Activities; + +namespace Elsa.Activities.IntegrationTests.Composition.Activities; + +/// +/// Outer composite activity for nested composite testing. +/// +public class OuterComposite : Composite +{ + public OuterComposite() + { + Root = new Sequence + { + Activities = + { + new InnerComposite(), + new WriteLine("After inner composite") + } + }; + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/SimpleComposite.cs b/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/SimpleComposite.cs new file mode 100644 index 000000000..22f2be050 --- /dev/null +++ b/test/integration/Elsa.Activities.IntegrationTests/Composition/Activities/SimpleComposite.cs @@ -0,0 +1,22 @@ +using Elsa.Workflows.Activities; + +namespace Elsa.Activities.IntegrationTests.Composition.Activities; + +/// +/// Simple composite activity that uses Complete to terminate early. +/// +public class SimpleComposite : Composite +{ + public SimpleComposite() + { + Root = new Sequence + { + Activities = + { + new WriteLine("Before Complete"), + new Complete(), + new WriteLine("This should not execute") + } + }; + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Activities.IntegrationTests/Composition/CompleteTests.cs b/test/integration/Elsa.Activities.IntegrationTests/Composition/CompleteTests.cs new file mode 100644 index 000000000..b0ea0651a --- /dev/null +++ b/test/integration/Elsa.Activities.IntegrationTests/Composition/CompleteTests.cs @@ -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; + +/// +/// Integration tests for the activity. +/// +public class CompleteTests(ITestOutputHelper testOutputHelper) +{ + private readonly WorkflowTestFixture _fixture = new WorkflowTestFixture(testOutputHelper) + .AddWorkflow() + .AddWorkflow(); + + [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); + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Activities.IntegrationTests/Composition/Workflows/CompleteInNestedCompositeWorkflow.cs b/test/integration/Elsa.Activities.IntegrationTests/Composition/Workflows/CompleteInNestedCompositeWorkflow.cs new file mode 100644 index 000000000..5f84b5e62 --- /dev/null +++ b/test/integration/Elsa.Activities.IntegrationTests/Composition/Workflows/CompleteInNestedCompositeWorkflow.cs @@ -0,0 +1,27 @@ +using Elsa.Activities.IntegrationTests.Composition.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Activities; + +namespace Elsa.Activities.IntegrationTests.Composition.Workflows; + +/// +/// Workflow with nested composite activities to verify Complete only affects immediate parent. +/// +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") + } + }; + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Activities.IntegrationTests/Composition/Workflows/CompleteTerminatesCompositeWorkflow.cs b/test/integration/Elsa.Activities.IntegrationTests/Composition/Workflows/CompleteTerminatesCompositeWorkflow.cs new file mode 100644 index 000000000..af589932b --- /dev/null +++ b/test/integration/Elsa.Activities.IntegrationTests/Composition/Workflows/CompleteTerminatesCompositeWorkflow.cs @@ -0,0 +1,26 @@ +using Elsa.Activities.IntegrationTests.Composition.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Activities; + +namespace Elsa.Activities.IntegrationTests.Composition.Workflows; + +/// +/// Workflow demonstrating that Complete terminates composite execution immediately. +/// +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") + } + }; + } +} \ No newline at end of file diff --git a/test/unit/Elsa.Activities.UnitTests/Composition/CompleteTests.cs b/test/unit/Elsa.Activities.UnitTests/Composition/CompleteTests.cs new file mode 100644 index 000000000..843e7fa65 --- /dev/null +++ b/test/unit/Elsa.Activities.UnitTests/Composition/CompleteTests.cs @@ -0,0 +1,34 @@ +using Elsa.Testing.Shared; +using Elsa.Workflows; + +namespace Elsa.Activities.UnitTests.Composition; + +/// +/// Unit tests for the activity. +/// +public class CompleteTests +{ + [Fact(DisplayName = "Complete implements ITerminalNode interface")] + public void Complete_ImplementsITerminalNode() + { + // Arrange + var completeActivity = new Complete(); + + // Assert + Assert.IsAssignableFrom(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); + } +}