SonarQube Analysis / sonarqube (pull_request) Successful in 3m41s
- **NUnit2046 (15)**: extend the prior regex sweep to also catch the
`Is.GreaterThan(n)` / `Is.GreaterThanOrEqualTo(n)` variants on
`.Count` and `.Length` — the previous pass only handled `Is.EqualTo`.
Affects PG/Snowflake `QueryBreakdownTests`, PG `StatementReaderTests`,
`LinqToSql` `QueryComparatorTests` / `QueryValidatorTests`,
`Markdown.Tests` `ExpressionGeneratorTests` /
`QueryBreakdownGeneratorTests`, and `JsonTokenReaderTests`.
- **CS8604 (8)**: `merged[someKey]` access inside `Assert.Multiple(() =>
{ ... })` lambdas where `someKey` is `string?` from `FirstOrDefault`.
The preceding `Assert.That(someKey, Is.Not.Null)` does not propagate
null-narrowing into the lambda scope, so add `!` to the dictionary
index. (Tests fail loud with a meaningful message if the key really
is null, so this is safe.)
- **NUnit2045 (1)**: wrap a four-assert block in
`JsonTokenReaderTests.cs:65-68` in `Assert.Multiple`. Mirrors the
surrounding two `Assert.Multiple` groups in that method.
All 1180 tests stay green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
683 lines
17 KiB
C#
683 lines
17 KiB
C#
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, Has.Count.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
|
|
}
|