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(() => 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.Multiple(() => { // 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.Multiple(() => { // 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.Multiple(() => { 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.Multiple(() => { // 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.Multiple(() => { // 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()); } [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, Is.Not.Empty); } #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 }