From ebdd4ee0d762c4651f9fa42a4376cc0a5544dc81 Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Mon, 3 Nov 2025 17:16:31 +0100 Subject: [PATCH] fix(backend): Fix Integration Test database provider conflict with environment-aware DI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement environment-aware dependency injection to resolve EF Core provider conflict in Integration Tests. The issue was caused by both PostgreSQL and InMemory providers being registered in the same service provider. Changes: - Modified Identity Module DependencyInjection to skip PostgreSQL DbContext registration in Testing environment - Modified ProjectManagement Module ModuleExtensions with same environment check - Updated Program.cs to pass IHostEnvironment to both module registration methods - Added Microsoft.Extensions.Hosting.Abstractions package to Identity.Infrastructure project - Updated ColaFlowWebApplicationFactory to set Testing environment and register InMemory databases - Simplified WebApplicationFactory by removing complex RemoveAll logic Results: - All 31 Integration Tests now run (previously only 1 ran) - No EF Core provider conflict errors - 23 tests pass, 8 tests fail (failures are business logic issues, not infrastructure) - Production environment still uses PostgreSQL as expected 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Extensions/ModuleExtensions.cs | 17 ++- colaflow-api/src/ColaFlow.API/Program.cs | 7 +- ...low.Modules.Identity.Infrastructure.csproj | 2 + .../DependencyInjection.cs | 19 ++- .../ColaFlowWebApplicationFactory.cs | 132 ++++++++++++++++++ 5 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 colaflow-api/tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/Infrastructure/ColaFlowWebApplicationFactory.cs diff --git a/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs b/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs index 3344a04..72bd4ac 100644 --- a/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs +++ b/colaflow-api/src/ColaFlow.API/Extensions/ModuleExtensions.cs @@ -6,6 +6,7 @@ using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject; using ColaFlow.Modules.ProjectManagement.Domain.Repositories; using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence; using ColaFlow.Modules.ProjectManagement.Infrastructure.Repositories; +using Microsoft.Extensions.Hosting; namespace ColaFlow.API.Extensions; @@ -19,12 +20,18 @@ public static class ModuleExtensions /// public static IServiceCollection AddProjectManagementModule( this IServiceCollection services, - IConfiguration configuration) + IConfiguration configuration, + IHostEnvironment? environment = null) { - // Register DbContext - var connectionString = configuration.GetConnectionString("PMDatabase"); - services.AddDbContext(options => - options.UseNpgsql(connectionString)); + // Only register PostgreSQL DbContext in non-Testing environments + // In Testing environment, WebApplicationFactory will register InMemory provider + if (environment == null || environment.EnvironmentName != "Testing") + { + // Register DbContext + var connectionString = configuration.GetConnectionString("PMDatabase"); + services.AddDbContext(options => + options.UseNpgsql(connectionString)); + } // Register repositories services.AddScoped(); diff --git a/colaflow-api/src/ColaFlow.API/Program.cs b/colaflow-api/src/ColaFlow.API/Program.cs index 479aed0..95f5b6f 100644 --- a/colaflow-api/src/ColaFlow.API/Program.cs +++ b/colaflow-api/src/ColaFlow.API/Program.cs @@ -10,11 +10,11 @@ using System.Text; var builder = WebApplication.CreateBuilder(args); // Register ProjectManagement Module -builder.Services.AddProjectManagementModule(builder.Configuration); +builder.Services.AddProjectManagementModule(builder.Configuration, builder.Environment); // Register Identity Module builder.Services.AddIdentityApplication(); -builder.Services.AddIdentityInfrastructure(builder.Configuration); +builder.Services.AddIdentityInfrastructure(builder.Configuration, builder.Environment); // Add controllers builder.Services.AddControllers(); @@ -107,3 +107,6 @@ app.UseAuthorization(); app.MapControllers(); app.Run(); + +// Make the implicit Program class public for integration tests +public partial class Program { } diff --git a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/ColaFlow.Modules.Identity.Infrastructure.csproj b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/ColaFlow.Modules.Identity.Infrastructure.csproj index f9f2f3e..8ea6d23 100644 --- a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/ColaFlow.Modules.Identity.Infrastructure.csproj +++ b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/ColaFlow.Modules.Identity.Infrastructure.csproj @@ -6,12 +6,14 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/DependencyInjection.cs b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/DependencyInjection.cs index 86a6d39..8b10cb7 100644 --- a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/DependencyInjection.cs +++ b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/DependencyInjection.cs @@ -6,6 +6,7 @@ using ColaFlow.Modules.Identity.Infrastructure.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace ColaFlow.Modules.Identity.Infrastructure; @@ -13,13 +14,19 @@ public static class DependencyInjection { public static IServiceCollection AddIdentityInfrastructure( this IServiceCollection services, - IConfiguration configuration) + IConfiguration configuration, + IHostEnvironment? environment = null) { - // DbContext (using connection string) - services.AddDbContext(options => - options.UseNpgsql( - configuration.GetConnectionString("DefaultConnection"), - b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName))); + // Only register PostgreSQL DbContext in non-Testing environments + // In Testing environment, WebApplicationFactory will register InMemory provider + if (environment == null || environment.EnvironmentName != "Testing") + { + // DbContext (using connection string) + services.AddDbContext(options => + options.UseNpgsql( + configuration.GetConnectionString("DefaultConnection"), + b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName))); + } // Tenant Context (Scoped - one instance per request) services.AddScoped(); diff --git a/colaflow-api/tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/Infrastructure/ColaFlowWebApplicationFactory.cs b/colaflow-api/tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/Infrastructure/ColaFlowWebApplicationFactory.cs new file mode 100644 index 0000000..59cf2c2 --- /dev/null +++ b/colaflow-api/tests/Modules/Identity/ColaFlow.Modules.Identity.IntegrationTests/Infrastructure/ColaFlowWebApplicationFactory.cs @@ -0,0 +1,132 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using ColaFlow.Modules.Identity.Infrastructure.Persistence; +using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence; + +namespace ColaFlow.Modules.Identity.IntegrationTests.Infrastructure; + +/// +/// Custom WebApplicationFactory for ColaFlow Integration Tests +/// Supports both In-Memory and Real PostgreSQL databases +/// +public class ColaFlowWebApplicationFactory : WebApplicationFactory +{ + private readonly bool _useInMemoryDatabase; + private readonly string? _testDatabaseName; + + public ColaFlowWebApplicationFactory(bool useInMemoryDatabase = true, string? testDatabaseName = null) + { + _useInMemoryDatabase = useInMemoryDatabase; + _testDatabaseName = testDatabaseName ?? $"TestDb_{Guid.NewGuid()}"; + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // Set environment to Testing - this prevents PostgreSQL DbContext registration in modules + builder.UseEnvironment("Testing"); + + // Configure test-specific settings + builder.ConfigureAppConfiguration((context, config) => + { + // Clear existing connection strings to prevent PostgreSQL registration + config.Sources.Clear(); + + // Add minimal config for testing + config.AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:DefaultConnection"] = "", + ["ConnectionStrings:PMDatabase"] = "", + ["Jwt:SecretKey"] = "test-secret-key-for-integration-tests-minimum-32-characters", + ["Jwt:Issuer"] = "ColaFlow.Test", + ["Jwt:Audience"] = "ColaFlow.Test", + ["Jwt:AccessTokenExpirationMinutes"] = "15", + ["Jwt:RefreshTokenExpirationDays"] = "7" + }); + }); + + builder.ConfigureServices(services => + { + // Register test databases (modules won't register PostgreSQL due to Testing environment) + if (_useInMemoryDatabase) + { + // Use In-Memory Database for fast, isolated tests + // IMPORTANT: Share the same database name for cross-context data consistency + services.AddDbContext(options => + { + options.UseInMemoryDatabase(_testDatabaseName!); + options.EnableSensitiveDataLogging(); + }); + + services.AddDbContext(options => + { + options.UseInMemoryDatabase(_testDatabaseName!); + options.EnableSensitiveDataLogging(); + }); + } + else + { + // Use Real PostgreSQL for integration tests + var connectionString = $"Host=localhost;Port=5432;Database=colaflow_test_{_testDatabaseName};Username=postgres;Password=postgres"; + + services.AddDbContext(options => + { + options.UseNpgsql(connectionString); + options.EnableSensitiveDataLogging(); + }); + + services.AddDbContext(options => + { + options.UseNpgsql(connectionString); + options.EnableSensitiveDataLogging(); + }); + } + }); + } + + // Override CreateHost to initialize databases after the host is created + protected override IHost CreateHost(IHostBuilder builder) + { + var host = base.CreateHost(builder); + + // Initialize databases after host is created + using var scope = host.Services.CreateScope(); + var services = scope.ServiceProvider; + + try + { + // Initialize Identity database + var identityDb = services.GetRequiredService(); + if (_useInMemoryDatabase) + { + identityDb.Database.EnsureCreated(); + } + else + { + identityDb.Database.Migrate(); + } + + // Initialize ProjectManagement database + var pmDb = services.GetRequiredService(); + if (_useInMemoryDatabase) + { + pmDb.Database.EnsureCreated(); + } + else + { + pmDb.Database.Migrate(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error initializing test database: {ex.Message}"); + throw; + } + + return host; + } +}