chore: initial git load of code space
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user