using Strata.SqlTools.SqlBreakdown.Expressions; using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate; using Strata.SqlTools.SqlBreakdown.Expressions.Literals; using Strata.SqlTools.Statements.SqlServer; namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer; [TestFixture] public class StatementExpressionParserTests { private StatementExpressionParser _parser = null!; [SetUp] public void Setup() { _parser = new StatementExpressionParser(); } #region Parse Method Tests [Test] public void Parse_SimpleNumber_ReturnsNumberLiteralExpression() { // Arrange var sql = "42"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_SimpleAddition_ReturnsCorrectExpression() { // Arrange var sql = "10 + 20"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_SimpleSubtraction_ReturnsCorrectExpression() { // Arrange var sql = "50 - 30"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_SimpleMultiplication_ReturnsCorrectExpression() { // Arrange var sql = "5 * 3"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_SimpleDivision_ReturnsCorrectExpression() { // Arrange var sql = "100 / 4"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_ComplexExpression_ReturnsCorrectExpression() { // Arrange var sql = "10 + 20 * 3 - 5"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_ParenthesizedExpression_ReturnsCorrectExpression() { // Arrange var sql = "(10 + 20) * 3"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_NestedParentheses_ReturnsCorrectExpression() { // Arrange var sql = "((10 + 5) * 2) / 3"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_SumFunction_ReturnsSumFunctionExpression() { // Arrange var sql = "SUM(3_REVENUE)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_AvgFunction_ReturnsAverageFunctionExpression() { // Arrange var sql = "AVG(3_REVENUE)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_FunctionWithExpression_ReturnsCorrectExpression() { // Arrange var sql = "SUM(3_REVENUE) + 100"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_ColumnIdentifier_ReturnsColumnExpression() { // Arrange var sql = "1_DEPARTMENT_ID"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_ColumnWithArithmetic_ReturnsCorrectExpression() { // Arrange var sql = "3_REVENUE * 2"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_DecimalNumber_ReturnsNumberLiteralExpression() { // Arrange var sql = "123.456"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_WithSingleLineComment_ParsesSuccessfully() { // Arrange var sql = "10 + 20 -- This is a comment"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_WithMultiLineComment_ParsesSuccessfully() { // Arrange var sql = "10 /* this is a comment */ + 20"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_WithExtraWhitespace_ParsesSuccessfully() { // Arrange var sql = " 10 + 20 "; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_WithNewlines_ParsesSuccessfully() { // Arrange var sql = @"10 + 20"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_NullInput_ThrowsArgumentNullException() { // Arrange string sql = null!; // Act & Assert var ex = Assert.Throws(() => _parser.Parse(sql)); Assert.Multiple(() => { Assert.That(ex.ParamName, Is.EqualTo("sqlStatement")); Assert.That(ex.Message, Does.Contain("SQL statement cannot be null or empty")); }); } [Test] public void Parse_EmptyString_ThrowsArgumentNullException() { // Arrange var sql = string.Empty; // Act & Assert var ex = Assert.Throws(() => _parser.Parse(sql)); Assert.That(ex.ParamName, Is.EqualTo("sqlStatement")); } [Test] public void Parse_WhitespaceOnly_ThrowsArgumentNullException() { // Arrange var sql = " "; // Act & Assert var ex = Assert.Throws(() => _parser.Parse(sql)); Assert.That(ex.ParamName, Is.EqualTo("sqlStatement")); } [Test] public void Parse_UnclosedParenthesis_ThrowsFormatException() { // Arrange var sql = "(10 + 20"; // Act & Assert var ex = Assert.Throws(() => _parser.Parse(sql)); Assert.That(ex.Message, Does.Contain("Failed to parse SQL statement")); } [Test] public void Parse_MismatchedParenthesis_ThrowsFormatException() { // Arrange var sql = "10 + 20)"; // Act & Assert Assert.Throws(() => _parser.Parse(sql)); } [Test] public void Parse_UnrecognizedFunction_ThrowsFormatException() { // Arrange var sql = "UNKNOWN(100)"; // Act & Assert var ex = Assert.Throws(() => _parser.Parse(sql)); Assert.That(ex.Message, Does.Contain("Failed to parse SQL statement")); } #endregion #region TryParse Method Tests [Test] public void TryParse_ValidSimpleExpression_ReturnsTrue() { // Arrange var sql = "42"; // Act var success = _parser.TryParse(sql, out var result); Assert.Multiple(() => { // Assert Assert.That(success, Is.True); Assert.That(result, Is.Not.Null); }); Assert.That(result, Is.InstanceOf()); } [Test] public void TryParse_ValidComplexExpression_ReturnsTrue() { // Arrange var sql = "(10 + 20) * 3 - 5"; // Act var success = _parser.TryParse(sql, out var result); Assert.Multiple(() => { // Assert Assert.That(success, Is.True); Assert.That(result, Is.Not.Null); }); } [Test] public void TryParse_ValidFunction_ReturnsTrue() { // Arrange var sql = "SUM(3_REVENUE)"; // Act var success = _parser.TryParse(sql, out var result); Assert.Multiple(() => { // Assert Assert.That(success, Is.True); Assert.That(result, Is.Not.Null); }); Assert.That(result, Is.InstanceOf()); } [Test] public void TryParse_NullInput_ReturnsFalse() { // Arrange string sql = null!; // Act var success = _parser.TryParse(sql, out var result); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(result, Is.Null); }); } [Test] public void TryParse_EmptyString_ReturnsFalse() { // Arrange var sql = string.Empty; // Act var success = _parser.TryParse(sql, out var result); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(result, Is.Null); }); } [Test] public void TryParse_WhitespaceOnly_ReturnsFalse() { // Arrange var sql = " "; // Act var success = _parser.TryParse(sql, out var result); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(result, Is.Null); }); } [Test] public void TryParse_InvalidSyntax_ReturnsFalse() { // Arrange var sql = "(10 + 20"; // Act var success = _parser.TryParse(sql, out var result); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(result, Is.Null); }); } [Test] public void TryParse_WithErrorMessage_ValidInput_ReturnsTrue() { // Arrange var sql = "100 + 50"; // Act var success = _parser.TryParse(sql, out var result, out var errorMessage); Assert.Multiple(() => { // Assert Assert.That(success, Is.True); Assert.That(result, Is.Not.Null); Assert.That(errorMessage, Is.Null); }); } [Test] public void TryParse_WithErrorMessage_NullInput_ReturnsFalseWithMessage() { // Arrange string sql = null!; // Act var success = _parser.TryParse(sql, out var result, out var errorMessage); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(result, Is.Null); Assert.That(errorMessage, Is.Not.Null); }); Assert.That(errorMessage, Does.Contain("SQL statement cannot be null or empty")); } [Test] public void TryParse_WithErrorMessage_InvalidSyntax_ReturnsFalseWithMessage() { // Arrange var sql = "(10 + 20"; // Act var success = _parser.TryParse(sql, out var result, out var errorMessage); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(result, Is.Null); Assert.That(errorMessage, Is.Not.Null); }); Assert.That(errorMessage, Does.Contain("Invalid syntax")); } [Test] public void TryParse_WithErrorMessage_UnsupportedToken_ReturnsFalseWithMessage() { // Arrange var sql = "UNKNOWN(100)"; // Act var success = _parser.TryParse(sql, out var result, out var errorMessage); Assert.Multiple(() => { // Assert Assert.That(success, Is.False); Assert.That(result, Is.Null); Assert.That(errorMessage, Is.Not.Null); }); Assert.That(errorMessage, Does.Contain("not recognized")); } #endregion #region Order of Operations Tests [Test] public void Parse_MultiplicationBeforeAddition_CorrectPrecedence() { // Arrange var sql = "2 + 3 * 4"; // Should be 2 + (3 * 4) = 14, not (2 + 3) * 4 = 20 // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); // Note: We can't easily verify the calculation result without evaluating the expression tree // But we can verify it parses without error } [Test] public void Parse_DivisionBeforeSubtraction_CorrectPrecedence() { // Arrange var sql = "20 - 10 / 2"; // Should be 20 - (10 / 2) = 15, not (20 - 10) / 2 = 5 // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_ParenthesesOverridePrecedence_CorrectPrecedence() { // Arrange var sql = "(2 + 3) * 4"; // Should be (2 + 3) * 4 = 20 // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_MultipleOperationsLeftToRight_CorrectAssociativity() { // Arrange var sql = "10 - 5 - 2"; // Should be (10 - 5) - 2 = 3, not 10 - (5 - 2) = 7 // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } #endregion #region Comment and Whitespace Normalization Tests [Test] public void Parse_SingleLineCommentAtEnd_IgnoresComment() { // Arrange var sql = "10 + 20 -- This should be ignored"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_SingleLineCommentInMiddle_IgnoresComment() { // Arrange var sql = @"10 + -- comment here 20"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_MultiLineCommentInMiddle_IgnoresComment() { // Arrange var sql = "10 /* this is a\n multi-line\n comment */ + 20"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_MultipleComments_IgnoresAllComments() { // Arrange var sql = "10 /* comment 1 */ + /* comment 2 */ 20 -- comment 3"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_CommentLikeTextInString_PreservesString() { // Arrange - Although this parser might not handle strings, testing the comment removal logic var sql = "10 + 20"; // Simple case without strings as parser may not support them // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_TabsAndSpaces_NormalizesCorrectly() { // Arrange var sql = "10\t+\t 20"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_MultipleNewlines_NormalizesCorrectly() { // Arrange var sql = "10\n\n\n+\n\n20"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } #endregion #region Edge Cases and Complex Scenarios [Test] public void Parse_VeryLongExpression_ParsesSuccessfully() { // Arrange var sql = "1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_DeeplyNestedParentheses_ParsesSuccessfully() { // Arrange var sql = "((((10 + 5) * 2) - 3) / 4)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_MixedColumnsNumbersAndFunctions_ParsesSuccessfully() { // Arrange var sql = "SUM(3_REVENUE) + 1_DEPARTMENT_ID * 100"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_FunctionInsideParentheses_ParsesSuccessfully() { // Arrange var sql = "(SUM(3_REVENUE) + 100) * 2"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_LargeDecimalNumber_ParsesSuccessfully() { // Arrange var sql = "123456789.987654321"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_ZeroValue_ParsesSuccessfully() { // Arrange var sql = "0"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_NegativeNumber_WithSubtraction_ParsesSuccessfully() { // Arrange var sql = "0 - 5"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_ConsecutiveOperators_ThrowsFormatException() { // Arrange var sql = "10 + + 20"; // Act & Assert Assert.Throws(() => _parser.Parse(sql)); } [Test] public void Parse_TrailingOperator_ThrowsFormatException() { // Arrange var sql = "10 + 20 +"; // Act & Assert Assert.Throws(() => _parser.Parse(sql)); } [Test] public void Parse_LeadingOperator_ThrowsFormatException() { // Arrange var sql = "+ 10 + 20"; // Act & Assert Assert.Throws(() => _parser.Parse(sql)); } #endregion #region Function-Specific Tests [Test] public void Parse_SumFunctionLowercase_ParsesSuccessfully() { // Arrange var sql = "sum(3_REVENUE)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_AvgFunctionMixedCase_ParsesSuccessfully() { // Arrange var sql = "AvG(3_REVENUE)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_MultipleFunctions_ParsesSuccessfully() { // Arrange var sql = "SUM(3_REVENUE) + AVG(3_REVENUE)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_FunctionWithNumberArgument_ParsesSuccessfully() { // Arrange var sql = "SUM(100)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } [Test] public void Parse_FunctionWithComplexExpression_ParsesSuccessfully() { // Arrange var sql = "SUM(3_REVENUE * 2)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } #endregion #region Column Expression Tests [Test] public void Parse_VariousColumnIds_ParsesSuccessfully() { // Arrange & Act & Assert var testCases = new[] { "1_DEPARTMENT_ID", "2_NAME", "3_REVENUE", "4_DISCHARGE_DATE", "586883_FIXED_COST", "586664_VARIABLE_COST" }; foreach (var sql in testCases) { var result = _parser.Parse(sql); Assert.That(result, Is.Not.Null); Assert.That(result, Is.InstanceOf()); } } [Test] public void Parse_ColumnInArithmeticExpression_ParsesSuccessfully() { // Arrange var sql = "(3_REVENUE - 586883_FIXED_COST) / 586664_VARIABLE_COST"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } [Test] public void Parse_ColumnWithFunction_ParsesSuccessfully() { // Arrange var sql = "SUM(3_REVENUE) - AVG(586883_FIXED_COST)"; // Act var result = _parser.Parse(sql); // Assert Assert.That(result, Is.Not.Null); } #endregion }