Driven by `dotnet format analyzers --diagnostics NUnit2045`. The fixer
groups consecutive independent `Assert.That(...)` calls into
`Assert.Multiple(() => { ... })`, so a failing assertion no longer
short-circuits the block — every failure inside the group is reported,
which gives much better diagnostics on multi-property tests.
Audit confirmed no Assert.Throws / Assert.Fail / Assert.Catch / Assert.Pass
/ Assert.DoesNotThrow got pulled inside a Multiple block (those need to
short-circuit). All 1180 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
308 lines
10 KiB
C#
308 lines
10 KiB
C#
using Strata.SqlTools.Breakdowns.LinqToSql;
|
|
using Strata.SqlTools.Builders.LinqToSql;
|
|
using Strata.SqlTools.SqlBreakdown.Classes;
|
|
|
|
namespace Strata.SqlTools.SqlBreakdown.Tests.LinqToSql;
|
|
|
|
[TestFixture]
|
|
public class LinqQueryBreakdownWithClauseTests
|
|
{
|
|
#region Basic WITH Clause Tests
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_SupportsWithClause_ViaInheritance()
|
|
{
|
|
// Arrange
|
|
var cte = new LinqQueryBreakdown("id, name", "users", "status = 'active'");
|
|
var mainQuery = new LinqQueryBreakdown("*", "active_users_cte");
|
|
|
|
// Act
|
|
mainQuery.AddWithClause("active_users", cte);
|
|
|
|
// Assert
|
|
Assert.That(mainQuery.WithClauses, Has.Count.EqualTo(1));
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(mainQuery.WithClauses[0].TableName, Is.EqualTo("active_users"));
|
|
Assert.That(mainQuery.WithClauses[0].Query, Is.EqualTo(cte));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_GeneratesSql_WithSimpleCTE()
|
|
{
|
|
// Arrange
|
|
var cte = new LinqQueryBreakdown("id, name", "users");
|
|
var mainQuery = new LinqQueryBreakdown("*", "active_users_cte");
|
|
mainQuery.AddWithClause("active_users", cte);
|
|
|
|
// Act
|
|
var sql = mainQuery.GetSql();
|
|
|
|
// Assert
|
|
Assert.That(sql, Contains.Substring("WITH"));
|
|
Assert.That(sql, Contains.Substring("active_users AS"));
|
|
}
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_WithMultipleCTEs_GeneratesAllClauses()
|
|
{
|
|
// Arrange
|
|
var cte1 = new LinqQueryBreakdown("id, status", "users", "status = 'active'");
|
|
var cte2 = new LinqQueryBreakdown("user_id, created", "orders", "created >= CURRENT_DATE");
|
|
|
|
var mainQuery = new LinqQueryBreakdown("*", "results");
|
|
mainQuery.AddWithClause("active_users", cte1);
|
|
mainQuery.AddWithClause("recent_orders", cte2);
|
|
|
|
// Act
|
|
var sql = mainQuery.GetSql();
|
|
|
|
// Assert
|
|
Assert.That(sql, Contains.Substring("WITH"));
|
|
Assert.That(sql, Contains.Substring("active_users AS"));
|
|
Assert.That(sql, Contains.Substring("recent_orders AS"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CTE Parameter Inheritance Tests
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_CollectsCTEParameters()
|
|
{
|
|
// Arrange
|
|
var cte = new LinqQueryBreakdown("id, name", "users");
|
|
cte.AddParameter("Status", "active");
|
|
|
|
var mainQuery = new LinqQueryBreakdown("*", "results");
|
|
mainQuery.AddParameter("Limit", 100);
|
|
mainQuery.AddWithClause("active_users", cte);
|
|
|
|
// Act
|
|
var merged = mainQuery.GetMergedParameters();
|
|
|
|
// Assert
|
|
var keys = merged.Keys.ToList();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(keys.Any(k => k.Contains("Status")), "Should include CTE parameters");
|
|
Assert.That(keys.Any(k => k.Contains("Limit")), "Should include main query parameters");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_MainQueryParamsPrecedence()
|
|
{
|
|
// Arrange
|
|
var cte = new LinqQueryBreakdown("id", "data");
|
|
cte.AddParameter("Value", "cte_value");
|
|
|
|
var mainQuery = new LinqQueryBreakdown("*", "results");
|
|
mainQuery.AddParameter("Value", "main_value");
|
|
mainQuery.AddWithClause("data_cte", cte);
|
|
|
|
// Act
|
|
var merged = mainQuery.GetMergedParameters();
|
|
|
|
// Assert
|
|
var valueKey = merged.Keys.FirstOrDefault(k => k.Contains("Value"));
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(valueKey, Is.Not.Null, "Should have a Value parameter");
|
|
Assert.That(merged[valueKey], Is.EqualTo("main_value"), "Main query parameter should take precedence");
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Builder Integration Tests
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdownBuilder_BuiltQuerySupportsWithClauses()
|
|
{
|
|
// Arrange
|
|
var builder = LinqQueryBreakdownBuilder.CreateForTable("users")
|
|
.SelectAll()
|
|
.Where("id > 0");
|
|
var mainQuery = builder.Build();
|
|
|
|
var cte = new LinqQueryBreakdown("id, name", "active_users_data");
|
|
cte.AddParameter("Status", "active");
|
|
|
|
// Act
|
|
mainQuery.AddWithClause("active_users", cte);
|
|
var sql = mainQuery.GetSql();
|
|
|
|
// Assert
|
|
Assert.That(sql, Contains.Substring("WITH"));
|
|
Assert.That(sql, Contains.Substring("active_users AS"));
|
|
}
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdownBuilder_CanAddCTEToBuiltQuery()
|
|
{
|
|
// Arrange
|
|
var builder = LinqQueryBreakdownBuilder.CreateForTable("orders");
|
|
builder.SelectAll().Where("status != 'cancelled'");
|
|
var query = builder.Build();
|
|
|
|
var cte1 = new LinqQueryBreakdown("id, order_id", "recent_orders", "created >= CURRENT_DATE");
|
|
cte1.AddParameter("MinDate", DateTime.Today.AddDays(-30));
|
|
|
|
var cte2 = new LinqQueryBreakdown("id, name", "users", "active = 1");
|
|
cte2.AddParameter("Status", "active");
|
|
|
|
// Act
|
|
query.AddWithClause("recent_orders", cte1);
|
|
query.AddWithClause("active_users", cte2);
|
|
var merged = query.GetMergedParameters();
|
|
|
|
// Assert
|
|
Assert.That(query.WithClauses, Has.Count.EqualTo(2));
|
|
var keys = merged.Keys.ToList();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(keys.Any(k => k.Contains("MinDate")));
|
|
Assert.That(keys.Any(k => k.Contains("Status")));
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Recursive CTE Tests
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_SupportsRecursiveCTE_ViaInheritance()
|
|
{
|
|
// Arrange
|
|
var anchorQuery = new LinqQueryBreakdown("id, manager_id, name, 1 as level", "employees", "manager_id IS NULL");
|
|
var recursiveQuery = new LinqQueryBreakdown(
|
|
"e.id, e.manager_id, e.name, h.level + 1",
|
|
"employees e INNER JOIN hierarchy h ON e.manager_id = h.id"
|
|
);
|
|
|
|
var mainQuery = new LinqQueryBreakdown("*", "hierarchy");
|
|
|
|
var withClause = new WithClause("hierarchy", anchorQuery)
|
|
{
|
|
IsRecursive = true,
|
|
RecursiveQuery = recursiveQuery
|
|
};
|
|
mainQuery.AddWithClause(withClause);
|
|
|
|
// Act
|
|
var sql = mainQuery.GetSql();
|
|
|
|
// Assert
|
|
Assert.That(sql, Contains.Substring("WITH RECURSIVE"));
|
|
Assert.That(sql, Contains.Substring("hierarchy AS"));
|
|
Assert.That(sql, Contains.Substring("UNION ALL"));
|
|
}
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_RecursiveCTE_CollectsAllParameters()
|
|
{
|
|
// Arrange
|
|
var anchorQuery = new LinqQueryBreakdown("id, manager_id", "employees");
|
|
anchorQuery.AddParameter("AnchorLevel", 1);
|
|
|
|
var recursiveQuery = new LinqQueryBreakdown("e.id, e.manager_id", "employees e INNER JOIN org_hier h ON e.manager_id = h.id");
|
|
recursiveQuery.AddParameter("MaxLevel", 10);
|
|
|
|
var mainQuery = new LinqQueryBreakdown("*", "org_hierarchy");
|
|
mainQuery.AddParameter("QueryLimit", 100);
|
|
|
|
var withClause = new WithClause("org_hierarchy", anchorQuery)
|
|
{
|
|
IsRecursive = true,
|
|
RecursiveQuery = recursiveQuery
|
|
};
|
|
mainQuery.AddWithClause(withClause);
|
|
|
|
// Act
|
|
var merged = mainQuery.GetMergedParameters();
|
|
|
|
// Assert
|
|
var keys = merged.Keys.ToList();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(keys.Any(k => k.Contains("AnchorLevel")), "Should include anchor parameters");
|
|
Assert.That(keys.Any(k => k.Contains("MaxLevel")), "Should include recursive parameters");
|
|
Assert.That(keys.Any(k => k.Contains("QueryLimit")), "Should include main query parameters");
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SQL Generation Tests
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_GeneratesProperSqlWithParameters()
|
|
{
|
|
// Arrange
|
|
var cte = new LinqQueryBreakdown("id, name, salary", "employees", "salary > @MinSalary");
|
|
cte.AddParameter("MinSalary", 50000);
|
|
|
|
var mainQuery = new LinqQueryBreakdown("user_id, user_name, compensation", "employee_summary");
|
|
mainQuery.AddParameter("DepartmentId", 1);
|
|
mainQuery.AddWithClause("high_earners", cte);
|
|
|
|
// Act
|
|
var sql = mainQuery.GetSql();
|
|
|
|
// Assert
|
|
Assert.That(sql, Contains.Substring("WITH"));
|
|
Assert.That(sql, Contains.Substring("high_earners AS"));
|
|
}
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_ToSqlServerSql_WithCTE()
|
|
{
|
|
// Arrange
|
|
var cte = new LinqQueryBreakdown("id, name", "base_data");
|
|
var mainQuery = new LinqQueryBreakdown("*", "cte_data");
|
|
mainQuery.AddWithClause("base", cte);
|
|
|
|
// Act
|
|
var sql = mainQuery.GetSql();
|
|
|
|
// Assert
|
|
Assert.That(sql, Is.Not.Null);
|
|
// GetSql() should include WITH clause (inherited from QueryBreakdown)
|
|
Assert.That(sql, Contains.Substring("WITH"), "GetSql() should generate WITH clause");
|
|
}
|
|
|
|
[Test]
|
|
public void LinqQueryBreakdown_ComplexQuery_WithNestdCTEsAndParameters()
|
|
{
|
|
// Arrange
|
|
var innerCte = new LinqQueryBreakdown("id, dept_id", "employees");
|
|
innerCte.AddParameter("InnerParam", "inner_value");
|
|
|
|
var outerCte = new LinqQueryBreakdown("*", "filtered_employees");
|
|
outerCte.AddParameter("OuterParam", "outer_value");
|
|
outerCte.AddWithClause("emp_summary", innerCte);
|
|
|
|
var mainQuery = new LinqQueryBreakdown("*", "final_results");
|
|
mainQuery.AddParameter("MainParam", "main_value");
|
|
mainQuery.AddWithClause("emp_data", outerCte);
|
|
|
|
// Act
|
|
var merged = mainQuery.GetMergedParameters();
|
|
var sql = mainQuery.GetSql();
|
|
|
|
// Assert
|
|
var keys = merged.Keys.ToList();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(keys.Any(k => k.Contains("InnerParam")));
|
|
Assert.That(keys.Any(k => k.Contains("OuterParam")));
|
|
Assert.That(keys.Any(k => k.Contains("MainParam")));
|
|
Assert.That(sql, Contains.Substring("WITH"));
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
}
|