diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/Events/EventBus/InMemory/NotificationDecorator.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/Events/EventBus/InMemory/NotificationDecorator.cs new file mode 100644 index 00000000..4d96fbc0 --- /dev/null +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/Events/EventBus/InMemory/NotificationDecorator.cs @@ -0,0 +1,20 @@ +namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.Events.EventBus.InMemory; + +using Fitnet.Common.Events; +using Fitnet.Common.Events.EventBus; + +internal sealed class NotificationDecorator(IEventBus eventBus, INotificationHandler? innerHandler) + : INotificationHandler + where TNotification : INotification +{ + public async Task Handle(TNotification notification, CancellationToken cancellationToken) + { + var @event = notification as IIntegrationEvent; + await eventBus.PublishAsync(@event!, cancellationToken); + + if (innerHandler is not null) + { + await innerHandler.Handle(notification, cancellationToken); + } + } +} diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/Configuration/ConfigurationExtensions.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/Configuration/ConfigurationExtensions.cs index 7ff40658..8846e3d1 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/Configuration/ConfigurationExtensions.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/Configuration/ConfigurationExtensions.cs @@ -1,11 +1,9 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine.Configuration; using System.Reflection; +using Events.EventBus.InMemory; using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus; using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus.InMemory; -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Time.Testing; internal static class ConfigurationExtensions { @@ -41,10 +39,73 @@ internal static WebApplicationFactory SetFakeSystemClock(this WebApplicati internal static WebApplicationFactory WithFakeEventBus(this WebApplicationFactory webApplicationFactory, IEventBus eventBusMock) where T : class => webApplicationFactory.WithWebHostBuilder(webHostBuilder => webHostBuilder.ConfigureTestServices(services => - services.AddSingleton(eventBusMock))); + { + AddNotificationDecorator(eventBusMock, services, true); + })); + + internal static WebApplicationFactory WithoutEventHandlers(this WebApplicationFactory webApplicationFactory, IEventBus eventBusMock) + where T : class => + webApplicationFactory.WithWebHostBuilder(webHostBuilder => webHostBuilder.ConfigureTestServices(services => + { + AddNotificationDecorator(eventBusMock, services, false); + })); internal static WebApplicationFactory WithFakeConsumers(this WebApplicationFactory webApplicationFactory) where T : class => webApplicationFactory.WithWebHostBuilder(webHostBuilder => webHostBuilder.ConfigureTestServices(services => - services.AddInMemoryEventBus(Assembly.GetExecutingAssembly()))); + { + services.AddInMemoryEventBus(Assembly.GetExecutingAssembly()); + var decorator = services.FirstOrDefault(s => s.ImplementationType == typeof(NotificationDecorator<>)); + if (decorator is not null) + { + services.Remove(decorator); + } + })); + + private static void AddNotificationDecorator(IEventBus eventBusMock, IServiceCollection services, bool hasSingleEventHandling) + { + var notificationHandlerServices = services.Where(s => + s.ServiceType.IsGenericType && + s.ServiceType.GetGenericTypeDefinition() == typeof(INotificationHandler<>)) + .ToList(); + + foreach (var notificationHandlerService in notificationHandlerServices) + { + var serviceType = notificationHandlerService.ServiceType; + var implementationType = notificationHandlerService.ImplementationType; + + services.Remove(notificationHandlerService); + services.AddTransient(implementationType!); + + services.AddTransient(serviceType, serviceProvider => + { + var notificationHandler = hasSingleEventHandling + ? NotificationHandlerWithFakeEventBus(eventBusMock, implementationType, serviceProvider) + : null; + + var decoratorType = typeof(NotificationDecorator<>).MakeGenericType(serviceType.GenericTypeArguments[0]); + return Activator.CreateInstance(decoratorType, eventBusMock, notificationHandler)!; + }); + } + } + + private static object NotificationHandlerWithFakeEventBus(IEventBus eventBusMock, Type? implementationType, + IServiceProvider serviceProvider) + { + var constructor = implementationType!.GetConstructors().FirstOrDefault()!; + var parameters = constructor.GetParameters(); + var handlerDependencies = new object[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType == typeof(IEventBus)) + { + handlerDependencies[i] = eventBusMock; + continue; + } + handlerDependencies[i] = serviceProvider.GetService(parameters[i]!.ParameterType)!; + } + + var notificationHandler = Activator.CreateInstance(implementationType, handlerDependencies)!; + return notificationHandler; + } } diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/IntegrationEvents/Handlers/IntegrationEventHandlerScope.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/IntegrationEvents/Handlers/IntegrationEventHandlerScope.cs index 96176640..78dfe8ee 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/IntegrationEvents/Handlers/IntegrationEventHandlerScope.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Common/TestEngine/IntegrationEvents/Handlers/IntegrationEventHandlerScope.cs @@ -1,7 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine.IntegrationEvents.Handlers; using EvolutionaryArchitecture.Fitnet.Common.Events; -using MediatR; internal sealed class IntegrationEventHandlerScope : IDisposable where TIntegrationEvent : IIntegrationEvent diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/PrepareContract/PrepareContractTests.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/PrepareContract/PrepareContractTests.cs index 8e1124cd..06b3e318 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/PrepareContract/PrepareContractTests.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/PrepareContract/PrepareContractTests.cs @@ -4,7 +4,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Contracts.PrepareCont using EvolutionaryArchitecture.Fitnet.Contracts.PrepareContract; using Common.TestEngine; using Common.TestEngine.Configuration; -using Microsoft.AspNetCore.Mvc; public sealed class PrepareContractTests( WebApplicationFactory applicationInMemoryFactory, diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/SignContract/SignContractTests.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/SignContract/SignContractTests.cs index b16c8ae0..cd478014 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/SignContract/SignContractTests.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Contracts/SignContract/SignContractTests.cs @@ -7,7 +7,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Contracts.SignContrac using Common.TestEngine.Configuration; using Fitnet.Contracts.SignContract.Events; using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus; -using Microsoft.AspNetCore.Mvc; public sealed class SignContractTests : IClassFixture>, IClassFixture { @@ -17,7 +16,7 @@ public sealed class SignContractTests : IClassFixture applicationInMemoryFactory, DatabaseContainer database) => _applicationHttpClient = applicationInMemoryFactory - .WithFakeEventBus(_fakeEventBus) + .WithoutEventHandlers(_fakeEventBus) .WithContainerDatabaseConfigured(database.ConnectionString!) .CreateClient(); diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/GlobalUsings.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/GlobalUsings.cs index 97012b70..2be29b78 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/GlobalUsings.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/GlobalUsings.cs @@ -3,9 +3,12 @@ global using System.Collections; global using System.Net; global using FluentAssertions; -global using Xunit; +global using MediatR; +global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc.Testing; +global using Microsoft.AspNetCore.TestHost; +global using Microsoft.Extensions.Time.Testing; global using Bogus; global using JetBrains.Annotations; global using NSubstitute; -global using EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine; \ No newline at end of file +global using EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine; diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Offers/Prepare/PrepareOfferTests.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Offers/Prepare/PrepareOfferTests.cs index 2143a2cd..df0f7106 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Offers/Prepare/PrepareOfferTests.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Offers/Prepare/PrepareOfferTests.cs @@ -1,12 +1,9 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Offers.Prepare; using Common.TestEngine.Configuration; -using Common.TestEngine.IntegrationEvents.Handlers; using Fitnet.Offers.Prepare; -using Fitnet.Passes.MarkPassAsExpired.Events; using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus; - public sealed class PrepareOfferTests : IClassFixture>, IClassFixture { @@ -27,13 +24,11 @@ public PrepareOfferTests(WebApplicationFactory applicationInMemoryFacto internal async Task Given_pass_expired_event_published_Then_new_offer_should_be_prepared() { // Arrange - using var integrationEventHandlerScope = - new IntegrationEventHandlerScope(_applicationInMemory); - var integrationEventHandler = integrationEventHandlerScope.IntegrationEventHandler; + using var serviceScope = _applicationInMemory.Services.CreateScope(); + var eventBus = serviceScope.ServiceProvider.GetRequiredService(); var @event = PassExpiredEventFaker.CreateValid(); - // Act - await integrationEventHandler.Handle(@event, CancellationToken.None); + await eventBus.PublishAsync(@event); // Assert EnsureThatOfferPreparedEventWasPublished(); @@ -41,4 +36,4 @@ internal async Task Given_pass_expired_event_published_Then_new_offer_should_be_ private void EnsureThatOfferPreparedEventWasPublished() => _fakeEventBus.Received(1) .PublishAsync(Arg.Any(), Arg.Any()); -} \ No newline at end of file +} diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/MarkPassAsExpired/MarkPassAsExpiredTests.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/MarkPassAsExpired/MarkPassAsExpiredTests.cs index 4c3d6285..480495a4 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/MarkPassAsExpired/MarkPassAsExpiredTests.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/MarkPassAsExpired/MarkPassAsExpiredTests.cs @@ -3,7 +3,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Passes.MarkPassAsExpi using Fitnet.Passes; using RegisterPass; using Common.TestEngine.Configuration; -using Common.TestEngine.IntegrationEvents.Handlers; using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus; using Fitnet.Contracts.SignContract.Events; using Fitnet.Passes.GetAllPasses; @@ -74,9 +73,9 @@ internal async Task Given_mark_pass_as_expired_request_with_not_existing_id_Then private async Task RegisterPass() { var @event = ContractSignedEventFaker.Create(); - using var integrationEventHandlerScope = - new IntegrationEventHandlerScope(_applicationInMemoryFactory); - await integrationEventHandlerScope.Consume(@event); + using var serviceScope = _applicationInMemoryFactory.Services.CreateScope(); + var eventBus = serviceScope.ServiceProvider.GetRequiredService(); + await eventBus.PublishAsync(@event); return @event; } diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/RegisterPass/RegisterPassTests.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/RegisterPass/RegisterPassTests.cs index df01bcb9..1464b959 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/RegisterPass/RegisterPassTests.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Passes/RegisterPass/RegisterPassTests.cs @@ -1,8 +1,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Passes.RegisterPass; using Common.TestEngine.Configuration; -using Common.TestEngine.IntegrationEvents.Handlers; -using Fitnet.Contracts.SignContract.Events; using Fitnet.Passes.RegisterPass.Events; using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus; @@ -25,12 +23,12 @@ public RegisterPassTests(WebApplicationFactory applicationInMemoryFacto internal async Task Given_contract_signed_event_Then_should_register_pass() { // Arrange - using var integrationEventHandlerScope = - new IntegrationEventHandlerScope(_applicationInMemory); + using var serviceScope = _applicationInMemory.Services.CreateScope(); + var eventBus = serviceScope.ServiceProvider.GetRequiredService(); var @event = ContractSignedEventFaker.Create(); // Act - await integrationEventHandlerScope.Consume(@event); + await eventBus.PublishAsync(@event); // Assert EnsureThatPassRegisteredEventWasPublished(); diff --git a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Reports/GenerateNewPassesPerMonthReport/GenerateNewPassesPerMonthReportTests.cs b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Reports/GenerateNewPassesPerMonthReport/GenerateNewPassesPerMonthReportTests.cs index 59013ff0..32c0d8b5 100644 --- a/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Reports/GenerateNewPassesPerMonthReport/GenerateNewPassesPerMonthReportTests.cs +++ b/Chapter-1-initial-architecture/Src/Fitnet.IntegrationTests/Reports/GenerateNewPassesPerMonthReport/GenerateNewPassesPerMonthReportTests.cs @@ -1,8 +1,7 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Reports.GenerateNewPassesPerMonthReport; using Common.TestEngine.Configuration; -using Common.TestEngine.IntegrationEvents.Handlers; -using Fitnet.Contracts.SignContract.Events; +using Fitnet.Common.Events.EventBus; using Fitnet.Reports; using Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos; using Passes.RegisterPass; @@ -51,10 +50,11 @@ private async Task RegisterPasses(List reportTestData private async Task RegisterPass(DateTimeOffset from, DateTimeOffset to) { - using var integrationEventHandlerScope = - new IntegrationEventHandlerScope(_applicationInMemoryFactory); - var integrationEventHandler = integrationEventHandlerScope.IntegrationEventHandler; + using var serviceScope = _applicationInMemoryFactory.Services.CreateScope(); + var eventBus = serviceScope.ServiceProvider.GetRequiredService(); var @event = ContractSignedEventFaker.Create(from, to); - await integrationEventHandler.Handle(@event, CancellationToken.None); + + // Act + await eventBus.PublishAsync(@event); } } diff --git a/Chapter-1-initial-architecture/Src/Fitnet/Passes/RegisterPass/RegisterEndpoint.cs b/Chapter-1-initial-architecture/Src/Fitnet/Passes/RegisterPass/ContractSignedEventHandler.cs similarity index 100% rename from Chapter-1-initial-architecture/Src/Fitnet/Passes/RegisterPass/RegisterEndpoint.cs rename to Chapter-1-initial-architecture/Src/Fitnet/Passes/RegisterPass/ContractSignedEventHandler.cs