Files
sql-utilities/tests/Strata.SqlTools.LinqToSql.Tests/QueryValidatorTests.cs
T
Thom LambandClaude Opus 4.7 505ada5017 chore(sonar): adopt Has.Count, Is.Empty, Does.Contain, char overloads in tests (NUnit2046, NUnit2011, CA1866)
- NUnit2046: `Assert.That(x.Count, Is.EqualTo(n))` → `Assert.That(x, Has.Count.EqualTo(n))` (or `Is.Empty` when n==0)
- NUnit2011: `Assert.That(s.Contains(x))` → `Assert.That(s, Does.Contain(x))` for richer failure messages
- CA1866: `.StartsWith("$"|"@"|":")` → `.StartsWith('$'|'@'|':')` char overload

Driven by `dotnet format analyzers --diagnostics NUnit2046 NUnit2011 CA1866`
for the cases the Roslyn fixer handles, plus a regex sweep for the remaining
`Count == n` (n>0) cases which the fixer doesn't address. CA1866 had no
associated code fix and was edited by hand (3 sites in 2 files). All tests
green (1180 passing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:58:00 -05:00

585 lines
16 KiB
C#

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, Has.Count.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, Has.Count.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, Is.Empty);
}
[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
}