fix(backend): Fix Integration Test database provider conflict with environment-aware DI
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 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
|
|||||||
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
|
||||||
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
|
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
|
||||||
using ColaFlow.Modules.ProjectManagement.Infrastructure.Repositories;
|
using ColaFlow.Modules.ProjectManagement.Infrastructure.Repositories;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace ColaFlow.API.Extensions;
|
namespace ColaFlow.API.Extensions;
|
||||||
|
|
||||||
@@ -19,12 +20,18 @@ public static class ModuleExtensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection AddProjectManagementModule(
|
public static IServiceCollection AddProjectManagementModule(
|
||||||
this IServiceCollection services,
|
this IServiceCollection services,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration,
|
||||||
|
IHostEnvironment? environment = null)
|
||||||
{
|
{
|
||||||
// Register DbContext
|
// Only register PostgreSQL DbContext in non-Testing environments
|
||||||
var connectionString = configuration.GetConnectionString("PMDatabase");
|
// In Testing environment, WebApplicationFactory will register InMemory provider
|
||||||
services.AddDbContext<PMDbContext>(options =>
|
if (environment == null || environment.EnvironmentName != "Testing")
|
||||||
options.UseNpgsql(connectionString));
|
{
|
||||||
|
// Register DbContext
|
||||||
|
var connectionString = configuration.GetConnectionString("PMDatabase");
|
||||||
|
services.AddDbContext<PMDbContext>(options =>
|
||||||
|
options.UseNpgsql(connectionString));
|
||||||
|
}
|
||||||
|
|
||||||
// Register repositories
|
// Register repositories
|
||||||
services.AddScoped<IProjectRepository, ProjectRepository>();
|
services.AddScoped<IProjectRepository, ProjectRepository>();
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ using System.Text;
|
|||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Register ProjectManagement Module
|
// Register ProjectManagement Module
|
||||||
builder.Services.AddProjectManagementModule(builder.Configuration);
|
builder.Services.AddProjectManagementModule(builder.Configuration, builder.Environment);
|
||||||
|
|
||||||
// Register Identity Module
|
// Register Identity Module
|
||||||
builder.Services.AddIdentityApplication();
|
builder.Services.AddIdentityApplication();
|
||||||
builder.Services.AddIdentityInfrastructure(builder.Configuration);
|
builder.Services.AddIdentityInfrastructure(builder.Configuration, builder.Environment);
|
||||||
|
|
||||||
// Add controllers
|
// Add controllers
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
@@ -107,3 +107,6 @@ app.UseAuthorization();
|
|||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
// Make the implicit Program class public for integration tests
|
||||||
|
public partial class Program { }
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using ColaFlow.Modules.Identity.Infrastructure.Services;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace ColaFlow.Modules.Identity.Infrastructure;
|
namespace ColaFlow.Modules.Identity.Infrastructure;
|
||||||
|
|
||||||
@@ -13,13 +14,19 @@ public static class DependencyInjection
|
|||||||
{
|
{
|
||||||
public static IServiceCollection AddIdentityInfrastructure(
|
public static IServiceCollection AddIdentityInfrastructure(
|
||||||
this IServiceCollection services,
|
this IServiceCollection services,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration,
|
||||||
|
IHostEnvironment? environment = null)
|
||||||
{
|
{
|
||||||
// DbContext (using connection string)
|
// Only register PostgreSQL DbContext in non-Testing environments
|
||||||
services.AddDbContext<IdentityDbContext>(options =>
|
// In Testing environment, WebApplicationFactory will register InMemory provider
|
||||||
options.UseNpgsql(
|
if (environment == null || environment.EnvironmentName != "Testing")
|
||||||
configuration.GetConnectionString("DefaultConnection"),
|
{
|
||||||
b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
|
// DbContext (using connection string)
|
||||||
|
services.AddDbContext<IdentityDbContext>(options =>
|
||||||
|
options.UseNpgsql(
|
||||||
|
configuration.GetConnectionString("DefaultConnection"),
|
||||||
|
b => b.MigrationsAssembly(typeof(IdentityDbContext).Assembly.FullName)));
|
||||||
|
}
|
||||||
|
|
||||||
// Tenant Context (Scoped - one instance per request)
|
// Tenant Context (Scoped - one instance per request)
|
||||||
services.AddScoped<ITenantContext, TenantContext>();
|
services.AddScoped<ITenantContext, TenantContext>();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom WebApplicationFactory for ColaFlow Integration Tests
|
||||||
|
/// Supports both In-Memory and Real PostgreSQL databases
|
||||||
|
/// </summary>
|
||||||
|
public class ColaFlowWebApplicationFactory : WebApplicationFactory<Program>
|
||||||
|
{
|
||||||
|
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<string, string?>
|
||||||
|
{
|
||||||
|
["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<IdentityDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseInMemoryDatabase(_testDatabaseName!);
|
||||||
|
options.EnableSensitiveDataLogging();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddDbContext<PMDbContext>(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<IdentityDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseNpgsql(connectionString);
|
||||||
|
options.EnableSensitiveDataLogging();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddDbContext<PMDbContext>(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<IdentityDbContext>();
|
||||||
|
if (_useInMemoryDatabase)
|
||||||
|
{
|
||||||
|
identityDb.Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
identityDb.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ProjectManagement database
|
||||||
|
var pmDb = services.GetRequiredService<PMDbContext>();
|
||||||
|
if (_useInMemoryDatabase)
|
||||||
|
{
|
||||||
|
pmDb.Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pmDb.Database.Migrate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error initializing test database: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user