diff --git a/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs b/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs index 4fe3c536b..9275937b8 100644 --- a/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs +++ b/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs @@ -8,7 +8,12 @@ public class SignalManager public async Task WaitAsync(object signal, int millisecondsTimeout = 8000) { - return await WaitAsync(signal, millisecondsTimeout) is T result ? result : throw new InvalidCastException($"Signal '{signal}' was not of type '{typeof(T).Name}'."); + var result = await WaitAsync(signal, millisecondsTimeout); + + if(result is not T typedResult) + throw new InvalidCastException($"Signal '{signal}' was not of type '{typeof(T).Name}'."); + + return typedResult; } public async Task WaitAsync(object signal, int millisecondsTimeout = 8000) diff --git a/src/modules/Elsa.Workflows.Management/Materializers/ClrWorkflowMaterializer.cs b/src/modules/Elsa.Workflows.Management/Materializers/ClrWorkflowMaterializer.cs index 1c907040c..5581879cd 100644 --- a/src/modules/Elsa.Workflows.Management/Materializers/ClrWorkflowMaterializer.cs +++ b/src/modules/Elsa.Workflows.Management/Materializers/ClrWorkflowMaterializer.cs @@ -40,7 +40,7 @@ public class ClrWorkflowMaterializer : IWorkflowMaterializer public async ValueTask MaterializeAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { var providerContext = _payloadSerializer.Deserialize(definition.MaterializerContext!); - var workflowBuilderType = providerContext.WorkflowBuilderType; + var workflowBuilderType = providerContext.WorkflowBuilderType == null! ? typeof(NotFoundWorkflowbuilder) : providerContext.WorkflowBuilderType; var workflowBuilder = (IWorkflow)ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, workflowBuilderType); var workflowDefinitionBuilder = _workflowBuilderFactory.CreateBuilder(); var workflow = await workflowDefinitionBuilder.BuildWorkflowAsync(workflowBuilder, cancellationToken); @@ -56,4 +56,11 @@ public class ClrWorkflowMaterializer : IWorkflowMaterializer /// Provides context for the CLR workflow materializer. /// /// The type of the workflow builder. -public record ClrWorkflowMaterializerContext(Type WorkflowBuilderType); \ No newline at end of file +public record ClrWorkflowMaterializerContext(Type WorkflowBuilderType); + +/// +/// A workflow builder that is used when the workflow builder type is not found. +/// +public class NotFoundWorkflowbuilder : WorkflowBase +{ +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs index 13060a0a9..327dcc28a 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs @@ -162,7 +162,7 @@ public class BulkDispatchWorkflows : Activity Arguments = itemDictionary }; - var inputDictionary = item as IDictionary ?? new Dictionary(); + var inputDictionary = item as IDictionary ?? itemDictionary; input["ParentInstanceId"] = parentInstanceId; input.Merge(inputDictionary); diff --git a/test/component/Elsa.Workflows.ComponentTests/Elsa.Workflows.ComponentTests.csproj.DotSettings b/test/component/Elsa.Workflows.ComponentTests/Elsa.Workflows.ComponentTests.csproj.DotSettings index cc1f78727..92b37b9c4 100644 --- a/test/component/Elsa.Workflows.ComponentTests/Elsa.Workflows.ComponentTests.csproj.DotSettings +++ b/test/component/Elsa.Workflows.ComponentTests/Elsa.Workflows.ComponentTests.csproj.DotSettings @@ -1,3 +1,5 @@ - - + + True + True + True \ No newline at end of file diff --git a/test/component/Elsa.Workflows.ComponentTests/Helpers/Activities/TriggerSignal.cs b/test/component/Elsa.Workflows.ComponentTests/Helpers/Activities/TriggerSignal.cs new file mode 100644 index 000000000..dc3f69cf4 --- /dev/null +++ b/test/component/Elsa.Workflows.ComponentTests/Helpers/Activities/TriggerSignal.cs @@ -0,0 +1,55 @@ +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; +using Elsa.Expressions.Models; +using Elsa.Extensions; +using Elsa.Workflows.Memory; +using Elsa.Workflows.Models; + +namespace Elsa.Workflows.ComponentTests.Activities; + +public class TriggerSignal : CodeActivity +{ + /// + [JsonConstructor] + private TriggerSignal(string? source = default, int? line = default) : base(source, line) + { + } + + /// + public TriggerSignal(string eventName, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Literal(eventName), source, line) + { + } + + /// + public TriggerSignal(Func eventName, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + : this(Expression.DelegateExpression(eventName), source, line) + { + } + + /// + public TriggerSignal(Func eventName, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + : this(Expression.DelegateExpression(eventName), source, line) + { + } + + /// + public TriggerSignal(Variable variable, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => EventName = new Input(variable); + + /// + public TriggerSignal(Literal literal, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => EventName = new Input(literal); + + /// + public TriggerSignal(Expression expression, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => EventName = new Input(expression, new MemoryBlockReference()); + + /// + public TriggerSignal(Input eventName, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => EventName = eventName; + + public Input EventName { get; set; } + + protected override void Execute(ActivityExecutionContext context) + { + var testEventManager = context.GetRequiredService(); + var eventName = EventName.Get(context); + testEventManager.Trigger(eventName); + } +} \ No newline at end of file diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/BulkDispatchWorkflowsTests.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/BulkDispatchWorkflowsTests.cs index 1468b4d5c..6d5a4b444 100644 --- a/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/BulkDispatchWorkflowsTests.cs +++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/BulkDispatchWorkflowsTests.cs @@ -1,4 +1,4 @@ -using Elsa.Common.Models; +using Elsa.Common.Models; using Elsa.Testing.Shared; using Elsa.Testing.Shared.Services; using Elsa.Workflows.ComponentTests.Helpers; @@ -15,7 +15,7 @@ public class BulkDispatchWorkflowsTests : AppComponentTest private readonly WorkflowEvents _workflowEvents; private readonly SignalManager _signalManager; private readonly IWorkflowRuntime _workflowRuntime; - private static readonly object ParentWorkflowCompletedSignal = new(); + private static readonly object GreetEmployeesWorkflowCompletedSignal = new(); public BulkDispatchWorkflowsTests(App app) : base(app) { @@ -35,10 +35,22 @@ public class BulkDispatchWorkflowsTests : AppComponentTest WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(GreetEmployeesWorkflow.DefinitionId, VersionOptions.Published) }); await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty); - var parentWorkflowInstanceArgs = await _signalManager.WaitAsync(ParentWorkflowCompletedSignal); + var parentWorkflowInstanceArgs = await _signalManager.WaitAsync(GreetEmployeesWorkflowCompletedSignal); Assert.Equal(WorkflowStatus.Finished, parentWorkflowInstanceArgs.WorkflowInstance.Status); } + + /// + /// Individual items are sent as input to child workflows. + /// + [Fact] + public async Task DispatchWorkflows_ChildWorkflowsShouldReceiveCurrentItem() + { + await _workflowRuntime.StartWorkflowAsync(MixFruitsWorkflow.DefinitionId); + await _signalManager.WaitAsync("Apple"); + await _signalManager.WaitAsync("Banana"); + await _signalManager.WaitAsync("Cherry"); + } private void OnWorkflowInstanceSaved(object? sender, WorkflowInstanceSavedEventArgs e) { @@ -46,7 +58,7 @@ public class BulkDispatchWorkflowsTests : AppComponentTest return; if(e.WorkflowInstance.DefinitionId == GreetEmployeesWorkflow.DefinitionId) - _signalManager.Trigger(ParentWorkflowCompletedSignal, e); + _signalManager.Trigger(GreetEmployeesWorkflowCompletedSignal, e); } protected override void OnDispose() diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/Workflows/FruitWorkflow.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/Workflows/FruitWorkflow.cs new file mode 100644 index 000000000..58f644d81 --- /dev/null +++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/Workflows/FruitWorkflow.cs @@ -0,0 +1,27 @@ +using Elsa.Extensions; +using Elsa.Workflows.Activities; +using Elsa.Workflows.ComponentTests.Activities; +using Elsa.Workflows.Contracts; +using Hangfire.Annotations; + +namespace Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows; + +[UsedImplicitly] +public class FruitWorkflow : WorkflowBase +{ + public static readonly string DefinitionId = Guid.NewGuid().ToString(); + + protected override void Build(IWorkflowBuilder builder) + { + builder.WithDefinitionId(DefinitionId); + var item = builder.WithInput("Item"); + builder.Root = new Sequence + { + Activities = + { + new WriteLine(x => $"Mixing {x.GetInput(item)}"), + new TriggerSignal(x => x.GetInput(item)) + } + }; + } +} \ No newline at end of file diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/Workflows/MixFruitsWorkflow.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/Workflows/MixFruitsWorkflow.cs new file mode 100644 index 000000000..a6fcc8151 --- /dev/null +++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/BulkDispatchWorkflows/Workflows/MixFruitsWorkflow.cs @@ -0,0 +1,31 @@ +using Elsa.Workflows.Activities; +using Elsa.Workflows.Contracts; + +namespace Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows; + +public class MixFruitsWorkflow : WorkflowBase +{ + public static readonly string DefinitionId = Guid.NewGuid().ToString(); + + protected override void Build(IWorkflowBuilder builder) + { + var fruits = new[] + { + "Apple", "Banana", "Cherry" + }; + + builder.WithDefinitionId(DefinitionId); + builder.Root = new Sequence + { + Activities = + { + new Runtime.Activities.BulkDispatchWorkflows + { + WorkflowDefinitionId = new(FruitWorkflow.DefinitionId), + Items = new(fruits), + WaitForCompletion = new(true) + } + } + }; + } +} \ No newline at end of file