using Strata.SqlTools.Breakdowns.PostgreSql; namespace Strata.SqlTools.SqlBreakdown.Tests.PostgreSql; [TestFixture] public class QueryBreakdownTests { #region Constructor Tests [Test] public void Constructor_Default_InitializesEmptyProperties() { // Arrange & Act var queryBreakdown = new QueryBreakdown(); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo(string.Empty)); Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo(string.Empty)); Assert.That(queryBreakdown.WhereClause.Clause, Is.EqualTo(string.Empty)); Assert.That(queryBreakdown.OrderByClause.Clause, Is.EqualTo(string.Empty)); }); } [Test] public void Constructor_WithSelectAndFrom_SetsProperties() { // Arrange var selectClause = "id, name"; var fromClause = "users"; // Act var queryBreakdown = new QueryBreakdown(selectClause, fromClause); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo(selectClause)); Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo(fromClause)); }); } [Test] public void Constructor_WithSelectFromAndWhere_SetsProperties() { // Arrange var selectClause = "id, name"; var fromClause = "users"; var whereClause = "active = true"; // Act var queryBreakdown = new QueryBreakdown(selectClause, fromClause, whereClause); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo(selectClause)); Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo(fromClause)); Assert.That(queryBreakdown.WhereClause.Clause, Is.EqualTo(whereClause)); }); } [Test] public void Constructor_WithAllBasicClauses_SetsProperties() { // Arrange var selectClause = "id, name"; var fromClause = "users"; var whereClause = "active = true"; var orderByClause = "id ASC"; // Act var queryBreakdown = new QueryBreakdown(selectClause, fromClause, whereClause, orderByClause); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo(selectClause)); Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo(fromClause)); Assert.That(queryBreakdown.WhereClause.Clause, Is.EqualTo(whereClause)); Assert.That(queryBreakdown.OrderByClause.Clause, Is.EqualTo(orderByClause)); }); } #endregion #region GetSql Tests [Test] public void GetSql_WithBasicQuery_GeneratesValidSql() { // Arrange var queryBreakdown = new QueryBreakdown("id, name", "users"); // Act var sql = queryBreakdown.GetSql(); // Assert Assert.That(sql, Is.Not.Null); Assert.That(sql, Does.Contain("SELECT").Or.Contain("select")); Assert.That(sql, Does.Contain("users")); } [Test] public void GetSql_WithWhereClause_GeneratesValidSql() { // Arrange var queryBreakdown = new QueryBreakdown("id, name", "users", "active = true"); // Act var sql = queryBreakdown.GetSql(); // Assert Assert.That(sql, Does.Contain("active")); Assert.That(sql, Does.Contain("true")); } [Test] public void GetSql_WithOrderByClause_GeneratesValidSql() { // Arrange var queryBreakdown = new QueryBreakdown("id, name", "users", "active = true", "name ASC"); // Act var sql = queryBreakdown.GetSql(); // Assert Assert.That(sql, Does.Contain("ORDER BY").Or.Contain("order by")); } #endregion #region Parameters Tests [Test] public void AddParameter_SingleParameter_StoresParameter() { // Arrange var queryBreakdown = new QueryBreakdown("id, name", "users"); // Act queryBreakdown.AddParameter("userId", 123); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.Parameters.ContainsKey("userId"), Is.True); Assert.That(queryBreakdown.Parameters["userId"], Is.EqualTo(123)); }); } [Test] public void AddParameter_MultipleParameters_StoresAll() { // Arrange var queryBreakdown = new QueryBreakdown("id, name", "users"); // Act queryBreakdown.AddParameter("param1", "value1"); queryBreakdown.AddParameter("param2", 123); queryBreakdown.AddParameter("param3", new DateTime(2025, 1, 1)); // Assert Assert.That(queryBreakdown.Parameters, Has.Count.GreaterThanOrEqualTo(3)); Assert.Multiple(() => { Assert.That(queryBreakdown.Parameters.ContainsKey("param1"), Is.True); Assert.That(queryBreakdown.Parameters.ContainsKey("param2"), Is.True); Assert.That(queryBreakdown.Parameters.ContainsKey("param3"), Is.True); }); } [Test] public void AddParameter_ParameterWithAtPrefix_NormalizesName() { // Arrange var queryBreakdown = new QueryBreakdown("id", "users"); // Act queryBreakdown.AddParameter("@userId", 999); // Assert // The @ prefix is stripped Assert.That(queryBreakdown.Parameters, Is.Not.Empty); } #endregion #region Parse Tests [Test] public void Parse_WithValidSelectStement_Succeeds() { // Arrange var sql = "SELECT id, name FROM users"; // Act var queryBreakdown = QueryBreakdown.Parse(sql); // Assert Assert.That(queryBreakdown, Is.Not.Null); Assert.Multiple(() => { Assert.That(queryBreakdown.SelectClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.FromClause.Clause, Is.Not.Empty); }); } [Test] public void Parse_WithWhereClause_ExtractsCorrectly() { // Arrange var sql = "SELECT id, name FROM users WHERE active = true"; // Act var queryBreakdown = QueryBreakdown.Parse(sql); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.WhereClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.FromClause.Clause, Does.Contain("users")); }); } [Test] public void Parse_WithOrderBy_ExtractsCorrectly() { // Arrange var sql = "SELECT id, name FROM users ORDER BY name ASC"; // Act var queryBreakdown = QueryBreakdown.Parse(sql); // Assert Assert.That(queryBreakdown.OrderByClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.OrderByClause.Clause, Does.Contain("name")); } [Test] public void Parse_WithPostgreSqlPositionalParameters_ExtractsParameters() { // Arrange var sql = "SELECT id, name FROM users WHERE user_id = $1 AND status = $2"; // Act var queryBreakdown = QueryBreakdown.Parse(sql); // Assert Assert.That(queryBreakdown.Parameters, Has.Count.GreaterThanOrEqualTo(0)); } [Test] public void Parse_WithComplexQuery_ExtractsAllClauses() { // Arrange var sql = @" 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 HAVING COUNT(o.id) > 0 ORDER BY order_count DESC "; // Act var queryBreakdown = QueryBreakdown.Parse(sql); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.SelectClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.FromClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.WhereClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.GroupByClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.HavingClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.OrderByClause.Clause, Is.Not.Empty); }); } [Test] public void TryParse_WithValidSql_Succeeds() { // Arrange var sql = "SELECT id, name FROM users"; // Act var success = QueryBreakdown.TryParse(sql, out var queryBreakdown, out var errorMessage); Assert.Multiple(() => { // Assert Assert.That(success, Is.True); Assert.That(queryBreakdown, Is.Not.Null); Assert.That(errorMessage, Is.Empty); }); } [Test] public void TryParse_WithEmptySql_ReturnsFalse() { // Arrange var sql = ""; // Act var success = QueryBreakdown.TryParse(sql, out var queryBreakdown, out var errorMessage); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(errorMessage, Is.Not.Empty); }); } #endregion #region PostgreSQL-Specific Features Tests [Test] public void GetSql_WithDoubleQuotedIdentifiers_PreservesQuotes() { // Arrange var queryBreakdown = new QueryBreakdown("\"Id\", \"Name\"", "\"Users\""); // Act var sql = queryBreakdown.GetSql(); // Assert Assert.That(sql, Does.Contain("\"")); } [Test] public void Parse_WithDoubleQuotedIdentifiers_Succeeds() { // Arrange var sql = "SELECT \"Id\", \"Name\" FROM \"Users\" WHERE \"Id\" = $1"; // Act var queryBreakdown = QueryBreakdown.Parse(sql); Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.SelectClause.Clause, Is.Not.Empty); Assert.That(queryBreakdown.FromClause.Clause, Is.Not.Empty); }); } [Test] public void Parse_WithLimitInOrderBy_ExtractsLimit() { // Arrange var sql = "SELECT id, name FROM users ORDER BY id LIMIT 10"; // Act var queryBreakdown = QueryBreakdown.Parse(sql); // Assert // LIMIT is appended to ORDER BY clause in PostgreSQL parsing Assert.That(queryBreakdown.OrderByClause.Clause, Does.Contain("LIMIT").Or.Contain("Limit").Or.Contain("limit")); } [Test] public void Parse_WithLimitAndOffset_ExtractsCorrectly() { // Arrange var sql = "SELECT id, name FROM users ORDER BY id LIMIT 10 OFFSET 5"; // Act var queryBreakdown = QueryBreakdown.Parse(sql); // Assert // Both LIMIT and OFFSET are appended to ORDER BY Assert.That(queryBreakdown.OrderByClause.Clause, Is.Not.Empty); } #endregion #region Edge Cases [Test] public void Constructor_DefaultConstructor_AllowsManualClauseAssignment() { // Arrange & Act var queryBreakdown = new QueryBreakdown(); queryBreakdown.SelectClause.Clause = "id, name"; queryBreakdown.FromClause.Clause = "users"; Assert.Multiple(() => { // Assert Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo("id, name")); Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo("users")); }); } [Test] public void GetSql_EmptyQueryBreakdown_GeneratesBasicSelect() { // Arrange var queryBreakdown = new QueryBreakdown(); // Act var sql = queryBreakdown.GetSql(); // Assert Assert.That(sql, Is.Not.Null); } #endregion #region WITH Clause Tests [Test] public void AddWithClause_WithCTE_AddsToWithClauses() { // Arrange var cte = new QueryBreakdown("id, name", "base_users"); var mainQuery = new QueryBreakdown("*", "active_users_cte"); // Act mainQuery.AddWithClause("active_users_cte", cte); // Assert Assert.That(mainQuery.WithClauses, Has.Count.EqualTo(1)); Assert.Multiple(() => { Assert.That(mainQuery.WithClauses[0].TableName, Is.EqualTo("active_users_cte")); Assert.That(mainQuery.WithClauses[0].Query, Is.EqualTo(cte)); }); } [Test] public void GetSql_WithSimpleCTE_GeneratesWithClause() { // Arrange var cte = new QueryBreakdown("id, name", "base_users"); var mainQuery = new QueryBreakdown("*", "active_users_cte"); mainQuery.AddWithClause("active_users_cte", cte); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("WITH")); Assert.That(sql, Contains.Substring("active_users_cte AS")); Assert.That(sql, Contains.Substring("id, name")); Assert.That(sql, Contains.Substring("base_users")); } [Test] public void GetSql_WithMultipleCTEs_GeneratesAllCTEs() { // Arrange var cte1 = new QueryBreakdown("id, status", "users", "status = 'active'"); var cte2 = new QueryBreakdown("user_id, count(*)", "orders", "created >= CURRENT_DATE"); var mainQuery = new QueryBreakdown("*", "results"); mainQuery.AddWithClause("active_users", cte1); mainQuery.AddWithClause("recent_orders", cte2); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("WITH")); Assert.That(sql, Contains.Substring("active_users AS")); Assert.That(sql, Contains.Substring("recent_orders AS")); } #endregion #region CTE Parameter Inheritance Tests - PostgreSQL [Test] public void GetMergedParameters_WithNoCTEs_ReturnsMainQueryParameters() { // Arrange var query = new QueryBreakdown("*", "USERS"); query.AddParameter("UserId", 123); query.AddParameter("ActiveStatus", true); // Act var merged = query.GetMergedParameters(); // Assert // PostgreSQL stores parameters with positional and name-based formats Assert.That(merged, Has.Count.GreaterThanOrEqualTo(2)); var keys = merged.Keys.ToList(); Assert.Multiple(() => { Assert.That(keys.Any(k => k.Contains("UserId"))); Assert.That(keys.Any(k => k.Contains("ActiveStatus"))); }); } [Test] public void GetMergedParameters_WithSingleCTE_MergesParameters() { // Arrange var cte = new QueryBreakdown("ID, NAME", "BASE_USERS", "ACTIVE = true"); cte.AddParameter("MinDate", DateTime.Today.AddDays(-30)); var mainQuery = new QueryBreakdown("*", "ACTIVE_USERS"); mainQuery.AddParameter("ResultLimit", 100); mainQuery.AddWithClause("ACTIVE_USERS", cte); // Act var merged = mainQuery.GetMergedParameters(); // Assert var keys = merged.Keys.ToList(); Assert.Multiple(() => { Assert.That(keys.Any(k => k.Contains("MinDate"))); Assert.That(keys.Any(k => k.Contains("ResultLimit"))); }); } [Test] public void GetMergedParameters_WithMultipleCTEs_MergesAllParameters() { // Arrange var cte1 = new QueryBreakdown("ID, STATUS", "USERS", "STATUS = 'active'"); cte1.AddParameter("Status1", "active"); var cte2 = new QueryBreakdown("USER_ID, CREATED", "ORDERS", "CREATED >= CURRENT_DATE"); cte2.AddParameter("MinDate", DateTime.Today.AddDays(-30)); var mainQuery = new QueryBreakdown("*", "RESULTS"); mainQuery.AddParameter("ResultLimit", 50); mainQuery.AddWithClause("ACTIVE_USERS", cte1); mainQuery.AddWithClause("RECENT_ORDERS", cte2); // Act var merged = mainQuery.GetMergedParameters(); // Assert var keys = merged.Keys.ToList(); Assert.Multiple(() => { Assert.That(keys.Any(k => k.Contains("Status1"))); Assert.That(keys.Any(k => k.Contains("MinDate"))); Assert.That(keys.Any(k => k.Contains("ResultLimit"))); }); } [Test] public void GetMergedParameters_WithParameterConflict_MainQueryTakesPrecedence() { // Arrange var cte = new QueryBreakdown("ID, NAME", "BASE_USERS"); cte.AddParameter("UserId", 999); // CTE version var mainQuery = new QueryBreakdown("*", "ACTIVE_USERS"); mainQuery.AddParameter("UserId", 123); // Main query version (should win) mainQuery.AddWithClause("ACTIVE_USERS", cte); // Act var merged = mainQuery.GetMergedParameters(); // Assert // Find the UserId parameter value var userIdKey = merged.Keys.FirstOrDefault(k => k.Contains("UserId") && !k.Contains('$')); Assert.Multiple(() => { Assert.That(userIdKey, Is.Not.Null); Assert.That(merged[userIdKey!], Is.EqualTo(123)); // Main query value, not CTE value }); } [Test] public void GetMergedParameters_WithNestedCTEs_CollectsAllParameters() { // Arrange var cte1 = new QueryBreakdown("id, name", "base_users"); cte1.AddParameter("Status", "active"); var cte2 = new QueryBreakdown("*", "NESTED_USERS"); cte2.AddParameter("Level", 1); cte2.AddWithClause("ACTIVE_USERS", cte1); var mainQuery = new QueryBreakdown("*", "FINAL_RESULTS"); mainQuery.AddParameter("Limit", 100); mainQuery.AddWithClause("FILTERED_USERS", cte2); // Act var merged = mainQuery.GetMergedParameters(); // Assert var keys = merged.Keys.ToList(); Assert.Multiple(() => { Assert.That(keys.Any(k => k.Contains("Status")), "Should include Status from nested CTE"); Assert.That(keys.Any(k => k.Contains("Level")), "Should include Level from middle CTE"); Assert.That(keys.Any(k => k.Contains("Limit")), "Should include Limit from main query"); }); } [Test] public void GetMergedParameters_PreservesParameterTypes() { // Arrange var cte = new QueryBreakdown("id, name", "users"); cte.AddParameter("CreatedDate", new DateTime(2024, 1, 1)); var mainQuery = new QueryBreakdown("*", "results"); mainQuery.AddParameter("IsActive", true); mainQuery.AddParameter("Threshold", 42.5); mainQuery.AddWithClause("users_cte", cte); // Act var merged = mainQuery.GetMergedParameters(); // Assert var dateKey = merged.Keys.FirstOrDefault(k => k.Contains("CreatedDate") && !k.Contains('$')); var boolKey = merged.Keys.FirstOrDefault(k => k.Contains("IsActive") && !k.Contains('$')); var doubleKey = merged.Keys.FirstOrDefault(k => k.Contains("Threshold") && !k.Contains('$')); Assert.Multiple(() => { Assert.That(dateKey, Is.Not.Null); Assert.That(merged[dateKey!], Is.TypeOf()); Assert.That(boolKey, Is.Not.Null); Assert.That(merged[boolKey!], Is.TypeOf()); Assert.That(doubleKey, Is.Not.Null); Assert.That(merged[doubleKey!], Is.TypeOf()); }); } #endregion }