Project Init
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
151
colaflow-api/tests/WebApplicationFactoryBase.cs
Normal file
151
colaflow-api/tests/WebApplicationFactoryBase.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Custom WebApplicationFactory for API integration tests
|
||||
/// Replaces production database with Testcontainers
|
||||
/// </summary>
|
||||
/// <typeparam name="TProgram">Program class from ColaFlow.API</typeparam>
|
||||
/// <typeparam name="TDbContext">DbContext class from ColaFlow.Infrastructure</typeparam>
|
||||
public class ColaFlowWebApplicationFactory<TProgram, TDbContext>
|
||||
: WebApplicationFactory<TProgram>, IAsyncDisposable
|
||||
where TProgram : class
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
private PostgreSqlContainer? _postgresContainer;
|
||||
private RedisContainer? _redisContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Configure services for testing
|
||||
/// </summary>
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureServices(async services =>
|
||||
{
|
||||
// Remove existing DbContext registration
|
||||
var dbContextDescriptor = services.SingleOrDefault(
|
||||
d => d.ServiceType == typeof(DbContextOptions<TDbContext>));
|
||||
|
||||
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<TDbContext>(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<TDbContext>();
|
||||
|
||||
// Apply migrations
|
||||
await dbContext.Database.MigrateAsync();
|
||||
});
|
||||
|
||||
// Optional: Disable HTTPS redirection for tests
|
||||
builder.UseEnvironment("Testing");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new scope with fresh DbContext
|
||||
/// </summary>
|
||||
public IServiceScope CreateScope()
|
||||
{
|
||||
return Services.CreateScope();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get DbContext from a scope
|
||||
/// </summary>
|
||||
public TDbContext GetDbContext(IServiceScope scope)
|
||||
{
|
||||
return scope.ServiceProvider.GetRequiredService<TDbContext>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup Testcontainers
|
||||
/// </summary>
|
||||
public new async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_postgresContainer != null)
|
||||
{
|
||||
await _postgresContainer.DisposeAsync();
|
||||
}
|
||||
|
||||
if (_redisContainer != null)
|
||||
{
|
||||
await _redisContainer.DisposeAsync();
|
||||
}
|
||||
|
||||
await base.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example usage in test class:
|
||||
///
|
||||
/// public class ProjectsApiTests : IClassFixture<ColaFlowWebApplicationFactory<Program, ColaFlowDbContext>>
|
||||
/// {
|
||||
/// private readonly HttpClient _client;
|
||||
/// private readonly ColaFlowWebApplicationFactory<Program, ColaFlowDbContext> _factory;
|
||||
///
|
||||
/// public ProjectsApiTests(ColaFlowWebApplicationFactory<Program, ColaFlowDbContext> 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();
|
||||
/// }
|
||||
/// }
|
||||
/// </summary>
|
||||
Reference in New Issue
Block a user