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.Multiple(() => { // 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.Multiple(() => { 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { 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.Multiple(() => { 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.Multiple(() => { 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.Multiple(() => { 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.Multiple(() => { // Assert Assert.That(merged["@IntValue"], Is.TypeOf()); Assert.That(merged["@StringValue"], Is.TypeOf()); Assert.That(merged["@DateValue"], Is.TypeOf()); Assert.That(merged["@DecimalValue"], Is.TypeOf()); }); } #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.Multiple(() => { // 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.Multiple(() => { 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.Multiple(() => { // 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.Multiple(() => { 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.Multiple(() => { 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.Multiple(() => { 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(() => mainQuery.AddWithClause(null!, cteQuery)); } [Test] public void AddWithClause_WithNullQuery_ThrowsCteValidationException() { // Arrange var mainQuery = new QueryBreakdown("*", "Products"); // Act & Assert Assert.Throws(() => mainQuery.AddWithClause("ProductOrders", (QueryBreakdown)null!)); } [Test] public void AddWithClause_WithNullSql_ThrowsCteValidationException() { // Arrange var mainQuery = new QueryBreakdown("*", "Products"); // Act & Assert Assert.Throws(() => 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.Multiple(() => { // 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.Multiple(() => { Assert.That(withIndex, Is.GreaterThan(-1)); Assert.That(mainSelectIndex, Is.GreaterThan(-1)); }); Assert.Multiple(() => { 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.Multiple(() => { // 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.Multiple(() => { 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { // 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(() => QueryBreakdown.Parse(null!)); } [Test] public void Parse_WithEmptySql_ThrowsArgumentNullException() { // Act & Assert Assert.Throws(() => QueryBreakdown.Parse(string.Empty)); } [Test] public void Parse_WithInvalidSql_ThrowsSqlParseException() { // Arrange var sql = "INVALID SQL STATEMENT"; // Act & Assert Assert.Throws(() => 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { 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(() => query.AddWithClause("", cte)); Assert.Throws(() => 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.Multiple(() => { // 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(() => 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(() => 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(() => { 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 } /// /// Test helper class to expose protected members of QueryBreakdown for testing. /// internal class QueryBreakdownTestHelper : QueryBreakdown { public void TestAddOrUpdateParameter(string parameterName, object? value) { AddOrUpdateParameter(parameterName, value); } public void TestExtractAndAddParameters(string sql) { ExtractAndAddParameters(sql); } }