diff --git a/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs b/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs index d78c278f1..2b4896b33 100644 --- a/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs +++ b/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs @@ -34,7 +34,7 @@ namespace Paramore.Brighter.MediatorWorkflow; /// The action to be taken with the step. /// The action to be taken upon completion of the step. /// The next step in the sequence. -public record Step(string Name, IWorkflowAction Action, Action OnCompletion, Step? Next); +public record Step(string Name, IWorkflowAction Action, Action OnCompletion, Step? Next, Action? OnFaulted = null, Step? FaultNext = null); /// /// Defines an interface for workflow actions. @@ -53,37 +53,24 @@ public interface IWorkflowAction /// /// Represents a workflow based on evaluating a specification to determine which one to send /// -/// The type of the true branch -/// The type of the false branch /// The rule that decides between the command issued by each branch -/// -/// -/// -public class Choice( - Func trueRequestFactory, - Func falseRequestFactory, +/// The workflow data, used to make the choice +public class Choice( + Func> OnTrue, + Func> OnFalse, ISpecification predicate ) : IWorkflowAction - where TTrueRequest : class, IRequest - where TFalseRequest : class, IRequest { public void Handle(Workflow state, IAmACommandProcessor commandProcessor) { - //NOTE: we chose the command handler by parameterized type from the argument to Send() so the type needs to be explicit here - // do not try to optimize this branch condition via a base type, it will not work - if (predicate.IsSatisfiedBy(state.Data)) - { - TTrueRequest command = trueRequestFactory(); - command.CorrelationId = state.Id; - commandProcessor.Send(command); - } - else + if (state.CurrentStep is null) + throw new InvalidOperationException("The workflow has not been initialized."); + + state.CurrentStep = state.CurrentStep with { - TFalseRequest command = falseRequestFactory(); - command.CorrelationId = state.Id; - commandProcessor.Send(command); - } + Next = (predicate.IsSatisfiedBy(state.Data) ? OnTrue(state.Data) : OnFalse(state.Data)) + }; } } diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/TestDoubles/MyFault.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/TestDoubles/MyFault.cs new file mode 100644 index 000000000..b46dfab69 --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Workflows/TestDoubles/MyFault.cs @@ -0,0 +1,33 @@ +#region Licence +/* The MIT License (MIT) +Copyright © 2014 Ian Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System; + +namespace Paramore.Brighter.Core.Tests.Workflows.TestDoubles +{ + internal class MyFault(string? value) : Event(Guid.NewGuid().ToString()) + { + public string Value { get; set; } = value ?? string.Empty; + } +} diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs index 6d69df292..e7ecbca88 100644 --- a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs +++ b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs @@ -13,6 +13,8 @@ public class MediatorFailingChoiceFlowTests private readonly Mediator? _mediator; private readonly Workflow _flow; private bool _stepCompletedOne; + private bool _stepCompletedTwo; + private bool _stepCompletedThree; public MediatorFailingChoiceFlowTests() { @@ -36,10 +38,20 @@ public MediatorFailingChoiceFlowTests() var workflowData= new WorkflowTestData(); workflowData.Bag.Add("MyValue", "Fail"); - var stepOne = new Step("Test of Workflow Step One", - new Choice( - () => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! }, - () => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! }, + var stepThree = new Step("Test of Workflow Step Three", + new FireAndForget(() => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! }), + () => { _stepCompletedThree = true; }, + null); + + var stepTwo = new Step("Test of Workflow Step Two", + new FireAndForget(() => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! }), + () => { _stepCompletedTwo = true; }, + null); + + var stepOne = new Step("Test of Workflow Step One", + new Choice( + (_) => stepTwo, + (_) => stepThree, new Specification(x => x.Bag["MyValue"] as string == "Pass")), () => { _stepCompletedOne = true; }, null); @@ -61,6 +73,8 @@ public void When_running_a_choice_workflow_step() _mediator?.RunWorkFlow(_flow); _stepCompletedOne.Should().BeTrue(); + _stepCompletedTwo.Should().BeFalse(); + _stepCompletedThree.Should().BeTrue(); MyOtherCommandHandler.ReceivedCommands.Any(c => c.Value == "Fail").Should().BeTrue(); MyCommandHandler.ReceivedCommands.Any().Should().BeFalse(); _stepCompletedOne.Should().BeTrue(); diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs index 4af16bf8c..3a07565d0 100644 --- a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs +++ b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs @@ -13,6 +13,8 @@ public class MediatorPassingChoiceFlowTests private readonly Mediator? _mediator; private readonly Workflow _flow; private bool _stepCompletedOne; + private bool _stepCompletedTwo; + private bool _stepCompletedThree; public MediatorPassingChoiceFlowTests() { @@ -35,11 +37,21 @@ public MediatorPassingChoiceFlowTests() var workflowData= new WorkflowTestData(); workflowData.Bag.Add("MyValue", "Pass"); + + var stepThree = new Step("Test of Workflow Step Three", + new FireAndForget(() => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! }), + () => { _stepCompletedThree = true; }, + null); + + var stepTwo = new Step("Test of Workflow Step Two", + new FireAndForget(() => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! }), + () => { _stepCompletedTwo = true; }, + null); var stepOne = new Step("Test of Workflow Step One", - new Choice( - () => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! }, - () => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! }, + new Choice( + (_) => stepTwo, + (_) => stepThree, new Specification(x => x.Bag["MyValue"] as string == "Pass")), () => { _stepCompletedOne = true; }, null); diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_workflow_with_robust_reply_nofault.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_workflow_with_robust_reply_nofault.cs new file mode 100644 index 000000000..db396da88 --- /dev/null +++ b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_workflow_with_robust_reply_nofault.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using Amazon.Runtime.Internal.Transform; +using FluentAssertions; +using Paramore.Brighter.Core.Tests.Workflows.TestDoubles; +using Paramore.Brighter.MediatorWorkflow; +using Polly.Registry; +using Xunit; + +namespace Paramore.Brighter.Core.Tests.Workflows; + +public class MediatorRobustReplyNoFaultStepFlowTests +{ + private readonly Mediator _mediator; + private bool _stepCompleted; + private bool _stepFaulted; + private readonly Workflow _flow; + + public MediatorRobustReplyNoFaultStepFlowTests() + { + var registry = new SubscriberRegistry(); + registry.Register(); + registry.Register(); + + IAmACommandProcessor commandProcessor = null; + var handlerFactory = new SimpleHandlerFactorySync((handlerType) => + handlerType switch + { + _ when handlerType == typeof(MyCommandHandler) => new MyCommandHandler(commandProcessor), + _ when handlerType == typeof(MyEventHandler) => new MyEventHandler(_mediator), + _ => throw new InvalidOperationException($"The handler type {handlerType} is not supported") + }); + + commandProcessor = new CommandProcessor(registry, handlerFactory, new InMemoryRequestContextFactory(), new PolicyRegistry()); + PipelineBuilder.ClearPipelineCache(); + + var workflowData= new WorkflowTestData(); + workflowData.Bag.Add("MyValue", "Test"); + + var firstStep = new Step("Test of Workflow", + new RobustRequestAndReaction( + () => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! }, + (reply) => workflowData.Bag.Add("MyReply", ((MyEvent)reply).Value), + (fault) => workflowData.Bag.Add("MyFault", ((MyFault)fault).Value)), + () => { _stepCompleted = true; }, + null, + () => { _stepFaulted = true; }, + null); + + _flow = new Workflow(firstStep, workflowData) ; + + _mediator = new Mediator( + commandProcessor, + new InMemoryWorkflowStore() + ); + } + + [Fact] + public void When_running_a_workflow_with_reply() + { + MyCommandHandler.ReceivedCommands.Clear(); + MyEventHandler.ReceivedEvents.Clear(); + + _mediator.RunWorkFlow(_flow); + + _stepCompleted.Should().BeTrue(); + _stepFaulted.Should().BeFalse(); + + MyCommandHandler.ReceivedCommands.Any(c => c.Value == "Test").Should().BeTrue(); + MyEventHandler.ReceivedEvents.Any(e => e.Value == "Test").Should().BeTrue(); + _flow.State.Should().Be(WorkflowState.Done); + } +}