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(() => 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(() => 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 }