Add ForEach tests, introduce asynchronous workflow runner and enhance workflow events. (#6926)
* Introduce asynchronous workflow runner and enhance workflow events. - Added `AsyncWorkflowRunner` to enable asynchronous workflow execution and result tracking. - Introduced new event arguments, such as `ActivityExecutedEventArgs` and `WorkflowStateCommittedEventArgs`. - Expanded `WorkflowEvents` class to include `ActivityExecuted`, `ActivityExecutedLogUpdated`, and `WorkflowStateCommitted` events. - Refactored event arguments into the `Elsa.Testing.Shared.EventArgs` namespace. - Enhanced tests with `AsyncWorkflowRunner` and new event-driven workflow scenarios. * Refactor event argument classes to unify namespace and simplify inheritance * Add shared component DotSettings file to support namespace exclusions
This commit is contained in:
parent
54c8a010fe
commit
bdfbd0886f
|
|
@ -0,0 +1,2 @@
|
||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=eventargs/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
using Elsa.Workflows;
|
||||||
|
|
||||||
|
namespace Elsa.Testing.Shared;
|
||||||
|
|
||||||
|
public class ActivityExecutedEventArgs(ActivityExecutionContext activityExecutionContext) : EventArgs
|
||||||
|
{
|
||||||
|
public ActivityExecutionContext ActivityExecutionContext { get; } = activityExecutionContext;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Elsa.Workflows;
|
||||||
|
using Elsa.Workflows.Runtime.Entities;
|
||||||
|
|
||||||
|
namespace Elsa.Testing.Shared;
|
||||||
|
|
||||||
|
public class ActivityExecutedLogUpdatedEventArgs(WorkflowExecutionContext workflowExecutionContext, ICollection<ActivityExecutionRecord> records) : EventArgs
|
||||||
|
{
|
||||||
|
public WorkflowExecutionContext WorkflowExecutionContext { get; } = workflowExecutionContext;
|
||||||
|
public ICollection<ActivityExecutionRecord> Records { get; } = records;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Elsa.Workflows;
|
||||||
|
using Elsa.Workflows.Management.Entities;
|
||||||
|
using Elsa.Workflows.State;
|
||||||
|
|
||||||
|
namespace Elsa.Testing.Shared;
|
||||||
|
|
||||||
|
public class WorkflowStateCommittedEventArgs(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, WorkflowInstance workflowInstance) : EventArgs
|
||||||
|
{
|
||||||
|
public WorkflowExecutionContext WorkflowExecutionContext { get; } = workflowExecutionContext;
|
||||||
|
public WorkflowState WorkflowState { get; } = workflowState;
|
||||||
|
public WorkflowInstance WorkflowInstance { get; } = workflowInstance;
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ using Elsa.Mediator.Contracts;
|
||||||
using Elsa.Testing.Shared.Services;
|
using Elsa.Testing.Shared.Services;
|
||||||
using Elsa.Workflows.Management.Notifications;
|
using Elsa.Workflows.Management.Notifications;
|
||||||
using Elsa.Workflows.Notifications;
|
using Elsa.Workflows.Notifications;
|
||||||
|
using Elsa.Workflows.Runtime.Notifications;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Elsa.Testing.Shared.Handlers;
|
namespace Elsa.Testing.Shared.Handlers;
|
||||||
|
|
@ -9,17 +10,38 @@ namespace Elsa.Testing.Shared.Handlers;
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class WorkflowEventHandlers(WorkflowEvents workflowEvents) :
|
public class WorkflowEventHandlers(WorkflowEvents workflowEvents) :
|
||||||
INotificationHandler<WorkflowFinished>,
|
INotificationHandler<WorkflowFinished>,
|
||||||
INotificationHandler<WorkflowInstanceSaved>
|
INotificationHandler<WorkflowInstanceSaved>,
|
||||||
|
INotificationHandler<WorkflowStateCommitted>,
|
||||||
|
INotificationHandler<ActivityExecuted>,
|
||||||
|
INotificationHandler<ActivityExecutionLogUpdated>
|
||||||
{
|
{
|
||||||
public Task HandleAsync(WorkflowFinished notification, CancellationToken cancellationToken)
|
public Task HandleAsync(WorkflowFinished notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
workflowEvents.OnWorkflowFinished(new WorkflowFinishedEventArgs(notification.Workflow, notification.WorkflowState));
|
workflowEvents.OnWorkflowFinished(new(notification.Workflow, notification.WorkflowState));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task HandleAsync(WorkflowInstanceSaved notification, CancellationToken cancellationToken)
|
public Task HandleAsync(WorkflowInstanceSaved notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
workflowEvents.OnWorkflowInstanceSaved(new WorkflowInstanceSavedEventArgs(notification.WorkflowInstance));
|
workflowEvents.OnWorkflowInstanceSaved(new(notification.WorkflowInstance));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleAsync(WorkflowStateCommitted notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
workflowEvents.OnWorkflowStateCommitted(new(notification.WorkflowExecutionContext, notification.WorkflowState, notification.WorkflowInstance));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleAsync(ActivityExecuted notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
workflowEvents.OnActivityExecuted(new(notification.ActivityExecutionContext));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleAsync(ActivityExecutionLogUpdated notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
workflowEvents.OnActivityExecutedLogUpdated(new(notification.WorkflowExecutionContext, notification.Records));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,12 @@ public class WorkflowEvents
|
||||||
{
|
{
|
||||||
public event EventHandler<WorkflowFinishedEventArgs>? WorkflowFinished;
|
public event EventHandler<WorkflowFinishedEventArgs>? WorkflowFinished;
|
||||||
public event EventHandler<WorkflowInstanceSavedEventArgs>? WorkflowInstanceSaved;
|
public event EventHandler<WorkflowInstanceSavedEventArgs>? WorkflowInstanceSaved;
|
||||||
|
public event EventHandler<WorkflowStateCommittedEventArgs>? WorkflowStateCommitted;
|
||||||
|
public event EventHandler<ActivityExecutedEventArgs>? ActivityExecuted;
|
||||||
|
public event EventHandler<ActivityExecutedLogUpdatedEventArgs>? ActivityExecutedLogUpdated;
|
||||||
public void OnWorkflowFinished(WorkflowFinishedEventArgs args) => WorkflowFinished?.Invoke(this, args);
|
public void OnWorkflowFinished(WorkflowFinishedEventArgs args) => WorkflowFinished?.Invoke(this, args);
|
||||||
public void OnWorkflowInstanceSaved(WorkflowInstanceSavedEventArgs args) => WorkflowInstanceSaved?.Invoke(this, args);
|
public void OnWorkflowInstanceSaved(WorkflowInstanceSavedEventArgs args) => WorkflowInstanceSaved?.Invoke(this, args);
|
||||||
|
public void OnWorkflowStateCommitted(WorkflowStateCommittedEventArgs args) => WorkflowStateCommitted?.Invoke(this, args);
|
||||||
|
public void OnActivityExecuted(ActivityExecutedEventArgs args) => ActivityExecuted?.Invoke(this, args);
|
||||||
|
public void OnActivityExecutedLogUpdated(ActivityExecutedLogUpdatedEventArgs args) => ActivityExecutedLogUpdated?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ public class LoggingMiddleware : IActivityExecutionMiddleware
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_stopwatch = new Stopwatch();
|
_stopwatch = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ public class WorkflowDefinitionEventConsumer(WorkflowDefinitionEvents workflowDe
|
||||||
{
|
{
|
||||||
public Task Consume(ConsumeContext<WorkflowDefinitionDeleted> context)
|
public Task Consume(ConsumeContext<WorkflowDefinitionDeleted> context)
|
||||||
{
|
{
|
||||||
workflowDefinitionEvents.OnWorkflowDefinitionDeleted(new WorkflowDefinitionDeletedEventArgs(context.Message.Id));
|
workflowDefinitionEvents.OnWorkflowDefinitionDeleted(new(context.Message.Id));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ public class EventPublishingChangeTokenSignaler(IChangeTokenSignaler decoratedSe
|
||||||
|
|
||||||
public ValueTask TriggerTokenAsync(string key, CancellationToken cancellationToken = default)
|
public ValueTask TriggerTokenAsync(string key, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
triggerChangeTokenSignalEvents.RaiseChangeTokenSignalTriggered(new TriggerChangeTokenSignalEventArgs(key));
|
triggerChangeTokenSignalEvents.RaiseChangeTokenSignalTriggered(new(key));
|
||||||
return decoratedService.TriggerTokenAsync(key, cancellationToken);
|
return decoratedService.TriggerTokenAsync(key, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,8 @@ using Elsa.Testing.Shared.Services;
|
||||||
using Elsa.Workflows.ComponentTests.Consumers;
|
using Elsa.Workflows.ComponentTests.Consumers;
|
||||||
using Elsa.Workflows.ComponentTests.Decorators;
|
using Elsa.Workflows.ComponentTests.Decorators;
|
||||||
using Elsa.Workflows.ComponentTests.Materializers;
|
using Elsa.Workflows.ComponentTests.Materializers;
|
||||||
|
using Elsa.Workflows.ComponentTests.Scenarios.Activities.ForEach;
|
||||||
|
using Elsa.Workflows.ComponentTests.Services;
|
||||||
using Elsa.Workflows.ComponentTests.WorkflowProviders;
|
using Elsa.Workflows.ComponentTests.WorkflowProviders;
|
||||||
using Elsa.Workflows.Management;
|
using Elsa.Workflows.Management;
|
||||||
using Elsa.Workflows.Runtime.Distributed.Extensions;
|
using Elsa.Workflows.Runtime.Distributed.Extensions;
|
||||||
|
|
@ -127,12 +129,13 @@ public class WorkflowServer(Infrastructure infrastructure, string url) : WebAppl
|
||||||
{
|
{
|
||||||
services
|
services
|
||||||
.AddSingleton<SignalManager>()
|
.AddSingleton<SignalManager>()
|
||||||
.AddScoped<WorkflowEvents>()
|
.AddScoped<AsyncWorkflowRunner>()
|
||||||
|
.AddSingleton<WorkflowEvents>()
|
||||||
.AddScoped<WorkflowDefinitionEvents>()
|
.AddScoped<WorkflowDefinitionEvents>()
|
||||||
.AddSingleton<TriggerChangeTokenSignalEvents>()
|
.AddSingleton<TriggerChangeTokenSignalEvents>()
|
||||||
.AddScoped<IWorkflowMaterializer, TestWorkflowMaterializer>()
|
.AddScoped<IWorkflowMaterializer, TestWorkflowMaterializer>()
|
||||||
.AddNotificationHandlersFrom<WorkflowServer>()
|
.AddNotificationHandlersFrom<WorkflowServer>()
|
||||||
.AddWorkflowDefinitionProvider<TestWorkflowProvider>()
|
.AddWorkflowsProvider<TestWorkflowProvider>()
|
||||||
.AddNotificationHandlersFrom<WorkflowEventHandlers>()
|
.AddNotificationHandlersFrom<WorkflowEventHandlers>()
|
||||||
.Decorate<IChangeTokenSignaler, EventPublishingChangeTokenSignaler>()
|
.Decorate<IChangeTokenSignaler, EventPublishingChangeTokenSignaler>()
|
||||||
;
|
;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Elsa.Workflows.Runtime.Entities;
|
||||||
|
|
||||||
|
namespace Elsa.Workflows.ComponentTests.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the result of a test workflow execution, including the workflow execution context and activity execution records.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="WorkflowExecutionContext">The workflow execution context after completion.</param>
|
||||||
|
/// <param name="ActivityExecutionRecords">The collection of activity execution records for the workflow.</param>
|
||||||
|
public record TestWorkflowExecutionResult(WorkflowExecutionContext WorkflowExecutionContext, ICollection<ActivityExecutionRecord> ActivityExecutionRecords);
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
using Elsa.Testing.Shared;
|
||||||
|
using Elsa.Testing.Shared.Services;
|
||||||
|
using Elsa.Workflows.ComponentTests.Models;
|
||||||
|
using Elsa.Workflows.Models;
|
||||||
|
using Elsa.Workflows.Runtime;
|
||||||
|
using Elsa.Workflows.Runtime.Entities;
|
||||||
|
using Elsa.Workflows.Runtime.Messages;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Elsa.Workflows.ComponentTests.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides functionality to execute workflows asynchronously and await their completion for testing purposes.
|
||||||
|
/// Tracks activity execution records and workflow completion signals.
|
||||||
|
/// </summary>
|
||||||
|
public class AsyncWorkflowRunner : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IWorkflowRuntime _workflowRuntime;
|
||||||
|
private readonly IIdentityGenerator _identityGenerator;
|
||||||
|
private readonly SignalManager _signalManager;
|
||||||
|
private readonly WorkflowEvents _workflowEvents;
|
||||||
|
private readonly ConcurrentDictionary<string, ActivityExecutionRecord> _activityExecutionRecords = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AsyncWorkflowRunner"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public AsyncWorkflowRunner(IWorkflowRuntime workflowRuntime, IIdentityGenerator identityGenerator, SignalManager signalManager, WorkflowEvents workflowEvents)
|
||||||
|
{
|
||||||
|
_workflowRuntime = workflowRuntime;
|
||||||
|
_identityGenerator = identityGenerator;
|
||||||
|
_signalManager = signalManager;
|
||||||
|
_workflowEvents = workflowEvents;
|
||||||
|
|
||||||
|
_workflowEvents.WorkflowStateCommitted += OnWorkflowStateCommitted;
|
||||||
|
_workflowEvents.ActivityExecutedLogUpdated += OnActivityExecutedLogUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the specified workflow definition asynchronously and waits for its completion.
|
||||||
|
/// Returns the workflow execution context and activity execution records.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="workflowDefinitionHandle">The handle of the workflow definition to execute.</param>
|
||||||
|
/// <returns>A <see cref="TestWorkflowExecutionResult"/> containing the workflow execution context and activity execution records.</returns>
|
||||||
|
public async Task<TestWorkflowExecutionResult> RunAndAwaitWorkflowCompletionAsync(WorkflowDefinitionHandle workflowDefinitionHandle)
|
||||||
|
{
|
||||||
|
var workflowInstanceId = _identityGenerator.GenerateId();
|
||||||
|
var workflowClient = await _workflowRuntime.CreateClientAsync(workflowInstanceId);
|
||||||
|
await workflowClient.CreateInstanceAsync(new()
|
||||||
|
{
|
||||||
|
WorkflowDefinitionHandle = workflowDefinitionHandle
|
||||||
|
});
|
||||||
|
_activityExecutionRecords.Clear();
|
||||||
|
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
|
||||||
|
var signalName = GetSignalName(workflowInstanceId);
|
||||||
|
var workflowExecutionContext = await _signalManager.WaitAsync<WorkflowExecutionContext>(signalName);
|
||||||
|
return new(workflowExecutionContext, _activityExecutionRecords.Values.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorkflowStateCommitted(object? sender, WorkflowStateCommittedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.WorkflowExecutionContext.Status != WorkflowStatus.Finished)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var signalName = GetSignalName(e.WorkflowExecutionContext.Id);
|
||||||
|
_signalManager.Trigger(signalName, e.WorkflowExecutionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnActivityExecutedLogUpdated(object? sender, ActivityExecutedLogUpdatedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var record in e.Records)
|
||||||
|
_activityExecutionRecords[record.Id] = record;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSignalName(string workflowInstanceId) => $"WorkflowInstanceCompleted-{workflowInstanceId}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes from workflow events and releases resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_workflowEvents.WorkflowStateCommitted -= OnWorkflowStateCommitted;
|
||||||
|
_workflowEvents.ActivityExecutedLogUpdated -= OnActivityExecutedLogUpdated;
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Elsa.Common.Models;
|
||||||
|
using Elsa.Workflows.ComponentTests.Abstractions;
|
||||||
|
using Elsa.Workflows.ComponentTests.Fixtures;
|
||||||
|
using Elsa.Workflows.ComponentTests.Scenarios.Activities.ForEach.Workflows;
|
||||||
|
using Elsa.Workflows.ComponentTests.Services;
|
||||||
|
using Elsa.Workflows.Models;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.ForEach;
|
||||||
|
|
||||||
|
public class ForEachWorkflowTests : AppComponentTest
|
||||||
|
{
|
||||||
|
private readonly AsyncWorkflowRunner _workflowRunner;
|
||||||
|
|
||||||
|
public ForEachWorkflowTests(App app) : base(app)
|
||||||
|
{
|
||||||
|
_workflowRunner = Scope.ServiceProvider.GetRequiredService<AsyncWorkflowRunner>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "ForEach activity executes child activity for each collection item and supports blocking activities")]
|
||||||
|
public async Task ForEachActivity_ExecutesChildActivity_ForEachCollectionItem_AndSupportsBlocking()
|
||||||
|
{
|
||||||
|
var result = await _workflowRunner.RunAndAwaitWorkflowCompletionAsync(WorkflowDefinitionHandle.ByDefinitionId(ForEachWorkflow.DefinitionId, VersionOptions.Published));
|
||||||
|
var writeLineExecutionRecords = result.ActivityExecutionRecords.Where(x => x.ActivityId == "WriteLine1").ToList();
|
||||||
|
|
||||||
|
// Assert that the workflow executed the expected number of activities.
|
||||||
|
Assert.Equal(3, writeLineExecutionRecords.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
using Elsa.Extensions;
|
||||||
|
using Elsa.Scheduling.Activities;
|
||||||
|
using Elsa.Workflows.Activities;
|
||||||
|
|
||||||
|
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.ForEach.Workflows;
|
||||||
|
|
||||||
|
public class ForEachWorkflow : WorkflowBase
|
||||||
|
{
|
||||||
|
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||||
|
protected override void Build(IWorkflowBuilder builder)
|
||||||
|
{
|
||||||
|
builder.WithDefinitionId(DefinitionId);
|
||||||
|
builder.Root = new Sequence
|
||||||
|
{
|
||||||
|
Activities =
|
||||||
|
{
|
||||||
|
new ForEach<string>(["a", "b", "c"])
|
||||||
|
{
|
||||||
|
Body = new Sequence
|
||||||
|
{
|
||||||
|
Activities =
|
||||||
|
{
|
||||||
|
new WriteLine(context => $"Processing item: {context.GetVariable<string>("CurrentValue")}"),
|
||||||
|
Delay.FromMilliseconds(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,11 +26,11 @@ public class DispatchWorkflowsTests : AppComponentTest
|
||||||
public async Task DispatchAndWaitWorkflow_ShouldWaitForChildWorkflowToComplete()
|
public async Task DispatchAndWaitWorkflow_ShouldWaitForChildWorkflowToComplete()
|
||||||
{
|
{
|
||||||
var workflowClient = await _workflowRuntime.CreateClientAsync();
|
var workflowClient = await _workflowRuntime.CreateClientAsync();
|
||||||
await workflowClient.CreateInstanceAsync(new CreateWorkflowInstanceRequest
|
await workflowClient.CreateInstanceAsync(new()
|
||||||
{
|
{
|
||||||
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(DispatchAndWaitWorkflow.DefinitionId, VersionOptions.Published)
|
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(DispatchAndWaitWorkflow.DefinitionId, VersionOptions.Published)
|
||||||
});
|
});
|
||||||
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
|
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
|
||||||
await _signalManager.WaitAsync<string>("Completed");
|
await _signalManager.WaitAsync<object>("Completed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Elsa.Extensions;
|
||||||
|
using Elsa.Testing.Shared;
|
||||||
|
using Elsa.Workflows.Activities;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Elsa.Activities.IntegrationTests;
|
||||||
|
|
||||||
|
public class ForEachTests
|
||||||
|
{
|
||||||
|
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public ForEachTests(ITestOutputHelper testOutputHelper)
|
||||||
|
{
|
||||||
|
_serviceProvider = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "ForEach executes each activity for every item in the collection")]
|
||||||
|
public async Task ForEach_ExecutesEachActivity_ForEveryItem()
|
||||||
|
{
|
||||||
|
var expectedLines = new[] {"a", "b", "c"};
|
||||||
|
var forEach = new ForEach<string>(expectedLines)
|
||||||
|
{
|
||||||
|
Body = new WriteLine(context => context.GetVariable<string>("CurrentValue"))
|
||||||
|
};
|
||||||
|
await _serviceProvider.RunActivityAsync(forEach);
|
||||||
|
Assert.Equal(expectedLines, _capturingTextWriter.Lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue