chore: initial git load of code space
This commit is contained in:
@@ -0,0 +1,512 @@
|
||||
using NUnit.Framework;
|
||||
using Strata.SqlTools.Builders.LinqToSql;
|
||||
|
||||
namespace Strata.SqlTools.LinqToSql.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class LinqQueryBreakdownBuilderTests
|
||||
{
|
||||
#region Basic Builder Tests
|
||||
|
||||
[Test]
|
||||
public void Build_WithValidConfiguration_ReturnsBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name", "Email")
|
||||
.FromTable("Users")
|
||||
.Where("Age > 18");
|
||||
|
||||
// Act
|
||||
var breakdown = builder.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.SelectClause.Clause, Contains.Substring("Id"));
|
||||
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Users"));
|
||||
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("Age > 18"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Build_WithoutSelectColumns_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.FromTable("Users");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() => builder.Build());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Build_WithoutFromTable_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() => builder.Build());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SelectAll_SetsSelectAllColumns()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.SelectClause.Clause, Is.EqualTo("*"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SelectColumns_WithEmptyArray_SelectsAll()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.SelectClause.Clause, Is.EqualTo("*"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SelectColumns_WithMultipleColumns_JoinsWithComma()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name", "Email", "Age")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.SelectClause.Clause, Contains.Substring("Id"));
|
||||
Assert.That(breakdown.SelectClause.Clause, Contains.Substring("Name"));
|
||||
Assert.That(breakdown.SelectClause.Clause, Contains.Substring("Email"));
|
||||
Assert.That(breakdown.SelectClause.Clause, Contains.Substring("Age"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Table Builder Tests
|
||||
|
||||
[Test]
|
||||
public void FromTable_WithValidTableName_SetsFromClause()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Customers")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Customers"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FromTable_WithNullTableName_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder.Create();
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
Assert.Throws<ArgumentException>(() => builder.FromTable(null));
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FromTable_WithEmptyTableName_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder.Create();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => builder.FromTable(""));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region WHERE Clause Tests
|
||||
|
||||
[Test]
|
||||
public void Where_WithValidCondition_SetsWhereClause()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true AND Age >= 21")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("IsActive = true AND Age >= 21"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Where_WithNullCondition_IgnoresAndUsesEmptyString()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where(null!)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.WhereClause.Clause, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Where_WithEmptyCondition_IgnoresAndUsesEmptyString()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.WhereClause.Clause, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Where_CalledMultipleTimes_LastValueWins()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("Age > 18")
|
||||
.Where("Status = 'Active'")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("Status = 'Active'"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GROUP BY Tests
|
||||
|
||||
[Test]
|
||||
public void GroupBy_WithSingleColumn_SetsGroupByClause()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*) as EmployeeCount")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.GroupByClause.Clause, Is.EqualTo("Department"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GroupBy_WithMultipleColumns_JoinsWithComma()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "Level", "COUNT(*) as Count")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department", "Level")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.GroupByClause.Clause, Is.EqualTo("Department, Level"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GroupBy_WithNoColumns_IgnoresGroupBy()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.GroupBy()
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.GroupByClause.Clause, Is.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HAVING Clause Tests
|
||||
|
||||
[Test]
|
||||
public void Having_WithValidCondition_SetsHavingClause()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*) as Count")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Having("COUNT(*) > 5")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.HavingClause.Clause, Is.EqualTo("COUNT(*) > 5"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Having_WithoutGroupBy_StillSetsHavingClause()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Having("COUNT(*) > 1")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.HavingClause.Clause, Is.EqualTo("COUNT(*) > 1"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ORDER BY Tests
|
||||
|
||||
[Test]
|
||||
public void OrderBy_WithCustomSpecification_SetsOrderByClause()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.OrderBy("LastName ASC, FirstName ASC")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.OrderByClause.Clause, Is.EqualTo("LastName ASC, FirstName ASC"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByAscending_WithColumn_SetsAscendingOrder()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.OrderByAscending("Name")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.OrderByClause.Clause, Is.EqualTo("Name ASC"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByDescending_WithColumn_SetsDescendingOrder()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.OrderByDescending("CreatedDate")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.OrderByClause.Clause, Is.EqualTo("CreatedDate DESC"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByAscending_WithNullColumn_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users");
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
Assert.Throws<ArgumentException>(() => builder.OrderByAscending(null));
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByDescending_WithEmptyColumn_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => builder.OrderByDescending(""));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Complex Query Tests
|
||||
|
||||
[Test]
|
||||
public void CompleteQuery_WithAllClauses_BuildsSuccessfully()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "Position", "COUNT(*) as EmployeeCount", "AVG(Salary) as AvgSalary")
|
||||
.FromTable("Employees")
|
||||
.Where("IsActive = true")
|
||||
.GroupBy("Department", "Position")
|
||||
.Having("COUNT(*) >= 3")
|
||||
.OrderByDescending("AvgSalary")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.SelectClause.Clause, Contains.Substring("Department"));
|
||||
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Employees"));
|
||||
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("IsActive = true"));
|
||||
Assert.That(breakdown.GroupByClause.Clause, Is.EqualTo("Department, Position"));
|
||||
Assert.That(breakdown.HavingClause.Clause, Is.EqualTo("COUNT(*) >= 3"));
|
||||
Assert.That(breakdown.OrderByClause.Clause, Is.EqualTo("AvgSalary DESC"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimplePaginationQuery_BuildsSuccessfully()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name", "Email")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.OrderByAscending("Id")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.SelectClause.Clause, Contains.Substring("Id"));
|
||||
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("IsActive = true"));
|
||||
Assert.That(breakdown.OrderByClause.Clause, Is.EqualTo("Id ASC"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Factory Tests
|
||||
|
||||
[Test]
|
||||
public void Create_ReturnsNewBuilder()
|
||||
{
|
||||
// Act
|
||||
var builder = LinqQueryBreakdownBuilder.Create();
|
||||
|
||||
// Assert
|
||||
Assert.That(builder, Is.Not.Null);
|
||||
Assert.That(builder, Is.TypeOf<LinqQueryBreakdownBuilder>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateForTable_SetsTableAndReturnsBuilder()
|
||||
{
|
||||
// Act & Assert
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.CreateForTable("Orders")
|
||||
.SelectAll()
|
||||
.Build();
|
||||
|
||||
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Orders"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Preview SQL Tests
|
||||
|
||||
[Test]
|
||||
public void PreviewSql_WithCompleteQuery_ReturnsSqlString()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("Age > 18");
|
||||
|
||||
// Act
|
||||
var sql = builder.PreviewSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(sql, Is.Not.Null);
|
||||
Assert.That(sql, Contains.Substring("Users").IgnoreCase);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PreviewSql_WithIncompleteQuery_ReturnsPlaceholder()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder.Create();
|
||||
|
||||
// Act
|
||||
var sql = builder.PreviewSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(sql, Contains.Substring("Incomplete"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Method Chaining Tests
|
||||
|
||||
[Test]
|
||||
public void MethodChaining_AllMethodsReturnBuilder()
|
||||
{
|
||||
// Arrange & Act
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id")
|
||||
.FromTable("Users")
|
||||
.Where("Active = 1")
|
||||
.GroupBy("Department")
|
||||
.Having("COUNT(*) > 1")
|
||||
.OrderByAscending("Department")
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MethodChaining_CanBeInterrupted()
|
||||
{
|
||||
// Arrange
|
||||
var builder = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users");
|
||||
|
||||
// Act
|
||||
var breakdown1 = builder.Build();
|
||||
var builder2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id")
|
||||
.FromTable("Orders");
|
||||
var breakdown2 = builder2.Build();
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown1.FromClause.Clause, Is.EqualTo("Users"));
|
||||
Assert.That(breakdown2.FromClause.Clause, Is.EqualTo("Orders"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,891 @@
|
||||
using Strata.SqlTools.Breakdowns.LinqToSql;
|
||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||
|
||||
namespace Strata.SqlTools.LinqToSql.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class LinqQueryBreakdownTests
|
||||
{
|
||||
private TestDataContext _context = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_context = new TestDataContext();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_SimpleSelectQuery_ExtractsCorrectClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = from user in _context.Users
|
||||
select user;
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.EntityType, Is.EqualTo(typeof(User)));
|
||||
Assert.That(breakdown.FromClause?.Clause, Does.Contain("User"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_WhereClause_ExtractsCondition()
|
||||
{
|
||||
// Arrange
|
||||
var query = from user in _context.Users
|
||||
where user.Age > 21
|
||||
select user;
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.WhereClause?.Clause, Is.Not.Null);
|
||||
Assert.That(breakdown.WhereClause?.Clause, Does.Contain("Age"));
|
||||
Assert.That(breakdown.WhereClause?.Clause, Does.Contain(">"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_SelectWithProjection_ExtractsSelectedFields()
|
||||
{
|
||||
// Arrange
|
||||
var query = from user in _context.Users
|
||||
select new { user.Id, user.Name };
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.SelectClause?.Clause, Is.Not.Null);
|
||||
Assert.That(breakdown.SelectClause?.Clause, Does.Contain("Id"));
|
||||
Assert.That(breakdown.SelectClause?.Clause, Does.Contain("Name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_OrderByClause_ExtractsOrdering()
|
||||
{
|
||||
// Arrange
|
||||
var query = from user in _context.Users
|
||||
orderby user.Name
|
||||
select user;
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.OrderByClause?.Clause, Is.Not.Null);
|
||||
Assert.That(breakdown.OrderByClause?.Clause, Does.Contain("Name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_MethodSyntax_ExtractsCorrectClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users
|
||||
.Where(u => u.Age > 18)
|
||||
.OrderBy(u => u.Name)
|
||||
.Select(u => new { u.Id, u.Name });
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.WhereClause?.Clause, Is.Not.Null);
|
||||
Assert.That(breakdown.OrderByClause?.Clause, Is.Not.Null);
|
||||
Assert.That(breakdown.SelectClause?.Clause, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_MethodSyntax_TracksMethodChain()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users
|
||||
.Where(u => u.Age > 18)
|
||||
.OrderBy(u => u.Name)
|
||||
.Select(u => u.Name);
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.MethodCallChain, Is.Not.Empty);
|
||||
Assert.That(breakdown.MethodCallChain, Contains.Item("Where"));
|
||||
Assert.That(breakdown.MethodCallChain, Contains.Item("OrderBy"));
|
||||
Assert.That(breakdown.MethodCallChain, Contains.Item("Select"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetQuerySummary_ReturnsFormattedString()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users.Where(u => u.Age > 21);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act
|
||||
var summary = breakdown.GetQuerySummary();
|
||||
|
||||
// Assert
|
||||
Assert.That(summary, Is.Not.Empty);
|
||||
Assert.That(summary, Does.Contain("SELECT"));
|
||||
Assert.That(summary, Does.Contain("FROM"));
|
||||
Assert.That(summary, Does.Contain("WHERE"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetMethodChain_ReturnsMethodSequence()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users.Where(u => u.Age > 18).OrderBy(u => u.Name);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act
|
||||
var methodChain = breakdown.GetMethodChain();
|
||||
|
||||
// Assert
|
||||
Assert.That(methodChain, Does.Contain("Where"));
|
||||
Assert.That(methodChain, Does.Contain("OrderBy"));
|
||||
Assert.That(methodChain, Does.Contain("->"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryAnalyze_ValidQuery_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users.Where(u => u.Age > 21);
|
||||
|
||||
// Act
|
||||
var success = LinqQueryBreakdown.TryAnalyze(query, out var breakdown, out var error);
|
||||
|
||||
// Assert
|
||||
Assert.That(success, Is.True);
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(error, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryAnalyze_NullQuery_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
IQueryable<User>? query = null;
|
||||
|
||||
// Act
|
||||
var success = LinqQueryBreakdown.TryAnalyze(query!, out var breakdown, out var error);
|
||||
|
||||
// Assert
|
||||
Assert.That(success, Is.False);
|
||||
Assert.That(error, Is.Not.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_ComplexQuery_HandlesCombinedClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = from user in _context.Users
|
||||
where user.Age > 21 && user.IsActive
|
||||
orderby user.Name descending
|
||||
select new { user.Id, user.Name, user.Email };
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown.SelectClause?.Clause, Does.Contain("Id"));
|
||||
Assert.That(breakdown.SelectClause?.Clause, Does.Contain("Name"));
|
||||
Assert.That(breakdown.SelectClause?.Clause, Does.Contain("Email"));
|
||||
Assert.That(breakdown.WhereClause?.Clause, Does.Contain("Age"));
|
||||
Assert.That(breakdown.WhereClause?.Clause, Does.Contain("IsActive"));
|
||||
Assert.That(breakdown.OrderByClause?.Clause, Does.Contain("Name"));
|
||||
}
|
||||
|
||||
#region Round-Trip and Query Modification Tests
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndModify_AddWhereClause_GeneratesUpdatedSql()
|
||||
{
|
||||
// Arrange - Analyze an existing LINQ query
|
||||
var originalQuery = _context.Users.Where(u => u.Age > 21);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(originalQuery);
|
||||
|
||||
// Act - Add additional filter using breakdown
|
||||
breakdown.AddWhereClause("IsActive = 1", "AND");
|
||||
var modifiedSql = breakdown.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(modifiedSql, Does.Contain("Age"));
|
||||
Assert.That(modifiedSql, Does.Contain("IsActive"));
|
||||
Assert.That(modifiedSql, Does.Contain("AND"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndModify_ChangeSelectClause_GeneratesNewProjection()
|
||||
{
|
||||
// Arrange - Analyze query with projection
|
||||
var originalQuery = _context.Users.Select(u => new { u.Id, u.Name });
|
||||
var breakdown = LinqQueryBreakdown.Analyze(originalQuery);
|
||||
|
||||
// Act - Modify the SELECT clause
|
||||
breakdown.SelectClause = new Strata.SqlTools.SqlBreakdown.Classes.SqlExpressionClause
|
||||
{
|
||||
Clause = "Id, Name, Email, Age"
|
||||
};
|
||||
var modifiedSql = breakdown.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(modifiedSql, Does.Contain("Email"));
|
||||
Assert.That(modifiedSql, Does.Contain("Age"));
|
||||
Assert.That(modifiedSql, Does.Contain("Id"));
|
||||
Assert.That(modifiedSql, Does.Contain("Name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndModify_CloneAndExtend_CreatesIndependentQuery()
|
||||
{
|
||||
// Arrange - Analyze base query
|
||||
var baseQuery = _context.Users.Where(u => u.Age > 18);
|
||||
var baseBreakdown = LinqQueryBreakdown.Analyze(baseQuery);
|
||||
|
||||
// Act - Clone and extend
|
||||
var extendedBreakdown = (LinqQueryBreakdown)baseBreakdown.Clone();
|
||||
extendedBreakdown.AddWhereClause("IsActive = 1", "AND");
|
||||
extendedBreakdown.OrderByClause = new Strata.SqlTools.SqlBreakdown.Classes.SqlExpressionClause(splitOnComma: true)
|
||||
{
|
||||
Clause = "Name ASC"
|
||||
};
|
||||
|
||||
// Assert - Original unchanged
|
||||
Assert.That(baseBreakdown.OrderByClause?.Clause, Is.Null.Or.Empty);
|
||||
|
||||
// Extended has new conditions
|
||||
var extendedSql = extendedBreakdown.GetSql();
|
||||
Assert.That(extendedSql, Does.Contain("IsActive"));
|
||||
Assert.That(extendedSql, Does.Contain("ORDER BY"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndCompose_MultipleQueries_CreatesUnionScenario()
|
||||
{
|
||||
// Arrange - Analyze two different queries
|
||||
var activeUsersQuery = _context.Users.Where(u => u.IsActive);
|
||||
var activeBreakdown = LinqQueryBreakdown.Analyze(activeUsersQuery);
|
||||
|
||||
var recentUsersQuery = _context.Users.Where(u => u.Age < 25);
|
||||
var recentBreakdown = LinqQueryBreakdown.Analyze(recentUsersQuery);
|
||||
|
||||
// Act - Get SQL for both (could be used in UNION scenario)
|
||||
var activeSql = activeBreakdown.GetSql();
|
||||
var recentSql = recentBreakdown.GetSql();
|
||||
|
||||
// Assert - Both queries are valid and different
|
||||
Assert.That(activeSql, Does.Contain("IsActive"));
|
||||
Assert.That(recentSql, Does.Contain("Age"));
|
||||
Assert.That(activeSql, Is.Not.EqualTo(recentSql));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndBuildFilter_IncrementallyAddConditions_BuildsComplexFilter()
|
||||
{
|
||||
// Arrange - Start with simple query
|
||||
var query = _context.Users;
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act - Incrementally add filter conditions (simulating filter builder UI)
|
||||
breakdown.AddWhereClause("Age >= 18", "AND");
|
||||
breakdown.AddWhereClause("Age <= 65", "AND");
|
||||
breakdown.AddWhereClause("IsActive = 1", "AND");
|
||||
|
||||
var filterSql = breakdown.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(filterSql, Does.Contain("Age >= 18"));
|
||||
Assert.That(filterSql, Does.Contain("Age <= 65"));
|
||||
Assert.That(filterSql, Does.Contain("IsActive = 1"));
|
||||
|
||||
// Should have multiple AND conditions
|
||||
var andCount = filterSql.Split(new[] { "AND" }, StringSplitOptions.None).Length - 1;
|
||||
Assert.That(andCount, Is.GreaterThanOrEqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndPaginate_AddOrderAndLimits_CreatesPaginatedQuery()
|
||||
{
|
||||
// Arrange - Analyze base query
|
||||
var query = _context.Users.Where(u => u.IsActive);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act - Add pagination (ORDER BY required for consistent pagination)
|
||||
breakdown.OrderByClause = new Strata.SqlTools.SqlBreakdown.Classes.SqlExpressionClause(splitOnComma: true)
|
||||
{
|
||||
Clause = "Id ASC"
|
||||
};
|
||||
|
||||
// Add pagination metadata as setup clause (simulating OFFSET/FETCH or similar)
|
||||
breakdown.SetupClauses.Add("-- Page 1, PageSize 10");
|
||||
|
||||
var paginatedSql = breakdown.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(paginatedSql, Does.Contain("ORDER BY"));
|
||||
Assert.That(paginatedSql, Does.Contain("Id ASC"));
|
||||
Assert.That(breakdown.SetupClauses, Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndGenerateReport_ExtractQueryMetrics_ProvidesAnalytics()
|
||||
{
|
||||
// Arrange - Complex query to analyze
|
||||
var query = _context.Users
|
||||
.Where(u => u.Age > 21)
|
||||
.Where(u => u.IsActive)
|
||||
.OrderBy(u => u.Name)
|
||||
.Select(u => new { u.Id, u.Name, u.Email });
|
||||
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act - Extract metrics
|
||||
var methodCount = breakdown.MethodCallChain.Count;
|
||||
var hasProjection = breakdown.SelectClause?.Clause != "*";
|
||||
var hasFilters = !string.IsNullOrEmpty(breakdown.WhereClause?.Clause);
|
||||
var hasOrdering = !string.IsNullOrEmpty(breakdown.OrderByClause?.Clause);
|
||||
var summary = breakdown.GetQuerySummary();
|
||||
|
||||
// Assert - Verify analytics
|
||||
Assert.That(methodCount, Is.GreaterThan(0));
|
||||
Assert.That(hasProjection, Is.True);
|
||||
Assert.That(hasFilters, Is.True);
|
||||
Assert.That(hasOrdering, Is.True);
|
||||
Assert.That(summary, Does.Contain("SELECT"));
|
||||
Assert.That(summary, Does.Contain("WHERE"));
|
||||
Assert.That(summary, Does.Contain("ORDER BY"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndOptimize_RemoveSelectStar_ImprovedProjection()
|
||||
{
|
||||
// Arrange - Analyze query with SELECT *
|
||||
var query = _context.Users.Where(u => u.IsActive);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Verify it initially has SELECT *
|
||||
var originalSql = breakdown.GetSql();
|
||||
Assert.That(originalSql, Does.Contain("*").Or.Not.Contain("Id, Name"));
|
||||
|
||||
// Act - Optimize by specifying only needed columns
|
||||
breakdown.SelectClause = new Strata.SqlTools.SqlBreakdown.Classes.SqlExpressionClause
|
||||
{
|
||||
Clause = "Id, Name, Email"
|
||||
};
|
||||
|
||||
var optimizedSql = breakdown.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(optimizedSql, Does.Contain("Id"));
|
||||
Assert.That(optimizedSql, Does.Contain("Name"));
|
||||
Assert.That(optimizedSql, Does.Contain("Email"));
|
||||
Assert.That(optimizedSql, Does.Not.Contain("SELECT *"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeMultipleQueries_CompareAndMerge_CreatesCompositeQuery()
|
||||
{
|
||||
// Arrange - Analyze two related queries
|
||||
var usersQuery = _context.Users.Where(u => u.Age > 21);
|
||||
var usersBreakdown = LinqQueryBreakdown.Analyze(usersQuery);
|
||||
|
||||
var activeQuery = _context.Users.Where(u => u.IsActive);
|
||||
var activeBreakdown = LinqQueryBreakdown.Analyze(activeQuery);
|
||||
|
||||
// Act - Merge conditions from both queries
|
||||
var mergedBreakdown = (LinqQueryBreakdown)usersBreakdown.Clone();
|
||||
|
||||
// Extract WHERE clause from activeBreakdown and add to merged
|
||||
if (!string.IsNullOrEmpty(activeBreakdown.WhereClause?.Clause))
|
||||
{
|
||||
mergedBreakdown.AddWhereClause(activeBreakdown.WhereClause.Clause, "AND");
|
||||
}
|
||||
|
||||
var mergedSql = mergedBreakdown.GetSql();
|
||||
|
||||
// Assert - Merged query has both conditions
|
||||
Assert.That(mergedSql, Does.Contain("Age"));
|
||||
Assert.That(mergedSql, Does.Contain("IsActive"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeAndDocument_GenerateQueryDocumentation_CreatesReadableOutput()
|
||||
{
|
||||
// Arrange - Analyze a business query
|
||||
var query = _context.Orders
|
||||
.Where(o => o.Amount > 1000)
|
||||
.Where(o => o.OrderDate > DateTime.Now.AddDays(-30))
|
||||
.OrderBy(o => o.OrderDate);
|
||||
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act - Generate documentation
|
||||
var documentation = new System.Text.StringBuilder();
|
||||
documentation.AppendLine("Query: High-Value Recent Orders");
|
||||
documentation.AppendLine($"Entity: {breakdown.EntityType}");
|
||||
documentation.AppendLine($"Method Chain: {breakdown.GetMethodChain()}");
|
||||
documentation.AppendLine($"Summary: {breakdown.GetQuerySummary()}");
|
||||
documentation.AppendLine();
|
||||
documentation.AppendLine("Generated SQL:");
|
||||
documentation.AppendLine(breakdown.GetSql());
|
||||
|
||||
var doc = documentation.ToString();
|
||||
|
||||
// Assert - Documentation contains all key information
|
||||
Assert.That(doc, Does.Contain("Query:"));
|
||||
Assert.That(doc, Does.Contain("Entity:"));
|
||||
Assert.That(doc, Does.Contain("Method Chain:"));
|
||||
Assert.That(doc, Does.Contain("Summary:"));
|
||||
Assert.That(doc, Does.Contain("Generated SQL:"));
|
||||
Assert.That(doc, Does.Contain("Amount"));
|
||||
Assert.That(doc, Does.Contain("OrderDate"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
// Test data context and entities
|
||||
public class TestDataContext
|
||||
{
|
||||
public IQueryable<User> Users => new List<User>().AsQueryable();
|
||||
public IQueryable<Order> Orders => new List<Order>().AsQueryable();
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public int Age { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
public class Order
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public DateTime OrderDate { get; set; }
|
||||
}
|
||||
|
||||
#region GetQuery Tests
|
||||
|
||||
public class GetQueryTests
|
||||
{
|
||||
private TestDataContext _context = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_context = new TestDataContext();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetQuery_LinqBreakdown_ReturnsNullByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users.Where(u => u.Age > 18);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act - GetQuery returns null because LinqQueryBreakdown needs the original provider
|
||||
var result = breakdown.GetQuery<User>();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetQuery_SqlServerQueryBreakdown_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = new QueryBreakdown("Id, Name", "Users", "Age > 18");
|
||||
|
||||
// Act
|
||||
var result = breakdown.GetQuery<User>();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetQuery_MultipleBreakdownTypes_AllReturnNull()
|
||||
{
|
||||
// Arrange
|
||||
var linqBreakdown = LinqQueryBreakdown.Analyze(_context.Users);
|
||||
var sqlBreakdown = new QueryBreakdown("*", "Users");
|
||||
|
||||
// Act
|
||||
var linqResult = linqBreakdown.GetQuery<User>();
|
||||
var sqlResult = sqlBreakdown.GetQuery<User>();
|
||||
|
||||
// Assert
|
||||
Assert.That(linqResult, Is.Null);
|
||||
Assert.That(sqlResult, Is.Null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region INSERT Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void AnalyzeInsert_SingleEntity_CreatesInsertBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var entity = new User { Id = 1, Name = "John Doe", Email = "john@example.com", Age = 30 };
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.AnalyzeInsert(entity);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.TableName.Clause, Is.EqualTo("User"));
|
||||
Assert.That(breakdown.InsertIntoClause.Clause, Is.Not.Empty);
|
||||
Assert.That(breakdown.ValuesClause.Clause, Is.Not.Empty);
|
||||
Assert.That(breakdown.InsertIntoClause.Clause, Does.Contain("Id"));
|
||||
Assert.That(breakdown.InsertIntoClause.Clause, Does.Contain("Name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeInsertRange_MultipleEntities_CreatesInsertBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var entities = new List<User>
|
||||
{
|
||||
new User { Id = 1, Name = "John Doe", Email = "john@example.com", Age = 30 },
|
||||
new User { Id = 2, Name = "Jane Smith", Email = "jane@example.com", Age = 28 }
|
||||
};
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.AnalyzeInsertRange(entities);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.TableName.Clause, Is.EqualTo("User"));
|
||||
Assert.That(breakdown.InsertIntoClause.Clause, Is.Not.Empty);
|
||||
Assert.That(breakdown.ValuesClause.Clause, Does.Contain("("));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeInsert_NullEntity_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => LinqQueryBreakdown.AnalyzeInsert<User>(null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DELETE Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void AnalyzeDelete_WithFilterExpression_CreatesDeleteBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var filterExpression = System.Linq.Expressions.Expression.Lambda<System.Func<User, bool>>(
|
||||
System.Linq.Expressions.Expression.Constant(true),
|
||||
System.Linq.Expressions.Expression.Parameter(typeof(User), "u")
|
||||
);
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.AnalyzeDelete(filterExpression);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("User"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeDelete_NullFilter_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
LinqQueryBreakdown.AnalyzeDelete<User>(null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UPDATE Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void AnalyzeUpdate_WithFilterAndUpdateExpressions_CreatesUpdateBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var filterExpr = System.Linq.Expressions.Expression.Lambda<System.Func<User, bool>>(
|
||||
System.Linq.Expressions.Expression.Constant(true),
|
||||
System.Linq.Expressions.Expression.Parameter(typeof(User), "u")
|
||||
);
|
||||
|
||||
var updateExpr = System.Linq.Expressions.Expression.Lambda<System.Func<User, User>>(
|
||||
System.Linq.Expressions.Expression.Constant(new User()),
|
||||
System.Linq.Expressions.Expression.Parameter(typeof(User), "u")
|
||||
);
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.AnalyzeUpdate(filterExpr, updateExpr);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.TableName.Clause, Is.EqualTo("User"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeUpdate_NullFilter_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var updateExpr = System.Linq.Expressions.Expression.Lambda<System.Func<User, User>>(
|
||||
System.Linq.Expressions.Expression.Constant(new User()),
|
||||
System.Linq.Expressions.Expression.Parameter(typeof(User), "u")
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
LinqQueryBreakdown.AnalyzeUpdate<User>(null!, updateExpr));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeUpdate_NullUpdateExpression_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var filterExpr = System.Linq.Expressions.Expression.Lambda<System.Func<User, bool>>(
|
||||
System.Linq.Expressions.Expression.Constant(true),
|
||||
System.Linq.Expressions.Expression.Parameter(typeof(User), "u")
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
LinqQueryBreakdown.AnalyzeUpdate<User>(filterExpr, null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PROCEDURE Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void AnalyzeProcedure_WithName_CreatesProcedureBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
const string procName = "sp_GetUsers";
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.AnalyzeProcedure(procName);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo(procName));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeProcedure_WithParameters_CreatesProcedureBreakdownWithParams()
|
||||
{
|
||||
// Arrange
|
||||
const string procName = "sp_GetUsersByAge";
|
||||
var parameters = new object[] { 18, 65 };
|
||||
|
||||
// Act
|
||||
var breakdown = LinqQueryBreakdown.AnalyzeProcedure(procName, parameters);
|
||||
|
||||
// Assert
|
||||
Assert.That(breakdown, Is.Not.Null);
|
||||
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo(procName));
|
||||
Assert.That(breakdown.Parameters, Is.Not.Empty);
|
||||
Assert.That(breakdown.Parameters.Count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeProcedure_NullOrEmptyName_ThrowsArgumentException()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => LinqQueryBreakdown.AnalyzeProcedure(null!));
|
||||
Assert.Throws<ArgumentException>(() => LinqQueryBreakdown.AnalyzeProcedure(""));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TRACE Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void AnalyzeTrace_WithValidQuery_ReturnsTraceString()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users.Where(u => u.Age > 18);
|
||||
|
||||
// Act
|
||||
var trace = LinqQueryBreakdown.AnalyzeTrace(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(trace, Is.Not.Null.And.Not.Empty);
|
||||
Assert.That(trace, Does.Contain("User"));
|
||||
Assert.That(trace, Does.Contain("Query Provider"));
|
||||
Assert.That(trace, Does.Contain("Timestamp"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeTrace_WithExecutionContext_IncludesContextInTrace()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users;
|
||||
const string context = "Initial Load";
|
||||
|
||||
// Act
|
||||
var trace = LinqQueryBreakdown.AnalyzeTrace(query, context);
|
||||
|
||||
// Assert
|
||||
Assert.That(trace, Is.Not.Null);
|
||||
Assert.That(trace, Does.Contain(context));
|
||||
Assert.That(trace, Does.Contain("Execution Context"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyzeTrace_NullQuery_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
LinqQueryBreakdown.AnalyzeTrace<User>(null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Conversion Tests
|
||||
|
||||
[Test]
|
||||
public void ConvertToSqlServerBreakdown_CopiesAllClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users.Where(u => u.Age > 21).OrderBy(u => u.Name);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act
|
||||
var sqlServerBreakdown = breakdown.ConvertToSqlServerBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(sqlServerBreakdown, Is.Not.Null);
|
||||
Assert.That(sqlServerBreakdown.SelectClause?.Clause, Is.EqualTo(breakdown.SelectClause?.Clause));
|
||||
Assert.That(sqlServerBreakdown.FromClause?.Clause, Is.EqualTo(breakdown.FromClause?.Clause));
|
||||
Assert.That(sqlServerBreakdown.WhereClause?.Clause, Is.EqualTo(breakdown.WhereClause?.Clause));
|
||||
Assert.That(sqlServerBreakdown.OrderByClause?.Clause, Is.EqualTo(breakdown.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToPostgreSqlBreakdown_CopiesAllClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users
|
||||
.Where(u => u.IsActive)
|
||||
.OrderBy(u => u.Name)
|
||||
.Select(u => new { u.Id, u.Name });
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act
|
||||
var postgresBreakdown = breakdown.ConvertToPostgreSqlBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(postgresBreakdown, Is.Not.Null);
|
||||
Assert.That(postgresBreakdown.SelectClause?.Clause, Is.EqualTo(breakdown.SelectClause?.Clause));
|
||||
Assert.That(postgresBreakdown.FromClause?.Clause, Is.EqualTo(breakdown.FromClause?.Clause));
|
||||
Assert.That(postgresBreakdown.WhereClause?.Clause, Is.EqualTo(breakdown.WhereClause?.Clause));
|
||||
Assert.That(postgresBreakdown.OrderByClause?.Clause, Is.EqualTo(breakdown.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToSnowflakeBreakdown_CopiesAllClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users.Where(u => u.Age > 21).OrderBy(u => u.Name);
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act
|
||||
var snowflakeBreakdown = breakdown.ConvertToSnowflakeBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(snowflakeBreakdown, Is.Not.Null);
|
||||
Assert.That(snowflakeBreakdown.SelectClause?.Clause, Is.EqualTo(breakdown.SelectClause?.Clause));
|
||||
Assert.That(snowflakeBreakdown.FromClause?.Clause, Is.EqualTo(breakdown.FromClause?.Clause));
|
||||
Assert.That(snowflakeBreakdown.WhereClause?.Clause, Is.EqualTo(breakdown.WhereClause?.Clause));
|
||||
Assert.That(snowflakeBreakdown.OrderByClause?.Clause, Is.EqualTo(breakdown.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToSqlServerBreakdown_WithComments_PreservesComments()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = new LinqQueryBreakdown("Id, Name", "Users", "Age > 21");
|
||||
breakdown.SelectClause.Comment = "Select user columns";
|
||||
breakdown.WhereClause.Comment = "Filter adult users";
|
||||
|
||||
// Act
|
||||
var sqlServerBreakdown = breakdown.ConvertToSqlServerBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(sqlServerBreakdown.SelectClause?.Comment, Is.EqualTo("Select user columns"));
|
||||
Assert.That(sqlServerBreakdown.WhereClause?.Comment, Is.EqualTo("Filter adult users"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToPostgreSqlBreakdown_WithComplexQuery_CopieAllClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = _context.Users
|
||||
.Where(u => u.IsActive && u.Age >= 18)
|
||||
.OrderByDescending(u => u.Age);
|
||||
|
||||
var breakdown = LinqQueryBreakdown.Analyze(query);
|
||||
|
||||
// Act
|
||||
var postgresBreakdown = breakdown.ConvertToPostgreSqlBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(postgresBreakdown.SelectClause?.Clause, Is.Not.Null);
|
||||
Assert.That(postgresBreakdown.FromClause?.Clause, Is.Not.Null);
|
||||
Assert.That(postgresBreakdown.WhereClause?.Clause, Is.Not.Null);
|
||||
Assert.That(postgresBreakdown.GroupByClause?.Clause, Is.Not.Null);
|
||||
Assert.That(postgresBreakdown.OrderByClause?.Clause, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToSnowflakeBreakdown_PreservesAllClauseInformation()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdown.Analyze(_context.Users.OrderBy(u => u.Name));
|
||||
|
||||
// Act
|
||||
var snowflakeBreakdown = breakdown.ConvertToSnowflakeBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(snowflakeBreakdown.FromClause?.Clause, Is.EqualTo(breakdown.FromClause?.Clause));
|
||||
Assert.That(snowflakeBreakdown.OrderByClause?.Clause, Is.EqualTo(breakdown.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToSqlServerBreakdown_InstancesAreIndependent()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdown.Analyze(_context.Users.Where(u => u.Age > 21));
|
||||
var sqlServerBreakdown = breakdown.ConvertToSqlServerBreakdown();
|
||||
|
||||
// Act - Modify the SQL Server breakdown
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id, Name, Email";
|
||||
|
||||
// Assert - Original should be unchanged
|
||||
Assert.That(breakdown.SelectClause?.Clause, Is.Not.EqualTo(sqlServerBreakdown.SelectClause?.Clause));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertChainMultipleTimes_EachConversionIndependent()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdown.Analyze(_context.Users.OrderBy(u => u.Name));
|
||||
|
||||
// Act
|
||||
var sqlServer1 = breakdown.ConvertToSqlServerBreakdown();
|
||||
var sqlServer2 = breakdown.ConvertToSqlServerBreakdown();
|
||||
|
||||
// Assert - Both should have same data but be different instances
|
||||
Assert.That(sqlServer1, Is.Not.SameAs(sqlServer2));
|
||||
Assert.That(sqlServer1.OrderByClause?.Clause, Is.EqualTo(sqlServer2.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
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.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.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.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.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.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.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
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
using NUnit.Framework;
|
||||
using Strata.SqlTools.Analyzers.LinqToSql;
|
||||
using Strata.SqlTools.Builders.LinqToSql;
|
||||
|
||||
namespace Strata.SqlTools.LinqToSql.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class QueryCollectionAnalyzerTests
|
||||
{
|
||||
#region Basic Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void Analyze_WithEmptyCollection_ReturnsZeroStatistics()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>();
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TotalQueries, Is.EqualTo(0));
|
||||
Assert.That(stats.UniqueQueries, Is.EqualTo(0));
|
||||
Assert.That(stats.DuplicateQueries.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_WithSingleQuery_ReturnsCorrectStatistics()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TotalQueries, Is.EqualTo(1));
|
||||
Assert.That(stats.UniqueQueries, Is.EqualTo(1));
|
||||
Assert.That(stats.DuplicateQueries.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_WithIdenticalQueries_IdentifiesDuplicates()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query3 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown> { query1, query2, query3 };
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TotalQueries, Is.EqualTo(3));
|
||||
Assert.That(stats.UniqueQueries, Is.EqualTo(1));
|
||||
Assert.That(stats.DuplicateQueries.Count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Deduplication Rate Tests
|
||||
|
||||
[Test]
|
||||
public void DeduplicationRate_WithAllUnique_ReturnsOne()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Products").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.DeduplicationRate, Is.EqualTo(1.0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeduplicationRate_WithSomeDuplicates_ReturnsCorrectRatio()
|
||||
{
|
||||
// Arrange
|
||||
var query = LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build();
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
query,
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(), // Duplicate
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.DeduplicationRate, Is.EqualTo(2.0 / 3.0).Within(0.01));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Query Characteristics Tests
|
||||
|
||||
[Test]
|
||||
public void Analyze_CountsQueriesWithoutWhere()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Where("Amount > 100").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Products").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.QueriesWithoutWhere, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_CountsQueriesWithoutOrderBy()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").OrderByAscending("Name").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Products").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.QueriesWithoutOrderBy, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_CountsQueriesWithSelectAll()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id", "Name").FromTable("Orders").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Products").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.QueriesWithSelectAll, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Table Usage Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void Analyze_AnalyzesTableUsageFrequency()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TableUsageFrequency["Users"], Is.EqualTo(3));
|
||||
Assert.That(stats.TableUsageFrequency["Orders"], Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_IgnoresNullTableNames()
|
||||
{
|
||||
// Arrange
|
||||
var query = LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build();
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown> { query };
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TableUsageFrequency.ContainsKey("Users"), Is.True);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Column Usage Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void Analyze_AnalyzesColumnSelectionFrequency()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id", "Name", "Email").FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id", "Name").FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id").FromTable("Users").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.ColumnSelectionFrequency.ContainsKey("Id"), Is.True);
|
||||
Assert.That(stats.ColumnSelectionFrequency["Id"], Is.EqualTo(3));
|
||||
Assert.That(stats.ColumnSelectionFrequency["Name"], Is.EqualTo(2));
|
||||
Assert.That(stats.ColumnSelectionFrequency["Email"], Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_IgnoresSelectAll_InColumnUsage()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id").FromTable("Users").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.ColumnSelectionFrequency.ContainsKey("*"), Is.False);
|
||||
Assert.That(stats.ColumnSelectionFrequency["Id"], Is.EqualTo(1));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Complexity Analysis Tests
|
||||
|
||||
[Test]
|
||||
public void Analyze_CalculatesAverageComplexity()
|
||||
{
|
||||
// Arrange
|
||||
var simpleQuery = LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build();
|
||||
var complexQuery = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*)")
|
||||
.FromTable("Employees")
|
||||
.Where("Salary > 50000 AND Department IN ('IT', 'HR')")
|
||||
.GroupBy("Department")
|
||||
.Having("COUNT(*) > 5")
|
||||
.OrderByDescending("COUNT(*)")
|
||||
.Build();
|
||||
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown> { simpleQuery, complexQuery };
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.AverageComplexity, Is.GreaterThan(0));
|
||||
Assert.That(stats.AverageComplexity, Is.LessThanOrEqualTo(10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Analyze_CountsComplexQueries()
|
||||
{
|
||||
// Arrange
|
||||
var simpleQuery = LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build();
|
||||
var complexQuery = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Dept", "COUNT(*)")
|
||||
.FromTable("Emp")
|
||||
.Where("Salary > 50000 AND Department IN ('IT', 'HR')")
|
||||
.GroupBy("Dept")
|
||||
.Having("COUNT(*) > 5")
|
||||
.Build();
|
||||
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown> { simpleQuery, complexQuery };
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.ComplexQueriesCount, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FindDuplicates Tests
|
||||
|
||||
[Test]
|
||||
public void FindDuplicates_WithNoDuplicates_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var duplicates = analyzer.FindDuplicates();
|
||||
|
||||
// Assert
|
||||
Assert.That(duplicates.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindDuplicates_WithDuplicates_ReturnsDuplicateQueries()
|
||||
{
|
||||
// Arrange
|
||||
var query = LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build();
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
query,
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var duplicates = analyzer.FindDuplicates();
|
||||
|
||||
// Assert
|
||||
Assert.That(duplicates.Count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FindSimilarQueries Tests
|
||||
|
||||
[Test]
|
||||
public void FindSimilarQueries_WithSimilarQueries_ReturnsSimilarPairs()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id", "Name").FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id", "Name", "Email").FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var similar = analyzer.FindSimilarQueries(0.5); // Lower threshold for testing
|
||||
|
||||
// Assert
|
||||
Assert.That(similar.Count, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindSimilarQueries_SimilarityScoresBounded()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id").FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectColumns("Id", "Name").FromTable("Users").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var similar = analyzer.FindSimilarQueries(0.5);
|
||||
|
||||
// Assert
|
||||
foreach (var pair in similar)
|
||||
{
|
||||
Assert.That(pair.Similarity, Is.GreaterThanOrEqualTo(0.5));
|
||||
Assert.That(pair.Similarity, Is.LessThan(1.0));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Report Generation Tests
|
||||
|
||||
[Test]
|
||||
public void GetReport_IncludesBasicStatistics()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var report = analyzer.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("Query Collection Analysis Report"));
|
||||
Assert.That(report, Contains.Substring("Total Queries: 1"));
|
||||
Assert.That(report, Contains.Substring("Unique Queries: 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetReport_IncludesTableUsage()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>
|
||||
{
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Users").Build(),
|
||||
LinqQueryBreakdownBuilder.Create().SelectAll().FromTable("Orders").Build()
|
||||
};
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var report = analyzer.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("Most Frequently Used Tables"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Null Handling Tests
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithNullQueries_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new QueryCollectionAnalyzer(null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Test]
|
||||
public void Analyze_WithLargeCollection_PerformsAnalysis()
|
||||
{
|
||||
// Arrange
|
||||
var queries = new List<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
queries.Add(LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build());
|
||||
}
|
||||
var analyzer = QueryCollectionAnalyzer.Analyze(queries);
|
||||
|
||||
// Act
|
||||
var stats = analyzer.Analyze();
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.TotalQueries, Is.EqualTo(100));
|
||||
Assert.That(stats.UniqueQueries, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
using NUnit.Framework;
|
||||
using Strata.SqlTools.Builders.LinqToSql;
|
||||
using Strata.SqlTools.Comparers.LinqToSql;
|
||||
|
||||
namespace Strata.SqlTools.LinqToSql.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class QueryComparatorTests
|
||||
{
|
||||
#region Equivalence Tests
|
||||
|
||||
[Test]
|
||||
public void AreEquivalent_WithIdenticalQueries_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(comparator.AreEquivalent, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AreEquivalent_WithDifferentSelectClauses_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name", "Email")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(comparator.AreEquivalent, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AreEquivalent_WithDifferentFromClauses_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Customers")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(comparator.AreEquivalent, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AreEquivalent_WithDifferentWhereClauses_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("Age > 18")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("Age > 21")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(comparator.AreEquivalent, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AreEquivalent_WithWhitespaceVariations_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(comparator.AreEquivalent, Is.True);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Similarity Score Tests
|
||||
|
||||
[Test]
|
||||
public void SimilarityScore_WithIdenticalQueries_ReturnsOne()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.OrderByAscending("Name")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.OrderByAscending("Name")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var score = QueryComparator.GetSimilarity(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(score, Is.EqualTo(1.0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimilarityScore_WithPartialMatch_ReturnsBetweenZeroAndOne()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = false")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var score = QueryComparator.GetSimilarity(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(score, Is.GreaterThan(0.0));
|
||||
Assert.That(score, Is.LessThan(1.0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimilarityScore_WithCompletelyDifferentQueries_ReturnsLow()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("OrderId")
|
||||
.FromTable("Orders")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var score = QueryComparator.GetSimilarity(query1, query2);
|
||||
|
||||
// Assert
|
||||
// Only SELECT and FROM different, rest are same (empty), so ~67% match
|
||||
Assert.That(score, Is.LessThan(0.75));
|
||||
Assert.That(score, Is.GreaterThan(0.5));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Difference Detection Tests
|
||||
|
||||
[Test]
|
||||
public void Differences_WithMultipleDifferences_ListsAll()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("Active = true")
|
||||
.GroupBy("Department")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id")
|
||||
.FromTable("Orders")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(comparator.Differences.Count, Is.GreaterThan(2));
|
||||
Assert.That(comparator.Differences, Does.Contain("SELECT clauses differ"));
|
||||
Assert.That(comparator.Differences, Does.Contain("FROM clauses differ"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Differences_WithNoDifferences_IsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(comparator.Differences, Is.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Clause-Specific Comparison Tests
|
||||
|
||||
[Test]
|
||||
public void Compare_TrackSelectClauseMatch()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.Compare(query1, query2).Result;
|
||||
|
||||
// Assert
|
||||
Assert.That(result.HaveSameSelectColumns, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Compare_TrackFromClauseMatch()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.Compare(query1, query2).Result;
|
||||
|
||||
// Assert
|
||||
Assert.That(result.HaveSameFromTable, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Compare_TrackWhereClauseMatch()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.Compare(query1, query2).Result;
|
||||
|
||||
// Assert
|
||||
Assert.That(result.HaveSameWhereClause, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Compare_TrackGroupByClauseMatch()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*)")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*)")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.Compare(query1, query2).Result;
|
||||
|
||||
// Assert
|
||||
Assert.That(result.HaveSameGroupBy, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Compare_TrackHavingClauseMatch()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*)")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Having("COUNT(*) > 5")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*)")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Having("COUNT(*) > 5")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.Compare(query1, query2).Result;
|
||||
|
||||
// Assert
|
||||
Assert.That(result.HaveSameHaving, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Compare_TrackOrderByClauseMatch()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.OrderByDescending("CreatedDate")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.OrderByDescending("CreatedDate")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.Compare(query1, query2).Result;
|
||||
|
||||
// Assert
|
||||
Assert.That(result.HaveSameOrderBy, Is.True);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Factory Method Tests
|
||||
|
||||
[Test]
|
||||
public void AreQueriesEquivalent_StaticMethod_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.AreQueriesEquivalent(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AreQueriesIdentical_WithIdenticalQueries_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.AreQueriesIdentical(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AreQueriesIdentical_WithSimilarQueries_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name", "Email")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.AreQueriesIdentical(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSimilarity_StaticMethod_ReturnsCorrectScore()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var score = QueryComparator.GetSimilarity(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(score, Is.EqualTo(1.0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Null Handling Tests
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithNullQuery1_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
Assert.Throws<ArgumentNullException>(() => new QueryComparator(null, query2));
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithNullQuery2_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
Assert.Throws<ArgumentNullException>(() => new QueryComparator(query1, null));
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Report Generation Tests
|
||||
|
||||
[Test]
|
||||
public void GetReport_WithIdenticalQueries_ShowsIdentical()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
var report = comparator.Result.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("identical"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetReport_WithDifferentQueries_ListsDifferences()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id")
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Orders")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
var report = comparator.Result.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("Differences"));
|
||||
Assert.That(report, Contains.Substring("SELECT clauses differ"));
|
||||
Assert.That(report, Contains.Substring("FROM clauses differ"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetReport_IncludesSimilarityScore()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Orders")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var comparator = QueryComparator.Compare(query1, query2);
|
||||
var report = comparator.Result.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("Similarity: "));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
|
||||
[Test]
|
||||
public void CompareWithEmptyQueriesStillWorks()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var result = QueryComparator.AreQueriesEquivalent(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SimilarityScore_IsBoundedBetweenZeroAndOne()
|
||||
{
|
||||
// Arrange
|
||||
var query1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
var query2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Orders")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var score = QueryComparator.GetSimilarity(query1, query2);
|
||||
|
||||
// Assert
|
||||
Assert.That(score, Is.GreaterThanOrEqualTo(0.0));
|
||||
Assert.That(score, Is.LessThanOrEqualTo(1.0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
using NUnit.Framework;
|
||||
using Strata.SqlTools.Builders.LinqToSql;
|
||||
using Strata.SqlTools.Validators.LinqToSql;
|
||||
|
||||
namespace Strata.SqlTools.LinqToSql.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class QueryValidatorTests
|
||||
{
|
||||
#region Basic Validation Tests
|
||||
|
||||
[Test]
|
||||
public void Validate_WithValidQuery_HasNoErrors()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.HasErrors, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_WithNull_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var validator = QueryValidator.Create();
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
Assert.Throws<ArgumentNullException>(() => validator.Validate(null));
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var validator = QueryValidator.Create();
|
||||
var breakdown1 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
var breakdown2 = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Orders")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
validator.Validate(breakdown1);
|
||||
var count1 = validator.Issues.Count;
|
||||
validator.Clear();
|
||||
validator.Validate(breakdown2);
|
||||
var count2 = validator.Issues.Count;
|
||||
|
||||
// Assert
|
||||
Assert.That(count1, Is.GreaterThan(0));
|
||||
Assert.That(count2, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SELECT Clause Validation Tests
|
||||
|
||||
[Test]
|
||||
public void Validate_SelectStarQuery_HasSelectAllColumnsWarning()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "SELECT_ALL_COLUMNS");
|
||||
Assert.That(issue, Is.Not.Null);
|
||||
Assert.That(issue!.Severity, Is.EqualTo(ValidationSeverity.Warning));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_SelectManyColumns_HasSelectTooManyWarning()
|
||||
{
|
||||
// Arrange
|
||||
var columns = Enumerable.Range(1, 25).Select(i => $"Column{i}").ToArray();
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns(columns)
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "SELECT_TOO_MANY");
|
||||
Assert.That(issue, Is.Not.Null);
|
||||
Assert.That(issue!.Severity, Is.EqualTo(ValidationSeverity.Warning));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_MissingSelectClause_HasSelectMissingError()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
breakdown.SelectClause.Clause = string.Empty;
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.HasErrors, Is.True);
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "SELECT_MISSING");
|
||||
Assert.That(issue, Is.Not.Null);
|
||||
Assert.That(issue!.Severity, Is.EqualTo(ValidationSeverity.Error));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FROM Clause Validation Tests
|
||||
|
||||
[Test]
|
||||
public void Validate_MissingFromClause_HasFromMissingError()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
breakdown.FromClause.Clause = string.Empty;
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.HasErrors, Is.True);
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "FROM_MISSING");
|
||||
Assert.That(issue, Is.Not.Null);
|
||||
Assert.That(issue!.Severity, Is.EqualTo(ValidationSeverity.Error));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GROUP BY + HAVING Validation Tests
|
||||
|
||||
[Test]
|
||||
public void Validate_HavingWithoutGroupBy_HasHavingWithoutGroupByError()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Having("COUNT(*) > 5")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.HasErrors, Is.True);
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "HAVING_WITHOUT_GROUPBY");
|
||||
Assert.That(issue, Is.Not.Null);
|
||||
Assert.That(issue!.Severity, Is.EqualTo(ValidationSeverity.Error));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_GroupByWithHaving_HasNoGroupByError()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*)")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Having("COUNT(*) > 5")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "HAVING_WITHOUT_GROUPBY");
|
||||
Assert.That(issue, Is.Null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region WHERE Clause Validation Tests
|
||||
|
||||
[Test]
|
||||
public void Validate_NoWhereClause_HasNoWhereClauseInfo()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "NO_WHERE_CLAUSE");
|
||||
Assert.That(issue, Is.Not.Null);
|
||||
Assert.That(issue!.Severity, Is.EqualTo(ValidationSeverity.Warning));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_WithWhereClause_NoWhereWarning()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "NO_WHERE_CLAUSE");
|
||||
Assert.That(issue, Is.Null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ORDER BY Validation Tests
|
||||
|
||||
[Test]
|
||||
public void Validate_NoOrderByOrGroupBy_HasNoOrderByInfo()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "NO_ORDER_BY");
|
||||
Assert.That(issue, Is.Not.Null);
|
||||
Assert.That(issue!.Severity, Is.EqualTo(ValidationSeverity.Info));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_WithOrderBy_NoOrderByInfo()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.OrderByAscending("Name")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "NO_ORDER_BY");
|
||||
Assert.That(issue, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_WithGroupBy_NoOrderByInfo()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Department", "COUNT(*)")
|
||||
.FromTable("Employees")
|
||||
.GroupBy("Department")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
var issue = validator.Issues.FirstOrDefault(i => i.Code == "NO_ORDER_BY");
|
||||
Assert.That(issue, Is.Null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Issue Filtering Tests
|
||||
|
||||
[Test]
|
||||
public void GetIssuesBySeverity_FiltersCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
var warnings = validator.GetIssuesBySeverity(ValidationSeverity.Warning);
|
||||
var infos = validator.GetIssuesBySeverity(ValidationSeverity.Info);
|
||||
var errors = validator.GetIssuesBySeverity(ValidationSeverity.Error);
|
||||
|
||||
// Assert
|
||||
Assert.That(warnings, Is.Not.Empty);
|
||||
Assert.That(infos, Is.Not.Empty);
|
||||
Assert.That(errors, Is.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Custom Issue Tests
|
||||
|
||||
[Test]
|
||||
public void AddIssue_AddsCustomIssue()
|
||||
{
|
||||
// Arrange
|
||||
var validator = QueryValidator.Create();
|
||||
|
||||
// Act
|
||||
validator.AddIssue(
|
||||
ValidationSeverity.Error,
|
||||
"CUSTOM_001",
|
||||
"Custom validation error",
|
||||
"This is a custom issue");
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.Issues.Count, Is.EqualTo(1));
|
||||
Assert.That(validator.Issues[0].Code, Is.EqualTo("CUSTOM_001"));
|
||||
Assert.That(validator.HasErrors, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddIssue_SupportsMethodChaining()
|
||||
{
|
||||
// Arrange
|
||||
var validator = QueryValidator.Create();
|
||||
|
||||
// Act
|
||||
var result = validator
|
||||
.AddIssue(ValidationSeverity.Error, "ERR_001", "Error 1")
|
||||
.AddIssue(ValidationSeverity.Warning, "WARN_001", "Warning 1")
|
||||
.AddIssue(ValidationSeverity.Info, "INFO_001", "Info 1");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(validator));
|
||||
Assert.That(validator.Issues.Count, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Clear Tests
|
||||
|
||||
[Test]
|
||||
public void Clear_RemovesAllIssues()
|
||||
{
|
||||
// Arrange
|
||||
var validator = QueryValidator.Create();
|
||||
validator.AddIssue(ValidationSeverity.Error, "ERR", "Error");
|
||||
validator.AddIssue(ValidationSeverity.Warning, "WARN", "Warning");
|
||||
|
||||
// Act
|
||||
validator.Clear();
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.Issues.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clear_ReturnsSelf()
|
||||
{
|
||||
// Arrange
|
||||
var validator = QueryValidator.Create();
|
||||
|
||||
// Act
|
||||
var result = validator.Clear();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(validator));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Report Generation Tests
|
||||
|
||||
[Test]
|
||||
public void GetReport_WithNoIssues_ShowsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.OrderByAscending("Name")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
var report = validator.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("No validation issues"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetReport_WithIssues_ListsEachIssue()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
var report = validator.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("WARNINGS").IgnoreCase);
|
||||
Assert.That(report, Contains.Substring("Validation Report").IgnoreCase);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetReport_IncludesIssueDetails()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
var report = validator.GetReport();
|
||||
|
||||
// Assert
|
||||
Assert.That(report, Contains.Substring("SELECT_ALL_COLUMNS"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Complex Anti-Pattern Tests
|
||||
|
||||
[Test]
|
||||
public void Validate_ComplexProblematicQuery_FindsMultipleIssues()
|
||||
{
|
||||
// Arrange - Query with SELECT *, no WHERE, no ORDER BY
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("LargeTable")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.Issues.Count, Is.GreaterThan(1));
|
||||
Assert.That(validator.HasWarnings, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_WellFormedAggregationQuery_HasMinimalIssues()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Category", "COUNT(*) as ProductCount", "SUM(Price) as TotalValue")
|
||||
.FromTable("Products")
|
||||
.Where("IsActive = true")
|
||||
.GroupBy("Category")
|
||||
.OrderByDescending("TotalValue")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(validator.HasErrors, Is.False);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Factory Tests
|
||||
|
||||
[Test]
|
||||
public void Create_ReturnsNewValidator()
|
||||
{
|
||||
// Act
|
||||
var validator = QueryValidator.Create();
|
||||
|
||||
// Assert
|
||||
Assert.That(validator, Is.Not.Null);
|
||||
Assert.That(validator, Is.TypeOf<QueryValidator>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidateQuery_ReturnsValidatorWithResults()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectAll()
|
||||
.FromTable("Users")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var validator = QueryValidator.ValidateQuery(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(validator, Is.Not.Null);
|
||||
Assert.That(validator.Issues.Count, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region String Representation Tests
|
||||
|
||||
[Test]
|
||||
public void QueryValidationIssue_ToString_FormatsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var issue = new QueryValidationIssue(
|
||||
ValidationSeverity.Error,
|
||||
"TEST_001",
|
||||
"Test error message",
|
||||
"Additional details");
|
||||
|
||||
// Act
|
||||
var str = issue.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.That(str, Contains.Substring("Error"));
|
||||
Assert.That(str, Contains.Substring("TEST_001"));
|
||||
Assert.That(str, Contains.Substring("Test error message"));
|
||||
Assert.That(str, Contains.Substring("Additional details"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QueryValidationIssue_ToStringWithoutDetails_FormatsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var issue = new QueryValidationIssue(
|
||||
ValidationSeverity.Warning,
|
||||
"WARN_001",
|
||||
"Warning message");
|
||||
|
||||
// Act
|
||||
var str = issue.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.That(str, Contains.Substring("Warning"));
|
||||
Assert.That(str, Contains.Substring("WARN_001"));
|
||||
Assert.That(str, Contains.Substring("Warning message"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
using NUnit.Framework;
|
||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||
using Strata.SqlTools.Builders.LinqToSql;
|
||||
using Strata.SqlTools.Converters.LinqToSql;
|
||||
using PostgreSqlBreakdown = Strata.SqlTools.Breakdowns.PostgreSql.QueryBreakdown;
|
||||
using SnowflakeBreakdown = Strata.SqlTools.Breakdowns.Snowflake.QueryBreakdown;
|
||||
|
||||
namespace Strata.SqlTools.LinqToSql.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class ReverseConverterTests
|
||||
{
|
||||
#region SQL Server to LINQ Conversion Tests
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_FromSqlServer_PreservesAllClauses()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id, Name, Email";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
sqlServerBreakdown.WhereClause.Clause = "IsActive = 1";
|
||||
sqlServerBreakdown.OrderByClause.Clause = "Name ASC";
|
||||
|
||||
// Act
|
||||
var linqBreakdown = sqlServerBreakdown.ToLinqQueryBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(linqBreakdown, Is.Not.Null);
|
||||
Assert.That(linqBreakdown.SelectClause.Clause, Contains.Substring("Id"));
|
||||
Assert.That(linqBreakdown.FromClause.Clause, Is.EqualTo("Users"));
|
||||
Assert.That(linqBreakdown.WhereClause.Clause, Is.EqualTo("IsActive = 1"));
|
||||
Assert.That(linqBreakdown.OrderByClause.Clause, Is.EqualTo("Name ASC"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_FromSqlServer_WithGroupBy_PreservesGroupByAndHaving()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Department, COUNT(*) as EmployeeCount";
|
||||
sqlServerBreakdown.FromClause.Clause = "Employees";
|
||||
sqlServerBreakdown.GroupByClause.Clause = "Department";
|
||||
sqlServerBreakdown.HavingClause.Clause = "COUNT(*) > 5";
|
||||
|
||||
// Act
|
||||
var linqBreakdown = sqlServerBreakdown.ToLinqQueryBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(linqBreakdown.GroupByClause.Clause, Is.EqualTo("Department"));
|
||||
Assert.That(linqBreakdown.HavingClause.Clause, Is.EqualTo("COUNT(*) > 5"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_FromSqlServer_WithNullInput_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
QueryBreakdown? sqlServerBreakdown = null;
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
Assert.Throws<ArgumentNullException>(() => sqlServerBreakdown.ToLinqQueryBreakdown());
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PostgreSQL to LINQ Conversion Tests
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_FromPostgreSQL_PreservesAllClauses()
|
||||
{
|
||||
// Arrange
|
||||
var pgBreakdown = new PostgreSqlBreakdown();
|
||||
pgBreakdown.SelectClause.Clause = "user_id, user_name";
|
||||
pgBreakdown.FromClause.Clause = "users";
|
||||
pgBreakdown.WhereClause.Clause = "is_active = true";
|
||||
|
||||
// Act
|
||||
var linqBreakdown = pgBreakdown.ToLinqQueryBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(linqBreakdown, Is.Not.Null);
|
||||
Assert.That(linqBreakdown.SelectClause.Clause, Contains.Substring("user_id"));
|
||||
Assert.That(linqBreakdown.FromClause.Clause, Is.EqualTo("users"));
|
||||
Assert.That(linqBreakdown.WhereClause.Clause, Is.EqualTo("is_active = true"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_FromPostgreSQL_WithNullInput_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
PostgreSqlBreakdown? pgBreakdown = null;
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
Assert.Throws<ArgumentNullException>(() => pgBreakdown.ToLinqQueryBreakdown());
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Snowflake to LINQ Conversion Tests
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_FromSnowflake_PreservesAllClauses()
|
||||
{
|
||||
// Arrange
|
||||
var sfBreakdown = new SnowflakeBreakdown();
|
||||
sfBreakdown.SelectClause.Clause = "USER_ID, USER_NAME";
|
||||
sfBreakdown.FromClause.Clause = "USERS";
|
||||
sfBreakdown.WhereClause.Clause = "IS_ACTIVE = TRUE";
|
||||
|
||||
// Act
|
||||
var linqBreakdown = sfBreakdown.ToLinqQueryBreakdown();
|
||||
|
||||
// Assert
|
||||
Assert.That(linqBreakdown, Is.Not.Null);
|
||||
Assert.That(linqBreakdown.SelectClause.Clause, Contains.Substring("USER_ID"));
|
||||
Assert.That(linqBreakdown.FromClause.Clause, Is.EqualTo("USERS"));
|
||||
Assert.That(linqBreakdown.WhereClause.Clause, Is.EqualTo("IS_ACTIVE = TRUE"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_FromSnowflake_WithNullInput_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
SnowflakeBreakdown? sfBreakdown = null;
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
Assert.Throws<ArgumentNullException>(() => sfBreakdown.ToLinqQueryBreakdown());
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dialect-to-Dialect Conversion Tests
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_SqlServerToPostgreSQL_ReturnsPostgreSQLBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id, Name";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
|
||||
// Act
|
||||
var result = sqlServerBreakdown.ConvertToDialect("postgresql");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<PostgreSqlBreakdown>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_SqlServerToSnowflake_ReturnsSnowflakeBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id, Name";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
|
||||
// Act
|
||||
var result = sqlServerBreakdown.ConvertToDialect("snowflake");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<SnowflakeBreakdown>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_SqlServerToLINQ_ReturnsLinqQueryBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id, Name";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
|
||||
// Act
|
||||
var result = sqlServerBreakdown.ConvertToDialect("linq");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<Strata.SqlTools.Breakdowns.LinqToSql.LinqQueryBreakdown>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_SqlServerToSqlServer_ReturnsSameSqlServerBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id, Name";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
|
||||
// Act
|
||||
var result = sqlServerBreakdown.ConvertToDialect("sqlserver");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(sqlServerBreakdown));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_PostgresToSqlServer_ReturnsSqlServerBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var pgBreakdown = new PostgreSqlBreakdown();
|
||||
pgBreakdown.SelectClause.Clause = "id, name";
|
||||
pgBreakdown.FromClause.Clause = "users";
|
||||
|
||||
// Act
|
||||
var result = pgBreakdown.ConvertToDialect("sqlserver");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<QueryBreakdown>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_SnowflakeToPostgreSQL_ReturnsPostgresBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var sfBreakdown = new SnowflakeBreakdown();
|
||||
sfBreakdown.SelectClause.Clause = "ID, NAME";
|
||||
sfBreakdown.FromClause.Clause = "USERS";
|
||||
|
||||
// Act
|
||||
var result = sfBreakdown.ConvertToDialect("postgres");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<PostgreSqlBreakdown>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_WithUnknownDialect_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => sqlServerBreakdown.ConvertToDialect("unknown"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_IsCaseInsensitive()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
|
||||
// Act
|
||||
var result1 = sqlServerBreakdown.ConvertToDialect("PostgreSQL");
|
||||
var result2 = sqlServerBreakdown.ConvertToDialect("POSTGRESQL");
|
||||
var result3 = sqlServerBreakdown.ConvertToDialect("postgresql");
|
||||
|
||||
// Assert
|
||||
Assert.That(result1, Is.TypeOf<PostgreSqlBreakdown>());
|
||||
Assert.That(result2, Is.TypeOf<PostgreSqlBreakdown>());
|
||||
Assert.That(result3, Is.TypeOf<PostgreSqlBreakdown>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Round-Trip Conversion Tests
|
||||
|
||||
[Test]
|
||||
public void RoundTrip_SqlServer_ToLinq_ToPostgres_ToLinq_ToSqlServer_PreservesData()
|
||||
{
|
||||
// Arrange
|
||||
var original = LinqQueryBreakdownBuilder
|
||||
.Create()
|
||||
.SelectColumns("Id", "Name")
|
||||
.FromTable("Users")
|
||||
.Where("IsActive = true")
|
||||
.OrderByAscending("Name")
|
||||
.Build();
|
||||
|
||||
// Act - Convert: LINQ -> SqlServer -> LINQ -> PostgreSQL -> LINQ -> SqlServer
|
||||
var toSqlServer = original.ConvertToSqlServerBreakdown() as QueryBreakdown;
|
||||
var backToLinq = toSqlServer!.ToLinqQueryBreakdown();
|
||||
var toPostgres = backToLinq.ConvertToPostgreSqlBreakdown() as PostgreSqlBreakdown;
|
||||
var backToLinq2 = toPostgres!.ToLinqQueryBreakdown();
|
||||
var finalToSqlServer = backToLinq2.ConvertToSqlServerBreakdown();
|
||||
|
||||
// Assert - Check that clauses are preserved
|
||||
Assert.That(backToLinq.SelectClause.Clause, Contains.Substring("Id"));
|
||||
Assert.That(backToLinq.FromClause.Clause, Is.EqualTo("Users"));
|
||||
Assert.That(backToLinq.WhereClause.Clause, Is.EqualTo("IsActive = true"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Null Handling Tests
|
||||
|
||||
[Test]
|
||||
public void ToLinqQueryBreakdown_WithNullPostgreSQL_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
PostgreSqlBreakdown? pgBreakdown = null;
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
Assert.Throws<ArgumentNullException>(() => pgBreakdown.ToLinqQueryBreakdown());
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_WithNullBreakdown_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
QueryBreakdown? sqlServerBreakdown = null;
|
||||
|
||||
// Act & Assert
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
Assert.Throws<ArgumentNullException>(() => sqlServerBreakdown.ConvertToDialect("postgresql"));
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Alternative Dialect Names Tests
|
||||
|
||||
[Test]
|
||||
public void ConvertToDialect_AcceptsAlternativeDialectNames()
|
||||
{
|
||||
// Arrange
|
||||
var sqlServerBreakdown = new QueryBreakdown();
|
||||
sqlServerBreakdown.SelectClause.Clause = "Id";
|
||||
sqlServerBreakdown.FromClause.Clause = "Users";
|
||||
|
||||
// Act
|
||||
var resultPostgres1 = sqlServerBreakdown.ConvertToDialect("postgres");
|
||||
var resultPostgres2 = sqlServerBreakdown.ConvertToDialect("postgresql");
|
||||
var resultSqlServer1 = sqlServerBreakdown.ConvertToDialect("sqlserver");
|
||||
var resultSqlServer2 = sqlServerBreakdown.ConvertToDialect("sql_server");
|
||||
|
||||
// Assert
|
||||
Assert.That(resultPostgres1, Is.TypeOf<PostgreSqlBreakdown>());
|
||||
Assert.That(resultPostgres2, Is.TypeOf<PostgreSqlBreakdown>());
|
||||
Assert.That(resultSqlServer1, Is.SameAs(sqlServerBreakdown));
|
||||
Assert.That(resultSqlServer2, Is.SameAs(sqlServerBreakdown));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Strata.SqlTools.SqlBreakdown\Strata.SqlTools.SqlBreakdown.csproj" />
|
||||
<ProjectReference Include="..\..\src\Strata.SqlTools.SqlServer\Strata.SqlTools.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\src\Strata.SqlTools.LinqToSql\Strata.SqlTools.LinqToSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user