From d97d4846c407090cdcb557afb7e87e527a31c043 Mon Sep 17 00:00:00 2001 From: Vildan Softic Date: Mon, 10 Mar 2025 16:42:08 +0100 Subject: [PATCH 1/2] feat: add calls to pre workflow middlewares to SyncWorkflowRunner --- src/WorkflowCore/Services/SyncWorkflowRunner.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/WorkflowCore/Services/SyncWorkflowRunner.cs b/src/WorkflowCore/Services/SyncWorkflowRunner.cs index 5317a8966..da567fd43 100644 --- a/src/WorkflowCore/Services/SyncWorkflowRunner.cs +++ b/src/WorkflowCore/Services/SyncWorkflowRunner.cs @@ -17,8 +17,9 @@ public class SyncWorkflowRunner : ISyncWorkflowRunner private readonly IExecutionPointerFactory _pointerFactory; private readonly IQueueProvider _queueService; private readonly IDateTimeProvider _dateTimeProvider; + private readonly IWorkflowMiddlewareRunner _middlewareRunner; - public SyncWorkflowRunner(IWorkflowHost host, IWorkflowExecutor executor, IDistributedLockProvider lockService, IWorkflowRegistry registry, IPersistenceProvider persistenceStore, IExecutionPointerFactory pointerFactory, IQueueProvider queueService, IDateTimeProvider dateTimeProvider) + public SyncWorkflowRunner(IWorkflowHost host, IWorkflowExecutor executor, IDistributedLockProvider lockService, IWorkflowRegistry registry, IPersistenceProvider persistenceStore, IExecutionPointerFactory pointerFactory, IQueueProvider queueService, IDateTimeProvider dateTimeProvider, IWorkflowMiddlewareRunner middlewareRunner) { _host = host; _executor = executor; @@ -28,6 +29,7 @@ public SyncWorkflowRunner(IWorkflowHost host, IWorkflowExecutor executor, IDistr _pointerFactory = pointerFactory; _queueService = queueService; _dateTimeProvider = dateTimeProvider; + _middlewareRunner = middlewareRunner; } public Task RunWorkflowSync(string workflowId, int version, TData data, @@ -67,7 +69,10 @@ public async Task RunWorkflowSync(string workflowId, in wf.Data = def.DataType.GetConstructor(new Type[0]).Invoke(new object[0]); } - wf.ExecutionPointers.Add(_pointerFactory.BuildGenesisPointer(def)); + var genPointer = _pointerFactory.BuildGenesisPointer(def); + wf.ExecutionPointers.Add(genPointer); + + await _middlewareRunner.RunPreMiddleware(wf, def); var id = Guid.NewGuid().ToString(); From 022c7bbd72e6414bd002764201d9dca828bb07dd Mon Sep 17 00:00:00 2001 From: Vildan Softic Date: Mon, 10 Mar 2025 16:42:20 +0100 Subject: [PATCH 2/2] tests: cover call of Pre-Middlware execution --- .../Services/SyncWorkflowRunnerTests.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 test/WorkflowCore.UnitTests/Services/SyncWorkflowRunnerTests.cs diff --git a/test/WorkflowCore.UnitTests/Services/SyncWorkflowRunnerTests.cs b/test/WorkflowCore.UnitTests/Services/SyncWorkflowRunnerTests.cs new file mode 100644 index 000000000..9ec7c09c8 --- /dev/null +++ b/test/WorkflowCore.UnitTests/Services/SyncWorkflowRunnerTests.cs @@ -0,0 +1,177 @@ +using FakeItEasy; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using WorkflowCore.Interface; +using WorkflowCore.Models; +using WorkflowCore.Services; +using Xunit; +using System.Threading; + +namespace WorkflowCore.UnitTests.Services +{ + public class SyncWorkflowRunnerTests + { + protected ISyncWorkflowRunner Subject; + protected IWorkflowHost Host; + protected IPersistenceProvider PersistenceProvider; + protected IWorkflowRegistry Registry; + protected IExecutionResultProcessor ResultProcesser; + protected ILifeCycleEventPublisher EventHub; + protected ICancellationProcessor CancellationProcessor; + protected IServiceProvider ServiceProvider; + protected IScopeProvider ScopeProvider; + protected IDateTimeProvider DateTimeProvider; + protected IStepExecutor StepExecutor; + protected IExecutionPointerFactory PointerFactory; + protected IDistributedLockProvider LockService; + protected IWorkflowMiddlewareRunner MiddlewareRunner; + protected WorkflowOptions Options; + protected WorkflowExecutor Executor; + + public SyncWorkflowRunnerTests() + { + Host = A.Fake(); + PersistenceProvider = A.Fake(); + ServiceProvider = A.Fake(); + ScopeProvider = A.Fake(); + Registry = A.Fake(); + ResultProcesser = A.Fake(); + EventHub = A.Fake(); + CancellationProcessor = A.Fake(); + DateTimeProvider = A.Fake(); + MiddlewareRunner = A.Fake(); + StepExecutor = A.Fake(); + PointerFactory = new ExecutionPointerFactory(); + LockService = A.Fake(); + + Options = new WorkflowOptions(A.Fake()); + + var stepExecutionScope = A.Fake(); + A.CallTo(() => ScopeProvider.CreateScope(A._)).Returns(stepExecutionScope); + A.CallTo(() => stepExecutionScope.ServiceProvider).Returns(ServiceProvider); + + var scope = A.Fake(); + var scopeFactory = A.Fake(); + A.CallTo(() => ServiceProvider.GetService(typeof(IServiceScopeFactory))).Returns(scopeFactory); + A.CallTo(() => scopeFactory.CreateScope()).Returns(scope); + A.CallTo(() => scope.ServiceProvider).Returns(ServiceProvider); + + A.CallTo(() => DateTimeProvider.Now).Returns(DateTime.Now); + A.CallTo(() => DateTimeProvider.UtcNow).Returns(DateTime.UtcNow); + + A + .CallTo(() => ServiceProvider.GetService(typeof(IWorkflowMiddlewareRunner))) + .Returns(MiddlewareRunner); + + A + .CallTo(() => ServiceProvider.GetService(typeof(IStepExecutor))) + .Returns(StepExecutor); + + A.CallTo(() => MiddlewareRunner + .RunPostMiddleware(A._, A._)) + .Returns(Task.CompletedTask); + + A.CallTo(() => MiddlewareRunner + .RunExecuteMiddleware(A._, A._)) + .Returns(Task.CompletedTask); + + A.CallTo(() => StepExecutor.ExecuteStep(A._, A._)) + .ReturnsLazily(call => + call.Arguments[1].As().RunAsync( + call.Arguments[0].As())); + + A.CallTo(() => PersistenceProvider.CreateNewWorkflow(A.Ignored, A.Ignored)).Returns(Guid.NewGuid().ToString()); + + A.CallTo(() => LockService.AcquireLock(A._, A._)).Returns(true); + + //config logging + var loggerFactory = new LoggerFactory(); + //loggerFactory.AddConsole(LogLevel.Debug); + + Executor = new WorkflowExecutor(Registry, ServiceProvider, ScopeProvider, DateTimeProvider, ResultProcesser, EventHub, CancellationProcessor, Options, loggerFactory); + Subject = new SyncWorkflowRunner(Host, Executor, LockService, Registry, PersistenceProvider, PointerFactory, A.Fake(), DateTimeProvider, MiddlewareRunner); + } + + [Fact(DisplayName = "Should run pre-middlewares for sync workflows")] + public async Task should_run_pre_middlewares() + { + //arrange + var step1Body = A.Fake(); + A.CallTo(() => step1Body.RunAsync(A.Ignored)).Returns(ExecutionResult.Next()); + WorkflowStep step1 = BuildFakeStep(step1Body); + Given1StepWorkflow(step1, "Workflow", 1); + + //act + await Subject.RunWorkflowSync("Workflow", 1, new { }, "Test", TimeSpan.FromMilliseconds(1)); + + //assert + A.CallTo(() => MiddlewareRunner.RunPreMiddleware(A.Ignored, A.Ignored)).MustHaveHappened(); + } + + private void Given1StepWorkflow(WorkflowStep step1, string id, int version) + { + A.CallTo(() => Registry.GetDefinition(id, version)).Returns(new WorkflowDefinition + { + Id = id, + Version = version, + DataType = typeof(object), + Steps = new WorkflowStepCollection + { + step1 + } + }); + } + + private WorkflowStep BuildFakeStep(IStepBody stepBody) + { + return BuildFakeStep(stepBody, new List(), new List()); + } + + private WorkflowStep BuildFakeStep(IStepBody stepBody, List inputs, List outputs) + { + var result = A.Fake(); + A.CallTo(() => result.Id).Returns(0); + A.CallTo(() => result.BodyType).Returns(stepBody.GetType()); + A.CallTo(() => result.ResumeChildrenAfterCompensation).Returns(true); + A.CallTo(() => result.RevertChildrenAfterCompensation).Returns(false); + A.CallTo(() => result.ConstructBody(ServiceProvider)).Returns(stepBody); + A.CallTo(() => result.Inputs).Returns(inputs); + A.CallTo(() => result.Outputs).Returns(outputs); + A.CallTo(() => result.Outcomes).Returns(new List()); + A.CallTo(() => result.InitForExecution(A.Ignored, A.Ignored, A.Ignored, A.Ignored)).Returns(ExecutionPipelineDirective.Next); + A.CallTo(() => result.BeforeExecute(A.Ignored, A.Ignored, A.Ignored, A.Ignored)).Returns(ExecutionPipelineDirective.Next); + return result; + } + + public interface IStepWithProperties : IStepBody + { + int Property1 { get; set; } + int Property2 { get; set; } + int Property3 { get; set; } + DataClass Property4 { get; set; } + } + + public class DataClass + { + public int Value1 { get; set; } + public int Value2 { get; set; } + public int Value3 { get; set; } + public object Value4 { get; set; } + } + + public class DynamicDataClass + { + public Dictionary Storage { get; set; } = new Dictionary(); + + public int this[string propertyName] + { + get => Storage[propertyName]; + set => Storage[propertyName] = value; + } + } + } +}