/// <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"); }