Project Init
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
197
colaflow-api/tests/IntegrationTestBase.cs
Normal file
197
colaflow-api/tests/IntegrationTestBase.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using DotNet.Testcontainers.Builders;
|
||||
using DotNet.Testcontainers.Configurations;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
using Testcontainers.PostgreSql;
|
||||
using Testcontainers.Redis;
|
||||
using Xunit;
|
||||
|
||||
namespace ColaFlow.IntegrationTests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for integration tests that require PostgreSQL and Redis
|
||||
/// Uses Testcontainers to spin up isolated database instances
|
||||
/// </summary>
|
||||
public abstract class IntegrationTestBase : IAsyncLifetime
|
||||
{
|
||||
// PostgreSQL Container
|
||||
protected PostgreSqlContainer PostgresContainer { get; private set; } = null!;
|
||||
|
||||
// Redis Container
|
||||
protected RedisContainer RedisContainer { get; private set; } = null!;
|
||||
|
||||
// Connection Strings
|
||||
protected string PostgresConnectionString => PostgresContainer.GetConnectionString();
|
||||
protected string RedisConnectionString => RedisContainer.GetConnectionString();
|
||||
|
||||
/// <summary>
|
||||
/// Initialize containers before tests
|
||||
/// Called by xUnit before any test in the class runs
|
||||
/// </summary>
|
||||
public virtual async Task InitializeAsync()
|
||||
{
|
||||
// Create PostgreSQL container
|
||||
PostgresContainer = new PostgreSqlBuilder()
|
||||
.WithImage("postgres:16-alpine")
|
||||
.WithDatabase("colaflow_test")
|
||||
.WithUsername("colaflow_test")
|
||||
.WithPassword("colaflow_test_password")
|
||||
.WithCleanUp(true)
|
||||
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(5432))
|
||||
.Build();
|
||||
|
||||
// Create Redis container
|
||||
RedisContainer = new RedisBuilder()
|
||||
.WithImage("redis:7-alpine")
|
||||
.WithCleanUp(true)
|
||||
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379))
|
||||
.Build();
|
||||
|
||||
// Start containers in parallel
|
||||
await Task.WhenAll(
|
||||
PostgresContainer.StartAsync(),
|
||||
RedisContainer.StartAsync()
|
||||
);
|
||||
|
||||
// Optional: Run migrations or seed data
|
||||
await SeedDatabaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup containers after tests
|
||||
/// Called by xUnit after all tests in the class complete
|
||||
/// </summary>
|
||||
public virtual async Task DisposeAsync()
|
||||
{
|
||||
// Stop containers in parallel
|
||||
await Task.WhenAll(
|
||||
PostgresContainer.StopAsync(),
|
||||
RedisContainer.StopAsync()
|
||||
);
|
||||
|
||||
// Dispose containers
|
||||
await PostgresContainer.DisposeAsync();
|
||||
await RedisContainer.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seed database with test data
|
||||
/// Override in derived classes for custom seeding
|
||||
/// </summary>
|
||||
protected virtual async Task SeedDatabaseAsync()
|
||||
{
|
||||
// Example: Create tables, seed data, etc.
|
||||
await using var connection = new NpgsqlConnection(PostgresConnectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Create extensions
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = @"
|
||||
CREATE EXTENSION IF NOT EXISTS ""uuid-ossp"";
|
||||
CREATE EXTENSION IF NOT EXISTS ""pg_trgm"";
|
||||
";
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create DbContextOptions for Entity Framework Core
|
||||
/// </summary>
|
||||
protected DbContextOptions<TContext> CreateDbContextOptions<TContext>()
|
||||
where TContext : DbContext
|
||||
{
|
||||
return new DbContextOptionsBuilder<TContext>()
|
||||
.UseNpgsql(PostgresConnectionString)
|
||||
.EnableSensitiveDataLogging()
|
||||
.EnableDetailedErrors()
|
||||
.Options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute SQL command on test database
|
||||
/// </summary>
|
||||
protected async Task ExecuteSqlAsync(string sql)
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(PostgresConnectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = sql;
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean database tables for test isolation
|
||||
/// </summary>
|
||||
protected async Task CleanDatabaseAsync()
|
||||
{
|
||||
await ExecuteSqlAsync(@"
|
||||
DO $$
|
||||
DECLARE
|
||||
r RECORD;
|
||||
BEGIN
|
||||
-- Disable triggers
|
||||
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
|
||||
EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE';
|
||||
END LOOP;
|
||||
END $$;
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection fixture for sharing Testcontainers across multiple test classes
|
||||
/// Use [Collection("IntegrationTests")] attribute on test classes
|
||||
/// </summary>
|
||||
[CollectionDefinition("IntegrationTests")]
|
||||
public class IntegrationTestCollection : ICollectionFixture<IntegrationTestFixture>
|
||||
{
|
||||
// This class has no code, and is never created.
|
||||
// Its purpose is simply to be the place to apply [CollectionDefinition]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared fixture for integration tests
|
||||
/// Containers are created once and shared across test classes
|
||||
/// </summary>
|
||||
public class IntegrationTestFixture : IAsyncLifetime
|
||||
{
|
||||
public PostgreSqlContainer PostgresContainer { get; private set; } = null!;
|
||||
public RedisContainer RedisContainer { get; private set; } = null!;
|
||||
|
||||
public string PostgresConnectionString => PostgresContainer.GetConnectionString();
|
||||
public string RedisConnectionString => RedisContainer.GetConnectionString();
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Create containers
|
||||
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();
|
||||
|
||||
// Start containers
|
||||
await Task.WhenAll(
|
||||
PostgresContainer.StartAsync(),
|
||||
RedisContainer.StartAsync()
|
||||
);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await Task.WhenAll(
|
||||
PostgresContainer.DisposeAsync().AsTask(),
|
||||
RedisContainer.DisposeAsync().AsTask()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user