diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index 4a88a42..62a34d3 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -72,7 +72,7 @@ builder.Services.AddLogging(x => x.AddSimpleConsole(console => console.IncludeScopes = true; })); -if (builder.Environment.IsProduction()) +if (builder.Environment.IsProduction() || !string.IsNullOrWhiteSpace(builder.Configuration["RedisHostName"])) { builder.Services.AddStackExchangeRedisCache(options => { @@ -95,12 +95,8 @@ using (var scope = app.Services.CreateScope()) var domainStoreDbContext = services.GetRequiredService(); appDbContext.EnsureMigrationsApplied(); - - if (app.Environment.EnvironmentName != "Integration") - { - storeDbContext.EnsureMigrationsApplied(); - domainStoreDbContext.EnsureMigrationsApplied(); - } + storeDbContext.EnsureMigrationsApplied(); + domainStoreDbContext.EnsureMigrationsApplied(); } if (app.Environment.IsDevelopment()) diff --git a/CleanArchitecture.Api/appsettings.Development.json b/CleanArchitecture.Api/appsettings.Development.json index 3506cff..342ecd0 100644 --- a/CleanArchitecture.Api/appsettings.Development.json +++ b/CleanArchitecture.Api/appsettings.Development.json @@ -13,6 +13,7 @@ "Audience": "CleanArchitectureClient", "Secret": "sD3v061gf8BxXgmxcHssasjdlkasjd87439284)@#(*" }, + "RedisHostName": "", "RabbitMQ": { "Host": "localhost", "Username": "guest", diff --git a/CleanArchitecture.Api/appsettings.Integration.json b/CleanArchitecture.Api/appsettings.Integration.json index 1913b40..973f184 100644 --- a/CleanArchitecture.Api/appsettings.Integration.json +++ b/CleanArchitecture.Api/appsettings.Integration.json @@ -5,6 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, + "RedisHostName": "", "RabbitMQ": { "Host": "localhost", "Username": "guest", diff --git a/CleanArchitecture.IntegrationTests/Fixtures/DatabaseFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs similarity index 57% rename from CleanArchitecture.IntegrationTests/Fixtures/DatabaseFixture.cs rename to CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs index a6c9ee0..a98b43d 100644 --- a/CleanArchitecture.IntegrationTests/Fixtures/DatabaseFixture.cs +++ b/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs @@ -5,7 +5,7 @@ using Xunit; namespace CleanArchitecture.IntegrationTests.Fixtures; -public sealed class DatabaseFixture : IAsyncLifetime +public sealed class AccessorFixture : IAsyncLifetime { public static string TestRunDbName { get; } = $"CleanArchitecture-Integration-{Guid.NewGuid()}"; @@ -13,11 +13,23 @@ public sealed class DatabaseFixture : IAsyncLifetime { var db = DatabaseAccessor.GetOrCreateAsync(TestRunDbName); await db.DisposeAsync(); + + var redis = RedisAccessor.GetOrCreateAsync(); + await redis.DisposeAsync(); + + var rabbit = RabbitmqAccessor.GetOrCreateAsync(); + await rabbit.DisposeAsync(); } public async Task InitializeAsync() { var db = DatabaseAccessor.GetOrCreateAsync(TestRunDbName); await db.InitializeAsync(); + + var redis = RedisAccessor.GetOrCreateAsync(); + await redis.InitializeAsync(); + + var rabbit = RabbitmqAccessor.GetOrCreateAsync(); + await rabbit.InitializeAsync(); } } \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs b/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs index c0311a5..3ba93ae 100644 --- a/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs +++ b/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs @@ -22,7 +22,7 @@ public class TestFixtureBase : IAsyncLifetime Factory = new CleanArchitectureWebApplicationFactory( RegisterCustomServicesHandler, useTestAuthentication, - DatabaseFixture.TestRunDbName); + AccessorFixture.TestRunDbName); ServerClient = Factory.CreateClient(); ServerClient.Timeout = TimeSpan.FromMinutes(5); diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs b/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs index c599b7d..43cb8a0 100644 --- a/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs +++ b/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs @@ -40,18 +40,31 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto base.ConfigureWebHost(builder); + var configuration = new ConfigurationBuilder() + .Build(); + builder.ConfigureAppConfiguration(configurationBuilder => { configurationBuilder.AddEnvironmentVariables(); - var accessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); + var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); + var redisAccessor = RedisAccessor.GetOrCreateAsync(); + var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync(); - // Overwrite default connection string to our test instance db + // Overwrite default connection strings configurationBuilder.AddInMemoryCollection([ new KeyValuePair( "ConnectionStrings:DefaultConnection", - accessor.GetConnectionString()) + dbAccessor.GetConnectionString()), + new KeyValuePair( + "RedisHostName", + redisAccessor.GetConnectionString()), + new KeyValuePair( + "RabbitMQ:Host", + rabbitAccessor.GetConnectionString()) ]); + + configuration = configurationBuilder.Build(); }); builder.ConfigureServices(services => @@ -70,8 +83,14 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto using var scope = sp.CreateScope(); var scopedServices = scope.ServiceProvider; - var accessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); - var applicationDbContext = accessor.CreateDatabase(scopedServices); + var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); + dbAccessor.CreateDatabase(scopedServices); + + var redisAccessor = RedisAccessor.GetOrCreateAsync(); + redisAccessor.RegisterRedis(services, configuration); + + var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync(); + rabbitAccessor.RegisterRabbitmq(services, configuration); _registerCustomServicesHandler?.Invoke(services, sp, scopedServices); }); @@ -85,7 +104,13 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto public override async ValueTask DisposeAsync() { - var accessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); - await accessor.DisposeAsync(); + var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); + await dbAccessor.DisposeAsync(); + + var redisAccessor = RedisAccessor.GetOrCreateAsync(); + await redisAccessor.DisposeAsync(); + + var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync(); + await rabbitAccessor.DisposeAsync(); } } \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs index 20b6f74..ab1d837 100644 --- a/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs +++ b/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs @@ -18,7 +18,7 @@ public sealed class DatabaseAccessor private bool _databaseCreated = false; private readonly object _databaseCreationLock = new(); - private const string _dbPassword = "12345678##as"; + private const string _dbPassword = "234#AD224fD#ss"; private static readonly MsSqlContainer s_dbContainer = new MsSqlBuilder() .WithPassword(_dbPassword) .WithPortBinding(1433) @@ -61,6 +61,7 @@ public sealed class DatabaseAccessor public async ValueTask DisposeAsync() { + // Reset the database to its original state var dropScript = $@" USE MASTER; diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs new file mode 100644 index 0000000..0e5ea8d --- /dev/null +++ b/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs @@ -0,0 +1,64 @@ +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using CleanArchitecture.Domain.Rabbitmq; +using CleanArchitecture.Domain.Rabbitmq.Extensions; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Testcontainers.RabbitMq; +using RabbitMqConfiguration = CleanArchitecture.Domain.Rabbitmq.RabbitMqConfiguration; + +namespace CleanArchitecture.IntegrationTests.Infrastructure; + +public sealed class RabbitmqAccessor +{ + private static readonly ConcurrentDictionary s_accessors = new(); + + private static readonly RabbitMqContainer s_rabbitContainer = new RabbitMqBuilder() + .WithPortBinding(5672) + .Build(); + + public async Task InitializeAsync() + { + await s_rabbitContainer.StartAsync(); + } + + public async ValueTask DisposeAsync() + { + await s_rabbitContainer.DisposeAsync(); + } + + public string GetConnectionString() + { + return s_rabbitContainer.GetConnectionString(); + } + + public void RegisterRabbitmq(IServiceCollection serviceCollection, IConfiguration configuration) + { + var rabbitService = serviceCollection.FirstOrDefault(x => + x.ServiceType == typeof(RabbitMqHandler)); + + if (rabbitService != null) + { + serviceCollection.Remove(rabbitService); + } + + var rabbitConfig = serviceCollection.FirstOrDefault(x => + x.ServiceType == typeof(RabbitMqConfiguration)); + + if (rabbitConfig != null) + { + serviceCollection.Remove(rabbitConfig); + } + + serviceCollection.AddRabbitMqHandler( + configuration, + "RabbitMQ"); + } + + public static RabbitmqAccessor GetOrCreateAsync() + { + return s_accessors.GetOrAdd("rabbimq", _ => new RabbitmqAccessor()); + } +} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs new file mode 100644 index 0000000..5b4cbc6 --- /dev/null +++ b/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Testcontainers.Redis; + +namespace CleanArchitecture.IntegrationTests.Infrastructure; + +public sealed class RedisAccessor +{ + private static readonly ConcurrentDictionary s_accessors = new(); + + private static readonly RedisContainer s_redisContainer = new RedisBuilder() + .WithPortBinding(6379) + .Build(); + + public async Task InitializeAsync() + { + await s_redisContainer.StartAsync(); + } + + public async ValueTask DisposeAsync() + { + await s_redisContainer.DisposeAsync(); + } + + public string GetConnectionString() + { + return s_redisContainer.GetConnectionString(); + } + + public void RegisterRedis(IServiceCollection serviceCollection, IConfiguration configuration) + { + var distributedCache = serviceCollection.FirstOrDefault(x => + x.ServiceType == typeof(IDistributedCache)); + + if (distributedCache != null) + { + serviceCollection.Remove(distributedCache); + } + + serviceCollection.AddStackExchangeRedisCache(options => + { + options.Configuration = configuration["RedisHostName"]; + options.InstanceName = "clean-architecture"; + }); + } + + public static RedisAccessor GetOrCreateAsync() + { + return s_accessors.GetOrAdd("redis", _ => new RedisAccessor()); + } +} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs b/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs index 5fce773..f44a4e5 100644 --- a/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs +++ b/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs @@ -5,6 +5,6 @@ namespace CleanArchitecture.IntegrationTests; [CollectionDefinition("IntegrationTests", DisableParallelization = true)] public sealed class IntegrationTestsCollection : - ICollectionFixture + ICollectionFixture { } \ No newline at end of file