using System; using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Testcontainers.PostgreSql; using Testcontainers.Redis; namespace ColaFlow.IntegrationTests.Infrastructure; /// /// Custom WebApplicationFactory for API integration tests /// Replaces production database with Testcontainers /// /// Program class from ColaFlow.API /// DbContext class from ColaFlow.Infrastructure public class ColaFlowWebApplicationFactory : WebApplicationFactory, IAsyncDisposable where TProgram : class where TDbContext : DbContext { private PostgreSqlContainer? _postgresContainer; private RedisContainer? _redisContainer; /// /// Configure services for testing /// protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(async services => { // Remove existing DbContext registration var dbContextDescriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions)); if (dbContextDescriptor != null) { services.Remove(dbContextDescriptor); } // Start Testcontainers _postgresContainer = new PostgreSqlBuilder() .WithImage("postgres:16-alpine") .WithDatabase("colaflow_test") .WithUsername("colaflow_test") .WithPassword("colaflow_test_password") .WithCleanUp(true) .Build(); _redisContainer = new RedisBuilder() .WithImage("redis:7-alpine") .WithCleanUp(true) .Build(); await _postgresContainer.StartAsync(); await _redisContainer.StartAsync(); // Add test DbContext with Testcontainers connection string services.AddDbContext(options => { options.UseNpgsql(_postgresContainer.GetConnectionString()); options.EnableSensitiveDataLogging(); options.EnableDetailedErrors(); }); // Replace Redis connection string // TODO: Configure Redis connection with Testcontainers connection string // Build service provider and apply migrations var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); // Apply migrations await dbContext.Database.MigrateAsync(); }); // Optional: Disable HTTPS redirection for tests builder.UseEnvironment("Testing"); } /// /// Create a new scope with fresh DbContext /// public IServiceScope CreateScope() { return Services.CreateScope(); } /// /// Get DbContext from a scope /// public TDbContext GetDbContext(IServiceScope scope) { return scope.ServiceProvider.GetRequiredService(); } /// /// Cleanup Testcontainers /// public new async ValueTask DisposeAsync() { if (_postgresContainer != null) { await _postgresContainer.DisposeAsync(); } if (_redisContainer != null) { await _redisContainer.DisposeAsync(); } await base.DisposeAsync(); } } /// /// Example usage in test class: /// /// public class ProjectsApiTests : IClassFixture> /// { /// private readonly HttpClient _client; /// private readonly ColaFlowWebApplicationFactory _factory; /// /// public ProjectsApiTests(ColaFlowWebApplicationFactory factory) /// { /// _factory = factory; /// _client = factory.CreateClient(); /// } /// /// [Fact] /// public async Task GetProjects_ReturnsSuccessStatusCode() /// { /// // Arrange /// using var scope = _factory.CreateScope(); /// var dbContext = _factory.GetDbContext(scope); /// /// // Seed test data /// // ... /// /// // Act /// var response = await _client.GetAsync("/api/v1/projects"); /// /// // Assert /// response.EnsureSuccessStatusCode(); /// } /// } ///