public async Task Simple01_Failed_DoNotAllowAsyncTransitionWhileWaitingEvent(bool mockWorkflowStore)
        {
            var workflowEngineBuildResult = BuildWorkflowEngine(mockWorkflowStore, false, new NoOpActivity(), new NoOpEventHandler());

            IEvent initialEvent = new SimpleDomainEntityEvent(Package01, "initial");
            await workflowEngineBuildResult.WorkflowEngine.ProcessEvent(WorkflowPackage01Id, initialEvent);

            var package01WorkflowInstance = workflowEngineBuildResult.GetWorkflowInstance(Package01);

            Assert.IsTrue(string.Equals(package01WorkflowInstance.CurrentStateCode, "process", StringComparison.OrdinalIgnoreCase));
            Assert.IsTrue(package01WorkflowInstance.CurrentStateProgress == StateExecutionProgress.AwaitingEvent);
            Assert.IsTrue(Package01.GetProperty <bool>("IsSent"));

            IWorkflowMessage workflowMessage = new AsynchronousTransitionWorkflowMessage(WorkflowPackage01Id, package01WorkflowInstance.Id, "{}");
            await workflowEngineBuildResult.WorkflowEngine.ProcessMessage(workflowMessage);

            package01WorkflowInstance = workflowEngineBuildResult.GetWorkflowInstance(Package01);
            Assert.IsTrue(string.Equals(package01WorkflowInstance.CurrentStateCode, "failed", StringComparison.OrdinalIgnoreCase));
            Assert.IsTrue(package01WorkflowInstance.CurrentStateProgress == StateExecutionProgress.Completed);
        }
        private async Task TransitionToState(WorkflowContext workflowContext, StateConfiguration stateConfiguration,
                                             TransitionConfiguration transitionConfiguration, CancellationToken cancellationToken = default)
        {
            // move to failed state with no additional checks in synchronous fashion
            if (stateConfiguration.Type == StateTypeConfiguration.Failed)
            {
                await ProcessState(workflowContext, stateConfiguration, cancellationToken).ConfigureAwait(false);

                return;
            }

            // in case if transition is trying to been done to the same state where workflow instance is ==> move to failed
            if (string.Equals(workflowContext.WorkflowInstance.CurrentStateCode, stateConfiguration.Code, StringComparison.OrdinalIgnoreCase))
            {
                Log.Warning("{workflowInstanceId} is attempted to be moved to the same state as it is now [{state}], moving it to failed state",
                            workflowContext.WorkflowInstance.Id, stateConfiguration.Code);

                await ProcessState(workflowContext, workflowContext.WorkflowConfiguration.GetFailedStateConfiguration(), cancellationToken).ConfigureAwait(false);

                return;
            }

            // if state has any event ==> move to awaiting event sub-state
            if (stateConfiguration.Events.Any())
            {
                // [assumption] state cannot have both event and incoming async transition (this check is done during WF configuration loading)
                workflowContext.WorkflowInstance.CurrentStateCode     = stateConfiguration.Code;
                workflowContext.WorkflowInstance.CurrentStateProgress = StateExecutionProgress.AwaitingEvent;
                await SaveWorkflowInstance(workflowContext.WorkflowInstance, cancellationToken).ConfigureAwait(false);

                Log.Debug("{workflowInstanceId} has been moved to [{state}::{subState}]",
                          workflowContext.WorkflowInstance.Id, stateConfiguration.Code, StateExecutionProgress.AwaitingEvent);

                return;
            }

            // process synchronous / delayed transition
            if (transitionConfiguration.Type.In(TransitionTypeConfiguration.Synchronous, TransitionTypeConfiguration.AsynchronousWithDelay))
            {
                // save current transition as a checkpoint
                workflowContext.WorkflowInstance.CurrentStateCode     = stateConfiguration.Code;
                workflowContext.WorkflowInstance.CurrentStateProgress = StateExecutionProgress.Started;
                await SaveWorkflowInstance(workflowContext.WorkflowInstance, cancellationToken).ConfigureAwait(false);

                Log.Debug("{workflowInstanceId} is moved to [{state}::{subState}]",
                          workflowContext.WorkflowInstance.Id, stateConfiguration.Code, StateExecutionProgress.Started);

                await ProcessState(workflowContext, stateConfiguration, cancellationToken).ConfigureAwait(false);

                return;
            }

            // process asynchronous-immediate transition
            if (transitionConfiguration.Type == TransitionTypeConfiguration.AsynchronousImmediate)
            {
                workflowContext.WorkflowInstance.CurrentStateCode     = stateConfiguration.Code;
                workflowContext.WorkflowInstance.CurrentStateProgress = StateExecutionProgress.AwaitingAsyncTransition;
                await SaveWorkflowInstance(workflowContext.WorkflowInstance, cancellationToken).ConfigureAwait(false);

                var workflowMessage = new AsynchronousTransitionWorkflowMessage(workflowContext.WorkflowConfiguration.Id, workflowContext.WorkflowInstance.Id);
                await WorkflowEngineBuilder.WorkflowMessageTransportFactoryProvider
                .CreateMessageTransportFactory(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration.Type)
                .CreateMessageTransport(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration.Address)
                .Send(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration, workflowMessage, cancellationToken)
                .ConfigureAwait(false);

                // TODO: make sure that asynchronous-immediate transition will not have race conditions with current workflow engine
                //    ==> send them as delayed workflow messages with default delay interval (1 second)
//                var delay = TimeSpan.FromSeconds(1D);
//                var currentStateCode = workflowContext.WorkflowInstance.CurrentStateCode;
//                var workflowMessage = new DelayedTransitionWorkflowMessage(workflowContext.WorkflowConfiguration.Id,
//                    workflowContext.WorkflowInstance.Id, delay, currentStateCode, transitionConfiguration.MoveToState);
//                await WorkflowEngineBuilder.WorkflowMessageTransportFactory
//                    .CreateMessageTransport(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration.Address)
//                    .SendWithDelay(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration, workflowMessage, cancellationToken).ConfigureAwait(false);

                Log.Debug("{workflowInstanceId} has been moved to [{state}::{subState}]",
                          workflowContext.WorkflowInstance.Id, stateConfiguration.Code, StateExecutionProgress.AwaitingAsyncTransition);

                return;
            }

            throw new WorkflowException("Non synchronous/asynchronous-immediate transitions are not supported");
        }