public async Task ExecuteAsync_WhenTransactionTimesOut_MustThrowException()
        {
            // Arrange
            TimeSpan transactionTimeOut = TimeSpan.FromMilliseconds(1);
            TimeSpan biggerTimeout      = TimeSpan.FromMilliseconds(1000);

            string    userName = $"test-user--{Guid.NewGuid():N}";
            IIdentity identity = new GenericIdentity(userName);

            string[]   roles         = { $"role--{Guid.NewGuid():N}" };
            IPrincipal flowInitiator = new GenericPrincipal(identity, roles);

            ITodoItemService todoItemService =
                testWebApplicationFactory.Services.GetRequiredService <ITodoItemService>();

            ILoggerFactory loggerFactory = testWebApplicationFactory.Services.GetRequiredService <ILoggerFactory>();
            ILogger        logger        = loggerFactory.CreateLogger <ApplicationFlowServingTestingPurposes>();
            string         namePrefix    = $"todo-item--{Guid.NewGuid():N}";

            // This flow is expected to fail
            ITodoItemService localTodoItemService = todoItemService;

            async Task <object> FlowExpectedToFailAsync()
            {
                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name       = $"{namePrefix}--#1",
                    IsComplete = false,
                    Owner      = flowInitiator
                });

                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name       = $"{namePrefix}--#2",
                    IsComplete = false,
                    Owner      = flowInitiator
                });

                // Ensure this flow step will take more time to execute than the configured transaction timeout used
                // by the application flow.
                Task.Delay(biggerTimeout).Wait();

                return(null);
            }

            var query = new TodoItemQuery
            {
                Owner       = flowInitiator,
                NamePattern = $"{namePrefix}%"
            };

            ApplicationFlowOptions applicationFlowOptions =
                testWebApplicationFactory.Services.GetRequiredService <ApplicationFlowOptions>();

            // Ensure the application flow will use a very short timeout value for its transaction.
            applicationFlowOptions.TransactionOptions.Timeout = transactionTimeOut;

            var applicationFlow =
                new ApplicationFlowServingTestingPurposes(FlowExpectedToFailAsync, applicationFlowOptions, logger);

            // Act
            Func <Task> executeAsyncCall = async() => await applicationFlow.ExecuteAsync(input : null, flowInitiator);

            // Get a new instance of ITodoItemService service to ensure data will be fetched from
            // a new DbContext.
            todoItemService = testWebApplicationFactory.Services.GetRequiredService <ITodoItemService>();
            IList <TodoItemInfo> list = await todoItemService.GetByQueryAsync(query);

            // Assert
            using (new AssertionScope())
            {
                await executeAsyncCall
                .Should()
                .ThrowExactlyAsync <TransactionAbortedException>(
                    "application flow must fail in case of transaction timeout");

                list.Count.Should().Be(expected: 0,
                                       "no entities must be created in the event of a transaction timeout");
            }
        }
        public async Task ExecuteAsync_WhenOneFlowStepThrowsException_MustThrowException()
        {
            // Arrange
            string    userName = $"test-user--{Guid.NewGuid():N}";
            IIdentity identity = new GenericIdentity(userName);

            string[]   roles         = { $"role--{Guid.NewGuid():N}" };
            IPrincipal flowInitiator = new GenericPrincipal(identity, roles);

            ITodoItemService todoItemService =
                testWebApplicationFactory.Services.GetRequiredService <ITodoItemService>();

            ILoggerFactory loggerFactory = testWebApplicationFactory.Services.GetRequiredService <ILoggerFactory>();
            ILogger        logger        = loggerFactory.CreateLogger <ApplicationFlowServingTestingPurposes>();
            string         namePrefix    = $"todo-item--{Guid.NewGuid():N}";

            ITodoItemService localTodoItemService = todoItemService;

            // This flow is expected to fail since the service is unable to persist invalid models
            async Task <object> FlowExpectedToThrowExceptionAsync()
            {
                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name  = $"{namePrefix}--#1",
                    Owner = flowInitiator
                });

                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name  = $"{namePrefix}--#2",
                    Owner = flowInitiator
                });

                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name  = $"{namePrefix}--#3",
                    Owner = flowInitiator
                });

                return(null);
            }

            ApplicationFlowOptions applicationFlowOptions =
                testWebApplicationFactory.Services.GetRequiredService <ApplicationFlowOptions>();

            var applicationFlow = new ApplicationFlowServingTestingPurposes(FlowExpectedToThrowExceptionAsync,
                                                                            applicationFlowOptions, logger);

            // Act
            Func <Task> executeAsyncCall = async() => await applicationFlow.ExecuteAsync(input : null, flowInitiator);

            // Assert
            using (new AssertionScope())
            {
                await executeAsyncCall
                .Should()
                .ThrowExactlyAsync <ValidationException>("application flow must fail in case of an error");

                var query = new TodoItemQuery
                {
                    Owner       = flowInitiator,
                    NamePattern = $"{namePrefix}%"
                };

                // Get a new instance of ITodoItemService service to ensure data will be fetched from
                // a new DbContext.
                todoItemService = testWebApplicationFactory.Services.GetRequiredService <ITodoItemService>();
                IList <TodoItemInfo> list = await todoItemService.GetByQueryAsync(query);

                list.Count.Should().Be(expected: 0, "the application flow failed to persist todo items");
            }
        }
        public async Task ExecuteAsync_WhenAllStepsSucceeds_MustSucceed()
        {
            // Arrange
            string    userName = $"test-user--{Guid.NewGuid():N}";
            IIdentity identity = new GenericIdentity(userName);

            string[]   roles         = { $"role--{Guid.NewGuid():N}" };
            IPrincipal flowInitiator = new GenericPrincipal(identity, roles);

            ITodoItemService todoItemService =
                testWebApplicationFactory.Services.GetRequiredService <ITodoItemService>();

            ILoggerFactory loggerFactory = testWebApplicationFactory.Services.GetRequiredService <ILoggerFactory>();
            ILogger        logger        = loggerFactory.CreateLogger <ApplicationFlowServingTestingPurposes>();
            string         namePrefix    = $"todo-item--{Guid.NewGuid():N}";

            // This flow is expected to succeed since the service is persisting valid models
            ITodoItemService localTodoItemService = todoItemService;

            async Task <object> FlowExpectedToSucceedAsync()
            {
                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name       = $"{namePrefix}--#1",
                    IsComplete = false,
                    Owner      = flowInitiator
                });

                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name       = $"{namePrefix}--#2",
                    IsComplete = false,
                    Owner      = flowInitiator
                });

                await localTodoItemService.AddAsync(new NewTodoItemInfo
                {
                    Name       = $"{namePrefix}--#3",
                    IsComplete = false,
                    Owner      = flowInitiator
                });

                return(null);
            }

            ApplicationFlowOptions applicationFlowOptions =
                testWebApplicationFactory.Services.GetRequiredService <ApplicationFlowOptions>();

            var applicationFlow = new ApplicationFlowServingTestingPurposes(FlowExpectedToSucceedAsync,
                                                                            applicationFlowOptions, logger);

            // Act
            await applicationFlow.ExecuteAsync(input : null, flowInitiator);

            // Assert
            var query = new TodoItemQuery
            {
                Owner       = flowInitiator,
                NamePattern = $"{namePrefix}%"
            };

            // Get a new instance of ITodoItemService service to ensure data will be fetched from
            // a new DbContext.
            todoItemService = testWebApplicationFactory.Services.GetRequiredService <ITodoItemService>();
            IList <TodoItemInfo> list = await todoItemService.GetByQueryAsync(query);

            list.Count.Should().Be(expected: 3, "several todo items have been previously created");
        }