Files
sql-utilities/tests/Strata.SqlTools.SqlServer.Tests/SqlServer/QueryBreakdownTests.cs
T
Thom LambandClaude Opus 4.7 505ada5017 chore(sonar): adopt Has.Count, Is.Empty, Does.Contain, char overloads in tests (NUnit2046, NUnit2011, CA1866)
- NUnit2046: `Assert.That(x.Count, Is.EqualTo(n))` → `Assert.That(x, Has.Count.EqualTo(n))` (or `Is.Empty` when n==0)
- NUnit2011: `Assert.That(s.Contains(x))` → `Assert.That(s, Does.Contain(x))` for richer failure messages
- CA1866: `.StartsWith("$"|"@"|":")` → `.StartsWith('$'|'@'|':')` char overload

Driven by `dotnet format analyzers --diagnostics NUnit2046 NUnit2011 CA1866`
for the cases the Roslyn fixer handles, plus a regex sweep for the remaining
`Count == n` (n>0) cases which the fixer doesn't address. CA1866 had no
associated code fix and was edited by hand (3 sites in 2 files). All tests
green (1180 passing).

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

2169 lines
66 KiB
C#

using Strata.SqlTools.Breakdowns.SqlServer;
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
using Strata.SqlTools.SqlServer.Exceptions;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer;
[TestFixture]
public class QueryBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyProperties()
{
// Arrange & Act
var query = new QueryBreakdown();
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo(string.Empty));
Assert.That(query.FromClause.Clause, Is.EqualTo(string.Empty));
Assert.That(query.WhereClause.Clause, Is.EqualTo(string.Empty));
Assert.That(query.OrderByClause.Clause, Is.EqualTo(string.Empty));
Assert.That(query.GroupByClause.Clause, Is.EqualTo(string.Empty));
Assert.That(query.HavingClause.Clause, Is.EqualTo(string.Empty));
Assert.That(query.SetupClauses, Is.Not.Null);
Assert.That(query.FinishClauses, Is.Not.Null);
Assert.That(query.WithClauses, Is.Not.Null);
Assert.That(query.WithClauses, Is.Empty);
Assert.That(query.Parameters, Is.Not.Null);
Assert.That(query.ParameterList, Is.Not.Null);
}
[Test]
public void Constructor_WithSelectAndFrom_SetsProperties()
{
// Arrange
var selectClause = "ID, Name";
var fromClause = "Users";
// Act
var query = new QueryBreakdown(selectClause, fromClause);
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo(selectClause));
Assert.That(query.FromClause.Clause, Is.EqualTo(fromClause));
Assert.That(query.WhereClause.Clause, Is.EqualTo(string.Empty));
}
[Test]
public void Constructor_WithSelectFromAndWhere_SetsProperties()
{
// Arrange
var selectClause = "ID, Name";
var fromClause = "Users";
var whereClause = "Active = 1";
// Act
var query = new QueryBreakdown(selectClause, fromClause, whereClause);
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo(selectClause));
Assert.That(query.FromClause.Clause, Is.EqualTo(fromClause));
Assert.That(query.WhereClause.Clause, Is.EqualTo(whereClause));
Assert.That(query.OrderByClause.Clause, Is.EqualTo(string.Empty));
}
[Test]
public void Constructor_WithAllClauses_SetsProperties()
{
// Arrange
var selectClause = "ID, Name";
var fromClause = "Users";
var whereClause = "Active = 1";
var orderByClause = "Name ASC";
// Act
var query = new QueryBreakdown(selectClause, fromClause, whereClause, orderByClause);
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo(selectClause));
Assert.That(query.FromClause.Clause, Is.EqualTo(fromClause));
Assert.That(query.WhereClause.Clause, Is.EqualTo(whereClause));
Assert.That(query.OrderByClause.Clause, Is.EqualTo(orderByClause));
}
#endregion
#region Property Tests
[Test]
public void IsUsingFromClause_WhenFromIsEmpty_ReturnsFalse()
{
// Arrange
var query = new QueryBreakdown();
query.FromClause.Clause = string.Empty;
// Act & Assert
Assert.That(query.IsUsingFromClause, Is.False);
}
[Test]
public void IsUsingFromClause_WhenFromHasValue_ReturnsTrue()
{
// Arrange
var query = new QueryBreakdown();
query.FromClause.Clause = "Users";
// Act & Assert
Assert.That(query.IsUsingFromClause, Is.True);
}
[Test]
public void IsUsingWithClause_WhenNoWithClauses_ReturnsFalse()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.That(query.IsUsingWithClause, Is.False);
}
[Test]
public void IsUsingWithClause_WhenWithClausesAdded_ReturnsTrue()
{
// Arrange
var query = new QueryBreakdown("*", "Products");
var cteQuery = new QueryBreakdown("ProductID, SUM(Quantity) AS Total", "OrderDetails");
// Act
query.AddWithClause("ProductSummary", cteQuery);
// Assert
Assert.That(query.IsUsingWithClause, Is.True);
}
#endregion
#region Parameter Tests
[Test]
public void AddParameter_WithNameAndValue_AddsToParameterList()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddParameter("UserId", 123);
// Assert
Assert.That(query.ParameterList.Count(), Is.EqualTo(1));
Assert.That(query.Parameters["@UserId"], Is.EqualTo(123));
}
[Test]
public void AddParameter_WithAtSymbol_AddsCorrectly()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddParameter("@UserId", 456);
// Assert
Assert.That(query.Parameters["@UserId"], Is.EqualTo(456));
}
[Test]
public void SetParameterValue_UpdatesExistingParameter()
{
// Arrange
var query = new QueryBreakdown();
query.AddParameter("UserId", 123);
// Act
query.SetParameterValue("UserId", 789);
// Assert
Assert.That(query.GetParameterValue("UserId"), Is.EqualTo(789));
}
[Test]
public void GetParameterValue_ForNonExistentParameter_ReturnsNull()
{
// Arrange
var query = new QueryBreakdown();
// Act
var value = query.GetParameterValue("NonExistent");
// Assert
Assert.That(value, Is.Null);
}
#endregion
#region CTE Parameter Inheritance Tests
[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
Assert.That(merged, Has.Count.EqualTo(2));
Assert.That(merged["@UserId"], Is.EqualTo(123));
Assert.That(merged["@ActiveStatus"], Is.EqualTo(true));
}
[Test]
public void GetMergedParameters_WithSingleCTE_MergesParameters()
{
// Arrange
var cte = new QueryBreakdown("id, name", "base_users", "active = 1");
cte.AddParameter("@MinDate", DateTime.Today.AddDays(-30));
var mainQuery = new QueryBreakdown("*", "active_users");
mainQuery.AddParameter("@Limit", 100);
mainQuery.AddWithClause("active_users", cte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
Assert.That(merged, Has.Count.EqualTo(2));
Assert.That(merged["@MinDate"], Is.EqualTo(DateTime.Today.AddDays(-30)));
Assert.That(merged["@Limit"], Is.EqualTo(100));
}
[Test]
public void GetMergedParameters_WithMultipleCTEs_MergesAllParameters()
{
// Arrange
var cte1 = new QueryBreakdown("id, status", "users", "status = @Status1");
cte1.AddParameter("@Status1", "active");
var cte2 = new QueryBreakdown("user_id, created", "orders", "created >= @MinDate");
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
Assert.That(merged, Has.Count.EqualTo(3));
Assert.That(merged["@Status1"], Is.EqualTo("active"));
Assert.That(merged["@MinDate"], Is.EqualTo(DateTime.Today.AddDays(-30)));
Assert.That(merged["@ResultLimit"], Is.EqualTo(50));
}
[Test]
public void GetMergedParameters_WithParameterConflict_MainQueryTakesPrecedence()
{
// Arrange
var cte = new QueryBreakdown("id", "users");
cte.AddParameter("@Limit", 10); // CTE sets Limit to 10
var mainQuery = new QueryBreakdown("*", "final_result");
mainQuery.AddParameter("@Limit", 100); // Main query sets Limit to 100
mainQuery.AddWithClause("base_data", cte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
Assert.That(merged, Has.Count.EqualTo(1));
Assert.That(merged["@Limit"], Is.EqualTo(100)); // Main query value wins
}
[Test]
public void GetMergedParameters_WithNestedCTEs_CollectsAllParameters()
{
// Arrange: Create deeply nested CTEs
var innerCte = new QueryBreakdown("id", "base_table");
innerCte.AddParameter("@InnerParam", "inner_value");
var middleCte = new QueryBreakdown("id, name", "layer1");
middleCte.AddParameter("@MiddleParam", "middle_value");
middleCte.AddWithClause("layer1_source", innerCte);
var mainQuery = new QueryBreakdown("id, name, status", "layer2");
mainQuery.AddParameter("@MainParam", "main_value");
mainQuery.AddWithClause("layer2_source", middleCte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert - All parameters from all levels should be present
Assert.That(merged, Has.Count.EqualTo(3));
Assert.That(merged["@InnerParam"], Is.EqualTo("inner_value"));
Assert.That(merged["@MiddleParam"], Is.EqualTo("middle_value"));
Assert.That(merged["@MainParam"], Is.EqualTo("main_value"));
}
[Test]
public void GetMergedParameters_WithEmptyParameters_ReturnsEmpty()
{
// Arrange
var cte = new QueryBreakdown("id", "table1");
// No parameters added
var mainQuery = new QueryBreakdown("*", "table2");
// No parameters added
mainQuery.AddWithClause("source", cte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
Assert.That(merged, Is.Empty);
}
[Test]
public void GetMergedParameters_PreservesParameterTypes()
{
// Arrange
var cte = new QueryBreakdown("id", "users");
cte.AddParameter("@IntValue", 123);
cte.AddParameter("@StringValue", "test");
cte.AddParameter("@DateValue", DateTime.Today);
cte.AddParameter("@DecimalValue", 99.99m);
var mainQuery = new QueryBreakdown("*", "results");
mainQuery.AddWithClause("source", cte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
Assert.That(merged["@IntValue"], Is.TypeOf<int>());
Assert.That(merged["@StringValue"], Is.TypeOf<string>());
Assert.That(merged["@DateValue"], Is.TypeOf<DateTime>());
Assert.That(merged["@DecimalValue"], Is.TypeOf<decimal>());
}
#endregion
#region AddWhereClause Tests
[Test]
public void AddWhereClause_WhenEmpty_SetsWhereClause()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddWhereClause("Active = 1");
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("Active = 1"));
}
[Test]
public void AddWhereClause_WhenExists_AppendsWithAnd()
{
// Arrange
var query = new QueryBreakdown();
query.WhereClause.Clause = "Active = 1";
// Act
query.AddWhereClause("Status = 'Open'");
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("Active = 1 and Status = 'Open'"));
}
[Test]
public void AddWhereClause_WithOrOperation_AppendsWithOr()
{
// Arrange
var query = new QueryBreakdown();
query.WhereClause.Clause = "Active = 1";
// Act
query.AddWhereClause("Status = 'Closed'", "or");
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("Active = 1 or Status = 'Closed'"));
}
[Test]
public void AddWhereClause_WithNullOrEmpty_DoesNothing()
{
// Arrange
var query = new QueryBreakdown();
query.WhereClause.Clause = "Active = 1";
// Act
query.AddWhereClause(null!);
query.AddWhereClause(string.Empty);
query.AddWhereClause(" ");
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("Active = 1"));
}
[Test]
public void AddWhereClause_WithParameters_ExtractsParametersAutomatically()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddWhereClause("UserID = @UserId AND Status = @Status");
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("UserID = @UserId AND Status = @Status"));
Assert.That(query.Parameters, Does.ContainKey("@UserId"));
Assert.That(query.Parameters, Does.ContainKey("@Status"));
Assert.That(query.Parameters["@UserId"], Is.Null);
Assert.That(query.Parameters["@Status"], Is.Null);
}
[Test]
public void AddWhereClause_WithExistingParameter_KeepsExistingValue()
{
// Arrange
var query = new QueryBreakdown();
query.Parameters["@UserId"] = 123;
// Act - Add where clause with same parameter
query.AddWhereClause("UserID = @UserId");
// Assert - Original value should be preserved
Assert.That(query.Parameters["@UserId"], Is.EqualTo(123));
}
[Test]
public void AddWhereClause_WithExistingNullParameter_SetsValue()
{
// Arrange
var query = new QueryBreakdown();
query.Parameters["@UserId"] = null!;
// Act - Try to set it via direct method (simulating manual set)
query.SetParameterValue("@UserId", 456);
// Add where clause with same parameter - should keep the set value
query.AddWhereClause("UserID = @UserId");
// Assert - Value should be set
Assert.That(query.Parameters["@UserId"], Is.EqualTo(456));
}
[Test]
public void AddWhereClause_WithMultipleCalls_AccumulatesParameters()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddWhereClause("UserID = @UserId");
query.AddWhereClause("Status = @Status", "and");
query.AddWhereClause("CreatedDate > @StartDate", "and");
// Assert
Assert.That(query.Parameters, Has.Count.EqualTo(3));
Assert.That(query.Parameters, Does.ContainKey("@UserId"));
Assert.That(query.Parameters, Does.ContainKey("@Status"));
Assert.That(query.Parameters, Does.ContainKey("@StartDate"));
}
[Test]
public void AddWhereClause_AfterSettingParameterValue_PreservesValue()
{
// Arrange
var query = new QueryBreakdown();
query.SetParameterValue("@UserId", 123);
query.SetParameterValue("@Status", "Active");
// Act - AddWhereClause should not overwrite existing non-null values
query.AddWhereClause("UserID = @UserId AND Status = @Status");
// Assert - Values should be preserved
Assert.That(query.Parameters["@UserId"], Is.EqualTo(123));
Assert.That(query.Parameters["@Status"], Is.EqualTo("Active"));
}
#endregion
#region Parameter Management Tests
[Test]
public void AddOrUpdateParameter_NewParameter_AddsToParameters()
{
// Arrange
var query = new QueryBreakdown();
// Act - Use AddWhereClause which internally calls AddOrUpdateParameter
query.AddWhereClause("UserID = @NewParam");
// Assert
Assert.That(query.Parameters, Does.ContainKey("@NewParam"));
}
[Test]
public void AddOrUpdateParameter_ExistingParameterWithNull_SetsValue()
{
// Arrange
var query = new QueryBreakdown();
query.AddWhereClause("UserID = @UserId"); // Creates parameter with null
Assert.That(query.Parameters["@UserId"], Is.Null);
// Act - Set value
query.SetParameterValue("@UserId", 789);
// Assert
Assert.That(query.Parameters["@UserId"], Is.EqualTo(789));
}
[Test]
public void SetParameterValue_ExistingParameterSameType_UpdatesValue()
{
// Arrange
var query = new QueryBreakdown();
query.SetParameterValue("@UserId", 100);
// Act
query.SetParameterValue("@UserId", 200);
// Assert
Assert.That(query.Parameters["@UserId"], Is.EqualTo(200));
}
[Test]
public void Parameters_ManuallySettingDifferentType_CanBeDetectedOnNextAddWhereClause()
{
// Arrange
var query = new QueryBreakdown();
query.SetParameterValue("@UserId", 100); // Set as int
// Manually change to different type (simulating external manipulation)
query.Parameters["@UserId"] = "StringValue";
// Act - AddWhereClause with same parameter should work since extraction creates null values
// This demonstrates the parameters dictionary can hold different types if manually set
query.AddWhereClause("UserID = @UserId");
// Assert - The manual string value is preserved because extracted value is null
Assert.That(query.Parameters["@UserId"], Is.EqualTo("StringValue"));
}
#endregion
#region AddWithClause Tests
[Test]
public void AddWithClause_WithQueryBreakdown_AddsToList()
{
// Arrange
var mainQuery = new QueryBreakdown("*", "Products");
var cteQuery = new QueryBreakdown("ProductID, COUNT(*) AS OrderCount", "Orders");
cteQuery.GroupByClause.Clause = "ProductID";
// Act
mainQuery.AddWithClause("ProductOrders", cteQuery);
// Assert
Assert.That(mainQuery.WithClauses, Has.Count.EqualTo(1));
Assert.That(mainQuery.WithClauses[0].TableName, Is.EqualTo("ProductOrders"));
Assert.That(mainQuery.WithClauses[0].Query, Is.EqualTo(cteQuery));
}
[Test]
public void AddWithClause_WithMultipleCTEs_MaintainsOrder()
{
// Arrange
var mainQuery = new QueryBreakdown("*", "FinalData");
var cte1 = new QueryBreakdown("*", "Table1");
var cte2 = new QueryBreakdown("*", "Table2");
var cte3 = new QueryBreakdown("*", "Table3");
// Act
mainQuery.AddWithClause("CTE1", cte1);
mainQuery.AddWithClause("CTE2", cte2);
mainQuery.AddWithClause("CTE3", cte3);
// Assert
Assert.That(mainQuery.WithClauses, Has.Count.EqualTo(3));
Assert.That(mainQuery.WithClauses[0].TableName, Is.EqualTo("CTE1"));
Assert.That(mainQuery.WithClauses[1].TableName, Is.EqualTo("CTE2"));
Assert.That(mainQuery.WithClauses[2].TableName, Is.EqualTo("CTE3"));
}
[Test]
public void AddWithClause_WithSqlString_ParsesAndAdds()
{
// Arrange
var mainQuery = new QueryBreakdown("*", "Products p INNER JOIN ProductOrders po ON p.ProductID = po.ProductID");
var cteSql = "SELECT ProductID, COUNT(*) AS OrderCount FROM Orders GROUP BY ProductID";
// Act
mainQuery.AddWithClause("ProductOrders", cteSql, isMicrosoftSql: true);
// Assert
Assert.That(mainQuery.WithClauses, Has.Count.EqualTo(1));
Assert.That(mainQuery.WithClauses[0].TableName, Is.EqualTo("ProductOrders"));
Assert.That(mainQuery.WithClauses[0].Query!.SelectClause.Clause, Does.Contain("ProductID"));
Assert.That(mainQuery.WithClauses[0].Query!.GroupByClause.Clause, Does.Contain("ProductID"));
}
[Test]
public void AddWithClause_WithNullName_ThrowsCteValidationException()
{
// Arrange
var mainQuery = new QueryBreakdown("*", "Products");
var cteQuery = new QueryBreakdown("*", "Orders");
// Act & Assert
Assert.Throws<CteValidationException>(() => mainQuery.AddWithClause(null!, cteQuery));
}
[Test]
public void AddWithClause_WithNullQuery_ThrowsCteValidationException()
{
// Arrange
var mainQuery = new QueryBreakdown("*", "Products");
// Act & Assert
Assert.Throws<CteValidationException>(() => mainQuery.AddWithClause("ProductOrders", (QueryBreakdown)null!));
}
[Test]
public void AddWithClause_WithNullSql_ThrowsCteValidationException()
{
// Arrange
var mainQuery = new QueryBreakdown("*", "Products");
// Act & Assert
Assert.Throws<CteValidationException>(() => mainQuery.AddWithClause("ProductOrders", (string)null!));
}
#endregion
#region CTE Integration Tests
[Test]
public void CTE_NestedCTEs_WithParameterFlow()
{
// Arrange: Create inner CTE with parameters
var innerCte = new QueryBreakdown("id, name, created_date", "users", "created_date >= @StartDate");
innerCte.AddParameter("@StartDate", DateTime.Today.AddDays(-30));
// Arrange: Create middle CTE that references inner CTE
var middleCte = new QueryBreakdown("id, name", "recent_users", "id > @MinUserId");
middleCte.AddParameter("@MinUserId", 100);
// Arrange: Create main query that references middle CTE
var mainQuery = new QueryBreakdown("COUNT(*) AS ActiveUserCount", "filtered_users");
mainQuery.AddWithClause("recent_users", innerCte);
mainQuery.AddWithClause("filtered_users", middleCte);
// Act
var sql = mainQuery.GetSql();
var innerParams = innerCte.ParameterList.Count();
var middleParams = middleCte.ParameterList.Count();
// Assert - Each CTE maintains its own parameters
Assert.That(innerParams, Is.EqualTo(1));
Assert.That(middleParams, Is.EqualTo(1));
// SQL should contain both WITH clauses
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("recent_users"));
Assert.That(sql, Does.Contain("filtered_users"));
}
[Test]
public void CTE_MultipleCTEs_WithComplexQueries()
{
// Arrange: Create CTE 1 - Active users
var activeCte = new QueryBreakdown("id, email, status", "users", "status = 'active'");
// Arrange: Create CTE 2 - Recent orders
var ordersCte = new QueryBreakdown("user_id, COUNT(*) AS order_count, SUM(total) AS total_spent", "orders");
ordersCte.GroupByClause.Clause = "user_id";
ordersCte.HavingClause.Clause = "COUNT(*) > 0";
// Arrange: Create main query combining both CTEs
var mainQuery = new QueryBreakdown("a.id, a.email, o.order_count, o.total_spent",
"active_users a LEFT JOIN user_orders o ON a.id = o.user_id");
mainQuery.OrderByClause.Clause = "o.total_spent DESC";
mainQuery.AddWithClause("active_users", activeCte);
mainQuery.AddWithClause("user_orders", ordersCte);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("active_users"));
Assert.That(sql, Does.Contain("user_orders"));
Assert.That(sql, Does.Contain("GROUP BY"));
Assert.That(sql, Does.Contain("HAVING"));
Assert.That(sql, Does.Contain("LEFT JOIN"));
Assert.That(sql, Does.Contain("ORDER BY"));
}
[Test]
public void CTE_GetSql_GeneratesProperCTESyntax()
{
// Arrange
var cteQuery = new QueryBreakdown("ProductID, SUM(Quantity) AS TotalQuantity", "OrderDetails");
cteQuery.GroupByClause.Clause = "ProductID";
var mainQuery = new QueryBreakdown("p.ProductName, pt.TotalQuantity", "Products p INNER JOIN ProductTotals pt ON p.ProductID = pt.ProductID");
mainQuery.AddWithClause("ProductTotals", cteQuery);
// Act
var sql = mainQuery.GetSql();
// Assert - Verify WITH clause appears before main SELECT
var withIndex = sql.IndexOf("WITH");
var mainSelectIndex = sql.LastIndexOf("SELECT");
Assert.That(withIndex, Is.GreaterThan(-1));
Assert.That(mainSelectIndex, Is.GreaterThan(-1));
Assert.That(withIndex, Is.LessThan(mainSelectIndex));
// Assert - Verify CTE definition
Assert.That(sql, Does.Contain("ProductTotals"));
Assert.That(sql, Does.Contain("AS"));
Assert.That(sql, Does.Contain("ProductID"));
Assert.That(sql, Does.Contain("TotalQuantity"));
}
[Test]
public void CTE_WithClauseProperties_RoundTrip()
{
// Arrange: Create original query
var cteQuery = new QueryBreakdown("id, name", "employees", "department = 'Sales'");
var mainQuery = new QueryBreakdown("*", "sales_team");
mainQuery.AddWithClause("sales_team", cteQuery);
// Act: Get SQL and verify CTE is properly included
var sql = mainQuery.GetSql();
var withClause = mainQuery.WithClauses[0];
// Assert: Verify WithClause properties
Assert.That(withClause.TableName, Is.EqualTo("sales_team"));
Assert.That(withClause.Query, Is.Not.Null);
Assert.That(withClause.Sql, Is.Not.Null);
Assert.That(withClause.Sql?.SelectClause?.Clause, Contains.Substring("id, name"));
Assert.That(withClause.Sql?.FromClause?.Clause, Contains.Substring("employees"));
Assert.That(withClause.Sql?.WhereClause?.Clause, Contains.Substring("department = 'Sales'"));
}
[Test]
public void CTE_IsUsingWithClause_ReturnsCorrectStatus()
{
// Arrange
var query = new QueryBreakdown("*", "data");
// Act & Assert - No WITH clauses yet
Assert.That(query.IsUsingWithClause, Is.False);
// Act - Add a CTE
var cte = new QueryBreakdown("id, name", "source_table");
query.AddWithClause("source_cte", cte);
// Assert - Now using WITH clause
Assert.That(query.IsUsingWithClause, Is.True);
Assert.That(query.WithClauses, Has.Count.EqualTo(1));
}
#endregion
#region GetSql Tests
[Test]
public void GetSql_SimpleSelectFrom_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users");
// Act
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
Assert.That(sql, Does.Contain("ID, Name"));
Assert.That(sql, Does.Contain("FROM"));
Assert.That(sql, Does.Contain("Users"));
}
[Test]
public void GetSql_WithAllClauses_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown("Department, COUNT(*) AS EmpCount", "Employees");
query.WhereClause.Clause = "Active = 1";
query.GroupByClause.Clause = "Department";
query.HavingClause.Clause = "COUNT(*) > 5";
query.OrderByClause.Clause = "Department ASC";
// Act
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
Assert.That(sql, Does.Contain("FROM"));
Assert.That(sql, Does.Contain("WHERE"));
Assert.That(sql, Does.Contain("GROUP BY"));
Assert.That(sql, Does.Contain("HAVING"));
Assert.That(sql, Does.Contain("ORDER BY"));
}
[Test]
public void GetSql_WithSingleWithClause_GeneratesCorrectSql()
{
// Arrange
var mainQuery = new QueryBreakdown("p.*, ps.TotalQty", "Products p INNER JOIN ProductSummary ps ON p.ProductID = ps.ProductID");
var cte = new QueryBreakdown("ProductID, SUM(Quantity) AS TotalQty", "OrderDetails");
cte.GroupByClause.Clause = "ProductID";
mainQuery.AddWithClause("ProductSummary", cte);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("ProductSummary AS ("));
Assert.That(sql, Does.Contain("SUM(Quantity) AS TotalQty"));
Assert.That(sql, Does.Contain("GROUP BY"));
}
[Test]
public void GetSql_WithMultipleWithClauses_GeneratesCorrectSqlWithCommas()
{
// Arrange
var mainQuery = new QueryBreakdown("*", "CTE3");
var cte1 = new QueryBreakdown("Col1", "Table1");
var cte2 = new QueryBreakdown("Col2", "Table2");
var cte3 = new QueryBreakdown("Col3", "Table3");
mainQuery.AddWithClause("CTE1", cte1);
mainQuery.AddWithClause("CTE2", cte2);
mainQuery.AddWithClause("CTE3", cte3);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("CTE1 AS ("));
Assert.That(sql, Does.Contain("CTE2 AS ("));
Assert.That(sql, Does.Contain("CTE3 AS ("));
// Verify commas between CTEs by checking the pattern
// CTE1 should be followed by ), then comma, then CTE2
var cte1Index = sql.IndexOf("CTE1 AS (");
var cte2Index = sql.IndexOf("CTE2 AS (", cte1Index);
var betweenCtes = sql.Substring(cte1Index, cte2Index - cte1Index);
Assert.That(betweenCtes, Does.Contain("),"), "CTE1 should be followed by a comma before CTE2");
// Verify comma between CTE2 and CTE3
var cte3Index = sql.IndexOf("CTE3 AS (", cte2Index);
var betweenCte2And3 = sql.Substring(cte2Index, cte3Index - cte2Index);
Assert.That(betweenCte2And3, Does.Contain("),"), "CTE2 should be followed by a comma before CTE3");
}
[Test]
public void GetSql_WithSetupClauses_IncludesSetup()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
query.SetupClauses.Add("DECLARE @Status INT = 1");
// Act
var sql = query.GetSql(includeSetupFinish: true);
// Assert
Assert.That(sql, Does.Contain("DECLARE @Status INT = 1"));
}
[Test]
public void GetSql_WithFinishClauses_IncludesFinish()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
query.FinishClauses.Add("DROP TABLE #TempUsers");
// Act
var sql = query.GetSql(includeSetupFinish: true);
// Assert
Assert.That(sql, Does.Contain("DROP TABLE #TempUsers"));
}
[Test]
public void GetSql_WithIncludeSetupFinishFalse_ExcludesSetupAndFinish()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
query.SetupClauses.Add("DECLARE @Status INT = 1");
query.FinishClauses.Add("DROP TABLE #TempUsers");
// Act
var sql = query.GetSql(includeSetupFinish: false);
// Assert
Assert.That(sql, Does.Not.Contain("DECLARE"));
Assert.That(sql, Does.Not.Contain("DROP TABLE"));
}
#endregion
#region Parse Tests
[Test]
public void Parse_SimpleSelectStatement_ParsesCorrectly()
{
// Arrange
var sql = "SELECT ID, Name FROM Users";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause?.Trim(), Does.Contain("ID, Name"));
Assert.That(query.FromClause.Clause?.Trim(), Does.Contain("Users"));
}
[Test]
public void Parse_WithWhereClause_ParsesCorrectly()
{
// Arrange
var sql = "SELECT ID, Name FROM Users WHERE Active = 1";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause?.Trim(), Does.Contain("ID, Name"));
Assert.That(query.FromClause.Clause?.Trim(), Does.Contain("Users"));
Assert.That(query.WhereClause.Clause?.Trim(), Does.Contain("Active = 1"));
}
[Test]
public void Parse_WithGroupByAndHaving_ParsesCorrectly()
{
// Arrange
var sql = "SELECT Department, COUNT(*) AS EmpCount FROM Employees GROUP BY Department HAVING COUNT(*) > 5";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause?.Trim(), Does.Contain("Department"));
Assert.That(query.GroupByClause.Clause?.Trim(), Does.Contain("Department"));
Assert.That(query.HavingClause.Clause?.Trim(), Does.Contain("COUNT(*) > 5"));
}
[Test]
public void Parse_WithOrderBy_ParsesCorrectly()
{
// Arrange
var sql = "SELECT ID, Name FROM Users ORDER BY Name ASC";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.OrderByClause.Clause?.Trim(), Does.Contain("Name ASC"));
}
[Test]
public void Parse_WithParameters_ExtractsParameters()
{
// Arrange
var sql = "SELECT ID, Name FROM Users WHERE UserID = @UserId AND Status = @Status";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.Parameters, Does.ContainKey("@UserId"));
Assert.That(query.Parameters, Does.ContainKey("@Status"));
}
[Test]
public void Parse_WithWithClause_ParsesCorrectly()
{
// Arrange
var sql = @"WITH ProductSummary AS (
SELECT ProductID, SUM(Quantity) AS TotalQty
FROM OrderDetails
GROUP BY ProductID
)
SELECT p.*, ps.TotalQty
FROM Products p
INNER JOIN ProductSummary ps ON p.ProductID = ps.ProductID";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.IsUsingWithClause, Is.True);
Assert.That(query.SelectClause.Clause?.Trim(), Does.Contain("p.*, ps.TotalQty"));
Assert.That(query.FromClause.Clause?.Trim(), Does.Contain("Products p"));
}
[Test]
public void Parse_WithNullSql_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(null!));
}
[Test]
public void Parse_WithEmptySql_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(string.Empty));
}
[Test]
public void Parse_WithInvalidSql_ThrowsSqlParseException()
{
// Arrange
var sql = "INVALID SQL STATEMENT";
// Act & Assert
Assert.Throws<SqlParseException>(() => QueryBreakdown.Parse(sql));
}
[Test]
public void TryParse_WithValidSql_ReturnsTrue()
{
// Arrange
var sql = "SELECT ID FROM Users";
// Act
var success = QueryBreakdown.TryParse(sql, out var query);
// Assert
Assert.That(success, Is.True);
Assert.That(query, Is.Not.Null);
}
[Test]
public void TryParse_WithInvalidSql_ReturnsFalse()
{
// Arrange
var sql = "INVALID SQL";
// Act
var success = QueryBreakdown.TryParse(sql, out var query, out var error);
// Assert
Assert.That(success, Is.False);
Assert.That(query, Is.Null);
Assert.That(error, Is.Not.Null);
}
#endregion
#region MergeWith Tests
[Test]
public void MergeWith_AnotherQuery_MergesFromClauses()
{
// Arrange
var query1 = new QueryBreakdown("*", "Users u");
var query2 = new QueryBreakdown();
query2.FromClause.Clause = "INNER JOIN Orders o ON u.UserID = o.UserID";
// Act
query1.MergeWith(query2);
// Assert
Assert.That(query1.FromClause.Clause, Does.Contain("Users u"));
Assert.That(query1.FromClause.Clause, Does.Contain("INNER JOIN Orders"));
}
[Test]
public void MergeWith_WithWhereClauses_MergesWithAnd()
{
// Arrange
var query1 = new QueryBreakdown();
query1.WhereClause.Clause = "Active = 1";
var query2 = new QueryBreakdown();
query2.WhereClause.Clause = "Status = 'Open'";
// Act
query1.MergeWith(query2);
// Assert
Assert.That(query1.WhereClause.Clause, Does.Contain("Active = 1"));
Assert.That(query1.WhereClause.Clause, Does.Contain("AND"));
Assert.That(query1.WhereClause.Clause, Does.Contain("Status = 'Open'"));
}
[Test]
public void MergeWith_Parameters_MergesParameters()
{
// Arrange
var query1 = new QueryBreakdown();
query1.AddParameter("Param1", 123);
var query2 = new QueryBreakdown();
query2.AddParameter("Param2", 456);
// Act
query1.MergeWith(query2);
// Assert
Assert.That(query1.Parameters, Does.ContainKey("@Param1"));
Assert.That(query1.Parameters, Does.ContainKey("@Param2"));
}
#endregion
#region Clone Tests
[Test]
public void Clone_CreatesDeepCopy()
{
// Arrange
var original = new QueryBreakdown("*", "Employees", "Active = 1", "Name ASC");
original.AddParameter("UserId", 123);
// Act
var clone = (QueryBreakdown)original.Clone();
// Assert
Assert.That(clone, Is.Not.SameAs(original));
Assert.That(clone.SelectClause.Clause, Is.EqualTo(original.SelectClause.Clause));
Assert.That(clone.FromClause.Clause, Is.EqualTo(original.FromClause.Clause));
Assert.That(clone.WhereClause.Clause, Is.EqualTo(original.WhereClause.Clause));
Assert.That(clone.OrderByClause.Clause, Is.EqualTo(original.OrderByClause.Clause));
}
[Test]
public void Clone_ModifyingClone_DoesNotAffectOriginal()
{
// Arrange
var original = new QueryBreakdown("*", "Users");
var clone = (QueryBreakdown)original.Clone();
// Act
clone.WhereClause.Clause = "Active = 1";
// Assert
Assert.That(original.WhereClause.Clause, Is.EqualTo(string.Empty));
Assert.That(clone.WhereClause.Clause, Is.EqualTo("Active = 1"));
}
#endregion
#region ToString Tests
[Test]
public void ToString_ReturnsSqlString()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users");
// Act
var result = query.ToString();
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("ID, Name"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("Users"));
}
#endregion
#region Outlier Conditions - SQL Injection & Malformed SQL
[Test]
public void Parse_WithSingleQuotesInString_ParsesCorrectly()
{
// Arrange
var sql = "SELECT Name FROM Users WHERE Name = 'O''Brien'";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query, Is.Not.Null);
Assert.That(query.WhereClause.Clause, Does.Contain("O''Brien"));
}
[Test]
public void Parse_WithDoubleQuotesInIdentifiers_ParsesCorrectly()
{
// Arrange
var sql = "SELECT \"User Name\", \"User ID\" FROM \"User Table\"";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("\"User Name\""));
Assert.That(query.FromClause.Clause, Does.Contain("\"User Table\""));
}
[Test]
public void Parse_WithSemicolonInString_DoesNotSplitStatement()
{
// Arrange
var sql = "SELECT Description FROM Products WHERE Description = 'Price: $10; Quantity: 5'";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("Price: $10; Quantity: 5"));
}
[Test]
public void Parse_WithCommentedOutClause_IgnoresComment()
{
// Arrange
var sql = @"SELECT ID, Name
FROM Users
-- WHERE Active = 1
ORDER BY Name";
// Act & Assert
// Parser may or may not handle comments - test gracefully
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql));
}
[Test]
public void Parse_WithMultiLineComment_HandlesGracefully()
{
// Arrange
var sql = @"SELECT ID /* This is the user ID */, Name /* User's full name */
FROM Users";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql));
}
[Test]
public void Parse_WithUnbalancedParenthesesInString_ParsesCorrectly()
{
// Arrange
var sql = "SELECT Description FROM Products WHERE Description = 'Test (unbalanced'";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql));
}
[Test]
public void AddParameter_WithSqlInjectionAttempt_StoresAsParameter()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
var maliciousValue = "1' OR '1'='1";
// Act
query.AddParameter("UserId", maliciousValue);
// Assert
Assert.That(query.Parameters["@UserId"], Is.EqualTo(maliciousValue));
}
#endregion
#region Outlier Conditions - Whitespace and Formatting
[Test]
public void Parse_WithExcessiveWhitespace_NormalizesCorrectly()
{
// Arrange
var sql = "SELECT ID, Name FROM Users WHERE Active=1";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query, Is.Not.Null);
Assert.That(query.SelectClause.Clause, Does.Contain("ID"));
}
[Test]
public void Parse_WithTabsAndNewlines_ParsesCorrectly()
{
// Arrange
var sql = "SELECT\tID,\nName\r\nFROM\tUsers\r\nWHERE\tActive=1";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query, Is.Not.Null);
}
[Test]
public void Parse_WithMixedLineEndings_HandlesCorrectly()
{
// Arrange
var sql = "SELECT ID\rFROM Users\nWHERE Active=1\r\nORDER BY Name";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query, Is.Not.Null);
}
[Test]
public void GetSql_WithVeryLongClause_GeneratesCorrectly()
{
// Arrange
var longSelectClause = string.Join(", ", Enumerable.Range(1, 100).Select(i => $"Column{i}"));
var query = new QueryBreakdown(longSelectClause, "LargeTable");
// Act
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("Column1"));
Assert.That(sql, Does.Contain("Column100"));
}
#endregion
#region Outlier Conditions - Special Characters and Unicode
[Test]
public void Parse_WithUnicodeCharacters_ParsesCorrectly()
{
// Arrange
var sql = "SELECT Name, Description FROM Products WHERE Name = '日本語テスト'";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query, Is.Not.Null);
Assert.That(query.WhereClause.Clause, Does.Contain("日本語テスト"));
}
[Test]
public void AddParameter_WithUnicodeValue_StoresCorrectly()
{
// Arrange
var query = new QueryBreakdown();
var unicodeValue = "Müller über größer äöü";
// Act
query.AddParameter("Name", unicodeValue);
// Assert
Assert.That(query.Parameters["@Name"], Is.EqualTo(unicodeValue));
}
[Test]
public void Parse_WithSpecialSqlCharacters_HandlesCorrectly()
{
// Arrange
var sql = "SELECT Price FROM Products WHERE Price >= 10.50 AND Discount <= 25%";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain(">="));
Assert.That(query.WhereClause.Clause, Does.Contain("<="));
}
#endregion
#region Outlier Conditions - Nested Queries and Complex Structures
[Test]
public void Parse_WithDeeplyNestedSubqueries_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT ID FROM Users
WHERE ID IN (
SELECT UserID FROM Orders
WHERE OrderID IN (
SELECT OrderID FROM OrderDetails
WHERE ProductID IN (SELECT ID FROM Products)
)
)";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query, Is.Not.Null);
Assert.That(query.WhereClause.Clause, Does.Contain("SELECT UserID FROM Orders"));
}
[Test]
public void Parse_WithCorrelatedSubquery_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT u.Name,
(SELECT COUNT(*) FROM Orders o WHERE o.UserID = u.ID) AS OrderCount
FROM Users u";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("SELECT COUNT(*)"));
}
[Test]
public void Parse_WithCaseExpression_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT
CASE
WHEN Status = 1 THEN 'Active'
WHEN Status = 0 THEN 'Inactive'
ELSE 'Unknown'
END AS StatusText
FROM Users";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("CASE"));
Assert.That(query.SelectClause.Clause, Does.Contain("WHEN"));
}
[Test]
public void Parse_WithMultipleJoins_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT u.Name, o.OrderDate, p.ProductName
FROM Users u
INNER JOIN Orders o ON u.ID = o.UserID
LEFT JOIN OrderDetails od ON o.ID = od.OrderID
RIGHT JOIN Products p ON od.ProductID = p.ID";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.FromClause.Clause, Does.Contain("INNER JOIN"));
Assert.That(query.FromClause.Clause, Does.Contain("LEFT JOIN"));
Assert.That(query.FromClause.Clause, Does.Contain("RIGHT JOIN"));
}
#endregion
#region Outlier Conditions - Reserved Words and Keywords
[Test]
public void Parse_WithReservedWordsAsIdentifiers_ParsesCorrectly()
{
// Arrange
var sql = "SELECT [Select], [From], [Where] FROM [Order]";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("[Select]"));
Assert.That(query.SelectClause.Clause, Does.Contain("[From]"));
Assert.That(query.SelectClause.Clause, Does.Contain("[Where]"));
Assert.That(query.FromClause.Clause, Does.Contain("[Order]"));
}
[Test]
public void Parse_WithKeywordInColumnAlias_ParsesCorrectly()
{
// Arrange
var sql = "SELECT ID AS [Select], Name AS [From] FROM Users";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("AS [Select]"));
}
#endregion
#region Outlier Conditions - Parameter Edge Cases
[Test]
public void Parse_WithDuplicateParameters_ExtractsBoth()
{
// Arrange
var sql = "SELECT * FROM Users WHERE ID = @UserId OR ParentID = @UserId";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.Parameters, Does.ContainKey("@UserId"));
}
[Test]
public void AddParameter_WithNullValue_StoresNull()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddParameter("NullParam", null!);
// Assert
Assert.That(query.Parameters, Does.ContainKey("@NullParam"));
Assert.That(query.Parameters["@NullParam"], Is.Null);
}
[Test]
public void AddParameter_WithDBNull_StoresCorrectly()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddParameter("DbNullParam", DBNull.Value);
// Assert
Assert.That(query.Parameters["@DbNullParam"], Is.EqualTo(DBNull.Value));
}
[Test]
public void AddParameter_WithVeryLongParameterName_StoresCorrectly()
{
// Arrange
var query = new QueryBreakdown();
var longName = "VeryLongParameterName" + new string('X', 100);
// Act
query.AddParameter(longName, 123);
// Assert
Assert.That(query.Parameters, Does.ContainKey($"@{longName}"));
}
[Test]
public void AddParameter_WithSpecialCharactersInName_StoresCorrectly()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddParameter("Param_With_Underscores", 123);
query.AddParameter("Param123WithNumbers", 456);
// Assert
Assert.That(query.Parameters, Does.ContainKey("@Param_With_Underscores"));
Assert.That(query.Parameters, Does.ContainKey("@Param123WithNumbers"));
}
[Test]
public void SetParameterValue_WithComplexObject_StoresCorrectly()
{
// Arrange
var query = new QueryBreakdown();
var complexObj = new { Id = 1, Name = "Test", Values = new[] { 1, 2, 3 } };
// Act
query.SetParameterValue("ComplexParam", complexObj);
// Assert
Assert.That(query.GetParameterValue("ComplexParam"), Is.EqualTo(complexObj));
}
#endregion
#region Outlier Conditions - WITH Clause Edge Cases
[Test]
public void AddWithClause_WithEmptyTableName_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
var cte = new QueryBreakdown("*", "Orders");
// Act & Assert
Assert.Throws<CteValidationException>(() => query.AddWithClause("", cte));
Assert.Throws<CteValidationException>(() => query.AddWithClause(" ", cte));
}
[Test]
public void AddWithClause_WithVeryLongCTEName_AddsCorrectly()
{
// Arrange
var query = new QueryBreakdown("*", "FinalTable");
var cte = new QueryBreakdown("*", "SourceTable");
var longName = "CTE_" + new string('X', 200);
// Act
query.AddWithClause(longName, cte);
// Assert
Assert.That(query.WithClauses[0].TableName, Is.EqualTo(longName));
}
[Test]
public void AddWithClause_WithSpecialCharactersInName_AddsCorrectly()
{
// Arrange
var query = new QueryBreakdown("*", "FinalTable");
var cte = new QueryBreakdown("*", "SourceTable");
// Act
query.AddWithClause("CTE_With_Underscores_123", cte);
// Assert
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("CTE_With_Underscores_123"));
}
[Test]
public void GetSql_WithVeryManyCTEs_GeneratesCorrectly()
{
// Arrange
var query = new QueryBreakdown("*", "FinalCTE");
for (int i = 0; i < 20; i++)
{
var cte = new QueryBreakdown($"Col{i}", $"Table{i}");
query.AddWithClause($"CTE{i}", cte);
}
// Act
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("CTE0 AS ("));
Assert.That(sql, Does.Contain("CTE19 AS ("));
}
#endregion
#region Outlier Conditions - Empty and Boundary Cases
[Test]
public void GetSql_WithAllEmptyClauses_GeneratesMinimalSql()
{
// Arrange
var query = new QueryBreakdown();
// Act
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
}
[Test]
public void MergeWith_WithEmptyQuery_HandlesGracefully()
{
// Arrange
var query1 = new QueryBreakdown("*", "Users");
var query2 = new QueryBreakdown();
// Act
query1.MergeWith(query2);
// Assert
Assert.That(query1.FromClause.Clause, Does.Contain("Users"));
}
[Test]
public void AddWhereClause_WithOnlyWhitespace_DoesNotAdd()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
// Act
query.AddWhereClause(" \t\n ");
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo(string.Empty));
}
[Test]
public void Parse_WithOnlySelectNoFrom_HandlesGracefully()
{
// Arrange - SQL Server allows SELECT without FROM for constants
var sql = "SELECT 1 AS One, 'Test' AS TestValue";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql));
}
#endregion
#region Outlier Conditions - Complex WHERE Clauses
[Test]
public void Parse_WithComplexBooleanLogic_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT * FROM Users
WHERE (Active = 1 AND Status = 'Valid')
OR (Active = 0 AND Status = 'Pending' AND CreateDate > '2024-01-01')";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("Active = 1"));
Assert.That(query.WhereClause.Clause, Does.Contain("AND"));
Assert.That(query.WhereClause.Clause, Does.Contain("OR"));
}
[Test]
public void Parse_WithInClauseWithMultipleValues_ParsesCorrectly()
{
// Arrange
var sql = "SELECT * FROM Users WHERE ID IN (1, 2, 3, 4, 5)";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("IN (1, 2, 3, 4, 5)"));
}
[Test]
public void Parse_WithBetweenClause_ParsesCorrectly()
{
// Arrange
var sql = "SELECT * FROM Orders WHERE OrderDate BETWEEN '2024-01-01' AND '2024-12-31'";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("BETWEEN"));
}
[Test]
public void Parse_WithLikePattern_ParsesCorrectly()
{
// Arrange
var sql = "SELECT * FROM Users WHERE Name LIKE '%Smith%' OR Email LIKE 'test@%'";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("LIKE"));
Assert.That(query.WhereClause.Clause, Does.Contain("%Smith%"));
}
[Test]
public void Parse_WithIsNullCheck_ParsesCorrectly()
{
// Arrange
var sql = "SELECT * FROM Users WHERE DeletedDate IS NULL AND Email IS NOT NULL";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("IS NULL"));
Assert.That(query.WhereClause.Clause, Does.Contain("IS NOT NULL"));
}
#endregion
#region Outlier Conditions - Aggregate Functions
[Test]
public void Parse_WithMultipleAggregateFunctions_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT
COUNT(*) AS TotalCount,
SUM(Amount) AS TotalAmount,
AVG(Price) AS AvgPrice,
MIN(CreateDate) AS FirstDate,
MAX(UpdateDate) AS LastDate
FROM Orders";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("COUNT(*)"));
Assert.That(query.SelectClause.Clause, Does.Contain("SUM(Amount)"));
Assert.That(query.SelectClause.Clause, Does.Contain("AVG(Price)"));
Assert.That(query.SelectClause.Clause, Does.Contain("MIN(CreateDate)"));
Assert.That(query.SelectClause.Clause, Does.Contain("MAX(UpdateDate)"));
}
[Test]
public void Parse_WithDistinct_ParsesCorrectly()
{
// Arrange
var sql = "SELECT DISTINCT Country, City FROM Customers";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("DISTINCT"));
}
[Test]
public void Parse_WithTop_ParsesCorrectly()
{
// Arrange
var sql = "SELECT TOP 10 Name FROM Users ORDER BY CreateDate DESC";
// Act
var query = QueryBreakdown.Parse(sql);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("TOP 10"));
}
#endregion
#region AddSelectExpression Tests
[Test]
public void AddSelectExpression_WhenEmpty_SetsSelectClause()
{
// Arrange
var query = new QueryBreakdown();
var expr = new GenericColumnExpression("CustomerID", "Customers");
// Act
query.AddSelectExpression(expr);
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("[Customers].[CustomerID]"));
}
[Test]
public void AddSelectExpression_WithMultipleExpressions_AppendsWithComma()
{
// Arrange
var query = new QueryBreakdown();
var expr1 = new GenericColumnExpression("CustomerID", "Customers");
var expr2 = new GenericColumnExpression("CustomerName", "Customers");
// Act
query.AddSelectExpression(expr1);
query.AddSelectExpression(expr2);
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("[Customers].[CustomerID], [Customers].[CustomerName]"));
}
[Test]
public void AddSelectExpression_WithComment_AddsCommentToSelectClause()
{
// Arrange
var query = new QueryBreakdown();
var expr = new GenericColumnExpression("CustomerID", "Customers");
// Act
query.AddSelectExpression(expr, "/* Primary key */");
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("[Customers].[CustomerID]"));
Assert.That(query.SelectClause.Comment, Is.EqualTo("/* Primary key */"));
}
[Test]
public void AddSelectExpression_WithMultipleComments_AppendsComments()
{
// Arrange
var query = new QueryBreakdown();
var expr1 = new GenericColumnExpression("CustomerID", "Customers");
var expr2 = new GenericColumnExpression("CustomerName", "Customers");
// Act
query.AddSelectExpression(expr1, "/* Primary key */");
query.AddSelectExpression(expr2, "/* Display name */");
// Assert
Assert.That(query.SelectClause.Comment, Is.EqualTo("/* Primary key */ /* Display name */"));
}
[Test]
public void AddSelectExpression_WithNullExpression_ThrowsArgumentNullException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => query.AddSelectExpression(null!));
}
[Test]
public void AddSelectExpression_WithLiteralExpression_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.Literals.NumberLiteralExpression(100);
// Act
query.AddSelectExpression(expr);
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("100"));
}
[Test]
public void AddSelectExpression_WithComparisonExpression_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var col = new GenericColumnExpression("Age", "Users");
var expr = col > 18;
// Act
query.AddSelectExpression(expr);
// Assert
Assert.That(query.SelectClause.Clause, Does.Contain("[Users].[Age]"));
Assert.That(query.SelectClause.Clause, Does.Contain(">"));
Assert.That(query.SelectClause.Clause, Does.Contain("18"));
}
#endregion
#region AddWhereExpression Tests
[Test]
public void AddWhereExpression_WhenEmpty_SetsWhereClause()
{
// Arrange
var query = new QueryBreakdown();
var col = new GenericColumnExpression("Active", "Users");
var expr = col == 1;
// Act
query.AddWhereExpression(expr);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("[Users].[Active]"));
Assert.That(query.WhereClause.Clause, Does.Contain("="));
Assert.That(query.WhereClause.Clause, Does.Contain("1"));
}
[Test]
public void AddWhereExpression_WithMultipleExpressions_AppendsWithAnd()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new GenericColumnExpression("Active", "Users");
var col2 = new GenericColumnExpression("Age", "Users");
var expr1 = col1 == 1;
var expr2 = col2 >= 18;
// Act
query.AddWhereExpression(expr1);
query.AddWhereExpression(expr2);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("[Users].[Active]"));
Assert.That(query.WhereClause.Clause, Does.Contain(" and "));
Assert.That(query.WhereClause.Clause, Does.Contain("[Users].[Age]"));
}
[Test]
public void AddWhereExpression_WithOrOperation_AppendsWithOr()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new GenericColumnExpression("Status", "Orders");
var col2 = new GenericColumnExpression("Priority", "Orders");
var expr1 = col1 == "Urgent";
var expr2 = col2 > 5;
// Act
query.AddWhereExpression(expr1);
query.AddWhereExpression(expr2, null, "or");
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("[Orders].[Status]"));
Assert.That(query.WhereClause.Clause, Does.Contain(" or "));
Assert.That(query.WhereClause.Clause, Does.Contain("[Orders].[Priority]"));
}
[Test]
public void AddWhereExpression_WithComment_AddsCommentToWhereClause()
{
// Arrange
var query = new QueryBreakdown();
var col = new GenericColumnExpression("Active", "Users");
var expr = col == 1;
// Act
query.AddWhereExpression(expr, "/* Filter active users */");
// Assert
Assert.That(query.WhereClause.Comment, Is.EqualTo("/* Filter active users */"));
}
[Test]
public void AddWhereExpression_WithMultipleComments_AppendsComments()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new GenericColumnExpression("Active", "Users");
var col2 = new GenericColumnExpression("Verified", "Users");
var expr1 = col1 == 1;
var expr2 = col2 == new BooleanLiteralExpression(true);
// Act
query.AddWhereExpression(expr1, "/* Active users */");
query.AddWhereExpression(expr2, "/* Verified only */");
// Assert
Assert.That(query.WhereClause.Comment, Is.EqualTo("/* Active users */ /* Verified only */"));
}
[Test]
public void AddWhereExpression_WithNullExpression_ThrowsArgumentNullException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => query.AddWhereExpression(null!));
}
[Test]
public void AddWhereExpression_WithStringComparison_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var col = new GenericColumnExpression("Name", "Users");
var expr = col == "John";
// Act
query.AddWhereExpression(expr);
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("[Users].[Name] = 'John'"));
}
[Test]
public void AddWhereExpression_WithComplexExpression_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new GenericColumnExpression("Price", "Products");
var col2 = new GenericColumnExpression("Discount", "Products");
var expr = (col1 - col2) > 100;
// Act
query.AddWhereExpression(expr);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("[Products].[Price]"));
Assert.That(query.WhereClause.Clause, Does.Contain("-"));
Assert.That(query.WhereClause.Clause, Does.Contain("[Products].[Discount]"));
Assert.That(query.WhereClause.Clause, Does.Contain(">"));
Assert.That(query.WhereClause.Clause, Does.Contain("100"));
}
[Test]
public void AddSelectExpression_ToExistingClause_PreservesExistingContent()
{
// Arrange
var query = new QueryBreakdown();
query.SelectClause.Clause = "ID";
var expr = new GenericColumnExpression("Name", "Users");
// Act
query.AddSelectExpression(expr);
// Assert
Assert.That(query.SelectClause.Clause, Does.StartWith("ID, "));
Assert.That(query.SelectClause.Clause, Does.Contain("[Users].[Name]"));
}
[Test]
public void AddWhereExpression_ToExistingClause_PreservesExistingContent()
{
// Arrange
var query = new QueryBreakdown();
query.WhereClause.Clause = "Status = 'Active'";
var col = new GenericColumnExpression("Age", "Users");
var expr = col > 18;
// Act
query.AddWhereExpression(expr);
// Assert
Assert.That(query.WhereClause.Clause, Does.StartWith("Status = 'Active' and "));
Assert.That(query.WhereClause.Clause, Does.Contain("[Users].[Age]"));
}
#endregion
#region Type Mismatch Tests
[Test]
public void AddOrUpdateParameter_WithTypeMismatch_ThrowsException()
{
// Arrange
var helper = new QueryBreakdownTestHelper();
helper.SetParameterValue("@TestParam", 123); // Set as int
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
{
helper.TestAddOrUpdateParameter("@TestParam", "StringValue"); // Try to set as string
});
Assert.That(ex.Message, Does.Contain("@TestParam"));
Assert.That(ex.Message, Does.Contain("Int32"));
Assert.That(ex.Message, Does.Contain("String"));
}
[Test]
public void AddOrUpdateParameter_WithSameType_KeepsExistingValue()
{
// Arrange
var helper = new QueryBreakdownTestHelper();
helper.SetParameterValue("@TestParam", 123); // Set as int
// Act - Try to update with another int value
helper.TestAddOrUpdateParameter("@TestParam", 456);
// Assert - Should keep the original value (123), not update to 456
// This is the expected behavior for AddWhereClause where we don't overwrite existing values
Assert.That(helper.Parameters["@TestParam"], Is.EqualTo(123));
}
[Test]
public void AddOrUpdateParameter_NullToValue_SetsValue()
{
// Arrange
var helper = new QueryBreakdownTestHelper();
helper.Parameters["@TestParam"] = null!;
// Act
helper.TestAddOrUpdateParameter("@TestParam", 999);
// Assert
Assert.That(helper.Parameters["@TestParam"], Is.EqualTo(999));
}
#endregion
}
/// <summary>
/// Test helper class to expose protected members of QueryBreakdown for testing.
/// </summary>
internal class QueryBreakdownTestHelper : QueryBreakdown
{
public void TestAddOrUpdateParameter(string parameterName, object? value)
{
AddOrUpdateParameter(parameterName, value);
}
public void TestExtractAndAddParameters(string sql)
{
ExtractAndAddParameters(sql);
}
}