From e1beb463c3afafebd4163134a9011720fd123942 Mon Sep 17 00:00:00 2001 From: Mehdi Hadeli Date: Sun, 1 Sep 2024 15:25:37 +0200 Subject: [PATCH] =?UTF-8?q?test:=20=F0=9F=A7=AA=20refactor=20identity=20in?= =?UTF-8?q?tegration=20tests=20(#237)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 4 + .husky/pre-commit | 1 + .../FoodDelivery.ApiGateway.csproj | 2 +- .../FoodDelivery.ApiGateway/Program.cs | 9 +- .../Persistence/ITestDataSeeder.cs | 7 ++ .../Extensions/QueryableExtensions.cs | 33 +++-- .../Messaging/MessagingConstants.cs | 2 +- .../DependencyInjectionExtensions.cs | 17 ++- .../Persistence/SeedWorker.cs | 30 +++-- .../CustomEntityNameFormatter.cs | 20 ++++ .../DependencyInjectionExtensions.cs | 70 ++++++++--- .../Brands/Configs.cs | 3 - .../Categories/Configs.cs | 3 - .../Products/MassTransitExtensions.cs | 77 +++++++++--- .../Products/ProductsConfigs.cs | 5 +- .../Infrastructure.cs | 2 +- .../Persistence.cs | 2 - .../Suppliers/Configs.cs | 3 - .../Suppliers/Services/SupplierChecker.cs | 11 +- .../Customers/CustomersConfigs.cs | 5 - .../Customers/MassTransitExtensions.cs | 9 +- .../Products/MassTransitExtensions.cs | 10 +- .../MassTransitExtensions.cs | 11 +- ...onBuilderExtensions.DependencyInjection.cs | 3 - ...icationBuilderExtensions.Infrastructure.cs | 10 +- .../Users/MassTransitExtensions.cs | 8 +- .../Identity/Data/IdentityDataSeeder.cs | 32 ++--- .../Identity/IdentityConfigs.cs | 2 - .../Shared/Data/IdentityDataSeeder.cs | 13 -- .../Identity.cs | 2 - .../Infrastructure.cs | 4 +- .../RegisteringUser/v1/RegisterUser.cs | 4 +- .../Users/MassTransitExtensions.cs | 51 ++++++-- .../Infrastructure.cs | 2 +- .../Persistence.cs | 2 - .../CustomerServiceEndToEndTestBase.cs | 6 - .../CustomerServiceIntegrationTestBase.cs | 6 - .../v1/GetCustomerByCustomerIdTests.cs | 11 +- .../v1/GetCustomerByIdTests.cs | 11 +- .../GettingCustomers/v1/GetCustomersTests.cs | 11 +- .../IdentityServiceIntegrationTestBase.cs | 18 +++ .../IdentityTestSeeder.cs | 45 +++++++ .../IntegrationTestCollection.cs | 14 +++ .../RegisteringUser/v1/RegisterUserTests.cs | 113 +++++++++--------- .../Factory/CustomWebApplicationFactory.cs | 2 + .../Tests.Shared/Fixtures/SharedFixture.cs | 2 + .../Tests.Shared/TestBase/EndToEndTestBase.cs | 42 +++---- .../TestBase/IntegrationTestBase.cs | 37 +----- 48 files changed, 479 insertions(+), 308 deletions(-) create mode 100644 src/BuildingBlocks/BuildingBlocks.Abstractions/Persistence/ITestDataSeeder.cs delete mode 100644 src/Services/Identity/FoodDelivery.Services.Identity/Shared/Data/IdentityDataSeeder.cs create mode 100644 tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityServiceIntegrationTestBase.cs create mode 100644 tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityTestSeeder.cs create mode 100644 tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IntegrationTestCollection.cs diff --git a/.editorconfig b/.editorconfig index 6a3f617b..873d72fa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -248,6 +248,8 @@ resharper_web_config_module_not_resolved_highlighting = warning resharper_web_config_type_not_resolved_highlighting = warning resharper_web_config_wrong_module_highlighting = warning +# https://www.jetbrains.com/help/rider/ClassNeverInstantiated.Global.html +resharper_class_never_instantiated_global_highlighting = none ################################################################################## ## https://github.com/DotNetAnalyzers/StyleCopAnalyzers/tree/master/documentation @@ -375,6 +377,8 @@ dotnet_diagnostic.sa1101.severity = None # The keywords within the declaration of an element do not follow a standard ordering scheme. dotnet_diagnostic.SA1206.severity = None +dotnet_diagnostic.SA1404.severity = None + ################################################################################## ## https://github.com/meziantou/Meziantou.Analyzer/tree/main/docs ## Meziantou.Analyzer diff --git a/.husky/pre-commit b/.husky/pre-commit index c94e823b..77b60d99 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,3 +3,4 @@ # dotnet format --verbosity diagnostic dotnet csharpier . && git add -A . +dotnet csharpier --check . \ No newline at end of file diff --git a/src/ApiGateway/FoodDelivery.ApiGateway/FoodDelivery.ApiGateway.csproj b/src/ApiGateway/FoodDelivery.ApiGateway/FoodDelivery.ApiGateway.csproj index 5e936e21..c450b44e 100644 --- a/src/ApiGateway/FoodDelivery.ApiGateway/FoodDelivery.ApiGateway.csproj +++ b/src/ApiGateway/FoodDelivery.ApiGateway/FoodDelivery.ApiGateway.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/ApiGateway/FoodDelivery.ApiGateway/Program.cs b/src/ApiGateway/FoodDelivery.ApiGateway/Program.cs index a7f811ae..65174962 100644 --- a/src/ApiGateway/FoodDelivery.ApiGateway/Program.cs +++ b/src/ApiGateway/FoodDelivery.ApiGateway/Program.cs @@ -1,21 +1,16 @@ using System.IdentityModel.Tokens.Jwt; -using BuildingBlocks.Core.Messaging; using BuildingBlocks.Logging; using MassTransit; -using Microsoft.IdentityModel.Logging; using Serilog; using Serilog.Events; -using Serilog.Sinks.SpectreConsole; +using Serilog.Sinks.Spectre; using Yarp.ReverseProxy.Transforms; using MessageHeaders = BuildingBlocks.Core.Messaging.MessageHeaders; Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) .Enrich.FromLogContext() - .WriteTo.SpectreConsole( - "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - LogEventLevel.Information - ) + .WriteTo.Spectre("{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", LogEventLevel.Information) .CreateLogger(); var builder = WebApplication.CreateBuilder(args); diff --git a/src/BuildingBlocks/BuildingBlocks.Abstractions/Persistence/ITestDataSeeder.cs b/src/BuildingBlocks/BuildingBlocks.Abstractions/Persistence/ITestDataSeeder.cs new file mode 100644 index 00000000..ee2bc7ac --- /dev/null +++ b/src/BuildingBlocks/BuildingBlocks.Abstractions/Persistence/ITestDataSeeder.cs @@ -0,0 +1,7 @@ +namespace BuildingBlocks.Abstractions.Persistence; + +public interface ITestDataSeeder +{ + int Order { get; } + Task SeedAllAsync(); +} diff --git a/src/BuildingBlocks/BuildingBlocks.Core/Extensions/QueryableExtensions.cs b/src/BuildingBlocks/BuildingBlocks.Core/Extensions/QueryableExtensions.cs index 0cf76152..9a629bb2 100644 --- a/src/BuildingBlocks/BuildingBlocks.Core/Extensions/QueryableExtensions.cs +++ b/src/BuildingBlocks/BuildingBlocks.Core/Extensions/QueryableExtensions.cs @@ -3,6 +3,7 @@ using AutoMapper.QueryableExtensions; using BuildingBlocks.Abstractions.Core.Paging; using BuildingBlocks.Core.Paging; +using Microsoft.EntityFrameworkCore; using Sieve.Models; using Sieve.Services; @@ -29,10 +30,13 @@ CancellationToken cancellationToken // https://github.com/Biarity/Sieve/issues/34#issuecomment-403817573 var result = sieveProcessor.Apply(sieveModel, queryable, applyPagination: false); +#pragma warning disable AsyncFixer02 + // The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations. var total = result.Count(); +#pragma warning restore AsyncFixer02 result = sieveProcessor.Apply(sieveModel, queryable, applyFiltering: false, applySorting: false); - var items = await result.ToAsyncEnumerable().ToListAsync(cancellationToken: cancellationToken); + var items = await result.AsNoTracking().ToAsyncEnumerable().ToListAsync(cancellationToken: cancellationToken); return PageList.Create(items.AsReadOnly(), pageRequest.PageNumber, pageRequest.PageSize, total); } @@ -57,12 +61,18 @@ CancellationToken cancellationToken // https://github.com/Biarity/Sieve/issues/34#issuecomment-403817573 var result = sieveProcessor.Apply(sieveModel, queryable, applyPagination: false); +#pragma warning disable AsyncFixer02 + // The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations. var total = result.Count(); +#pragma warning restore AsyncFixer02 result = sieveProcessor.Apply(sieveModel, queryable, applyFiltering: false, applySorting: false); // Only applies pagination var projectedQuery = result.ProjectTo(configurationProvider); - var items = await projectedQuery.ToAsyncEnumerable().ToListAsync(cancellationToken: cancellationToken); + var items = await projectedQuery + .AsNoTracking() + .ToAsyncEnumerable() + .ToListAsync(cancellationToken: cancellationToken); return PageList.Create(items.AsReadOnly(), pageRequest.PageNumber, pageRequest.PageSize, total); } @@ -87,12 +97,18 @@ CancellationToken cancellationToken // https://github.com/Biarity/Sieve/issues/34#issuecomment-403817573 var result = sieveProcessor.Apply(sieveModel, queryable, applyPagination: false); +#pragma warning disable AsyncFixer02 + // The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations. var total = result.Count(); +#pragma warning restore AsyncFixer02 result = sieveProcessor.Apply(sieveModel, queryable, applyFiltering: false, applySorting: false); // Only applies pagination var projectedQuery = projectionFunc(result); - var items = await projectedQuery.ToAsyncEnumerable().ToListAsync(cancellationToken: cancellationToken); + var items = await projectedQuery + .AsNoTracking() + .ToAsyncEnumerable() + .ToListAsync(cancellationToken: cancellationToken); return PageList.Create(items.AsReadOnly(), pageRequest.PageNumber, pageRequest.PageSize, total); } @@ -120,12 +136,7 @@ public static async Task> ApplyPagingAsync( - pageRequest, - sieveProcessor, - projectionFunc, - cancellationToken - ); + return await query.ApplyPagingAsync(pageRequest, sieveProcessor, projectionFunc, cancellationToken); } public static async Task> ApplyPagingAsync( @@ -148,11 +159,15 @@ CancellationToken cancellationToken // https://github.com/Biarity/Sieve/issues/34#issuecomment-403817573 var result = sieveProcessor.Apply(sieveModel, queryable, applyPagination: false); +#pragma warning disable AsyncFixer02 + // The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations. var total = result.Count(); +#pragma warning restore AsyncFixer02 result = sieveProcessor.Apply(sieveModel, queryable, applyFiltering: false, applySorting: false); // Only applies pagination var items = await result .Select(x => map(x)) + .AsNoTracking() .ToAsyncEnumerable() .ToListAsync(cancellationToken: cancellationToken); diff --git a/src/BuildingBlocks/BuildingBlocks.Core/Messaging/MessagingConstants.cs b/src/BuildingBlocks/BuildingBlocks.Core/Messaging/MessagingConstants.cs index 13360a9a..2f757094 100644 --- a/src/BuildingBlocks/BuildingBlocks.Core/Messaging/MessagingConstants.cs +++ b/src/BuildingBlocks/BuildingBlocks.Core/Messaging/MessagingConstants.cs @@ -2,5 +2,5 @@ namespace BuildingBlocks.Core.Messaging; public static class MessagingConstants { - public const string PrimaryExchangePostfix = "_primary_exchange"; + public const string PrimaryExchangePostfix = ".primary_exchange"; } diff --git a/src/BuildingBlocks/BuildingBlocks.Core/Persistence/Extensions/DependencyInjectionExtensions.cs b/src/BuildingBlocks/BuildingBlocks.Core/Persistence/Extensions/DependencyInjectionExtensions.cs index f07ed726..cb1459db 100644 --- a/src/BuildingBlocks/BuildingBlocks.Core/Persistence/Extensions/DependencyInjectionExtensions.cs +++ b/src/BuildingBlocks/BuildingBlocks.Core/Persistence/Extensions/DependencyInjectionExtensions.cs @@ -3,7 +3,7 @@ namespace BuildingBlocks.Core.Persistence.Extensions; -internal static class DependencyInjectionExtensions +public static class DependencyInjectionExtensions { internal static IServiceCollection AddPersistenceCore( this IServiceCollection services, @@ -12,9 +12,24 @@ params Assembly[] assembliesToScan { services.ScanAndRegisterDbExecutors(assembliesToScan); + services.RegisterDataSeeders(assembliesToScan); + services.AddHostedService(); services.AddScoped(); return services; } + + public static void RegisterDataSeeders(this IServiceCollection services, Assembly[] assembliesToScan) + { + services.Scan(scan => + scan.FromAssemblies(assembliesToScan) + .AddClasses(classes => classes.AssignableTo()) + .AsImplementedInterfaces() + .WithScopedLifetime() + .AddClasses(classes => classes.AssignableTo()) + .AsImplementedInterfaces() + .WithScopedLifetime() + ); + } } diff --git a/src/BuildingBlocks/BuildingBlocks.Core/Persistence/SeedWorker.cs b/src/BuildingBlocks/BuildingBlocks.Core/Persistence/SeedWorker.cs index b9e63191..6213479c 100644 --- a/src/BuildingBlocks/BuildingBlocks.Core/Persistence/SeedWorker.cs +++ b/src/BuildingBlocks/BuildingBlocks.Core/Persistence/SeedWorker.cs @@ -1,4 +1,5 @@ using BuildingBlocks.Abstractions.Persistence; +using BuildingBlocks.Core.Web.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -15,15 +16,25 @@ IWebHostEnvironment webHostEnvironment { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - if (!webHostEnvironment.IsEnvironment("test")) - { - logger.LogInformation("Seed worker started"); + logger.LogInformation("Seed worker started"); - // https://stackoverflow.com/questions/38238043/how-and-where-to-call-database-ensurecreated-and-database-migrate - // https://www.michalbialecki.com/2020/07/20/adding-entity-framework-core-5-migrations-to-net-5-project/ - using var serviceScope = serviceScopeFactory.CreateScope(); - var seeders = serviceScope.ServiceProvider.GetServices(); + using var serviceScope = serviceScopeFactory.CreateScope(); + // https://stackoverflow.com/questions/38238043/how-and-where-to-call-database-ensurecreated-and-database-migrate + // https://www.michalbialecki.com/2020/07/20/adding-entity-framework-core-5-migrations-to-net-5-project/ + var testSeeders = serviceScope.ServiceProvider.GetServices(); + var seeders = serviceScope.ServiceProvider.GetServices(); + if (webHostEnvironment.IsTest()) + { + foreach (var testDataSeeder in testSeeders.OrderBy(x => x.Order)) + { + logger.LogInformation("Seeding '{Seed}' started...", testDataSeeder.GetType().Name); + await testDataSeeder.SeedAllAsync(); + logger.LogInformation("Seeding '{Seed}' ended...", testDataSeeder.GetType().Name); + } + } + else + { foreach (var seeder in seeders.OrderBy(x => x.Order)) { logger.LogInformation("Seeding '{Seed}' started...", seeder.GetType().Name); @@ -35,10 +46,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) public override Task StopAsync(CancellationToken cancellationToken) { - if (!webHostEnvironment.IsEnvironment("test")) - { - logger.LogInformation("Seed worker stopped"); - } + logger.LogInformation("Seed worker stopped"); return base.StopAsync(cancellationToken); } diff --git a/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/CustomEntityNameFormatter.cs b/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/CustomEntityNameFormatter.cs index cca72ce0..8f795783 100644 --- a/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/CustomEntityNameFormatter.cs +++ b/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/CustomEntityNameFormatter.cs @@ -27,3 +27,23 @@ public string FormatEntityName() return $"{typeof(T).Name.Underscore()}{MessagingConstants.PrimaryExchangePostfix}"; } } + +public class CustomEntityNameFormatter : IMessageEntityNameFormatter + where TMessage : class +{ + public string FormatEntityName() + { + // Check if T implements IEventEnvelope + if (typeof(IEventEnvelope).IsAssignableFrom(typeof(TMessage))) + { + var messageProperty = typeof(TMessage).GetProperty(nameof(IEventEnvelope.Message)); + if (typeof(IMessage).IsAssignableFrom(messageProperty!.PropertyType)) + { + return $"{messageProperty.PropertyType.Name.Underscore()}{MessagingConstants.PrimaryExchangePostfix}"; + } + } + + // Return a default value if T does not implement IEventEnvelop or Message property is not found + return $"{typeof(TMessage).Name.Underscore()}{MessagingConstants.PrimaryExchangePostfix}"; + } +} diff --git a/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/DependencyInjectionExtensions.cs b/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/DependencyInjectionExtensions.cs index f632170d..e3c49b61 100644 --- a/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/DependencyInjectionExtensions.cs +++ b/src/BuildingBlocks/BuildingBlocks.Integration.MassTransit/DependencyInjectionExtensions.cs @@ -12,6 +12,7 @@ using Humanizer; using MassTransit; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using RabbitMQ.Client; using IExternalEventBus = BuildingBlocks.Abstractions.Messaging.IExternalEventBus; @@ -20,9 +21,18 @@ namespace BuildingBlocks.Integration.MassTransit; public static class DependencyInjectionExtensions { + /// + /// Add Masstransit. + /// + /// + /// All configurations related to message topology, publish configuration and recive endpoint configurations. + /// + /// + /// + /// public static WebApplicationBuilder AddCustomMassTransit( this WebApplicationBuilder builder, - Action? configureReceiveEndpoints = null, + Action? configureMessagesTopologies = null, Action? configureBusRegistration = null, Action? configureMessagingOptions = null, params Assembly[] scanAssemblies @@ -50,12 +60,21 @@ params Assembly[] scanAssemblies void ConfiguratorAction(IBusRegistrationConfigurator busRegistrationConfigurator) { + // https://masstransit.io/documentation/configuration#health-check-options + busRegistrationConfigurator.ConfigureHealthCheckOptions(options => + { + options.Name = "masstransit"; + options.MinimalFailureStatus = HealthStatus.Unhealthy; + options.Tags.Add("health"); + }); + configureBusRegistration?.Invoke(busRegistrationConfigurator); // https://masstransit-project.com/usage/configuration.html#receive-endpoints busRegistrationConfigurator.AddConsumers(assemblies); - // exclude namespace for the messages + // https://masstransit.io/documentation/configuration#endpoint-name-formatters + // uses by `ConfigureEndpoints` for naming `queues` and `receive endpoint` names busRegistrationConfigurator.SetEndpointNameFormatter(new SnakeCaseEndpointNameFormatter(false)); // Ref: https://masstransit-project.com/advanced/topology/rabbitmq.html @@ -72,11 +91,20 @@ void ConfiguratorAction(IBusRegistrationConfigurator busRegistrationConfigurator { cfg.UseConsumeFilter(typeof(CorrelationConsumeFilter<>), context); - cfg.PublishTopology.BrokerTopologyOptions = PublishBrokerTopologyOptions.FlattenHierarchy; + // https: // github.com/MassTransit/MassTransit/issues/2018 + // https://github.com/MassTransit/MassTransit/issues/4831 + cfg.Publish(p => p.Exclude = true); + cfg.Publish(p => p.Exclude = true); + cfg.Publish(p => p.Exclude = true); + cfg.Publish(p => p.Exclude = true); + cfg.Publish(p => p.Exclude = true); + cfg.Publish(p => p.Exclude = true); if (messagingOptions.AutoConfigEndpoints) { - // https://masstransit-project.com/usage/consumers.html#consumer + // https://masstransit.io/documentation/configuration#consumer-registration + // https://masstransit.io/documentation/configuration#configure-endpoints + // MassTransit is able to configure receive endpoints for all registered consumer types. Receive endpoint names are generated using an endpoint name formatter cfg.ConfigureEndpoints(context); } @@ -95,32 +123,39 @@ void ConfiguratorAction(IBusRegistrationConfigurator busRegistrationConfigurator ); // for setting exchange name for message type as default. masstransit by default uses fully message type name for primary exchange name. + // https://masstransit.io/documentation/configuration/topology/message cfg.MessageTopology.SetEntityNameFormatter(new CustomEntityNameFormatter()); ApplyMessagesPublishTopology(cfg.PublishTopology, assemblies); ApplyMessagesConsumeTopology(cfg, context, assemblies); ApplyMessagesSendTopology(cfg.SendTopology, assemblies); - configureReceiveEndpoints?.Invoke(context, cfg); + configureMessagesTopologies?.Invoke(context, cfg); // https://masstransit-project.com/usage/exceptions.html#retry // https://markgossa.com/2022/06/masstransit-exponential-back-off.html cfg.UseMessageRetry(r => AddRetryConfiguration(r)); - - // cfg.UseInMemoryOutbox(); - - // https: // github.com/MassTransit/MassTransit/issues/2018 - // https://github.com/MassTransit/MassTransit/issues/4831 - cfg.Publish(p => p.Exclude = true); - cfg.Publish(p => p.Exclude = true); - cfg.Publish(p => p.Exclude = true); - cfg.Publish(p => p.Exclude = true); - cfg.Publish(p => p.Exclude = true); - cfg.Publish(p => p.Exclude = true); } ); } + builder + .Services.AddOptions() + .Configure(options => + { + options.WaitUntilStarted = true; + options.StartTimeout = TimeSpan.FromSeconds(30); + options.StopTimeout = TimeSpan.FromSeconds(60); + }); + + builder + .Services.AddOptions() + .Configure(options => + { + options.StartupTimeout = TimeSpan.FromSeconds(60); + options.ShutdownTimeout = TimeSpan.FromSeconds(60); + }); + builder.Services.AddTransient(); builder.Services.AddTransient(); @@ -150,6 +185,9 @@ Assembly[] assemblies var eventEnvelopeInterfaceConfigurator = consumeTopology.GetMessageTopology( eventEnvelopeInterfaceMessageType ); + + // indicate whether the topic or exchange for the message type should be created and subscribed to the queue when consumed on a reception endpoint. + // // with setting `ConfigureConsumeTopology` to `false`, we should create `primary exchange` and its bounded exchange manually with using `re.Bind` otherwise with `ConfigureConsumeTopology=true` it get publish topology for message type `T` with `_publishTopology.GetMessageTopology()` and use its ExchangeType and ExchangeName based ofo default EntityFormatter eventEnvelopeInterfaceConfigurator.ConfigureConsumeTopology = true; // none event-envelope message types diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Brands/Configs.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Brands/Configs.cs index 01ff5a29..e21235b2 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Brands/Configs.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Brands/Configs.cs @@ -1,7 +1,5 @@ -using BuildingBlocks.Abstractions.Persistence; using BuildingBlocks.Abstractions.Web.Module; using FoodDelivery.Services.Catalogs.Brands.Contracts; -using FoodDelivery.Services.Catalogs.Brands.Data; using FoodDelivery.Services.Catalogs.Brands.Services; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -11,7 +9,6 @@ internal class Configs : IModuleConfiguration { public WebApplicationBuilder AddModuleServices(WebApplicationBuilder builder) { - builder.Services.TryAddScoped(); builder.Services.TryAddScoped(); return builder; diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Categories/Configs.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Categories/Configs.cs index 24e01613..4b7e2ec5 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Categories/Configs.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Categories/Configs.cs @@ -1,7 +1,5 @@ -using BuildingBlocks.Abstractions.Persistence; using BuildingBlocks.Abstractions.Web.Module; using FoodDelivery.Services.Catalogs.Categories.Contracts; -using FoodDelivery.Services.Catalogs.Categories.Data; using FoodDelivery.Services.Catalogs.Categories.Services; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -11,7 +9,6 @@ internal class Configs : IModuleConfiguration { public WebApplicationBuilder AddModuleServices(WebApplicationBuilder builder) { - builder.Services.TryAddScoped(); builder.Services.TryAddScoped(); return builder; diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/MassTransitExtensions.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/MassTransitExtensions.cs index a76afc52..64ed9d0c 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/MassTransitExtensions.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/MassTransitExtensions.cs @@ -1,37 +1,84 @@ +using BuildingBlocks.Abstractions.Events; +using BuildingBlocks.Integration.MassTransit; using FoodDelivery.Services.Shared.Catalogs.Products.Events.V1.Integration; using Humanizer; using MassTransit; -using RabbitMQ.Client; namespace FoodDelivery.Services.Catalogs.Products; public static class MassTransitExtensions { - internal static void AddProductPublishers(this IRabbitMqBusFactoryConfigurator cfg) + internal static void ConfigureProductMessagesTopology(this IRabbitMqBusFactoryConfigurator cfg) { - cfg.Message(e => e.SetEntityName($"{nameof(ProductCreatedV1).Underscore()}.input_exchange")); // name of the primary exchange - cfg.Publish(e => e.ExchangeType = ExchangeType.Direct); // primary exchange type - cfg.Send(e => + // https://masstransit.io/documentation/transports/rabbitmq + cfg.Message>(e => + { + // https://masstransit.io/documentation/configuration/topology/message + // Name of the `primary exchange` for type based message publishing and sending + // e.SetEntityName($"{nameof(ProductCreatedV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}"); + e.SetEntityNameFormatter(new CustomEntityNameFormatter>()); + }); + + // configuration for MessagePublishTopologyConfiguration and using IPublishEndpoint + cfg.Publish>(e => + { + // we configured some shared settings for all publish message in masstransit publish topologies + + // // setup primary exchange + // e.Durable = true; + // e.ExchangeType = ExchangeType.Direct; + }); + + cfg.Send>(e => { // route by message type to binding fanout exchange (exchange to exchange binding) e.UseRoutingKeyFormatter(context => context.Message.GetType().Name.Underscore()); }); - cfg.Message(e => - e.SetEntityName($"{nameof(ProductStockDebitedV1).Underscore()}.input_exchange") - ); // name of the primary exchange - cfg.Publish(e => e.ExchangeType = ExchangeType.Direct); // primary exchange type - cfg.Send(e => + // https://masstransit.io/documentation/transports/rabbitmq + cfg.Message>(e => + { + // https://masstransit.io/documentation/configuration/topology/message + // Name of the `primary exchange` for type based message publishing and sending + // e.SetEntityName($"{nameof(ProductStockDebitedV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}"); + e.SetEntityNameFormatter(new CustomEntityNameFormatter>()); + }); + + // configuration for MessagePublishTopologyConfiguration and using IPublishEndpoint + cfg.Publish>(e => + { + // we configured some shared settings for all publish message in masstransit publish topologies + + // // setup primary exchange + // e.Durable = true; + // e.ExchangeType = ExchangeType.Direct; + }); + + cfg.Send>(e => { // route by message type to binding fanout exchange (exchange to exchange binding) e.UseRoutingKeyFormatter(context => context.Message.GetType().Name.Underscore()); }); - cfg.Message(e => - e.SetEntityName($"{nameof(ProductStockReplenishedV1).Underscore()}.input_exchange") - ); // name of the primary exchange - cfg.Publish(e => e.ExchangeType = ExchangeType.Direct); // primary exchange type - cfg.Send(e => + // https://masstransit.io/documentation/transports/rabbitmq + cfg.Message>(e => + { + // https://masstransit.io/documentation/configuration/topology/message + // Name of the `primary exchange` for type based message publishing and sending + // e.SetEntityName($"{nameof(ProductStockDebitedV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}"); + e.SetEntityNameFormatter(new CustomEntityNameFormatter>()); + }); + + cfg.Publish>(e => + { + // we configured some shared settings for all publish message in masstransit publish topologies + + // // setup primary exchange + // e.Durable = true; + // e.ExchangeType = ExchangeType.Direct; + }); + + cfg.Send>(e => { // route by message type to binding fanout exchange (exchange to exchange binding) e.UseRoutingKeyFormatter(context => context.Message.GetType().Name.Underscore()); diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/ProductsConfigs.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/ProductsConfigs.cs index ff87822e..58e58b3f 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/ProductsConfigs.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Products/ProductsConfigs.cs @@ -20,7 +20,6 @@ internal class ProductsConfigs : IModuleConfiguration public WebApplicationBuilder AddModuleServices(WebApplicationBuilder builder) { - builder.Services.TryAddScoped(); builder.Services.TryAddSingleton(); return builder; @@ -38,10 +37,10 @@ public IEndpointRouteBuilder MapEndpoints(IEndpointRouteBuilder endpoints) var routeCategoryName = Tag; var products = endpoints.NewVersionedApi(name: routeCategoryName).WithTags(Tag); - // create a new sub group for each version + // create a new subgroup for each version var productsV1 = products.MapGroup(ProductsPrefixUri).HasDeprecatedApiVersion(0.9).HasApiVersion(1.0); - // create a new sub group for each version + // create a new subgroup for each version var productsV2 = products.MapGroup(ProductsPrefixUri).HasApiVersion(2.0); // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-7.0#route-groups diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs index 29a1f165..bb4873d8 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs @@ -100,7 +100,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.AddCustomMassTransit( (busRegistrationContext, busFactoryConfigurator) => { - busFactoryConfigurator.AddProductPublishers(); + busFactoryConfigurator.ConfigureProductMessagesTopology(); } ); diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs index aaf55857..05af7fc2 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs @@ -36,8 +36,6 @@ private static void AddPostgresWriteStorage(IServiceCollection services, IConfig { services.AddPostgresDbContext(configuration); - // add migrations and seeders dependencies, or we could add seeders inner each modules - services.TryAddScoped(); services.TryAddScoped(); } diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Configs.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Configs.cs index 51185476..1b6eacf2 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Configs.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Configs.cs @@ -1,7 +1,5 @@ -using BuildingBlocks.Abstractions.Persistence; using BuildingBlocks.Abstractions.Web.Module; using FoodDelivery.Services.Catalogs.Suppliers.Contracts; -using FoodDelivery.Services.Catalogs.Suppliers.Data; using FoodDelivery.Services.Catalogs.Suppliers.Services; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -11,7 +9,6 @@ internal class Configs : IModuleConfiguration { public WebApplicationBuilder AddModuleServices(WebApplicationBuilder builder) { - builder.Services.TryAddScoped(); builder.Services.TryAddScoped(); return builder; diff --git a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Services/SupplierChecker.cs b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Services/SupplierChecker.cs index eb9944c1..10afeee7 100644 --- a/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Services/SupplierChecker.cs +++ b/src/Services/Catalogs/FoodDelivery.Services.Catalogs/Suppliers/Services/SupplierChecker.cs @@ -5,19 +5,12 @@ namespace FoodDelivery.Services.Catalogs.Suppliers.Services; -public class SupplierChecker : ISupplierChecker +public class SupplierChecker(ICatalogDbContext catalogDbContext) : ISupplierChecker { - private readonly ICatalogDbContext _catalogDbContext; - - public SupplierChecker(ICatalogDbContext catalogDbContext) - { - _catalogDbContext = catalogDbContext; - } - public bool SupplierExists(SupplierId supplierId) { supplierId.NotBeNull(); - var category = _catalogDbContext.FindSupplierById(supplierId); + var category = catalogDbContext.FindSupplierById(supplierId); return category is not null; } diff --git a/src/Services/Customers/FoodDelivery.Services.Customers/Customers/CustomersConfigs.cs b/src/Services/Customers/FoodDelivery.Services.Customers/Customers/CustomersConfigs.cs index 0329706c..90d5f371 100644 --- a/src/Services/Customers/FoodDelivery.Services.Customers/Customers/CustomersConfigs.cs +++ b/src/Services/Customers/FoodDelivery.Services.Customers/Customers/CustomersConfigs.cs @@ -1,9 +1,6 @@ using Asp.Versioning.Builder; -using BuildingBlocks.Abstractions.Persistence; using BuildingBlocks.Abstractions.Web.Module; -using FoodDelivery.Services.Customers.Customers.Data; using FoodDelivery.Services.Customers.Shared; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace FoodDelivery.Services.Customers.Customers; @@ -15,8 +12,6 @@ internal class CustomersConfigs : IModuleConfiguration public WebApplicationBuilder AddModuleServices(WebApplicationBuilder builder) { - builder.Services.TryAddScoped(); - //// we could add event mappers manually, also they can find automatically by scanning assemblies // builder.Services.TryAddSingleton(); return builder; diff --git a/src/Services/Customers/FoodDelivery.Services.Customers/Customers/MassTransitExtensions.cs b/src/Services/Customers/FoodDelivery.Services.Customers/Customers/MassTransitExtensions.cs index c75814c2..770ee030 100644 --- a/src/Services/Customers/FoodDelivery.Services.Customers/Customers/MassTransitExtensions.cs +++ b/src/Services/Customers/FoodDelivery.Services.Customers/Customers/MassTransitExtensions.cs @@ -1,5 +1,6 @@ using BuildingBlocks.Abstractions.Events; using BuildingBlocks.Core.Messaging; +using BuildingBlocks.Integration.MassTransit; using FoodDelivery.Services.Shared.Customers.Customers.Events.V1.Integration; using Humanizer; using MassTransit; @@ -8,13 +9,15 @@ namespace FoodDelivery.Services.Customers.Customers; internal static class MassTransitExtensions { - internal static void AddCustomerPublishers(this IRabbitMqBusFactoryConfigurator cfg) + internal static void ConfigureCustomerMessagesTopology(this IRabbitMqBusFactoryConfigurator cfg) { // https://masstransit.io/documentation/transports/rabbitmq cfg.Message>(e => { - // name of the `primary exchange` for type based message publishing and sending - e.SetEntityName($"{nameof(CustomerCreatedV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}"); + // https://masstransit.io/documentation/configuration/topology/message + // Name of the `primary exchange` for type based message publishing and sending + // e.SetEntityName($"{nameof(CustomerCreatedV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}"); + e.SetEntityNameFormatter(new CustomEntityNameFormatter>()); }); // configuration for MessagePublishTopologyConfiguration and using IPublishEndpoint diff --git a/src/Services/Customers/FoodDelivery.Services.Customers/Products/MassTransitExtensions.cs b/src/Services/Customers/FoodDelivery.Services.Customers/Products/MassTransitExtensions.cs index 182e60e4..c53b9719 100644 --- a/src/Services/Customers/FoodDelivery.Services.Customers/Products/MassTransitExtensions.cs +++ b/src/Services/Customers/FoodDelivery.Services.Customers/Products/MassTransitExtensions.cs @@ -10,7 +10,10 @@ namespace FoodDelivery.Services.Customers.Products; internal static class MassTransitExtensions { - internal static void AddProductEndpoints(this IRabbitMqBusFactoryConfigurator cfg, IBusRegistrationContext context) + internal static void ConfigureProductMessagesTopology( + this IRabbitMqBusFactoryConfigurator cfg, + IBusRegistrationContext context + ) { // we configured some shared settings for all receive endpoint message in masstransit consuming topologies @@ -33,11 +36,12 @@ internal static void AddProductEndpoints(this IRabbitMqBusFactoryConfigurator cf // re.SetQuorumQueue(); // // // with setting `ConfigureConsumeTopology` to `false`, we should create `primary exchange` and its bounded exchange manually with using `re.Bind` otherwise with `ConfigureConsumeTopology=true` it get publish topology for message type `T` with `_publishTopology.GetMessageTopology()` and use its ExchangeType and ExchangeName based ofo default EntityFormatter - // re.ConfigureConsumeTopology = false; + // // indicate whether the topic or exchange for the message type should be created and subscribed to the queue when consumed on a reception endpoint. + // re.ConfigureConsumeTopology = false; // // // https://spring.io/blog/2011/04/01/routing-topologies-for-performance-and-scalability-with-rabbitmq // // masstransit uses `wire-tapping` pattern for defining exchanges. Primary exchange will send the message to intermediary fanout exchange - // // setup primary exchange and its type + // // setup primary exchange and its type from message type and receive-endpoint formatter // re.Bind>(e => // { // e.RoutingKey = nameof(ProductStockReplenishedV1).Underscore(); diff --git a/src/Services/Customers/FoodDelivery.Services.Customers/RestockSubscriptions/MassTransitExtensions.cs b/src/Services/Customers/FoodDelivery.Services.Customers/RestockSubscriptions/MassTransitExtensions.cs index bf5f9a69..f0b2a6db 100644 --- a/src/Services/Customers/FoodDelivery.Services.Customers/RestockSubscriptions/MassTransitExtensions.cs +++ b/src/Services/Customers/FoodDelivery.Services.Customers/RestockSubscriptions/MassTransitExtensions.cs @@ -1,5 +1,6 @@ using BuildingBlocks.Abstractions.Events; using BuildingBlocks.Core.Messaging; +using BuildingBlocks.Integration.MassTransit; using FoodDelivery.Services.Shared.Customers.RestockSubscriptions.Events.V1.Integration; using Humanizer; using MassTransit; @@ -8,16 +9,18 @@ namespace FoodDelivery.Services.Customers.RestockSubscriptions; internal static class MassTransitExtensions { - internal static void AddRestockSubscriptionPublishers(this IRabbitMqBusFactoryConfigurator cfg) + internal static void ConfigureRestockSubscriptionMessagesTopology(this IRabbitMqBusFactoryConfigurator cfg) { cfg.Message>(e => { // we configured some shared settings for all publish message in masstransit publish topologies // name of the primary exchange - e.SetEntityName( - $"{nameof(RestockSubscriptionCreatedV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}" - ); + // https://masstransit.io/documentation/configuration/topology/message + // e.SetEntityName( + // $"{nameof(RestockSubscriptionCreatedV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}" + // ); + e.SetEntityNameFormatter(new CustomEntityNameFormatter>()); }); cfg.Publish>(e => diff --git a/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Data/Extensions/WebApplicationBuilderExtensions.DependencyInjection.cs b/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Data/Extensions/WebApplicationBuilderExtensions.DependencyInjection.cs index abe18ee2..ece57253 100644 --- a/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Data/Extensions/WebApplicationBuilderExtensions.DependencyInjection.cs +++ b/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Data/Extensions/WebApplicationBuilderExtensions.DependencyInjection.cs @@ -35,9 +35,6 @@ private static void AddPostgresWriteStorage(IServiceCollection services, IConfig else { services.AddPostgresDbContext(configuration); - - // add migrations and seeders dependencies, or we could add seeders inner each modules - services.TryAddScoped(); services.TryAddScoped(); } diff --git a/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Extensions/WebApplicationBuilderExtensions/WebApplicationBuilderExtensions.Infrastructure.cs b/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Extensions/WebApplicationBuilderExtensions/WebApplicationBuilderExtensions.Infrastructure.cs index 17bc609d..68a33b63 100644 --- a/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Extensions/WebApplicationBuilderExtensions/WebApplicationBuilderExtensions.Infrastructure.cs +++ b/src/Services/Customers/FoodDelivery.Services.Customers/Shared/Extensions/WebApplicationBuilderExtensions/WebApplicationBuilderExtensions.Infrastructure.cs @@ -136,13 +136,13 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.AddCustomRateLimit(); builder.AddCustomMassTransit( - configureReceiveEndpoints: (context, cfg) => + configureMessagesTopologies: (context, cfg) => { - cfg.AddUsersEndpoints(context); - cfg.AddProductEndpoints(context); + cfg.ConfigureUsersMessagesTopology(context); + cfg.ConfigureProductMessagesTopology(context); - cfg.AddCustomerPublishers(); - cfg.AddRestockSubscriptionPublishers(); + cfg.ConfigureCustomerMessagesTopology(); + cfg.ConfigureRestockSubscriptionMessagesTopology(); }, configureMessagingOptions: msgCfg => { diff --git a/src/Services/Customers/FoodDelivery.Services.Customers/Users/MassTransitExtensions.cs b/src/Services/Customers/FoodDelivery.Services.Customers/Users/MassTransitExtensions.cs index 4f54b506..d0e7c6c3 100644 --- a/src/Services/Customers/FoodDelivery.Services.Customers/Users/MassTransitExtensions.cs +++ b/src/Services/Customers/FoodDelivery.Services.Customers/Users/MassTransitExtensions.cs @@ -9,7 +9,10 @@ namespace FoodDelivery.Services.Customers.Users; internal static class MassTransitExtensions { - internal static void AddUsersEndpoints(this IRabbitMqBusFactoryConfigurator cfg, IBusRegistrationContext context) + internal static void ConfigureUsersMessagesTopology( + this IRabbitMqBusFactoryConfigurator cfg, + IBusRegistrationContext context + ) { // we configured some shared settings for all publish message in masstransit publish topologies @@ -32,11 +35,12 @@ internal static void AddUsersEndpoints(this IRabbitMqBusFactoryConfigurator cfg, // re.SetQuorumQueue(); // // // with setting `ConfigureConsumeTopology` to `false`, we should create `primary exchange` and its bounded exchange manually with using `re.Bind` otherwise with `ConfigureConsumeTopology=true` it get publish topology for message type `T` with `_publishTopology.GetMessageTopology()` and use its ExchangeType and ExchangeName based ofo default EntityFormatter + // // indicate whether the topic or exchange for the message type should be created and subscribed to the queue when consumed on a reception endpoint. // re.ConfigureConsumeTopology = true; // // // // https://spring.io/blog/2011/04/01/routing-topologies-for-performance-and-scalability-with-rabbitmq // // // masstransit uses `wire-tapping` pattern for defining exchanges. Primary exchange will send the message to intermediary fanout exchange - // // // setup primary exchange and its type + // // // setup primary exchange and its type from message type and receive-endpoint formatter // // re.Bind>(e => // // { // // e.RoutingKey = nameof(UserRegisteredV1).Underscore(); diff --git a/src/Services/Identity/FoodDelivery.Services.Identity/Identity/Data/IdentityDataSeeder.cs b/src/Services/Identity/FoodDelivery.Services.Identity/Identity/Data/IdentityDataSeeder.cs index a3aadb42..fb8c3c14 100644 --- a/src/Services/Identity/FoodDelivery.Services.Identity/Identity/Data/IdentityDataSeeder.cs +++ b/src/Services/Identity/FoodDelivery.Services.Identity/Identity/Data/IdentityDataSeeder.cs @@ -4,17 +4,9 @@ namespace FoodDelivery.Services.Identity.Identity.Data; -public class IdentityDataSeeder : IDataSeeder +public class IdentityDataSeeder(UserManager userManager, RoleManager roleManager) + : IDataSeeder { - private readonly RoleManager _roleManager; - private readonly UserManager _userManager; - - public IdentityDataSeeder(UserManager userManager, RoleManager roleManager) - { - _userManager = userManager; - _roleManager = roleManager; - } - public async Task SeedAllAsync() { await SeedRoles(); @@ -25,16 +17,16 @@ public async Task SeedAllAsync() private async Task SeedRoles() { - if (!await _roleManager.RoleExistsAsync(ApplicationRole.Admin.Name)) - await _roleManager.CreateAsync(ApplicationRole.Admin); + if (!await roleManager.RoleExistsAsync(ApplicationRole.Admin.Name)) + await roleManager.CreateAsync(ApplicationRole.Admin); - if (!await _roleManager.RoleExistsAsync(ApplicationRole.User.Name)) - await _roleManager.CreateAsync(ApplicationRole.User); + if (!await roleManager.RoleExistsAsync(ApplicationRole.User.Name)) + await roleManager.CreateAsync(ApplicationRole.User); } private async Task SeedUsers() { - if (await _userManager.FindByEmailAsync("mehdi@test.com") == null) + if (await userManager.FindByEmailAsync("mehdi@test.com") == null) { var user = new ApplicationUser { @@ -44,13 +36,13 @@ private async Task SeedUsers() Email = "mehdi@test.com", }; - var result = await _userManager.CreateAsync(user, "123456"); + var result = await userManager.CreateAsync(user, "123456"); if (result.Succeeded) - await _userManager.AddToRoleAsync(user, ApplicationRole.Admin.Name); + await userManager.AddToRoleAsync(user, ApplicationRole.Admin.Name); } - if (await _userManager.FindByEmailAsync("mehdi2@test.com") == null) + if (await userManager.FindByEmailAsync("mehdi2@test.com") == null) { var user = new ApplicationUser { @@ -60,10 +52,10 @@ private async Task SeedUsers() Email = "mehdi2@test.com" }; - var result = await _userManager.CreateAsync(user, "123456"); + var result = await userManager.CreateAsync(user, "123456"); if (result.Succeeded) - await _userManager.AddToRoleAsync(user, ApplicationRole.User.Name); + await userManager.AddToRoleAsync(user, ApplicationRole.User.Name); } } } diff --git a/src/Services/Identity/FoodDelivery.Services.Identity/Identity/IdentityConfigs.cs b/src/Services/Identity/FoodDelivery.Services.Identity/Identity/IdentityConfigs.cs index a8f43039..a485040f 100644 --- a/src/Services/Identity/FoodDelivery.Services.Identity/Identity/IdentityConfigs.cs +++ b/src/Services/Identity/FoodDelivery.Services.Identity/Identity/IdentityConfigs.cs @@ -28,8 +28,6 @@ public WebApplicationBuilder AddModuleServices(WebApplicationBuilder builder) { builder.AddCustomIdentity(builder.Configuration); - builder.Services.TryAddScoped(); - if (builder.Environment.IsTest() == false) builder.AddCustomIdentityServer(); diff --git a/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Data/IdentityDataSeeder.cs b/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Data/IdentityDataSeeder.cs deleted file mode 100644 index 9fc30de1..00000000 --- a/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Data/IdentityDataSeeder.cs +++ /dev/null @@ -1,13 +0,0 @@ -using BuildingBlocks.Abstractions.Persistence; - -namespace FoodDelivery.Services.Identity.Shared.Data; - -public class IdentityDataSeeder : IDataSeeder -{ - public int Order => 2; - - public Task SeedAllAsync() - { - return Task.CompletedTask; - } -} diff --git a/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Identity.cs b/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Identity.cs index 67951adf..1d36e953 100644 --- a/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Identity.cs +++ b/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Identity.cs @@ -36,8 +36,6 @@ public static WebApplicationBuilder AddCustomIdentity( // Postgres builder.Services.AddPostgresDbContext(configuration); - // add migrations and seeders dependencies, or we could add seeders inner each modules - builder.Services.TryAddScoped(); builder.Services.TryAddScoped(); } diff --git a/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs b/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs index 4082fb44..256e55d7 100644 --- a/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs +++ b/src/Services/Identity/FoodDelivery.Services.Identity/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs @@ -114,9 +114,9 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.AddCustomRateLimit(); builder.AddCustomMassTransit( - configureReceiveEndpoints: (context, cfg) => + configureMessagesTopologies: (context, cfg) => { - cfg.AddUserPublishers(); + cfg.ConfigureUserMessagesTopology(); }, configureMessagingOptions: msgCfg => { diff --git a/src/Services/Identity/FoodDelivery.Services.Identity/Users/Features/RegisteringUser/v1/RegisterUser.cs b/src/Services/Identity/FoodDelivery.Services.Identity/Users/Features/RegisteringUser/v1/RegisterUser.cs index bb8cd572..caec97ce 100644 --- a/src/Services/Identity/FoodDelivery.Services.Identity/Users/Features/RegisteringUser/v1/RegisterUser.cs +++ b/src/Services/Identity/FoodDelivery.Services.Identity/Users/Features/RegisteringUser/v1/RegisterUser.cs @@ -68,8 +68,8 @@ public RegisterUserValidator() .WithMessage("Phone Number is required.") .MinimumLength(7) .WithMessage("PhoneNumber must not be less than 7 characters.") - .MaximumLength(15) - .WithMessage("PhoneNumber must not exceed 15 characters."); + .MaximumLength(20) + .WithMessage("PhoneNumber must not exceed 20 characters."); RuleFor(v => v.ConfirmPassword) .Equal(x => x.Password) .WithMessage("The password and confirmation password do not match.") diff --git a/src/Services/Identity/FoodDelivery.Services.Identity/Users/MassTransitExtensions.cs b/src/Services/Identity/FoodDelivery.Services.Identity/Users/MassTransitExtensions.cs index d14610ca..cc79bdbe 100644 --- a/src/Services/Identity/FoodDelivery.Services.Identity/Users/MassTransitExtensions.cs +++ b/src/Services/Identity/FoodDelivery.Services.Identity/Users/MassTransitExtensions.cs @@ -1,26 +1,61 @@ +using BuildingBlocks.Abstractions.Events; +using BuildingBlocks.Integration.MassTransit; using FoodDelivery.Services.Identity.Users.Features.UpdatingUserState.v1.Events.Integration; using FoodDelivery.Services.Shared.Identity.Users.Events.V1.Integration; using Humanizer; using MassTransit; -using RabbitMQ.Client; namespace FoodDelivery.Services.Identity.Users; internal static class MassTransitExtensions { - internal static void AddUserPublishers(this IRabbitMqBusFactoryConfigurator cfg) + internal static void ConfigureUserMessagesTopology(this IRabbitMqBusFactoryConfigurator cfg) { - cfg.Message(e => e.SetEntityName($"{nameof(UserRegisteredV1).Underscore()}.input_exchange")); // name of the primary exchange - cfg.Publish(e => e.ExchangeType = ExchangeType.Direct); // primary exchange type - cfg.Send(e => + // https://masstransit.io/documentation/transports/rabbitmq + cfg.Message>(e => + { + // https://masstransit.io/documentation/configuration/topology/message + // Name of the `primary exchange` for type based message publishing and sending + // e.SetEntityName($"{nameof(UserRegisteredV1).Underscore()}{MessagingConstants.PrimaryExchangePostfix}"); + e.SetEntityNameFormatter(new CustomEntityNameFormatter>()); + }); + + // configuration for MessagePublishTopologyConfiguration and using IPublishEndpoint + cfg.Publish>(e => + { + // we configured some shared settings for all publish message in masstransit publish topologies + + // // setup primary exchange + // e.Durable = true; + // e.ExchangeType = ExchangeType.Direct; + }); + + // configuration for MessageSendTopologyConfiguration and using ISendEndpointProvider + cfg.Send>(e => { // route by message type to binding fanout exchange (exchange to exchange binding) e.UseRoutingKeyFormatter(context => context.Message.GetType().Name.Underscore()); }); - cfg.Message(e => e.SetEntityName($"{nameof(UserStateUpdated).Underscore()}.input_exchange")); // name of the primary exchange - cfg.Publish(e => e.ExchangeType = ExchangeType.Direct); // primary exchange type - cfg.Send(e => + cfg.Message>(e => + { + // https://masstransit.io/documentation/configuration/topology/message + // Name of the `primary exchange` for type based message publishing and sending + // e.SetEntityName($"{nameof(UserStateUpdated).Underscore()}{MessagingConstants.PrimaryExchangePostfix}"); + e.SetEntityNameFormatter(new CustomEntityNameFormatter>()); + }); + + cfg.Publish>(e => + { + // we configured some shared settings for all publish message in masstransit publish topologies + + // // setup primary exchange + // e.Durable = true; + // e.ExchangeType = ExchangeType.Direct; + }); + + // configuration for MessageSendTopologyConfiguration and using ISendEndpointProvider + cfg.Send>(e => { // route by message type to binding fanout exchange (exchange to exchange binding) e.UseRoutingKeyFormatter(context => context.Message.GetType().Name.Underscore()); diff --git a/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs b/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs index e37bd2b2..4f2b698e 100644 --- a/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs +++ b/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Infrastructure.cs @@ -134,7 +134,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder }); builder.AddCustomMassTransit( - configureReceiveEndpoints: (context, cfg) => + configureMessagesTopologies: (context, cfg) => { cfg.AddCustomerEndpoints(context); }, diff --git a/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs b/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs index 7cd12501..35b9f083 100644 --- a/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs +++ b/src/Services/Orders/FoodDelivery.Services.Orders/Shared/Extensions/WebApplicationBuilderExtensions/Persistence.cs @@ -34,8 +34,6 @@ private static void AddPostgresWriteStorage(IServiceCollection services, IConfig { services.AddPostgresDbContext(configuration); - // add migrations and seeders dependencies, or we could add seeders inner each modules - services.TryAddScoped(); services.TryAddScoped(); } diff --git a/tests/Services/Customers/FoodDelivery.Services.Customers.EndToEndTests/CustomerServiceEndToEndTestBase.cs b/tests/Services/Customers/FoodDelivery.Services.Customers.EndToEndTests/CustomerServiceEndToEndTestBase.cs index b4af2d46..15bab4ec 100644 --- a/tests/Services/Customers/FoodDelivery.Services.Customers.EndToEndTests/CustomerServiceEndToEndTestBase.cs +++ b/tests/Services/Customers/FoodDelivery.Services.Customers.EndToEndTests/CustomerServiceEndToEndTestBase.cs @@ -75,10 +75,4 @@ ITestOutputHelper outputHelper // identityApiOptions.Value.BaseApiAddress = MockServersFixture.IdentityServiceMock.Url!; // catalogApiOptions.Value.BaseApiAddress = MockServersFixture.CatalogsServiceMock.Url!; } - - protected override void RegisterTestConfigureServices(IServiceCollection services) - { - //// here we use same data seeder of service but if we need different data seeder for test for can replace it - // services.ReplaceScoped(); - } } diff --git a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/CustomerServiceIntegrationTestBase.cs b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/CustomerServiceIntegrationTestBase.cs index 58b20ca4..06323605 100644 --- a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/CustomerServiceIntegrationTestBase.cs +++ b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/CustomerServiceIntegrationTestBase.cs @@ -74,10 +74,4 @@ ITestOutputHelper outputHelper // identityApiOptions.Value.BaseApiAddress = MockServersFixture.IdentityServiceMock.Url!; // catalogApiOptions.Value.BaseApiAddress = MockServersFixture.CatalogsServiceMock.Url!; } - - protected override void RegisterTestConfigureServices(IServiceCollection services) - { - //// here we use same data seeder of service but if we need different data seeder for test for can replace it - // services.ReplaceScoped(); - } } diff --git a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerByCustomerId/v1/GetCustomerByCustomerIdTests.cs b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerByCustomerId/v1/GetCustomerByCustomerIdTests.cs index 3d0b1454..a5621156 100644 --- a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerByCustomerId/v1/GetCustomerByCustomerIdTests.cs +++ b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerByCustomerId/v1/GetCustomerByCustomerIdTests.cs @@ -12,14 +12,11 @@ namespace FoodDelivery.Services.Customers.IntegrationTests.Customers.Features.GettingCustomerByCustomerId.v1; -public class GetCustomerByCustomerIdTests : CustomerServiceIntegrationTestBase +public class GetCustomerByCustomerIdTests( + SharedFixtureWithEfCoreAndMongo sharedFixture, + ITestOutputHelper outputHelper +) : CustomerServiceIntegrationTestBase(sharedFixture, outputHelper) { - public GetCustomerByCustomerIdTests( - SharedFixtureWithEfCoreAndMongo sharedFixture, - ITestOutputHelper outputHelper - ) - : base(sharedFixture, outputHelper) { } - [Fact] [CategoryTrait(TestCategory.Integration)] internal async Task can_returns_valid_read_customer_model() diff --git a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerById/v1/GetCustomerByIdTests.cs b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerById/v1/GetCustomerByIdTests.cs index 429856d1..afdd3659 100644 --- a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerById/v1/GetCustomerByIdTests.cs +++ b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomerById/v1/GetCustomerByIdTests.cs @@ -11,14 +11,11 @@ namespace FoodDelivery.Services.Customers.IntegrationTests.Customers.Features.GettingCustomerById.v1; -public class GetCustomerByIdTests : CustomerServiceIntegrationTestBase +public class GetCustomerByIdTests( + SharedFixtureWithEfCoreAndMongo sharedFixture, + ITestOutputHelper outputHelper +) : CustomerServiceIntegrationTestBase(sharedFixture, outputHelper) { - public GetCustomerByIdTests( - SharedFixtureWithEfCoreAndMongo sharedFixture, - ITestOutputHelper outputHelper - ) - : base(sharedFixture, outputHelper) { } - [Fact] [CategoryTrait(TestCategory.Integration)] internal async Task can_returns_valid_read_customer_model() diff --git a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomers/v1/GetCustomersTests.cs b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomers/v1/GetCustomersTests.cs index 7d05bbf4..66e688a8 100644 --- a/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomers/v1/GetCustomersTests.cs +++ b/tests/Services/Customers/FoodDelivery.Services.Customers.IntegrationTests/Customers/Features/GettingCustomers/v1/GetCustomersTests.cs @@ -9,14 +9,11 @@ namespace FoodDelivery.Services.Customers.IntegrationTests.Customers.Features.GettingCustomers.v1; -public class GetCustomersTests : CustomerServiceIntegrationTestBase +public class GetCustomersTests( + SharedFixtureWithEfCoreAndMongo sharedFixture, + ITestOutputHelper outputHelper +) : CustomerServiceIntegrationTestBase(sharedFixture, outputHelper) { - public GetCustomersTests( - SharedFixtureWithEfCoreAndMongo sharedFixture, - ITestOutputHelper outputHelper - ) - : base(sharedFixture, outputHelper) { } - [Fact] [CategoryTrait(TestCategory.Integration)] internal async Task can_get_existing_customers_list_from_db() diff --git a/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityServiceIntegrationTestBase.cs b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityServiceIntegrationTestBase.cs new file mode 100644 index 00000000..d4df8048 --- /dev/null +++ b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityServiceIntegrationTestBase.cs @@ -0,0 +1,18 @@ +using FoodDelivery.Services.Identity.Api; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Tests.Shared.Fixtures; +using Tests.Shared.TestBase; +using Xunit.Abstractions; + +namespace FoodDelivery.Services.Identity.IntegrationTests; + +//https://stackoverflow.com/questions/43082094/use-multiple-collectionfixture-on-my-test-class-in-xunit-2-x +// note: each class could have only one collection +[Collection(IntegrationTestCollection.Name)] +public class IdentityServiceIntegrationTestBase( + SharedFixtureWithEfCore sharedFixture, + ITestOutputHelper outputHelper +) : IntegrationTestBase(sharedFixture, outputHelper) +{ + // We don't need to inject `CustomersServiceMockServersFixture` class fixture in the constructor because it initialized by `collection fixture` and its static properties are accessible in the codes +} diff --git a/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityTestSeeder.cs b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityTestSeeder.cs new file mode 100644 index 00000000..41b9f10a --- /dev/null +++ b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IdentityTestSeeder.cs @@ -0,0 +1,45 @@ +using BuildingBlocks.Abstractions.Persistence; +using FoodDelivery.Services.Identity.Shared.Models; +using Microsoft.AspNetCore.Identity; + +namespace FoodDelivery.Services.Identity.IntegrationTests; + +public class IdentityTestSeeder(UserManager userManager, RoleManager roleManager) + : ITestDataSeeder +{ + public int Order => 1; + + public async Task SeedAllAsync() + { + await SeedRoles(); + await SeedUsers(); + } + + private async Task SeedRoles() + { + if (!await roleManager.RoleExistsAsync(ApplicationRole.Admin.Name)) + await roleManager.CreateAsync(ApplicationRole.Admin); + + if (!await roleManager.RoleExistsAsync(ApplicationRole.User.Name)) + await roleManager.CreateAsync(ApplicationRole.User); + } + + private async Task SeedUsers() + { + if (await userManager.FindByEmailAsync("mehdi@test.com") == null) + { + var user = new ApplicationUser + { + UserName = "mehdi", + FirstName = "Mehdi", + LastName = "test", + Email = "mehdi@test.com", + }; + + var result = await userManager.CreateAsync(user, "123456"); + + if (result.Succeeded) + await userManager.AddToRoleAsync(user, ApplicationRole.Admin.Name); + } + } +} diff --git a/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IntegrationTestCollection.cs b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IntegrationTestCollection.cs new file mode 100644 index 00000000..84869951 --- /dev/null +++ b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/IntegrationTestCollection.cs @@ -0,0 +1,14 @@ +using FoodDelivery.Services.Identity.Api; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Tests.Shared.Fixtures; + +namespace FoodDelivery.Services.Identity.IntegrationTests; + +// https://stackoverflow.com/questions/43082094/use-multiple-collectionfixture-on-my-test-class-in-xunit-2-x +// note: each class could have only one collection, but it can implement multiple ICollectionFixture in its definitions +[CollectionDefinition(Name)] +public class IntegrationTestCollection + : ICollectionFixture> +{ + public const string Name = "Integration Test"; +} diff --git a/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/Users/Features/RegisteringUser/v1/RegisterUserTests.cs b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/Users/Features/RegisteringUser/v1/RegisterUserTests.cs index ba05eb83..97d9275d 100644 --- a/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/Users/Features/RegisteringUser/v1/RegisterUserTests.cs +++ b/tests/Services/Identity/FoodDelivery.Services.Identity.IntegrationTests/Users/Features/RegisteringUser/v1/RegisterUserTests.cs @@ -1,59 +1,64 @@ +using Bogus; +using FluentAssertions; +using FoodDelivery.Services.Identity.Api; +using FoodDelivery.Services.Identity.Users.Features.GettingUserById.v1; +using FoodDelivery.Services.Identity.Users.Features.RegisteringUser.v1; +using FoodDelivery.Services.Shared.Identity.Users.Events.V1.Integration; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Tests.Shared.Fixtures; +using Xunit.Abstractions; + namespace FoodDelivery.Services.Identity.IntegrationTests.Users.Features.RegisteringUser.v1; +public class RegisterUserTests : IdentityServiceIntegrationTestBase +{ + private static RegisterUser _registerUser = default!; + + public RegisterUserTests( + SharedFixtureWithEfCore sharedFixture, + ITestOutputHelper outputHelper + ) + : base(sharedFixture, outputHelper) + { + // Arrange + _registerUser = new Faker() + .CustomInstantiator(faker => new RegisterUser( + faker.Person.FirstName, + faker.Person.LastName, + faker.Person.UserName, + faker.Person.Email, + faker.Phone.PhoneNumber(), + "123456", + "123456" + )) + .Generate(); + } + + [Fact] + public async Task register_new_user_command_should_persist_new_user_in_db() + { + // Act + var result = await SharedFixture.SendAsync(_registerUser, CancellationToken); + + // Assert + result.UserIdentity.Should().NotBeNull(); + + // var user = await IdentityModule.FindWriteAsync(result.UserIdentity.InternalCommandId); + // user.Should().NotBeNull(); + + var userByIdResponse = await SharedFixture.QueryAsync(new GetUserById(result.UserIdentity!.Id)); + userByIdResponse.IdentityUser.Should().NotBeNull(); + userByIdResponse.IdentityUser.Id.Should().Be(result.UserIdentity.Id); + } + [Fact] + public async Task register_new_user_command_should_publish_message_to_broker() + { + // Act + await SharedFixture.SendAsync(_registerUser, CancellationToken); -// public class RegisterUserTests : IntegrationTestBase -// { -// private static RegisterUser _registerUser; -// -// public RegisterUserTests(IntegrationTestFixture integrationTestFixture, -// ITestOutputHelper outputHelper) : base(integrationTestFixture, outputHelper) -// { -// // Arrange -// _registerUser = new Faker().CustomInstantiator(faker => -// new RegisterUser( -// faker.Person.FirstName, -// faker.Person.LastName, -// faker.Person.UserName, -// faker.Person.Email, -// "123456", -// "123456")) -// .Generate(); -// } -// -// protected override void RegisterTestsServices(IServiceCollection services) -// { -// base.RegisterTestsServices(services); -// // services.ReplaceScoped(); -// } -// -// [Fact] -// public async Task register_new_user_command_should_persist_new_user_in_db() -// { -// // Act -// var result = await IntegrationTestFixture.SendAsync(_registerUser, CancellationToken); -// -// // Assert -// result.UserIdentity.Should().NotBeNull(); -// -// // var user = await IdentityModule.FindWriteAsync(result.UserIdentity.InternalCommandId); -// // user.Should().NotBeNull(); -// -// var userByIdResponse = -// await IntegrationTestFixture.QueryAsync(new GetUserById(result.UserIdentity.Id)); -// -// userByIdResponse.IdentityUser.Should().NotBeNull(); -// userByIdResponse.IdentityUser.Id.Should().Be(result.UserIdentity.Id); -// } -// -// [Fact] -// public async Task register_new_user_command_should_publish_message_to_broker() -// { -// // Act -// await IntegrationTestFixture.SendAsync(_registerUser, CancellationToken); -// -// // Assert -// await IntegrationTestFixture.WaitForPublishing(); -// } -// } + // Assert + await SharedFixture.WaitForPublishing(); + } +} diff --git a/tests/Shared/Tests.Shared/Factory/CustomWebApplicationFactory.cs b/tests/Shared/Tests.Shared/Factory/CustomWebApplicationFactory.cs index de651a4f..396d3319 100644 --- a/tests/Shared/Tests.Shared/Factory/CustomWebApplicationFactory.cs +++ b/tests/Shared/Tests.Shared/Factory/CustomWebApplicationFactory.cs @@ -1,3 +1,5 @@ +using System.Reflection; +using BuildingBlocks.Core.Persistence.Extensions; using BuildingBlocks.Core.Web.Extensions; using BuildingBlocks.Security.Jwt; using Microsoft.AspNetCore.Authentication; diff --git a/tests/Shared/Tests.Shared/Fixtures/SharedFixture.cs b/tests/Shared/Tests.Shared/Fixtures/SharedFixture.cs index 73bd8e9c..50771a53 100644 --- a/tests/Shared/Tests.Shared/Fixtures/SharedFixture.cs +++ b/tests/Shared/Tests.Shared/Fixtures/SharedFixture.cs @@ -1,4 +1,5 @@ using System.Net.Http.Headers; +using System.Reflection; using System.Security.Claims; using AutoBogus; using BuildingBlocks.Abstractions.Commands; @@ -9,6 +10,7 @@ using BuildingBlocks.Core.Events; using BuildingBlocks.Core.Extensions; using BuildingBlocks.Core.Messaging.MessagePersistence; +using BuildingBlocks.Core.Persistence.Extensions; using BuildingBlocks.Core.Types; using BuildingBlocks.Integration.MassTransit; using BuildingBlocks.Persistence.EfCore.Postgres; diff --git a/tests/Shared/Tests.Shared/TestBase/EndToEndTestBase.cs b/tests/Shared/Tests.Shared/TestBase/EndToEndTestBase.cs index 7a79a66f..3fe632f9 100644 --- a/tests/Shared/Tests.Shared/TestBase/EndToEndTestBase.cs +++ b/tests/Shared/Tests.Shared/TestBase/EndToEndTestBase.cs @@ -5,42 +5,28 @@ namespace Tests.Shared.Fixtures; -public class EndToEndTestTest : IntegrationTest - where TEntryPoint : class -{ - public EndToEndTestTest(SharedFixture sharedFixture, ITestOutputHelper outputHelper) - : base(sharedFixture, outputHelper) { } -} +public class EndToEndTestTest(SharedFixture sharedFixture, ITestOutputHelper outputHelper) + : IntegrationTest(sharedFixture, outputHelper) + where TEntryPoint : class; -public abstract class EndToEndTestTestBase : EndToEndTestTest +public abstract class EndToEndTestTestBase( + SharedFixtureWithEfCore sharedFixture, + ITestOutputHelper outputHelper +) : EndToEndTestTest(sharedFixture, outputHelper) where TEntryPoint : class where TContext : DbContext { - protected EndToEndTestTestBase( - SharedFixtureWithEfCore sharedFixture, - ITestOutputHelper outputHelper - ) - : base(sharedFixture, outputHelper) - { - SharedFixture = sharedFixture; - } - - public new SharedFixtureWithEfCore SharedFixture { get; } + public new SharedFixtureWithEfCore SharedFixture { get; } = sharedFixture; } -public abstract class EndToEndTestTestBase : EndToEndTestTest +public abstract class EndToEndTestTestBase( + SharedFixtureWithEfCoreAndMongo sharedFixture, + ITestOutputHelper outputHelper +) : EndToEndTestTest(sharedFixture, outputHelper) where TEntryPoint : class where TWContext : DbContext where TRContext : MongoDbContext { - protected EndToEndTestTestBase( - SharedFixtureWithEfCoreAndMongo sharedFixture, - ITestOutputHelper outputHelper - ) - : base(sharedFixture, outputHelper) - { - SharedFixture = sharedFixture; - } - - public new SharedFixtureWithEfCoreAndMongo SharedFixture { get; } + public new SharedFixtureWithEfCoreAndMongo SharedFixture { get; } = + sharedFixture; } diff --git a/tests/Shared/Tests.Shared/TestBase/IntegrationTestBase.cs b/tests/Shared/Tests.Shared/TestBase/IntegrationTestBase.cs index 966fd147..bcbb2104 100644 --- a/tests/Shared/Tests.Shared/TestBase/IntegrationTestBase.cs +++ b/tests/Shared/Tests.Shared/TestBase/IntegrationTestBase.cs @@ -1,11 +1,10 @@ -using BuildingBlocks.Abstractions.Persistence; +using BuildingBlocks.Core.Persistence.Extensions; using BuildingBlocks.Persistence.Mongo; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Tests.Shared.Fixtures; -using Tests.Shared.XunitCategories; namespace Tests.Shared.TestBase; @@ -34,6 +33,10 @@ protected IntegrationTest(SharedFixture sharedFixture, ITestOutputH CancellationTokenSource = new(TimeSpan.FromSeconds(Timeout)); CancellationToken.ThrowIfCancellationRequested(); + SharedFixture.ConfigureTestServices(services => + { + services.RegisterDataSeeders([GetType().Assembly]); + }); SharedFixture.ConfigureTestServices(RegisterTestConfigureServices); SharedFixture.ConfigureTestConfigureApp( @@ -45,10 +48,7 @@ protected IntegrationTest(SharedFixture sharedFixture, ITestOutputH } // we use IAsyncLifetime in xunit instead of constructor when we have async operation - public virtual async Task InitializeAsync() - { - await RunSeedAndMigrationAsync(); - } + public virtual async Task InitializeAsync() { } public virtual async Task DisposeAsync() { @@ -61,31 +61,6 @@ public virtual async Task DisposeAsync() Scope.Dispose(); } - private async Task RunSeedAndMigrationAsync() - { - var migrations = Scope.ServiceProvider.GetServices(); - var seeders = Scope.ServiceProvider.GetServices(); - - if (!SharedFixture.AlreadyMigrated) - { - foreach (var migration in migrations) - { - SharedFixture.Logger.Information("Migration '{Migration}' started...", migrations.GetType().Name); - await migration.ExecuteAsync(CancellationToken); - SharedFixture.Logger.Information("Migration '{Migration}' ended...", migration.GetType().Name); - } - - SharedFixture.AlreadyMigrated = true; - } - - foreach (var seeder in seeders) - { - SharedFixture.Logger.Information("Seeder '{Seeder}' started...", seeder.GetType().Name); - await seeder.SeedAllAsync(); - SharedFixture.Logger.Information("Seeder '{Seeder}' ended...", seeder.GetType().Name); - } - } - protected virtual void RegisterTestConfigureServices(IServiceCollection services) { } protected virtual void RegisterTestAppConfigurations(