Project Init

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-02 23:55:18 +01:00
commit 014d62bcc2
169 changed files with 28867 additions and 0 deletions

View 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>