From fc4acd60c5b8ead42f628db7c610b9e2a9d1f383 Mon Sep 17 00:00:00 2001 From: Niel de Wet Date: Mon, 7 Apr 2025 14:46:23 +0200 Subject: [PATCH 1/4] feat: use Mediator with code gen --- src/MinimalApiTemplate.Api/GlobalUsings.cs | 2 +- .../Common/Behaviours/PerformanceBehaviour.cs | 22 +-- .../Behaviours/UnhandledExceptionBehaviour.cs | 8 +- .../Common/Behaviours/ValidationBehaviour.cs | 8 +- .../Events/BasePublishExternalEventHander.cs | 2 +- .../Common/Events/EventMapper.cs | 3 +- .../ConfigureServices.cs | 14 +- .../CreateTodoItem/CreateTodoItemCommand.cs | 2 +- .../DeleteTodoItem/DeleteTodoItemCommand.cs | 4 +- .../UpdateTodoItem/UpdateTodoItemCommand.cs | 4 +- .../TodoItemCreatedEventHandler.cs | 4 +- .../TodoItemDeletedEventHandler.cs | 4 +- .../Queries/GetToDoItem/GetToDoItemQuery.cs | 2 +- .../GetTodoItemsWithPaginationQuery.cs | 2 +- .../GlobalUsings.cs | 2 +- .../MinimalApiTemplate.Application.csproj | 5 +- .../Common/BaseEvent.cs | 2 +- .../MinimalApiTemplate.Domain.csproj | 2 +- .../GlobalUsings.cs | 3 +- .../DispatchDomainEventsInterceptor.cs | 3 +- .../BaseTestFixture.cs | 3 +- .../GlobalUsings.cs | 1 + .../Behaviours/PerformanceBehaviourTests.cs | 128 +++++++++--------- .../UnhandledExceptionBehaviourTests.cs | 92 ++++++------- .../Behaviours/ValidationBehaviourTests.cs | 102 +++++++------- .../Events/BasePublishEventHanderTests.cs | 2 +- .../UpdateTodoItemCommandHandlerTests.cs | 8 +- .../BaseTestFixture.cs | 2 +- .../DispatchDomainEventsInterceptorTests.cs | 3 +- 29 files changed, 226 insertions(+), 213 deletions(-) diff --git a/src/MinimalApiTemplate.Api/GlobalUsings.cs b/src/MinimalApiTemplate.Api/GlobalUsings.cs index d762342..a3310c9 100644 --- a/src/MinimalApiTemplate.Api/GlobalUsings.cs +++ b/src/MinimalApiTemplate.Api/GlobalUsings.cs @@ -1,6 +1,6 @@ global using System.Net; global using Riok.Mapperly.Abstractions; -global using MediatR; +global using Mediator; global using Microsoft.AspNetCore.Http.HttpResults; global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.OutputCaching; diff --git a/src/MinimalApiTemplate.Application/Common/Behaviours/PerformanceBehaviour.cs b/src/MinimalApiTemplate.Application/Common/Behaviours/PerformanceBehaviour.cs index 9148cde..db1305c 100644 --- a/src/MinimalApiTemplate.Application/Common/Behaviours/PerformanceBehaviour.cs +++ b/src/MinimalApiTemplate.Application/Common/Behaviours/PerformanceBehaviour.cs @@ -1,36 +1,38 @@ using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using MinimalApiTemplate.Application.Common.Settings; namespace MinimalApiTemplate.Application.Common.Behaviours; public class PerformanceBehaviour : IPipelineBehavior - where TRequest : IRequest + where TRequest : IMessage { private readonly Stopwatch _timer; private readonly ILogger _logger; - private readonly ICurrentUserService _currentUserService; + private readonly IServiceScopeFactory _serviceScope; private readonly AppSettings _appSettings; public PerformanceBehaviour( #pragma warning disable S6672 // Generic logger injection should match enclosing type ILogger logger, #pragma warning restore S6672 // Generic logger injection should match enclosing type - ICurrentUserService currentUserService, + IServiceScopeFactory serviceScope, IOptions appSettings) { _timer = new Stopwatch(); _logger = logger; - _currentUserService = currentUserService; + _serviceScope = serviceScope; _appSettings = appSettings.Value; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + public async ValueTask Handle(TRequest message, CancellationToken cancellationToken, MessageHandlerDelegate next) { + _timer.Start(); - var response = await next(); + var response = await next(message, cancellationToken); _timer.Stop(); @@ -39,11 +41,15 @@ public async Task Handle(TRequest request, RequestHandlerDelegate= _appSettings.Logs.Performance.SlowRunningHandlerThreshold) { + using var scope = _serviceScope.CreateScope(); + var requestName = typeof(TRequest).Name; - var userId = _currentUserService.UserId ?? string.Empty; + + var currentUserService = scope.ServiceProvider.GetRequiredService(); + var userId = currentUserService.UserId ?? string.Empty; _logger.LogWarning("Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@Request}", - requestName, elapsedMilliseconds, userId, request); + requestName, elapsedMilliseconds, userId, message); } return response; diff --git a/src/MinimalApiTemplate.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/MinimalApiTemplate.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs index 333d218..4fa4651 100644 --- a/src/MinimalApiTemplate.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs +++ b/src/MinimalApiTemplate.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs @@ -1,7 +1,7 @@ namespace MinimalApiTemplate.Application.Common.Behaviours; public class UnhandledExceptionBehaviour : IPipelineBehavior - where TRequest : IRequest + where TRequest : IMessage { private readonly ILogger _logger; @@ -12,17 +12,17 @@ public UnhandledExceptionBehaviour(ILogger logger) _logger = logger; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + public async ValueTask Handle(TRequest message, CancellationToken cancellationToken, MessageHandlerDelegate next) { try { - return await next(); + return await next(message, cancellationToken); } catch (Exception ex) { var requestName = typeof(TRequest).Name; - _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, message); throw; } diff --git a/src/MinimalApiTemplate.Application/Common/Behaviours/ValidationBehaviour.cs b/src/MinimalApiTemplate.Application/Common/Behaviours/ValidationBehaviour.cs index bcb1460..10069ae 100644 --- a/src/MinimalApiTemplate.Application/Common/Behaviours/ValidationBehaviour.cs +++ b/src/MinimalApiTemplate.Application/Common/Behaviours/ValidationBehaviour.cs @@ -1,7 +1,7 @@ namespace MinimalApiTemplate.Application.Common.Behaviours; public class ValidationBehaviour : IPipelineBehavior - where TRequest : IRequest + where TRequest : IMessage { private readonly IEnumerable> _validators; @@ -10,11 +10,11 @@ public ValidationBehaviour(IEnumerable> validators) _validators = validators; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + public async ValueTask Handle(TRequest message, CancellationToken cancellationToken, MessageHandlerDelegate next) { if (_validators.Any()) { - var context = new ValidationContext(request); + var context = new ValidationContext(message); var validationResults = await Task.WhenAll( _validators.Select(v => @@ -28,6 +28,6 @@ public async Task Handle(TRequest request, RequestHandlerDelegate + builder.Services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>)); + builder.Services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)); + builder.Services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>)); + + builder.Services.AddMediator(options => { - options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()); - options.AddBehavior(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>)); - options.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)); - options.AddBehavior(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>)); + options.ServiceLifetime = ServiceLifetime.Scoped; }); return builder; diff --git a/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs b/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs index 26ffa13..109e599 100644 --- a/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs +++ b/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs @@ -22,7 +22,7 @@ public CreateTodoItemCommandHandler( _toDoItemMetrics = toDoItemMetrics; } - public async Task Handle(CreateTodoItemCommand request, CancellationToken cancellationToken) + public async ValueTask Handle(CreateTodoItemCommand request, CancellationToken cancellationToken) { var entity = request.MapToEntity(); diff --git a/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/DeleteTodoItem/DeleteTodoItemCommand.cs b/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/DeleteTodoItem/DeleteTodoItemCommand.cs index d4b827b..b0b2d47 100644 --- a/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/DeleteTodoItem/DeleteTodoItemCommand.cs +++ b/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/DeleteTodoItem/DeleteTodoItemCommand.cs @@ -11,7 +11,7 @@ public DeleteTodoItemCommandHandler(IToDoItemRepository toDoItemRepository) _toDoItemRepository = toDoItemRepository; } - public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken) + public async ValueTask Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken) { var entity = await _toDoItemRepository.GetByIdAsync(request.Id, cancellationToken) ?? throw new NotFoundException(nameof(TodoItem), request.Id); @@ -23,5 +23,7 @@ public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancel await _toDoItemRepository.DeleteAsync(entity, cancellationToken); } + + return Unit.Value; } } diff --git a/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/UpdateTodoItem/UpdateTodoItemCommand.cs b/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/UpdateTodoItem/UpdateTodoItemCommand.cs index a296ea4..fc42f6e 100644 --- a/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/UpdateTodoItem/UpdateTodoItemCommand.cs +++ b/src/MinimalApiTemplate.Application/Features/TodoItems/Commands/UpdateTodoItem/UpdateTodoItemCommand.cs @@ -18,7 +18,7 @@ public UpdateTodoItemCommandHandler(IToDoItemRepository toDoItemRepository) _toDoItemRepository = toDoItemRepository; } - public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken) + public async ValueTask Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken) { var entity = await _toDoItemRepository.GetByIdAsync(request.Id, cancellationToken) ?? throw new NotFoundException(nameof(TodoItem), request.Id); @@ -29,5 +29,7 @@ public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancel await _toDoItemRepository.UpdateAsync(entity, cancellationToken); } + + return Unit.Value; } } diff --git a/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemCreated/TodoItemCreatedEventHandler.cs b/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemCreated/TodoItemCreatedEventHandler.cs index f103053..8940896 100644 --- a/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemCreated/TodoItemCreatedEventHandler.cs +++ b/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemCreated/TodoItemCreatedEventHandler.cs @@ -9,10 +9,10 @@ public TodoItemCreatedEventHandler(ILogger logger) _logger = logger; } - public Task Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken) + public ValueTask Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken) { _logger.LogInformation("Domain Event: {DomainEvent}", nameof(TodoItemCreatedEvent)); - return Task.CompletedTask; + return ValueTask.CompletedTask; } } diff --git a/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemDeleted/TodoItemDeletedEventHandler.cs b/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemDeleted/TodoItemDeletedEventHandler.cs index 1371450..3604d23 100644 --- a/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemDeleted/TodoItemDeletedEventHandler.cs +++ b/src/MinimalApiTemplate.Application/Features/TodoItems/EventHandlers/TodoItemDeleted/TodoItemDeletedEventHandler.cs @@ -9,10 +9,10 @@ public TodoItemDeletedEventHandler(ILogger logger) _logger = logger; } - public Task Handle(TodoItemDeletedEvent notification, CancellationToken cancellationToken) + public ValueTask Handle(TodoItemDeletedEvent notification, CancellationToken cancellationToken) { _logger.LogInformation("Domain Event: {DomainEvent}", nameof(TodoItemDeletedEvent)); - return Task.CompletedTask; + return ValueTask.CompletedTask; } } diff --git a/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetToDoItem/GetToDoItemQuery.cs b/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetToDoItem/GetToDoItemQuery.cs index 50bf682..97f5b8f 100644 --- a/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetToDoItem/GetToDoItemQuery.cs +++ b/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetToDoItem/GetToDoItemQuery.cs @@ -16,7 +16,7 @@ public GetToDoItemQueryHandler(IApplicationDbContext context) _context = context; } - public async Task Handle(GetToDoItemQuery request, CancellationToken cancellationToken) + public async ValueTask Handle(GetToDoItemQuery request, CancellationToken cancellationToken) { var data = await _context.TodoItems .AsNoTracking() diff --git a/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetTodoItemsWithPagination/GetTodoItemsWithPaginationQuery.cs b/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetTodoItemsWithPagination/GetTodoItemsWithPaginationQuery.cs index 25c0270..9e01c9e 100644 --- a/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetTodoItemsWithPagination/GetTodoItemsWithPaginationQuery.cs +++ b/src/MinimalApiTemplate.Application/Features/TodoItems/Queries/GetTodoItemsWithPagination/GetTodoItemsWithPaginationQuery.cs @@ -19,7 +19,7 @@ public GetTodoItemsWithPaginationQueryHandler(IApplicationDbContext context) _context = context; } - public async Task> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken) + public async ValueTask> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken) { var querableToDoItems = _context.TodoItems.AsNoTracking(); diff --git a/src/MinimalApiTemplate.Application/GlobalUsings.cs b/src/MinimalApiTemplate.Application/GlobalUsings.cs index c9323b0..fae22c1 100644 --- a/src/MinimalApiTemplate.Application/GlobalUsings.cs +++ b/src/MinimalApiTemplate.Application/GlobalUsings.cs @@ -2,7 +2,7 @@ global using Audit.Core; global using Riok.Mapperly.Abstractions; global using FluentValidation; -global using MediatR; +global using Mediator; global using Microsoft.Extensions.Logging; global using MinimalApiTemplate.Application.Common; global using MinimalApiTemplate.Application.Common.Exceptions; diff --git a/src/MinimalApiTemplate.Application/MinimalApiTemplate.Application.csproj b/src/MinimalApiTemplate.Application/MinimalApiTemplate.Application.csproj index 8fc8e51..6ea7e93 100644 --- a/src/MinimalApiTemplate.Application/MinimalApiTemplate.Application.csproj +++ b/src/MinimalApiTemplate.Application/MinimalApiTemplate.Application.csproj @@ -10,7 +10,10 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/MinimalApiTemplate.Domain/Common/BaseEvent.cs b/src/MinimalApiTemplate.Domain/Common/BaseEvent.cs index dc43982..b3b19ad 100644 --- a/src/MinimalApiTemplate.Domain/Common/BaseEvent.cs +++ b/src/MinimalApiTemplate.Domain/Common/BaseEvent.cs @@ -1,4 +1,4 @@ -using MediatR; +using Mediator; namespace MinimalApiTemplate.Domain.Common; diff --git a/src/MinimalApiTemplate.Domain/MinimalApiTemplate.Domain.csproj b/src/MinimalApiTemplate.Domain/MinimalApiTemplate.Domain.csproj index cb8eec6..2b3e067 100644 --- a/src/MinimalApiTemplate.Domain/MinimalApiTemplate.Domain.csproj +++ b/src/MinimalApiTemplate.Domain/MinimalApiTemplate.Domain.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/MinimalApiTemplate.Infrastructure/GlobalUsings.cs b/src/MinimalApiTemplate.Infrastructure/GlobalUsings.cs index a14b1de..855c6d0 100644 --- a/src/MinimalApiTemplate.Infrastructure/GlobalUsings.cs +++ b/src/MinimalApiTemplate.Infrastructure/GlobalUsings.cs @@ -9,4 +9,5 @@ global using MinimalApiTemplate.Domain.Enums; global using MinimalApiTemplate.Messages.Common; global using MassTransit; -global using Microsoft.Extensions.Logging; \ No newline at end of file +global using Microsoft.Extensions.Logging; +global using Mediator; \ No newline at end of file diff --git a/src/MinimalApiTemplate.Infrastructure/Persistence/Interceptors/DispatchDomainEventsInterceptor.cs b/src/MinimalApiTemplate.Infrastructure/Persistence/Interceptors/DispatchDomainEventsInterceptor.cs index d97b1d6..6e5df36 100644 --- a/src/MinimalApiTemplate.Infrastructure/Persistence/Interceptors/DispatchDomainEventsInterceptor.cs +++ b/src/MinimalApiTemplate.Infrastructure/Persistence/Interceptors/DispatchDomainEventsInterceptor.cs @@ -1,5 +1,4 @@ -using MediatR; -using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace MinimalApiTemplate.Infrastructure.Persistence.Interceptors; diff --git a/tests/MinimalApiTemplate.Api.Tests/BaseTestFixture.cs b/tests/MinimalApiTemplate.Api.Tests/BaseTestFixture.cs index 0d3fce2..8b8eda1 100644 --- a/tests/MinimalApiTemplate.Api.Tests/BaseTestFixture.cs +++ b/tests/MinimalApiTemplate.Api.Tests/BaseTestFixture.cs @@ -1,5 +1,4 @@ -using MediatR; -using Microsoft.AspNetCore.OutputCaching; +using Microsoft.AspNetCore.OutputCaching; namespace MinimalApiTemplate.Api.Tests; diff --git a/tests/MinimalApiTemplate.Api.Tests/GlobalUsings.cs b/tests/MinimalApiTemplate.Api.Tests/GlobalUsings.cs index c578822..6f687c0 100644 --- a/tests/MinimalApiTemplate.Api.Tests/GlobalUsings.cs +++ b/tests/MinimalApiTemplate.Api.Tests/GlobalUsings.cs @@ -4,3 +4,4 @@ global using Microsoft.Extensions.Logging; global using NSubstitute; global using Xunit; +global using Mediator; diff --git a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs index f5ae012..663f69a 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs @@ -1,78 +1,78 @@ -using MediatR; -using Microsoft.Extensions.Options; -using MinimalApiTemplate.Application.Common.Behaviours; -using MinimalApiTemplate.Application.Common.Settings; +//using Mediator; +//using Microsoft.Extensions.Options; +//using MinimalApiTemplate.Application.Common.Behaviours; +//using MinimalApiTemplate.Application.Common.Settings; -namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; +//namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; -public class PerformanceBehaviourTests -{ - private PerformanceBehaviour? _performanceBehaviour; - private readonly ILogger _loggerMock = Substitute.For>(); - private readonly ICurrentUserService _currentUserServiceMock = Substitute.For(); - private RequestHandlerDelegate? _pipelineBehaviourDelegateMock = null; +//public class PerformanceBehaviourTests +//{ +// private PerformanceBehaviour? _performanceBehaviour; +// private readonly ILogger _loggerMock = Substitute.For>(); +// private readonly ICurrentUserService _currentUserServiceMock = Substitute.For(); +// private RequestHandlerDelegate? _pipelineBehaviourDelegateMock = null; - public PerformanceBehaviourTests() - { - _currentUserServiceMock.UserId - .Returns("1"); - } +// public PerformanceBehaviourTests() +// { +// _currentUserServiceMock.UserId +// .Returns("1"); +// } - public void Setup(AppSettings appSettings) - { - _performanceBehaviour = new(_loggerMock, - _currentUserServiceMock, - Options.Create(appSettings)); - } +// public void Setup(AppSettings appSettings) +// { +// _performanceBehaviour = new(_loggerMock, +// _currentUserServiceMock, +// Options.Create(appSettings)); +// } - [Theory] - [InlineData(false, 1, false, 0)] - [InlineData(true, 10000, false, 5)] - [InlineData(true, 1, true, 10)] - public async Task When_ConditionsAreMet_Then_LogSlowRunningRequests( - bool logRequests, int threshold, bool shouldHaveLog, int delay) - { - _pipelineBehaviourDelegateMock = new RequestHandlerDelegate(async () => - { - await Task.Delay(delay); +// [Theory] +// [InlineData(false, 1, false, 0)] +// [InlineData(true, 10000, false, 5)] +// [InlineData(true, 1, true, 10)] +// public async Task When_ConditionsAreMet_Then_LogSlowRunningRequests( +// bool logRequests, int threshold, bool shouldHaveLog, int delay) +// { +// _pipelineBehaviourDelegateMock = new RequestHandlerDelegate(async () => +// { +// await Task.Delay(delay); - return Unit.Value; - }); +// return Unit.Value; +// }); - Setup(new AppSettings - { - Logs = new Logs - { - Performance = new Performance - { - LogSlowRunningHandlers = logRequests, - SlowRunningHandlerThreshold = threshold - } - } - }); +// Setup(new AppSettings +// { +// Logs = new Logs +// { +// Performance = new Performance +// { +// LogSlowRunningHandlers = logRequests, +// SlowRunningHandlerThreshold = threshold +// } +// } +// }); - if (_performanceBehaviour is null) throw new NullReferenceException("Setup was not called"); +// if (_performanceBehaviour is null) throw new NullReferenceException("Setup was not called"); - await _performanceBehaviour.Handle(new PerformanceBehaviourTestInput(), - _pipelineBehaviourDelegateMock, - CancellationToken.None); +// await _performanceBehaviour.Handle(new PerformanceBehaviourTestInput(), +// _pipelineBehaviourDelegateMock, +// CancellationToken.None); - _loggerMock.Received(shouldHaveLog ? 1 : 0) - .Log(LogLevel.Warning, Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any>()); - } -} +// _loggerMock.Received(shouldHaveLog ? 1 : 0) +// .Log(LogLevel.Warning, Arg.Any(), Arg.Any(), +// Arg.Any(), Arg.Any>()); +// } +//} -public class PerformanceBehaviourTestInput : IRequest -{ +//public class PerformanceBehaviourTestInput : IRequest +//{ -} +//} -public class PerformanceBehaviourTestHandler : IRequestHandler -{ - public Task Handle(PerformanceBehaviourTestInput request, CancellationToken cancellationToken) - { - return Unit.Task; - } -} +//public class PerformanceBehaviourTestHandler : IRequestHandler +//{ +// public ValueTask Handle(PerformanceBehaviourTestInput request, CancellationToken cancellationToken) +// { +// return Unit.ValueTask; +// } +//} diff --git a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs index adf18f3..9fde9f8 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs @@ -1,46 +1,46 @@ -using MediatR; -using MinimalApiTemplate.Application.Common.Behaviours; -using NSubstitute.ExceptionExtensions; - -namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; - -public class UnhandledExceptionBehaviourTests -{ - private readonly UnhandledExceptionBehaviour _unhandledExceptionBehaviour; - private readonly ILogger _loggerMock = Substitute.For>(); - private readonly RequestHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); - - - public UnhandledExceptionBehaviourTests() - { - _unhandledExceptionBehaviour = new(_loggerMock); - } - - [Fact] - public async Task When_UnhandledExceptionIsThrown_Then_LogTheErrorMessage() - { - _pipelineBehaviourDelegateMock.Invoke() - .Throws(new Exception("Unhandled exception")); - - await Assert.ThrowsAsync(() => _unhandledExceptionBehaviour.Handle(new UnhandledExceptionBehaviourTestInput(), - _pipelineBehaviourDelegateMock, - CancellationToken.None)); - - _loggerMock.Received() - .Log(LogLevel.Error, Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any>()); - } -} - -public class UnhandledExceptionBehaviourTestInput : IRequest -{ - -} - -public class UnhandledExceptionBehaviourTestHandler : IRequestHandler -{ - public Task Handle(UnhandledExceptionBehaviourTestInput request, CancellationToken cancellationToken) - { - return Unit.Task; - } -} \ No newline at end of file +//using MediatR; +//using MinimalApiTemplate.Application.Common.Behaviours; +//using NSubstitute.ExceptionExtensions; + +//namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; + +//public class UnhandledExceptionBehaviourTests +//{ +// private readonly UnhandledExceptionBehaviour _unhandledExceptionBehaviour; +// private readonly ILogger _loggerMock = Substitute.For>(); +// private readonly RequestHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); + + +// public UnhandledExceptionBehaviourTests() +// { +// _unhandledExceptionBehaviour = new(_loggerMock); +// } + +// [Fact] +// public async Task When_UnhandledExceptionIsThrown_Then_LogTheErrorMessage() +// { +// _pipelineBehaviourDelegateMock.Invoke() +// .Throws(new Exception("Unhandled exception")); + +// await Assert.ThrowsAsync(() => _unhandledExceptionBehaviour.Handle(new UnhandledExceptionBehaviourTestInput(), +// _pipelineBehaviourDelegateMock, +// CancellationToken.None)); + +// _loggerMock.Received() +// .Log(LogLevel.Error, Arg.Any(), Arg.Any(), +// Arg.Any(), Arg.Any>()); +// } +//} + +//public class UnhandledExceptionBehaviourTestInput : IRequest +//{ + +//} + +//public class UnhandledExceptionBehaviourTestHandler : IRequestHandler +//{ +// public Task Handle(UnhandledExceptionBehaviourTestInput request, CancellationToken cancellationToken) +// { +// return Unit.Task; +// } +//} \ No newline at end of file diff --git a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs index dd02125..047a66d 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs @@ -1,51 +1,51 @@ -using FluentValidation.Results; -using MediatR; -using MinimalApiTemplate.Application.Common.Behaviours; - -namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; - -public class ValidationBehaviourTests -{ - private readonly ValidationBehaviour _validationBehaviour; - private readonly RequestHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); - private readonly IValidator _validatorMock = Substitute.For>(); - - public ValidationBehaviourTests() - { - var failures = new List - { - { new ValidationFailure("prop1", "required") } - }; - - _validatorMock.ValidateAsync(Arg.Any(), Arg.Any()) - .Returns(new ValidationResult(failures)); - - _validationBehaviour = new([_validatorMock]); - } - - [Fact] - public async Task When_ValidationExists_Then_ReturnTheErrorMessage() - { - _pipelineBehaviourDelegateMock.Invoke() - .Returns(Unit.Value); - - var sut = await Assert.ThrowsAsync(() => _validationBehaviour.Handle(new ValidationBehaviourTestInput(), - _pipelineBehaviourDelegateMock, - CancellationToken.None)); - - sut.Errors.Count.Should().Be(1); - } -} - -public class ValidationBehaviourTestInput : IRequest -{ - -} - -public class ValidationBehaviourTestHandler : IRequestHandler -{ - public Task Handle(ValidationBehaviourTestInput request, CancellationToken cancellationToken) - { - return Unit.Task; - } -} \ No newline at end of file +//using FluentValidation.Results; +//using MediatR; +//using MinimalApiTemplate.Application.Common.Behaviours; + +//namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; + +//public class ValidationBehaviourTests +//{ +// private readonly ValidationBehaviour _validationBehaviour; +// private readonly RequestHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); +// private readonly IValidator _validatorMock = Substitute.For>(); + +// public ValidationBehaviourTests() +// { +// var failures = new List +// { +// { new ValidationFailure("prop1", "required") } +// }; + +// _validatorMock.ValidateAsync(Arg.Any(), Arg.Any()) +// .Returns(new ValidationResult(failures)); + +// _validationBehaviour = new([_validatorMock]); +// } + +// [Fact] +// public async Task When_ValidationExists_Then_ReturnTheErrorMessage() +// { +// _pipelineBehaviourDelegateMock.Invoke() +// .Returns(Unit.Value); + +// var sut = await Assert.ThrowsAsync(() => _validationBehaviour.Handle(new ValidationBehaviourTestInput(), +// _pipelineBehaviourDelegateMock, +// CancellationToken.None)); + +// sut.Errors.Count.Should().Be(1); +// } +//} + +//public class ValidationBehaviourTestInput : IRequest +//{ + +//} + +//public class ValidationBehaviourTestHandler : IRequestHandler +//{ +// public Task Handle(ValidationBehaviourTestInput request, CancellationToken cancellationToken) +// { +// return Unit.Task; +// } +//} \ No newline at end of file diff --git a/tests/MinimalApiTemplate.Application.Tests/Common/Events/BasePublishEventHanderTests.cs b/tests/MinimalApiTemplate.Application.Tests/Common/Events/BasePublishEventHanderTests.cs index 79443d7..d174519 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Common/Events/BasePublishEventHanderTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Common/Events/BasePublishEventHanderTests.cs @@ -1,4 +1,4 @@ -using MediatR; +using Mediator; using MinimalApiTemplate.Application.Common.Events; using MinimalApiTemplate.Messages.Common; diff --git a/tests/MinimalApiTemplate.Application.Tests/Features/ToDoItems/Commands/UpdateTodoItem/UpdateTodoItemCommandHandlerTests.cs b/tests/MinimalApiTemplate.Application.Tests/Features/ToDoItems/Commands/UpdateTodoItem/UpdateTodoItemCommandHandlerTests.cs index f860e86..d6c6f34 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Features/ToDoItems/Commands/UpdateTodoItem/UpdateTodoItemCommandHandlerTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Features/ToDoItems/Commands/UpdateTodoItem/UpdateTodoItemCommandHandlerTests.cs @@ -20,10 +20,12 @@ public async Task Given_IdDoesNotExists_Then_ThrowAnException() _templateRepositoryMock.GetByIdAsync(id, Arg.Any()) .ReturnsNull(); - await Assert.ThrowsAsync(() => - _handler.Handle(Builder.CreateNew() + var command = Builder.CreateNew() .With(x => id) - .Build(), CancellationToken.None)); + .Build(); + + await Assert.ThrowsAsync(() => + _handler.Handle(command, CancellationToken.None).AsTask()); await _templateRepositoryMock.DidNotReceive() .UpdateAsync(Arg.Any(), Arg.Any()); diff --git a/tests/MinimalApiTemplate.Infrastructure.Tests/BaseTestFixture.cs b/tests/MinimalApiTemplate.Infrastructure.Tests/BaseTestFixture.cs index d336728..a923cea 100644 --- a/tests/MinimalApiTemplate.Infrastructure.Tests/BaseTestFixture.cs +++ b/tests/MinimalApiTemplate.Infrastructure.Tests/BaseTestFixture.cs @@ -1,4 +1,4 @@ -using MediatR; +using Mediator; namespace MinimalApiTemplate.Infrastructure.Tests; diff --git a/tests/MinimalApiTemplate.Infrastructure.Tests/Persistence/Interceptors/DispatchDomainEventsInterceptorTests.cs b/tests/MinimalApiTemplate.Infrastructure.Tests/Persistence/Interceptors/DispatchDomainEventsInterceptorTests.cs index 8e9ff20..04c48b7 100644 --- a/tests/MinimalApiTemplate.Infrastructure.Tests/Persistence/Interceptors/DispatchDomainEventsInterceptorTests.cs +++ b/tests/MinimalApiTemplate.Infrastructure.Tests/Persistence/Interceptors/DispatchDomainEventsInterceptorTests.cs @@ -1,5 +1,4 @@ -using MediatR; -using MinimalApiTemplate.Domain.Common; +using MinimalApiTemplate.Domain.Common; using MinimalApiTemplate.Infrastructure.Persistence.Interceptors; namespace MinimalApiTemplate.Infrastructure.Tests.Persistence.Interceptors; From 253f777f231acd9de0aa37b2abf3582250ccf9c2 Mon Sep 17 00:00:00 2001 From: Niel de Wet Date: Mon, 7 Apr 2025 16:04:02 +0200 Subject: [PATCH 2/4] test: fix unit tests --- .../Behaviours/PerformanceBehaviourTests.cs | 127 +++++++++--------- .../UnhandledExceptionBehaviourTests.cs | 85 ++++++------ .../Behaviours/ValidationBehaviourTests.cs | 95 ++++++------- 3 files changed, 146 insertions(+), 161 deletions(-) diff --git a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs index 663f69a..69e65cc 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/PerformanceBehaviourTests.cs @@ -1,78 +1,77 @@ -//using Mediator; -//using Microsoft.Extensions.Options; -//using MinimalApiTemplate.Application.Common.Behaviours; -//using MinimalApiTemplate.Application.Common.Settings; +using Mediator; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MinimalApiTemplate.Application.Common.Behaviours; +using MinimalApiTemplate.Application.Common.Settings; -//namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; +namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; -//public class PerformanceBehaviourTests -//{ -// private PerformanceBehaviour? _performanceBehaviour; -// private readonly ILogger _loggerMock = Substitute.For>(); -// private readonly ICurrentUserService _currentUserServiceMock = Substitute.For(); -// private RequestHandlerDelegate? _pipelineBehaviourDelegateMock = null; +public class PerformanceBehaviourTests +{ + private PerformanceBehaviour? _performanceBehaviour; + private readonly ILogger _loggerMock = Substitute.For>(); + private readonly ICurrentUserService _currentUserServiceMock = Substitute.For(); + private readonly IServiceScopeFactory _serviceScopeFactoryMock = Substitute.For(); + private readonly IServiceScope _serviceScopeMock = Substitute.For(); + private MessageHandlerDelegate? _pipelineBehaviourDelegateMock = null; -// public PerformanceBehaviourTests() -// { -// _currentUserServiceMock.UserId -// .Returns("1"); -// } + public PerformanceBehaviourTests() + { + _serviceScopeFactoryMock.CreateScope().Returns(_serviceScopeMock); -// public void Setup(AppSettings appSettings) -// { -// _performanceBehaviour = new(_loggerMock, -// _currentUserServiceMock, -// Options.Create(appSettings)); -// } + _serviceScopeMock.ServiceProvider.GetService().Returns(_currentUserServiceMock); -// [Theory] -// [InlineData(false, 1, false, 0)] -// [InlineData(true, 10000, false, 5)] -// [InlineData(true, 1, true, 10)] -// public async Task When_ConditionsAreMet_Then_LogSlowRunningRequests( -// bool logRequests, int threshold, bool shouldHaveLog, int delay) -// { -// _pipelineBehaviourDelegateMock = new RequestHandlerDelegate(async () => -// { -// await Task.Delay(delay); + _currentUserServiceMock.UserId + .Returns("1"); + } -// return Unit.Value; -// }); + public void Setup(AppSettings appSettings) + { + _performanceBehaviour = new(_loggerMock, + _serviceScopeFactoryMock, + Options.Create(appSettings)); + } -// Setup(new AppSettings -// { -// Logs = new Logs -// { -// Performance = new Performance -// { -// LogSlowRunningHandlers = logRequests, -// SlowRunningHandlerThreshold = threshold -// } -// } -// }); + [Theory] + [InlineData(false, 1, false, 0)] + [InlineData(true, 10000, false, 5)] + [InlineData(true, 1, true, 10)] + public async Task When_ConditionsAreMet_Then_LogSlowRunningRequests( + bool logRequests, int threshold, bool shouldHaveLog, int delay) + { + _pipelineBehaviourDelegateMock = new MessageHandlerDelegate(async (input, ct) => + { + await Task.Delay(delay, ct); -// if (_performanceBehaviour is null) throw new NullReferenceException("Setup was not called"); + return Unit.Value; + }); -// await _performanceBehaviour.Handle(new PerformanceBehaviourTestInput(), -// _pipelineBehaviourDelegateMock, -// CancellationToken.None); + Setup(new AppSettings + { + Logs = new Logs + { + Performance = new Performance + { + LogSlowRunningHandlers = logRequests, + SlowRunningHandlerThreshold = threshold + } + } + }); -// _loggerMock.Received(shouldHaveLog ? 1 : 0) -// .Log(LogLevel.Warning, Arg.Any(), Arg.Any(), -// Arg.Any(), Arg.Any>()); -// } -//} + if (_performanceBehaviour is null) throw new NullReferenceException("Setup was not called"); -//public class PerformanceBehaviourTestInput : IRequest -//{ + await _performanceBehaviour.Handle(new PerformanceBehaviourTestInput(), + CancellationToken.None, + _pipelineBehaviourDelegateMock + ); -//} + _loggerMock.Received(shouldHaveLog ? 1 : 0) + .Log(LogLevel.Warning, Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any>()); + } +} -//public class PerformanceBehaviourTestHandler : IRequestHandler -//{ -// public ValueTask Handle(PerformanceBehaviourTestInput request, CancellationToken cancellationToken) -// { -// return Unit.ValueTask; -// } -//} +public class PerformanceBehaviourTestInput : IRequest +{ +} diff --git a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs index 9fde9f8..5802a68 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/UnhandledExceptionBehaviourTests.cs @@ -1,46 +1,39 @@ -//using MediatR; -//using MinimalApiTemplate.Application.Common.Behaviours; -//using NSubstitute.ExceptionExtensions; - -//namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; - -//public class UnhandledExceptionBehaviourTests -//{ -// private readonly UnhandledExceptionBehaviour _unhandledExceptionBehaviour; -// private readonly ILogger _loggerMock = Substitute.For>(); -// private readonly RequestHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); - - -// public UnhandledExceptionBehaviourTests() -// { -// _unhandledExceptionBehaviour = new(_loggerMock); -// } - -// [Fact] -// public async Task When_UnhandledExceptionIsThrown_Then_LogTheErrorMessage() -// { -// _pipelineBehaviourDelegateMock.Invoke() -// .Throws(new Exception("Unhandled exception")); - -// await Assert.ThrowsAsync(() => _unhandledExceptionBehaviour.Handle(new UnhandledExceptionBehaviourTestInput(), -// _pipelineBehaviourDelegateMock, -// CancellationToken.None)); - -// _loggerMock.Received() -// .Log(LogLevel.Error, Arg.Any(), Arg.Any(), -// Arg.Any(), Arg.Any>()); -// } -//} - -//public class UnhandledExceptionBehaviourTestInput : IRequest -//{ - -//} - -//public class UnhandledExceptionBehaviourTestHandler : IRequestHandler -//{ -// public Task Handle(UnhandledExceptionBehaviourTestInput request, CancellationToken cancellationToken) -// { -// return Unit.Task; -// } -//} \ No newline at end of file +using Mediator; +using MinimalApiTemplate.Application.Common.Behaviours; +using NSubstitute.ExceptionExtensions; + +namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; + +public class UnhandledExceptionBehaviourTests +{ + private readonly UnhandledExceptionBehaviour _unhandledExceptionBehaviour; + private readonly ILogger _loggerMock = Substitute.For>(); + private readonly MessageHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); + + + public UnhandledExceptionBehaviourTests() + { + _unhandledExceptionBehaviour = new(_loggerMock); + } + + [Fact] + public async Task When_UnhandledExceptionIsThrown_Then_LogTheErrorMessage() + { + _pipelineBehaviourDelegateMock.Invoke(Arg.Any(), Arg.Any()) + .Throws(new Exception("Unhandled exception")); + + await Assert.ThrowsAsync(() => _unhandledExceptionBehaviour.Handle(new UnhandledExceptionBehaviourTestInput(), + CancellationToken.None, + _pipelineBehaviourDelegateMock + ).AsTask()); + + _loggerMock.Received() + .Log(LogLevel.Error, Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any>()); + } +} + +public class UnhandledExceptionBehaviourTestInput : IRequest +{ + +} \ No newline at end of file diff --git a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs index 047a66d..c017a6b 100644 --- a/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs +++ b/tests/MinimalApiTemplate.Application.Tests/Common/Behaviours/ValidationBehaviourTests.cs @@ -1,51 +1,44 @@ -//using FluentValidation.Results; -//using MediatR; -//using MinimalApiTemplate.Application.Common.Behaviours; - -//namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; - -//public class ValidationBehaviourTests -//{ -// private readonly ValidationBehaviour _validationBehaviour; -// private readonly RequestHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); -// private readonly IValidator _validatorMock = Substitute.For>(); - -// public ValidationBehaviourTests() -// { -// var failures = new List -// { -// { new ValidationFailure("prop1", "required") } -// }; - -// _validatorMock.ValidateAsync(Arg.Any(), Arg.Any()) -// .Returns(new ValidationResult(failures)); - -// _validationBehaviour = new([_validatorMock]); -// } - -// [Fact] -// public async Task When_ValidationExists_Then_ReturnTheErrorMessage() -// { -// _pipelineBehaviourDelegateMock.Invoke() -// .Returns(Unit.Value); - -// var sut = await Assert.ThrowsAsync(() => _validationBehaviour.Handle(new ValidationBehaviourTestInput(), -// _pipelineBehaviourDelegateMock, -// CancellationToken.None)); - -// sut.Errors.Count.Should().Be(1); -// } -//} - -//public class ValidationBehaviourTestInput : IRequest -//{ - -//} - -//public class ValidationBehaviourTestHandler : IRequestHandler -//{ -// public Task Handle(ValidationBehaviourTestInput request, CancellationToken cancellationToken) -// { -// return Unit.Task; -// } -//} \ No newline at end of file +using FluentValidation.Results; +using Mediator; +using MinimalApiTemplate.Application.Common.Behaviours; + +namespace MinimalApiTemplate.Application.Tests.Common.Behaviours; + +public class ValidationBehaviourTests +{ + private readonly ValidationBehaviour _validationBehaviour; + private readonly MessageHandlerDelegate _pipelineBehaviourDelegateMock = Substitute.For>(); + private readonly IValidator _validatorMock = Substitute.For>(); + + public ValidationBehaviourTests() + { + var failures = new List + { + { new ValidationFailure("prop1", "required") } + }; + + _validatorMock.ValidateAsync(Arg.Any(), Arg.Any()) + .Returns(new ValidationResult(failures)); + + _validationBehaviour = new([_validatorMock]); + } + + [Fact] + public async Task When_ValidationExists_Then_ReturnTheErrorMessage() + { + _pipelineBehaviourDelegateMock.Invoke(Arg.Any(), CancellationToken.None) + .Returns(Unit.ValueTask); + + var sut = await Assert.ThrowsAsync(() + => _validationBehaviour.Handle(new ValidationBehaviourTestInput(), + CancellationToken.None, + _pipelineBehaviourDelegateMock).AsTask()); + + sut.Errors.Count.Should().Be(1); + } +} + +public class ValidationBehaviourTestInput : IRequest +{ + +} \ No newline at end of file From 625d9aa3c1934ec28c02c65155e7cba8190a9f2e Mon Sep 17 00:00:00 2001 From: Niel de Wet Date: Tue, 15 Apr 2025 16:46:04 +0200 Subject: [PATCH 3/4] feat: add keycloak to Aspire startup --- scripts/keycloak/realm-export.json | 36 +++++++++++++++++++ .../Configurations/Swagger.cs | 3 +- .../Properties/launchSettings.json | 2 +- src/MinimalApiTemplate.Api/appsettings.json | 6 ++-- .../MinimalApiTemplate.AppHost.csproj | 15 ++++---- src/MinimalApiTemplate.AppHost/Program.cs | 9 ++++- 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/scripts/keycloak/realm-export.json b/scripts/keycloak/realm-export.json index 7a8102d..132d3fd 100644 --- a/scripts/keycloak/realm-export.json +++ b/scripts/keycloak/realm-export.json @@ -47,6 +47,42 @@ "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 30, + "users": [ + { + "username": "AdminUser", + "enabled": true, + "emailVerified": true, + "firstName": "admin", + "lastName": "user", + "email": "admin@mail.com", + "credentials": [ + { + "type": "password", + "value": "password" + } + ], + "groups": [ + "Admin" + ] + }, + { + "username": "StandardUser", + "enabled": true, + "emailVerified": true, + "firstName": "Standard", + "lastName": "User", + "email": "standard@mail.com", + "credentials": [ + { + "type": "password", + "value": "password" + } + ], + "groups": [ + "Standard" + ] + } + ], "roles": { "realm": [ { diff --git a/src/MinimalApiTemplate.Api/Configurations/Swagger.cs b/src/MinimalApiTemplate.Api/Configurations/Swagger.cs index 91873d2..7b6ed64 100644 --- a/src/MinimalApiTemplate.Api/Configurations/Swagger.cs +++ b/src/MinimalApiTemplate.Api/Configurations/Swagger.cs @@ -24,8 +24,7 @@ public static void ConfigureSwagger(this IServiceCollection services, IConfigura TokenUrl = new Uri(config["AuthorityOptions:TokenUrl"]!), Scopes = new Dictionary { - { "openid", "OpenID scope" }, - { "profile", "Profile scope" } + { "minimal-api-aud", "Minimal Api" } } } } diff --git a/src/MinimalApiTemplate.Api/Properties/launchSettings.json b/src/MinimalApiTemplate.Api/Properties/launchSettings.json index 4512e19..d1121cb 100644 --- a/src/MinimalApiTemplate.Api/Properties/launchSettings.json +++ b/src/MinimalApiTemplate.Api/Properties/launchSettings.json @@ -6,7 +6,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:8000/swagger" + "applicationUrl": "https://localhost:8901/swagger" }, "IIS Express": { "commandName": "IISExpress", diff --git a/src/MinimalApiTemplate.Api/appsettings.json b/src/MinimalApiTemplate.Api/appsettings.json index 3c1c414..508225d 100644 --- a/src/MinimalApiTemplate.Api/appsettings.json +++ b/src/MinimalApiTemplate.Api/appsettings.json @@ -21,11 +21,11 @@ "AuthorityOptions": { "Authority": "http://localhost:8930/realms/my-realm", "AuthorizationUrl": "http://localhost:8930/realms/my-realm/protocol/openid-connect/auth", - "MetaDataUrl": "http://host.docker.internal:8930/realms/my-realm/.well-known/openid-configuration", - "TokenUrl": "http://host.docker.internal:8930/realms/my-realm/protocol/openid-connect/token", + "MetaDataUrl": "http://localhost:8930/realms/my-realm/.well-known/openid-configuration", + "TokenUrl": "http://localhost:8930/realms/my-realm/protocol/openid-connect/token", "Issuer": "http://localhost:8930/realms/my-realm", "Client": "minimal-api-client", - "Audience": "account" + "Audience": "minimal-api-client" }, "Serilog": { "Using": [ diff --git a/src/MinimalApiTemplate.AppHost/MinimalApiTemplate.AppHost.csproj b/src/MinimalApiTemplate.AppHost/MinimalApiTemplate.AppHost.csproj index 2130970..8f9d9b6 100644 --- a/src/MinimalApiTemplate.AppHost/MinimalApiTemplate.AppHost.csproj +++ b/src/MinimalApiTemplate.AppHost/MinimalApiTemplate.AppHost.csproj @@ -12,13 +12,14 @@ - - - - - - - + + + + + + + + diff --git a/src/MinimalApiTemplate.AppHost/Program.cs b/src/MinimalApiTemplate.AppHost/Program.cs index cebe08b..2b9832a 100644 --- a/src/MinimalApiTemplate.AppHost/Program.cs +++ b/src/MinimalApiTemplate.AppHost/Program.cs @@ -18,7 +18,12 @@ .WithLifetime(ContainerLifetime.Persistent) .WithManagementPlugin(8001); -builder.AddProject("minimalapitemplate-api") +var keycloak = builder.AddKeycloak("Keycloak", 8930) + .WithDataVolume() + .WithRealmImport("../../scripts/keycloak/") + .WithLifetime(ContainerLifetime.Persistent); + +builder.AddProject("minimalapitemplate-api") .WithReference(database) .WaitFor(database) .WithReference(rabbit) @@ -26,6 +31,8 @@ .WithReference(redis) .WaitFor(redis) .WaitFor(seq) + .WithReference(keycloak) + .WaitFor(keycloak) .WithEnvironment("MassTransit__PublishEnabled", "true") .WithEnvironment("SEQ_SERVER_URL", "http://localhost:8002"); From 7682ac042ac5d9b6ef7ab26e805818c80bd9c72d Mon Sep 17 00:00:00 2001 From: Niel de Wet Date: Tue, 15 Apr 2025 16:52:52 +0200 Subject: [PATCH 4/4] fix: add missing audience to keycloack client --- scripts/keycloak/realm-export.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/keycloak/realm-export.json b/scripts/keycloak/realm-export.json index 132d3fd..45d22fe 100644 --- a/scripts/keycloak/realm-export.json +++ b/scripts/keycloak/realm-export.json @@ -804,7 +804,8 @@ "phone", "offline_access", "organization", - "microprofile-jwt" + "microprofile-jwt", + "minimal-api-aud" ] }, {