Skip to content

Commit 103063e

Browse files
committed
Refactor Finish activity tests and workflows. Migrate integration tests to component tests, introduce new workflows for advanced scenarios, and enhance runner utilities to simplify execution logic.
1 parent 9e61449 commit 103063e

9 files changed

Lines changed: 275 additions & 50 deletions

File tree

src/common/Elsa.Testing.Shared.Component/Services/AsyncWorkflowRunner.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Concurrent;
2+
using Elsa.Common.Models;
23
using Elsa.Testing.Shared.Models;
34
using Elsa.Workflows;
45
using Elsa.Workflows.Models;
@@ -55,6 +56,17 @@ await workflowClient.CreateInstanceAsync(new()
5556
return new(workflowExecutionContext, _activityExecutionRecords.Values.ToList());
5657
}
5758

59+
public async Task RunWorkflowAsync(string definitionId, string? correlationId = null)
60+
{
61+
var workflowClient = await _workflowRuntime.CreateClientAsync();
62+
await workflowClient.CreateInstanceAsync(new()
63+
{
64+
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(definitionId, VersionOptions.Published),
65+
CorrelationId = correlationId
66+
});
67+
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
68+
}
69+
5870
private void OnWorkflowStateCommitted(object? sender, WorkflowStateCommittedEventArgs e)
5971
{
6072
if (e.WorkflowExecutionContext.Status != WorkflowStatus.Finished)

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ public async Task<RunWorkflowResult> RunActivityAsync(IActivity activity, RunWor
207207
/// <returns>The workflow state after execution</returns>
208208
public async Task<WorkflowState> RunWorkflowAsync(string definitionId, IDictionary<string, object>? input = null, RunWorkflowOptions? options = null)
209209
{
210-
211210
await BuildAsync();
212211
return await Services.RunWorkflowUntilEndAsync(definitionId, input, runWorkflowOptions: options);
213212
}

test/component/Elsa.Workflows.ComponentTests/Scenarios/Activities/Primitives/Event/PublishEventTests.cs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public async Task PublishEvent_GlobalEvent_TriggersConsumerWorkflow()
4646
var correlationId = Guid.NewGuid().ToString();
4747

4848
// Act - Publish global event
49-
await RunWorkflowAsync(PublishGlobalEventWorkflow.DefinitionId, correlationId);
49+
await _workflowRunner.RunWorkflowAsync(PublishGlobalEventWorkflow.DefinitionId, correlationId);
5050

5151
// Assert - Consumer workflow was triggered and completed
5252
var consumerInstance = await GetSingleWorkflowInstanceAsync(ConsumerWorkflow.DefinitionId, correlationId);
@@ -61,7 +61,7 @@ public async Task PublishEvent_WithPayload_TransmitsPayloadToConsumer()
6161
var correlationId = Guid.NewGuid().ToString();
6262

6363
// Act - Publish global event with payload
64-
await RunWorkflowAsync(PublishGlobalEventWorkflow.DefinitionId, correlationId);
64+
await _workflowRunner.RunWorkflowAsync(PublishGlobalEventWorkflow.DefinitionId, correlationId);
6565

6666
// Assert - Consumer workflow received the payload
6767
var consumerInstance = await GetSingleWorkflowInstanceAsync(ConsumerWorkflow.DefinitionId, correlationId);
@@ -78,17 +78,6 @@ public async Task PublishEvent_WithPayload_TransmitsPayloadToConsumer()
7878
Assert.Contains("\"Shipped\"", payloadJson);
7979
}
8080

81-
private async Task RunWorkflowAsync(string definitionId, string? correlationId = null)
82-
{
83-
var workflowClient = await _workflowRuntime.CreateClientAsync();
84-
await workflowClient.CreateInstanceAsync(new()
85-
{
86-
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(definitionId, VersionOptions.Published),
87-
CorrelationId = correlationId
88-
});
89-
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
90-
}
91-
9281
private async Task<WorkflowInstance> GetSingleWorkflowInstanceAsync(string definitionId, string correlationId, int timeoutMs = 5000)
9382
{
9483
var tcs = new TaskCompletionSource<WorkflowInstance>();
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using Elsa.Common.Models;
2+
using Elsa.Testing.Shared.Models;
3+
using Elsa.Testing.Shared.Services;
4+
using Elsa.Workflows.ComponentTests.Abstractions;
5+
using Elsa.Workflows.ComponentTests.Fixtures;
6+
using Elsa.Workflows.ComponentTests.Scenarios.Activities.Primitives.Finish.Workflows;
7+
using Elsa.Workflows.Models;
8+
using Microsoft.Extensions.DependencyInjection;
9+
10+
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.Primitives.Finish;
11+
12+
public class FinishTests : AppComponentTest
13+
{
14+
private readonly AsyncWorkflowRunner _workflowRunner;
15+
16+
public FinishTests(App app) : base(app)
17+
{
18+
_workflowRunner = Scope.ServiceProvider.GetRequiredService<AsyncWorkflowRunner>();
19+
}
20+
21+
[Fact]
22+
public async Task Finish_SimpleWorkflow_MarksWorkflowAsFinished()
23+
{
24+
// Act
25+
var result = await RunWorkflowAsync(SimpleFinishWorkflow.DefinitionId);
26+
27+
// Assert
28+
AssertWorkflowFinished(result);
29+
}
30+
31+
[Fact]
32+
public async Task Finish_ClearsScheduler_NoScheduledActivities()
33+
{
34+
// Act
35+
var result = await RunWorkflowAsync(SimpleFinishWorkflow.DefinitionId);
36+
37+
// Assert
38+
Assert.Empty(result.WorkflowExecutionContext.Scheduler.List());
39+
}
40+
41+
[Fact]
42+
public async Task Finish_ClearsBookmarks_NoBookmarksRemain()
43+
{
44+
// Act
45+
var result = await RunWorkflowAsync(SimpleFinishWorkflow.DefinitionId);
46+
47+
// Assert
48+
Assert.Empty(result.WorkflowExecutionContext.Bookmarks);
49+
}
50+
51+
[Fact]
52+
public async Task Finish_WithFork_ClearsCompletionCallbacks()
53+
{
54+
// Act - Workflow publishes an event, then forks into two branches waiting for events
55+
// One branch receives the event and completes (due to WaitAny join mode)
56+
// Finish then clears the remaining completion callbacks from the other branch
57+
var result = await RunWorkflowAsync(FinishWithForkWorkflow.DefinitionId);
58+
59+
// Assert - Finish should have cleared all completion callbacks, bookmarks, and scheduler
60+
AssertWorkflowFullyCleared(result);
61+
}
62+
63+
[Fact]
64+
public async Task Finish_WithMultipleBookmarks_ClearsAllBookmarksAndCallbacks()
65+
{
66+
// Act - Workflow publishes EventA, forks into 3 branches, one receives event and completes
67+
// Finish then clears remaining bookmarks/callbacks from the other two branches
68+
var result = await RunWorkflowAsync(FinishWithMultipleBookmarksWorkflow.DefinitionId);
69+
70+
// Assert - All bookmarks and callbacks should be cleared
71+
AssertWorkflowFinished(result);
72+
Assert.Empty(result.WorkflowExecutionContext.Bookmarks);
73+
Assert.Empty(result.WorkflowExecutionContext.CompletionCallbacks);
74+
}
75+
76+
[Fact]
77+
public async Task Finish_InParallelBranch_TerminatesEntireWorkflow()
78+
{
79+
// Act
80+
var result = await RunWorkflowAsync(FinishInParallelBranchWorkflow.DefinitionId);
81+
82+
// Assert - Workflow should be finished (not suspended waiting for the other branch)
83+
AssertWorkflowFullyCleared(result);
84+
}
85+
86+
[Fact]
87+
public async Task Finish_VerifiesAllClearingOperations_InSingleTest()
88+
{
89+
// Act - Run workflow demonstrating Finish's clearing behavior:
90+
// 1. Fork creates completion callbacks for multiple branches
91+
// 2. One branch completes, triggering Fork's WaitAny completion
92+
// 3. Finish executes and clears: completion callbacks, bookmarks, and scheduler
93+
var result = await RunWorkflowAsync(FinishWithForkWorkflow.DefinitionId);
94+
95+
// Assert - Comprehensive verification of Finish behavior
96+
AssertWorkflowFullyCleared(result);
97+
}
98+
99+
private async Task<TestWorkflowExecutionResult> RunWorkflowAsync(string definitionId) =>
100+
await _workflowRunner.RunAndAwaitWorkflowCompletionAsync(
101+
WorkflowDefinitionHandle.ByDefinitionId(definitionId, VersionOptions.Published));
102+
103+
private static void AssertWorkflowFinished(TestWorkflowExecutionResult result)
104+
{
105+
Assert.Equal(WorkflowStatus.Finished, result.WorkflowExecutionContext.Status);
106+
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowExecutionContext.SubStatus);
107+
}
108+
109+
private static void AssertWorkflowFullyCleared(TestWorkflowExecutionResult result)
110+
{
111+
AssertWorkflowFinished(result);
112+
Assert.Empty(result.WorkflowExecutionContext.Bookmarks);
113+
Assert.Empty(result.WorkflowExecutionContext.Scheduler.List());
114+
Assert.Empty(result.WorkflowExecutionContext.CompletionCallbacks);
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Elsa.Workflows.Activities;
2+
3+
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.Primitives.Finish.Workflows;
4+
5+
/// <summary>
6+
/// A workflow where Finish is executed in one branch of a Fork while other branches have Event activities.
7+
/// Tests that Finish clears completion callbacks from all branches.
8+
/// </summary>
9+
public class FinishInParallelBranchWorkflow : WorkflowBase
10+
{
11+
public static readonly string DefinitionId = Guid.NewGuid().ToString();
12+
13+
protected override void Build(IWorkflowBuilder workflow)
14+
{
15+
workflow.WithDefinitionId(DefinitionId);
16+
17+
workflow.Root = new Sequence
18+
{
19+
Activities =
20+
{
21+
new Start(),
22+
new Fork
23+
{
24+
JoinMode = ForkJoinMode.WaitAll,
25+
Branches =
26+
{
27+
new Elsa.Workflows.Activities.Finish(),
28+
new Runtime.Activities.Event("ShouldNotExecute")
29+
}
30+
}
31+
}
32+
};
33+
}
34+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Elsa.Workflows.Activities;
2+
using Elsa.Workflows.Runtime.Activities;
3+
4+
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.Primitives.Finish.Workflows;
5+
6+
/// <summary>
7+
/// A workflow that publishes an event locally, then forks execution into two paths with Event activities,
8+
/// then merges into a Finish activity. Tests completion callback clearance.
9+
/// </summary>
10+
public class FinishWithForkWorkflow : WorkflowBase
11+
{
12+
public static readonly string DefinitionId = Guid.NewGuid().ToString();
13+
14+
protected override void Build(IWorkflowBuilder workflow)
15+
{
16+
workflow.WithDefinitionId(DefinitionId);
17+
18+
workflow.Root = new Sequence
19+
{
20+
Activities =
21+
{
22+
new Start(),
23+
// Publish local event first
24+
new PublishEvent
25+
{
26+
EventName = new("TriggerEvent"),
27+
IsLocalEvent = new(true)
28+
},
29+
// Fork into two branches waiting for events
30+
new Fork
31+
{
32+
JoinMode = ForkJoinMode.WaitAny,
33+
Branches =
34+
{
35+
new Runtime.Activities.Event("TriggerEvent"),
36+
new Runtime.Activities.Event("UnusedEvent")
37+
}
38+
},
39+
// Finish clears remaining completion callbacks
40+
new Elsa.Workflows.Activities.Finish()
41+
}
42+
};
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Elsa.Workflows.Activities;
2+
using Elsa.Workflows.Runtime.Activities;
3+
4+
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.Primitives.Finish.Workflows;
5+
6+
/// <summary>
7+
/// A workflow with multiple Event branches and a Finish activity.
8+
/// Tests that Finish clears all scheduled work when executed.
9+
/// </summary>
10+
public class FinishWithMultipleBookmarksWorkflow : WorkflowBase
11+
{
12+
public static readonly string DefinitionId = Guid.NewGuid().ToString();
13+
14+
protected override void Build(IWorkflowBuilder workflow)
15+
{
16+
workflow.WithDefinitionId(DefinitionId);
17+
18+
workflow.Root = new Sequence
19+
{
20+
Activities =
21+
{
22+
new Start(),
23+
// Publish local event that one branch will receive
24+
new PublishEvent
25+
{
26+
EventName = new("EventA"),
27+
IsLocalEvent = new(true)
28+
},
29+
new Fork
30+
{
31+
JoinMode = ForkJoinMode.WaitAny,
32+
Branches =
33+
{
34+
new Runtime.Activities.Event("EventA"),
35+
new Runtime.Activities.Event("EventB"),
36+
new Runtime.Activities.Event("EventC")
37+
}
38+
},
39+
new Elsa.Workflows.Activities.Finish()
40+
}
41+
};
42+
}
43+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Elsa.Workflows.Activities;
2+
3+
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.Primitives.Finish.Workflows;
4+
5+
/// <summary>
6+
/// A simple workflow that executes Finish activity.
7+
/// </summary>
8+
public class SimpleFinishWorkflow : WorkflowBase
9+
{
10+
public static readonly string DefinitionId = Guid.NewGuid().ToString();
11+
12+
protected override void Build(IWorkflowBuilder workflow)
13+
{
14+
workflow.WithDefinitionId(DefinitionId);
15+
workflow.Root = new Sequence
16+
{
17+
Activities =
18+
{
19+
new Start(),
20+
new Elsa.Workflows.Activities.Finish()
21+
}
22+
};
23+
}
24+
}

test/integration/Elsa.Activities.IntegrationTests/Primitives/FinishTests.cs

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)