- 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>
2169 lines
66 KiB
C#
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);
|
|
}
|
|
}
|