/// <summary>
 /// Creates a new instance of a particular application flow.
 /// </summary>
 /// <param name="flowName">The name used for identifying the flow.</param>
 /// <param name="applicationFlowOptions">The options to use when executing this application flow.</param>
 /// <param name="logger">The <see cref="ILogger"/> instance used for logging any message originating
 /// from the flow.</param>
 /// <exception cref="ArgumentException">Thrown in case the given <paramref name="flowName"/> is null or
 /// white-space only.</exception>
 /// <exception cref="ArgumentNullException">Thrown when the given <paramref name="logger"/> is null</exception>
 protected TransactionalBaseApplicationFlow(string flowName,
                                            ApplicationFlowOptions applicationFlowOptions,
                                            ILogger logger) : base(flowName, logger)
 {
     this.applicationFlowOptions = applicationFlowOptions ??
                                   throw new ArgumentNullException(nameof(applicationFlowOptions));
 }
        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_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 ApplicationFlowServingTestingPurposes(Func <Task <object> > applicationFlow,
                                              ApplicationFlowOptions applicationFlowOptions, ILogger logger)
     : base(nameof(ApplicationFlowServingTestingPurposes), applicationFlowOptions, logger)
 {
     this.applicationFlow = applicationFlow;
 }
        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");
        }