using Microsoft.EntityFrameworkCore; using Npgsql; using Xunit; namespace BuildFlow.Workflow.Tests.Reliability; /// /// 为工作流可靠性测试创建并销毁独立 PostgreSQL 数据库。 /// public sealed class PostgresWorkflowFixture : IAsyncLifetime { private const string DefaultConnection = "Host=127.0.0.1;Port=55432;Username=buildflow;Password=buildflow;Database=postgres"; private string _databaseName = default!; private string _adminConnectionString = default!; /// 当前测试数据库连接串。 public string ConnectionString { get; private set; } = default!; public async Task InitializeAsync() { var configured = Environment.GetEnvironmentVariable("BUILDFLOW_WORKFLOW_TEST_CONNECTION") ?? DefaultConnection; var adminBuilder = new NpgsqlConnectionStringBuilder(configured) { Database = "postgres", Pooling = false, }; _adminConnectionString = adminBuilder.ConnectionString; _databaseName = $"workflow_reliability_{Guid.NewGuid():N}"; await using (var connection = new NpgsqlConnection(_adminConnectionString)) { await connection.OpenAsync(); await using var command = connection.CreateCommand(); command.CommandText = $"CREATE DATABASE \"{_databaseName}\""; await command.ExecuteNonQueryAsync(); } var databaseBuilder = new NpgsqlConnectionStringBuilder(configured) { Database = _databaseName, Pooling = true, }; ConnectionString = databaseBuilder.ConnectionString; await using var db = CreateDbContext(); await db.Database.EnsureCreatedAsync(); } /// 创建指向独立测试数据库的 WorkflowDbContext。 public WorkflowDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseNpgsql(ConnectionString) .UseSnakeCaseNamingConvention() .Options; var db = new WorkflowDbContext(options); db.SavingChanges += AuditStub.OnSave; return db; } public async Task DisposeAsync() { NpgsqlConnection.ClearAllPools(); await using var connection = new NpgsqlConnection(_adminConnectionString); await connection.OpenAsync(); await using (var terminate = connection.CreateCommand()) { terminate.CommandText = "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = @database AND pid <> pg_backend_pid()"; terminate.Parameters.AddWithValue("database", _databaseName); await terminate.ExecuteNonQueryAsync(); } await using var drop = connection.CreateCommand(); drop.CommandText = $"DROP DATABASE IF EXISTS \"{_databaseName}\""; await drop.ExecuteNonQueryAsync(); } }