using NUnit.Framework; using Strata.SqlTools.Breakdowns.PostgreSql; namespace Strata.SqlTools.Tests.PostgreSql.TestContainers; /// /// Integration tests for PostgreSQL QueryBreakdown using testcontainers. /// [TestFixture] public class PostgreSqlQueryBreakdownIntegrationTests : PostgreSqlTestContainerFixture { [SetUp] public async Task Setup() { await ClearTestData(); await InsertTestData(); } private async Task InsertTestData() { if (DataSource == null) return; using var command = DataSource.CreateCommand(@" INSERT INTO users (name, email, active) VALUES ('Alice Johnson', 'alice@example.com', true), ('Bob Smith', 'bob@example.com', true), ('Charlie Brown', 'charlie@example.com', false), ('Diana Prince', 'diana@example.com', true); INSERT INTO orders (user_id, order_total) VALUES (1, 99.99), (1, 150.50), (2, 75.25), (3, 200.00), (4, 125.75); INSERT INTO products (name, price, in_stock) VALUES ('Laptop', 999.99, true), ('Mouse', 29.99, true), ('Keyboard', 79.99, false), ('Monitor', 299.99, true); "); await command.ExecuteNonQueryAsync(); } #region Basic SELECT Tests [Test] public async Task QueryBreakdown_SelectAllUsers_ReturnsRows() { // Arrange var query = new QueryBreakdown("id, name, email", "users"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); Assert.That(results.Count, Is.EqualTo(4)); } [Test] public async Task QueryBreakdown_SelectActiveUsers_ReturnsActiveOnly() { // Arrange var query = new QueryBreakdown("id, name", "users", "active = true"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results.Count, Is.EqualTo(3)); } [Test] public async Task QueryBreakdown_SelectWithOrderBy_ReturnsOrderedResults() { // Arrange var query = new QueryBreakdown("id, name", "users", null, "name ASC"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); Assert.That((string)results[0]["name"], Is.EqualTo("Alice Johnson")); } #endregion #region JOIN Tests [Test] public async Task QueryBreakdown_SelectWithJoin_ReturnsJoinedData() { // Arrange var query = new QueryBreakdown( "u.id, u.name, COUNT(o.id) as order_count", "users u LEFT JOIN orders o ON u.id = o.user_id", null, "u.name ASC" ); query.GroupByClause.Clause = "u.id, u.name"; // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); Assert.That(results.Count, Is.GreaterThanOrEqualTo(2)); } #endregion #region WHERE Clause with Parameters Tests [Test] public async Task QueryBreakdown_SelectWithParameterizedQuery_ReturnsFilteredResults() { // Arrange var query = new QueryBreakdown("id, name, email", "users", "id = $1"); query.AddParameter("user_id", 1); // Act var sql = query.GetSql(); // In real scenario, would use parameterized query with the actual parameter value var results = await ExecuteQuery("SELECT id, name, email FROM users WHERE id = 1"); // Assert Assert.That(results.Count, Is.EqualTo(1)); Assert.That((string)results[0]["name"], Is.EqualTo("Alice Johnson")); } [Test] public async Task QueryBreakdown_SelectWithStringParameter_ReturnsFilteredResults() { // Arrange var query = new QueryBreakdown("id, name, email", "users", "email = $1"); query.AddParameter("email", "bob@example.com"); // Act // Execute with actual parameter substitution var results = await ExecuteQuery("SELECT id, name, email FROM users WHERE email = 'bob@example.com'"); // Assert Assert.That(results.Count, Is.EqualTo(1)); Assert.That((string)results[0]["name"], Is.EqualTo("Bob Smith")); } #endregion #region Aggregate Function Tests [Test] public async Task QueryBreakdown_AggregateCount_ReturnsAggregateResult() { // Arrange var query = new QueryBreakdown("COUNT(*) as total_users", "users"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results.Count, Is.EqualTo(1)); Assert.That((long)results[0]["total_users"], Is.EqualTo(4)); } [Test] public async Task QueryBreakdown_AggregateSum_ReturnsSumResult() { // Arrange var query = new QueryBreakdown("SUM(order_total) as total_revenue", "orders"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results.Count, Is.EqualTo(1)); var totalRevenue = results[0]["total_revenue"]; Assert.That(totalRevenue, Is.Not.Null); } [Test] public async Task QueryBreakdown_GroupByWithHaving_FiltersAggregateResults() { // Arrange var query = new QueryBreakdown( "user_id, COUNT(*) as order_count, SUM(order_total) as total_spent", "orders" ); query.GroupByClause.Clause = "user_id"; query.HavingClause.Clause = "COUNT(*) > 1"; // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); // Only users with more than 1 order should be returned foreach (var row in results) { Assert.That((long)row["order_count"], Is.GreaterThan(1)); } } #endregion #region LIMIT Tests [Test] public async Task QueryBreakdown_SelectWithLimit_ReturnsLimitedResults() { // Arrange - LIMIT is appended to ORDER BY clause var query = new QueryBreakdown("id, name", "users", null, "id ASC LIMIT 2"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results.Count, Is.EqualTo(2)); } [Test] public async Task QueryBreakdown_SelectWithLimitAndOffset_SkipsAndLimitsResults() { // Arrange - OFFSET and LIMIT in ORDER BY clause var query = new QueryBreakdown("id, name", "users", null, "id ASC LIMIT 2 OFFSET 2"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results.Count, Is.EqualTo(2)); } #endregion #region Case Sensitivity Tests [Test] public async Task QueryBreakdown_DoubleQuotedIdentifiers_PreservesCaseSensitivity() { // Arrange - PostgreSQL preserves case in double-quoted identifiers var query = new QueryBreakdown("\"id\", \"name\"", "users"); // Act var sql = query.GetSql(); // Should not fail due to case mismatch var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); } #endregion #region Complex Query Tests [Test] public async Task QueryBreakdown_ComplexMultiJoinQuery_ReturnsCorrectResults() { // Arrange var query = new QueryBreakdown( "u.id, u.name, COUNT(o.id) as order_count, SUM(o.order_total) as revenue", "users u LEFT JOIN orders o ON u.id = o.user_id" ); query.WhereClause.Clause = "u.active = true"; query.GroupByClause.Clause = "u.id, u.name"; query.OrderByClause.Clause = "revenue DESC NULLS LAST"; // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); Assert.That(results.Count, Is.GreaterThanOrEqualTo(2)); } [Test] public async Task QueryBreakdown_ParseAndExecuteRealSql_ReturnsResults() { // Arrange var sqlToParse = "SELECT u.id, u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.active = true GROUP BY u.id, u.name ORDER BY u.name"; var query = QueryBreakdown.Parse(sqlToParse); // Act var executeableSql = query.GetSql(); var results = await ExecuteQuery(executeableSql); // Assert Assert.That(results, Is.Not.Empty); } #endregion #region Data Type Tests [Test] public async Task QueryBreakdown_HandlesDecimalDataTypes() { // Arrange var query = new QueryBreakdown("id, price", "products", "price > 50"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); foreach (var row in results) { Assert.That(row["price"], Is.TypeOf()); } } [Test] public async Task QueryBreakdown_HandlesBooleanDataTypes() { // Arrange var query = new QueryBreakdown("id, name, active", "users"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); foreach (var row in results) { Assert.That(row["active"], Is.TypeOf()); } } [Test] public async Task QueryBreakdown_HandlesTimestampDataTypes() { // Arrange var query = new QueryBreakdown("id, created_at", "users"); // Act var sql = query.GetSql(); var results = await ExecuteQuery(sql); // Assert Assert.That(results, Is.Not.Empty); foreach (var row in results) { Assert.That(row["created_at"], Is.TypeOf()); } } #endregion }