131 lines
3.8 KiB
C#
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();
|
|
}
|
|
}
|