Files
sql-utilities/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CTEColumnListTests.cs
T
Thom LambandClaude Opus 4.7 67512d23e1 chore(sonar): wrap independent assertions in Assert.Multiple (NUnit2045)
Driven by `dotnet format analyzers --diagnostics NUnit2045`. The fixer
groups consecutive independent `Assert.That(...)` calls into
`Assert.Multiple(() => { ... })`, so a failing assertion no longer
short-circuits the block — every failure inside the group is reported,
which gives much better diagnostics on multi-property tests.

Audit confirmed no Assert.Throws / Assert.Fail / Assert.Catch / Assert.Pass
/ Assert.DoesNotThrow got pulled inside a Multiple block (those need to
short-circuit). All 1180 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:59:47 -05:00

284 lines
8.5 KiB
C#

using Strata.SqlTools.Breakdowns.PostgreSql;
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.PostgreSql.Tests;
[TestFixture]
public class CTEColumnListTests
{
#region Basic Column List Tests
[Test]
public void WithClause_ColumnList_CanBeSet()
{
// Arrange
var cte = new QueryBreakdown("id, name, email", "users");
var withClause = new WithClause("user_summary", cte)
{
ColumnList = new List<string> { "user_id", "user_name", "user_email" }
};
// Act & Assert
Assert.That(withClause.ColumnList, Has.Count.EqualTo(3));
Assert.Multiple(() =>
{
Assert.That(withClause.ColumnList[0], Is.EqualTo("user_id"));
Assert.That(withClause.ColumnList[1], Is.EqualTo("user_name"));
Assert.That(withClause.ColumnList[2], Is.EqualTo("user_email"));
});
}
[Test]
public void GetSql_WithColumnList_IncludesColumnsInWithClauseName()
{
// Arrange
var cte = new QueryBreakdown("id, name, email", "users", "active = true");
var mainQuery = new QueryBreakdown("*", "active_users");
var withClause = new WithClause("active_users", cte)
{
ColumnList = new List<string> { "user_id", "user_name", "user_email" }
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Contains.Substring("active_users (user_id, user_name, user_email)"),
"SQL should include column list in CTE definition");
}
[Test]
public void GetSql_WithoutColumnList_DoesNotIncludeParens()
{
// Arrange
var cte = new QueryBreakdown("id, name, email", "users");
var mainQuery = new QueryBreakdown("*", "all_users");
var withClause = new WithClause("all_users", cte)
{
ColumnList = null
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Contains.Substring("all_users AS"));
Assert.That(sql, Does.Not.Contain("all_users ()"));
}
[Test]
public void GetSql_WithEmptyColumnList_DoesNotIncludeParens()
{
// Arrange
var cte = new QueryBreakdown("id, name", "users");
var mainQuery = new QueryBreakdown("*", "data");
var withClause = new WithClause("data", cte)
{
ColumnList = new List<string>()
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Contains.Substring("data AS"));
Assert.That(sql, Does.Not.Contain("data ()"));
}
#endregion
#region Multi-CTE Column List Tests
[Test]
public void GetSql_WithMultipleCTEsWithColumnLists_IncludesAllLists()
{
// Arrange
var cte1 = new QueryBreakdown("id, name", "users");
var cte2 = new QueryBreakdown("user_id, order_count", "orders");
var mainQuery = new QueryBreakdown("*", "results");
var withClause1 = new WithClause("active_users", cte1)
{
ColumnList = new List<string> { "user_id", "user_name" }
};
var withClause2 = new WithClause("user_orders", cte2)
{
ColumnList = new List<string> { "uid", "order_count" }
};
mainQuery.AddWithClause(withClause1);
mainQuery.AddWithClause(withClause2);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Contains.Substring("active_users (user_id, user_name)"));
Assert.That(sql, Contains.Substring("user_orders (uid, order_count)"));
}
#endregion
#region Column List with Parameters (PostgreSQL-style positional parameters)
[Test]
public void ColumnList_PreservesPostgresqlParameters()
{
// Arrange
var cte = new QueryBreakdown("id, name, salary", "employees", "salary > $1");
cte.AddParameter("MinSalary", 50000);
var mainQuery = new QueryBreakdown("*", "high_earners");
var withClause = new WithClause("high_earners", cte)
{
ColumnList = new List<string> { "emp_id", "emp_name", "emp_salary" }
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
var merged = mainQuery.GetMergedParameters();
// Assert
Assert.That(sql, Contains.Substring("high_earners (emp_id, emp_name, emp_salary)"));
var keys = merged.Keys.ToList();
Assert.That(keys.Any(k => k.Contains("MinSalary")), "Parameters should be preserved with column list");
}
#endregion
#region Column List with Recursive CTEs
[Test]
public void ColumnList_WorksWithRecursiveCTE()
{
// Arrange
var anchorQuery = new QueryBreakdown("id, manager_id, name, 1 as level", "employees", "manager_id IS NULL");
var recursiveQuery = new QueryBreakdown(
"e.id, e.manager_id, e.name, h.level + 1",
"employees e INNER JOIN hierarchy h ON e.manager_id = h.id"
);
var mainQuery = new QueryBreakdown("*", "hierarchy");
var withClause = new WithClause("hierarchy", anchorQuery)
{
IsRecursive = true,
RecursiveQuery = recursiveQuery,
ColumnList = new List<string> { "emp_id", "mgr_id", "emp_name", "lvl" }
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Contains.Substring("WITH RECURSIVE"));
Assert.That(sql, Contains.Substring("hierarchy (emp_id, mgr_id, emp_name, lvl)"));
Assert.That(sql, Contains.Substring("UNION ALL"));
}
#endregion
#region Column List Length and Naming
[Test]
public void ColumnList_PreservesColumnOrder()
{
// Arrange
var cte = new QueryBreakdown("a, b, c", "source");
var mainQuery = new QueryBreakdown("*", "output");
var columns = new List<string> { "z", "y", "x" };
var withClause = new WithClause("ordered_cte", cte)
{
ColumnList = columns
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Contains.Substring("ordered_cte (z, y, x)"));
}
[Test]
public void ColumnList_AllowsQuotedIdentifiers()
{
// Arrange
var cte = new QueryBreakdown("col1, col2", "source");
var mainQuery = new QueryBreakdown("*", "output");
var columns = new List<string> { "\"Column 1\"", "\"column-2\"" };
var withClause = new WithClause("quoted_cte", cte)
{
ColumnList = columns
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Contains.Substring("quoted_cte (\"Column 1\", \"column-2\")"));
}
#endregion
#region Real-World Scenarios
[Test]
public void ColumnList_PostgresqlAnalyticsScenario()
{
// Scenario: PostgreSQL analytics with window functions and renamed columns
// Arrange
var analyticsData = new QueryBreakdown(
"transaction_date, product_id, customer_id, amount, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY transaction_date) as rank",
"transactions",
"transaction_date >= $1 AND amount >= $2"
);
analyticsData.AddParameter("StartDate", DateTime.Today.AddMonths(-6));
analyticsData.AddParameter("MinAmount", 100);
var mainQuery = new QueryBreakdown("*", "ranked_transactions");
var withClause = new WithClause("customer_transactions", analyticsData)
{
ColumnList = new List<string>
{
"trans_date",
"prod_id",
"cust_id",
"trans_amount",
"transaction_rank"
}
};
mainQuery.AddWithClause(withClause);
// Act
var sql = mainQuery.GetSql();
var params_all = mainQuery.GetMergedParameters();
// Assert
Assert.That(sql, Contains.Substring("customer_transactions (trans_date, prod_id, cust_id, trans_amount, transaction_rank)"));
var paramKeys = params_all.Keys.ToList();
Assert.Multiple(() =>
{
Assert.That(paramKeys.Any(k => k.Contains("StartDate")));
Assert.That(paramKeys.Any(k => k.Contains("MinAmount")));
});
}
#endregion
}