using NUnit.Framework; using Strata.SqlTools.Breakdowns.SqlServer; using Strata.SqlTools.SqlBreakdown.Classes; using Strata.SqlTools.SqlBreakdown.Exceptions; using Strata.SqlTools.SqlServer.Exceptions; namespace Strata.SqlTools.SqlBreakdown.Tests.Exceptions; [TestFixture] public class ExceptionHandlingTests { #region SqlParseException Tests [Test] public void SqlParseException_StoresProperties() { // Arrange var message = "Unexpected token"; var sql = "SELECT * FRM users"; var position = 10; // Act var exception = new SqlParseException(message, sql, position); Assert.Multiple(() => { // Assert Assert.That(exception.Message, Does.Contain(message)); Assert.That(exception.Sql, Is.EqualTo(sql)); Assert.That(exception.Position, Is.EqualTo(position)); Assert.That(exception.NearText, Is.Not.Empty); }); } [Test] public void SqlParseException_FormatsMessageWithContext() { // Arrange var message = "Invalid syntax"; var sql = "SELECT id, name FROM users WHERE active = INVALID"; var position = 40; // Act var exception = new SqlParseException(message, sql, position); // Assert Assert.That(exception.Message, Does.Contain("Invalid syntax")); Assert.That(exception.Message, Does.Contain("Position: 40")); Assert.That(exception.Message, Does.Contain("Near:")); } [Test] public void SqlParseException_HandlesNullSql() { // Arrange var message = "SQL is null"; string? sql = null; var position = 0; // Act var exception = new SqlParseException(message, sql!, position); Assert.Multiple(() => { // Assert Assert.That(exception.Message, Does.Contain("SQL is null")); Assert.That(exception.Sql, Is.EqualTo(string.Empty)); Assert.That(exception.NearText, Is.EqualTo(string.Empty)); }); } [Test] public void SqlParseException_TruncatesLongSql() { // Arrange var message = "Parse error"; var sql = new string('A', 250) + " FROM users"; var position = 100; // Act var exception = new SqlParseException(message, sql, position); // Assert Assert.That(exception.Message, Does.Contain("truncated")); } [Test] public void SqlParseException_WithInnerException() { // Arrange var message = "Parse failed"; var sql = "SELECT * FROM users"; var position = 5; var innerException = new InvalidOperationException("Original error"); // Act var exception = new SqlParseException(message, sql, position, innerException); Assert.Multiple(() => { // Assert Assert.That(exception.InnerException, Is.EqualTo(innerException)); Assert.That(exception.Message, Does.Contain("Parse failed")); }); } #endregion #region CteValidationException Tests [Test] public void CteValidationException_StoresProperties() { // Arrange var message = "CTE name required"; var cteName = "my_cte"; var validationRule = "TableNameRequired"; // Act var exception = new CteValidationException(message, cteName, validationRule); Assert.Multiple(() => { // Assert Assert.That(exception.Message, Does.Contain(message)); Assert.That(exception.CteName, Is.EqualTo(cteName)); Assert.That(exception.ValidationRule, Is.EqualTo(validationRule)); }); } [Test] public void CteValidationException_IncludesHintForKnownRules() { // Arrange var message = "Table name is required"; var validationRule = "TableNameRequired"; // Act var exception = new CteValidationException(message, null, validationRule); // Assert Assert.That(exception.Message, Does.Contain("Hint:")); Assert.That(exception.Message, Does.Contain("non-empty table name")); } [Test] public void CteValidationException_HandlesUnknownRule() { // Arrange var message = "Validation failed"; var validationRule = "UnknownRule123"; // Act var exception = new CteValidationException(message, "my_cte", validationRule); Assert.Multiple(() => { // Assert Assert.That(exception.ValidationRule, Is.EqualTo(validationRule)); Assert.That(exception.Message, Does.Contain("Validation failed")); }); } [Test] public void CteValidationException_WithInnerException() { // Arrange var message = "Validation error"; var cteName = "test_cte"; var validationRule = "DuplicateCteName"; var innerException = new ArgumentException("Duplicate"); // Act var exception = new CteValidationException(message, cteName, validationRule, innerException); // Assert Assert.That(exception.InnerException, Is.EqualTo(innerException)); } #endregion #region QueryBreakdown.Parse Exception Tests [Test] public void Parse_WithNullSql_ThrowsArgumentNullException() { // Arrange string? sql = null; // Act & Assert Assert.Throws(() => QueryBreakdown.Parse(sql!)); } [Test] public void Parse_WithEmptySql_ThrowsArgumentNullException() { // Arrange var sql = ""; // Act & Assert Assert.Throws(() => QueryBreakdown.Parse(sql)); } [Test] public void Parse_WithWhitespaceSql_ThrowsArgumentNullException() { // Arrange var sql = " "; // Act & Assert Assert.Throws(() => QueryBreakdown.Parse(sql)); } [Test] public void Parse_WithInvalidSql_ThrowsSqlParseException() { // Arrange var sql = "INVALID SQL STATEMENT"; // Act & Assert var exception = Assert.Throws(() => QueryBreakdown.Parse(sql)); Assert.Multiple(() => { Assert.That(exception!.Sql, Is.EqualTo(sql)); Assert.That(exception.Message, Does.Contain("Failed to parse")); }); } [Test] public void Parse_WithMalformedSql_ProvidesSqlParseExceptionWithContext() { // Arrange - Use SQL that will definitely fail parsing var sql = "SELECT WHERE FROM"; // Act var exception = Assert.Throws(() => QueryBreakdown.Parse(sql)); // Assert Assert.That(exception, Is.Not.Null); Assert.That(exception!.Sql, Is.EqualTo(sql)); } #endregion #region AddWithClause Validation Tests [Test] public void AddWithClause_WithNullTableName_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); var cteQuery = new QueryBreakdown("id, name", "users"); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause(null!, cteQuery)); Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired")); } [Test] public void AddWithClause_WithEmptyTableName_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); var cteQuery = new QueryBreakdown("id, name", "users"); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause("", cteQuery)); Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired")); } [Test] public void AddWithClause_WithWhitespaceTableName_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); var cteQuery = new QueryBreakdown("id, name", "users"); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause(" ", cteQuery)); Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired")); } [Test] public void AddWithClause_WithNullQuery_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause("test_cte", (QueryBreakdown)null!)); Assert.Multiple(() => { Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired")); Assert.That(exception.CteName, Is.EqualTo("test_cte")); }); } [Test] public void AddWithClause_WithDuplicateName_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); var cte1 = new QueryBreakdown("id", "users"); var cte2 = new QueryBreakdown("name", "products"); query.AddWithClause("my_cte", cte1); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause("my_cte", cte2)); Assert.Multiple(() => { Assert.That(exception!.ValidationRule, Is.EqualTo("DuplicateCteName")); Assert.That(exception.Message, Does.Contain("already exists")); }); } [Test] public void AddWithClause_WithDuplicateName_CaseInsensitive_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); var cte1 = new QueryBreakdown("id", "users"); var cte2 = new QueryBreakdown("name", "products"); query.AddWithClause("MY_CTE", cte1); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause("my_cte", cte2)); Assert.That(exception!.ValidationRule, Is.EqualTo("DuplicateCteName")); } [Test] public void AddWithClause_WithSql_NullTableName_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause(null!, "SELECT * FROM users")); Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired")); } [Test] public void AddWithClause_WithSql_NullSql_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause("my_cte", (string)null!)); Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired")); } [Test] public void AddWithClause_WithSql_InvalidSql_ThrowsSqlParseException() { // Arrange var query = new QueryBreakdown(); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause("my_cte", "INVALID SQL")); Assert.Multiple(() => { Assert.That(exception!.Message, Does.Contain("my_cte")); Assert.That(exception.Sql, Is.EqualTo("INVALID SQL")); }); } [Test] public void AddWithClause_WithIWithClause_NullClause_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); // Act & Assert var exception = Assert.Throws(() => query.AddWithClause((WithClause)null!)); Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired")); } [Test] public void AddWithClause_WithIWithClause_NoQueryOrSql_ThrowsCteValidationException() { // Arrange var query = new QueryBreakdown(); var withClause = new WithClause { TableName = "test_cte" // Query and Sql are both null }; // Act & Assert var exception = Assert.Throws(() => query.AddWithClause(withClause)); Assert.Multiple(() => { Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired")); Assert.That(exception.Message, Does.Contain("Query or Sql")); }); } [Test] public void AddWithClause_ValidCte_Succeeds() { // Arrange var query = new QueryBreakdown(); var cteQuery = new QueryBreakdown("id, name", "users", "active = 1"); // Act query.AddWithClause("active_users", cteQuery); // Assert Assert.That(query.WithClauses, Has.Count.EqualTo(1)); Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users")); } #endregion #region Exception Message Quality Tests [Test] public void CteValidationException_TableNameRequired_HasHelpfulMessage() { // Act var exception = new CteValidationException( "CTE table name cannot be null", null, "TableNameRequired"); // Assert Assert.That(exception.Message, Does.Contain("non-empty table name")); Assert.That(exception.Message, Does.Contain("Hint:")); } [Test] public void CteValidationException_DuplicateCteName_HasHelpfulMessage() { // Act var exception = new CteValidationException( "Duplicate CTE name", "my_cte", "DuplicateCteName"); // Assert Assert.That(exception.Message, Does.Contain("unique")); Assert.That(exception.Message, Does.Contain("Hint:")); } [Test] public void SqlParseException_ShowsNearText() { // Arrange var sql = "SELECT id, name FROM users WHERE active = 1 AND status = 'INVALID'"; var position = 50; // Act var exception = new SqlParseException("Syntax error", sql, position); Assert.Multiple(() => { // Assert Assert.That(exception.NearText, Does.Contain("status")); Assert.That(exception.Message, Does.Contain("Near:")); }); } #endregion }