Files

131 lines
3.8 KiB
C#

using NUnit.Framework;
using Npgsql;
using Testcontainers.PostgreSql;
namespace Strata.SqlTools.Tests.PostgreSql.TestContainers;
/// <summary>
/// Base class for PostgreSQL testcontainer tests that handles container lifecycle.
/// </summary>
[TestFixture]
[Category("Integration")]
public abstract class PostgreSqlTestContainerFixture
{
protected PostgreSqlContainer? Container { get; set; }
protected NpgsqlDataSource? DataSource { get; set; }
[OneTimeSetUp]
[Timeout(120000)] // 2 minutes for container startup + schema initialization
public async Task OneTimeSetup()
{
// Create and start PostgreSQL container
Container = new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.WithDatabase("testdb")
.WithUsername("testuser")
.WithPassword("testpassword")
.WithCleanUp(true)
.Build();
await Container.StartAsync();
// Create data source for connection pooling
var connectionString = Container.GetConnectionString();
DataSource = NpgsqlDataSource.Create(connectionString);
// Initialize test database schema
await InitializeSchema();
}
[OneTimeTearDown]
public async Task OneTimeTearDown()
{
if (DataSource != null)
{
await DataSource.DisposeAsync();
}
if (Container != null)
{
await Container.StopAsync();
await Container.DisposeAsync();
}
}
/// <summary>
/// Override to initialize the test database schema with tables, etc.
/// </summary>
protected virtual async Task InitializeSchema()
{
if (DataSource == null)
return;
using var command = DataSource.CreateCommand(@"
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
order_total DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
in_stock BOOLEAN DEFAULT true
);
");
await command.ExecuteNonQueryAsync();
}
/// <summary>
/// Executes a SQL query and returns the result set.
/// </summary>
protected async Task<List<Dictionary<string, object>>> ExecuteQuery(string sql)
{
if (DataSource == null)
throw new InvalidOperationException("DataSource is not initialized");
var results = new List<Dictionary<string, object>>();
using var command = DataSource.CreateCommand(sql);
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
row[reader.GetName(i)] = reader.GetValue(i);
}
results.Add(row);
}
return results;
}
/// <summary>
/// Clears all test data from tables.
/// </summary>
protected async Task ClearTestData()
{
if (DataSource == null)
return;
using var command = DataSource.CreateCommand(@"
TRUNCATE TABLE orders, users, products RESTART IDENTITY CASCADE;
");
await command.ExecuteNonQueryAsync();
}
}