chore: initial git load of code space

This commit is contained in:
Thom Lamb
2026-05-12 08:52:33 -05:00
parent 9abada692f
commit 5e467bcc9c
384 changed files with 65960 additions and 2 deletions
@@ -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>