This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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<ArgumentNullException>(() => QueryBreakdown.Parse(sql!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_WithEmptySql_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var sql = "";
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(sql));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_WithWhitespaceSql_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var sql = " ";
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(sql));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_WithInvalidSql_ThrowsSqlParseException()
|
||||
{
|
||||
// Arrange
|
||||
var sql = "INVALID SQL STATEMENT";
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<SqlParseException>(() => QueryBreakdown.Parse(sql));
|
||||
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<SqlParseException>(() => 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<CteValidationException>(() =>
|
||||
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<CteValidationException>(() =>
|
||||
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<CteValidationException>(() =>
|
||||
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<CteValidationException>(() =>
|
||||
query.AddWithClause("test_cte", (QueryBreakdown)null!));
|
||||
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<CteValidationException>(() =>
|
||||
query.AddWithClause("my_cte", cte2));
|
||||
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<CteValidationException>(() =>
|
||||
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<CteValidationException>(() =>
|
||||
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<CteValidationException>(() =>
|
||||
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<SqlParseException>(() =>
|
||||
query.AddWithClause("my_cte", "INVALID SQL"));
|
||||
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<CteValidationException>(() =>
|
||||
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<CteValidationException>(() =>
|
||||
query.AddWithClause(withClause));
|
||||
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.Count, Is.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
|
||||
Assert.That(exception.NearText, Does.Contain("status"));
|
||||
Assert.That(exception.Message, Does.Contain("Near:"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class ArithmeticExpressionTests : ExpressionTestsBase
|
||||
{
|
||||
private static IEnumerable<TestCaseData> ArithmeticExpressionTestCases()
|
||||
{
|
||||
var testCases = new[]
|
||||
{
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "WithSum_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES")),
|
||||
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var revenue = columns["revenue"];
|
||||
var cost = columns["cost"];
|
||||
var addExp = new SumFunction(revenue - cost - 1001);
|
||||
return addExp.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("SUM"));
|
||||
Assert.That(sql, Does.Contain("-"));
|
||||
Assert.That(sql, Does.Contain("1001"));
|
||||
Assert.That(sql, Is.EqualTo("SUM(PES.NET_REVENUE - PES.COST - 1001)"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "Addition_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var revenue = columns["revenue"];
|
||||
var result = revenue + new NumberLiteralExpression(1000);
|
||||
return result.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("NET_REVENUE"));
|
||||
Assert.That(sql, Does.Contain("+"));
|
||||
Assert.That(sql, Does.Contain("1000"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "Multiplication_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var cost = columns["cost"];
|
||||
var result = cost * new NumberLiteralExpression(1.5m);
|
||||
return result.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("COST"));
|
||||
Assert.That(sql, Does.Contain("*"));
|
||||
Assert.That(sql, Does.Contain("1.5"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "Division_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var revenue = columns["revenue"];
|
||||
var result = revenue / new NumberLiteralExpression(2);
|
||||
return result.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("NET_REVENUE"));
|
||||
Assert.That(sql, Does.Contain("/"));
|
||||
Assert.That(sql, Does.Contain("2"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
{
|
||||
yield return new TestCaseData(testCase).SetName(testCase.Name);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ArithmeticExpressionTestCases))]
|
||||
public void ArithmeticExpression_GeneratesCorrectSql(ExpressionTestCase testCase)
|
||||
=> ExecuteExpressionTest(testCase);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Comparisons;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Logical;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class BooleanExpressionTests : ExpressionTestsBase
|
||||
{
|
||||
[Test]
|
||||
public void BooleanExpression_AndOperator_ChainsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var greaterThan250Exp = new GreaterThanExpression(_costColumnExp, 250);
|
||||
BooleanExpression original = BooleanExpression.False;
|
||||
|
||||
// Act
|
||||
original &= greaterThan250Exp;
|
||||
var firstResult = original;
|
||||
original &= greaterThan250Exp;
|
||||
var secondResult = original;
|
||||
|
||||
// Assert
|
||||
Assert.That(firstResult, Is.Not.SameAs(BooleanExpression.False));
|
||||
Assert.That(firstResult, Is.AssignableTo<BooleanExpression>());
|
||||
Assert.That(secondResult, Is.Not.SameAs(BooleanExpression.False));
|
||||
Assert.That(secondResult, Is.TypeOf<AndExpression>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BooleanExpression_ComplexWithOperators_CreatesExpression()
|
||||
{
|
||||
// Arrange
|
||||
var paramFoobar = new ParameterExpression("FOOBAR");
|
||||
|
||||
// Act
|
||||
#pragma warning disable S2178 // Short-circuit logic should be used in boolean contexts
|
||||
var andComp = _revenueColumnExp > 100 & _costColumnExp >= 250 | _nameColumnExp == paramFoobar;
|
||||
#pragma warning restore S2178
|
||||
|
||||
// Assert
|
||||
Assert.That(andComp, Is.Not.Null);
|
||||
Assert.That(andComp, Is.AssignableTo<BooleanExpression>());
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> BooleanExpressionTestCases()
|
||||
{
|
||||
var testCases = new[]
|
||||
{
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "GreaterThan_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var revenue = columns["revenue"];
|
||||
var greaterThan100Exp = new GreaterThanExpression(revenue, 100);
|
||||
return greaterThan100Exp;
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var expression = (BooleanExpression)result;
|
||||
Assert.That(expression, Is.Not.Null);
|
||||
Assert.That(expression, Is.TypeOf<GreaterThanExpression>());
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "And_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES")),
|
||||
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var revenue = columns["revenue"];
|
||||
var cost = columns["cost"];
|
||||
var greaterThan100Exp = new GreaterThanExpression(revenue, 100);
|
||||
var greaterThan250Exp = new GreaterThanExpression(cost, 250);
|
||||
var andExp1 = new AndExpression(greaterThan100Exp, greaterThan250Exp);
|
||||
return andExp1;
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var expression = (BooleanExpression)result;
|
||||
Assert.That(expression, Is.Not.Null);
|
||||
Assert.That(expression, Is.TypeOf<AndExpression>());
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "Or_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES")),
|
||||
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var revenue = columns["revenue"];
|
||||
var cost = columns["cost"];
|
||||
#pragma warning disable S2178 // Short-circuit logic should be used in boolean contexts
|
||||
var orComp = revenue > 100 | cost >= 250;
|
||||
#pragma warning restore S2178
|
||||
return orComp;
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var expression = (BooleanExpression)result;
|
||||
Assert.That(expression, Is.Not.Null);
|
||||
Assert.That(expression, Is.AssignableTo<BooleanExpression>());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
{
|
||||
yield return new TestCaseData(testCase).SetName(testCase.Name);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(BooleanExpressionTestCases))]
|
||||
public void BooleanExpression_GeneratesCorrectExpression(ExpressionTestCase testCase)
|
||||
=> ExecuteExpressionTest(testCase);
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Conditional;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class ConditionalExpressionTests : ExpressionTestsBase
|
||||
{
|
||||
private static IEnumerable<TestCaseData> ConditionalExpressionTestCases()
|
||||
{
|
||||
var testCases = new[]
|
||||
{
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "CaseExpression_{m}",
|
||||
Arrange = new Dictionary<string, Expression>(),
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
|
||||
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
|
||||
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
|
||||
var caseExp = new CaseExpression(nameCol == "department 1", revenueCol, 0);
|
||||
return caseExp.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("CASE"));
|
||||
Assert.That(sql, Does.Contain("WHEN"));
|
||||
Assert.That(sql, Does.Contain("THEN"));
|
||||
Assert.That(sql, Does.Contain("ELSE"));
|
||||
Assert.That(sql, Does.Contain("END"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "CaseExpressionWithSum_{m}",
|
||||
Arrange = new Dictionary<string, Expression>(),
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
|
||||
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
|
||||
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
|
||||
var caseExp = new CaseExpression(nameCol == "department 1", revenueCol, 0);
|
||||
var sumCaseExp = new SumFunction(caseExp);
|
||||
return sumCaseExp.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("SUM"));
|
||||
Assert.That(sql, Does.Contain("CASE"));
|
||||
Assert.That(sql, Does.Contain("WHEN"));
|
||||
Assert.That(sql, Is.EqualTo("SUM(CASE\n WHEN PES.DEPARTMENT_NAME = 'department 1' THEN PES.NET_REVENUE\n ELSE 0\nEND)"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "CaseExpressionWithSumInSelectColumn_{m}",
|
||||
Arrange = new Dictionary<string, Expression>(),
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
|
||||
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
|
||||
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
|
||||
var caseExp = new CaseExpression(nameCol == "department 1", revenueCol, 0);
|
||||
var sumCaseExp = new SumFunction(caseExp);
|
||||
var column = new SelectClauseColumn(sumCaseExp, "mySumCaseCol");
|
||||
return column.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("SUM"));
|
||||
Assert.That(sql, Does.Contain("CASE"));
|
||||
Assert.That(sql, Does.Contain("mySumCaseCol"));
|
||||
Assert.That(sql, Is.EqualTo("SUM(CASE\n WHEN PES.DEPARTMENT_NAME = 'department 1' THEN PES.NET_REVENUE\n ELSE 0\nEND) AS mySumCaseCol"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "IfThenElseExpression_{m}",
|
||||
Arrange = new Dictionary<string, Expression>(),
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
|
||||
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
|
||||
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
|
||||
var costCol = new RegisteredTableColumnExpression(3, "COST", table);
|
||||
var ifElseExp = new IfThenElseExpression(nameCol == "myDeptName", revenueCol, costCol);
|
||||
return ifElseExp.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("IFF"));
|
||||
Assert.That(sql, Is.EqualTo("IFF(PES.DEPARTMENT_NAME = 'myDeptName', PES.NET_REVENUE, PES.COST)"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
{
|
||||
yield return new TestCaseData(testCase).SetName(testCase.Name);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ConditionalExpressionTestCases))]
|
||||
public void ConditionalExpression_GeneratesCorrectSql(ExpressionTestCase testCase)
|
||||
=> ExecuteExpressionTest(testCase);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class DateFunctionTests : ExpressionTestsBase
|
||||
{
|
||||
private static IEnumerable<TestCaseData> DateFunctionTestCases()
|
||||
{
|
||||
var testCases = new[]
|
||||
{
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "GetFiscalYearMonthExpression_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["dischargeDate"] = new RegisteredTableColumnExpression(4, "DISCHARGE_DATE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var dischargeDate = columns["dischargeDate"];
|
||||
var fiscal = TestExpressionFactory.GetFiscalYearMonthExpression(dischargeDate, 7, 1);
|
||||
return fiscal.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Is.Not.Null);
|
||||
Assert.That(sql.Length, Is.GreaterThan(0));
|
||||
Assert.That(sql, Is.EqualTo("DATE_TRUNC('month', DATEADD('month', 6, PES.DISCHARGE_DATE))"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
{
|
||||
yield return new TestCaseData(testCase).SetName(testCase.Name);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(DateFunctionTestCases))]
|
||||
public void DateFunction_GeneratesCorrectSql(ExpressionTestCase testCase)
|
||||
=> ExecuteExpressionTest(testCase);
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using Strata.SqlTools.SqlServer.ExpressionFactory.Query;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class ExpressionFactoryFilterTests : ExpressionTestsBase
|
||||
{
|
||||
private static IEnumerable<TestCaseData> FilterTestCases()
|
||||
{
|
||||
var dischargeDate = DateTime.Now.Date.AddMonths(1);
|
||||
var startOfCurrentMonth = new DateTime(dischargeDate.Year, dischargeDate.Month, 1);
|
||||
var startOfEndMonth = startOfCurrentMonth.AddMonths(3);
|
||||
yield return new TestCaseData(
|
||||
new Filter(2, FilterType.List, new object[] { "foo", "bar", "baz", 1234 }, Array.Empty<FilterCondition>(), DatePart.Continuous, false, 0, 0),
|
||||
"DEPT.NAME IN ('foo', 'bar', 'baz', 1234)"
|
||||
).SetName("ListFilterContinuous_{m}");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new Filter(4, FilterType.List, new[] { "FY2019", "FY2020", "FY2021", "FY2022" }, Array.Empty<FilterCondition>(), DatePart.FiscalYear, false, 0, 0),
|
||||
"(DEPT.DISCHARGE_DATE >= '2018-07-01' AND DEPT.DISCHARGE_DATE < '2019-07-01') OR \n(DEPT.DISCHARGE_DATE >= '2019-07-01' AND DEPT.DISCHARGE_DATE < '2020-07-01') OR \n(DEPT.DISCHARGE_DATE >= '2020-07-01' AND DEPT.DISCHARGE_DATE < '2021-07-01') OR \n(DEPT.DISCHARGE_DATE >= '2021-07-01' AND DEPT.DISCHARGE_DATE < '2022-07-01')"
|
||||
).SetName("DateListFilterFiscalYear_{m}");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new Filter(4, FilterType.List, new[] { "01-2020" }, Array.Empty<FilterCondition>(), DatePart.Month, false, 0, 0),
|
||||
"DEPT.DISCHARGE_DATE >= '2020-01-01' AND DEPT.DISCHARGE_DATE < '2020-02-01'"
|
||||
).SetName("MonthListFilter_{m}");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new Filter(4, FilterType.Calendar, new[] { "01/01/2019", "01/01/2023" }, Array.Empty<FilterCondition>(), DatePart.Month, false, 0, 0),
|
||||
"DEPT.DISCHARGE_DATE >= '2019-01-01' AND DEPT.DISCHARGE_DATE < '2023-01-01'"
|
||||
).SetName("CalendarFilter_{m}");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new Filter(4, FilterType.Timeframe, new object[] { }, Array.Empty<FilterCondition>(), DatePart.Month, false, 1, 3),
|
||||
$"DEPT.DISCHARGE_DATE >= '{startOfCurrentMonth:yyyy-MM-dd}' AND DEPT.DISCHARGE_DATE < '{startOfEndMonth:yyyy-MM-dd}'"
|
||||
).SetName("TimeframeFilter_{m}");
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(FilterTestCases))]
|
||||
public void ExpressionFactory_Filter_GeneratesCorrectSql(Filter filter, string expectedSql)
|
||||
{
|
||||
// Arrange
|
||||
var factory = new TestExpressionFactory();
|
||||
|
||||
// Act
|
||||
var condition = factory.CreateBooleanExpression(filter);
|
||||
var result = condition.Accept(_sqlVisitor);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Length, Is.GreaterThan(0));
|
||||
Assert.That(result, Is.EqualTo(expectedSql));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Comparisons;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class ExpressionObjectTests : ExpressionTestsBase
|
||||
{
|
||||
[Test]
|
||||
public void RegisteredTableColumnExpression_CreatesWithProperties()
|
||||
{
|
||||
// Arrange & Act
|
||||
var columnExp = new RegisteredTableColumnExpression(5, "TEST_COLUMN", _table);
|
||||
|
||||
// Assert
|
||||
Assert.That(columnExp, Is.Not.Null);
|
||||
Assert.That(columnExp.ColumnName, Is.EqualTo("TEST_COLUMN"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParameterExpression_CreatesWithName()
|
||||
{
|
||||
// Arrange & Act
|
||||
var paramExp = new ParameterExpression("MY_PARAM");
|
||||
|
||||
// Assert
|
||||
Assert.That(paramExp, Is.Not.Null);
|
||||
Assert.That(paramExp.ParameterName, Is.EqualTo("MY_PARAM"));
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> ComparisonExpressionTestCases()
|
||||
{
|
||||
yield return new TestCaseData("GreaterThanOrEqual", 250, typeof(GreaterThanOrEqualToExpression))
|
||||
.SetName("GreaterThanOrEqual_{m}");
|
||||
|
||||
yield return new TestCaseData("LessThan", 1000, typeof(LessThanExpression))
|
||||
.SetName("LessThan_{m}");
|
||||
|
||||
yield return new TestCaseData("Equals", "TestDept", typeof(EqualToExpression))
|
||||
.SetName("Equals_{m}");
|
||||
|
||||
yield return new TestCaseData("NotIn", new[] { "value1", "value2", "value3" }, typeof(NotInExpression))
|
||||
.SetName("NotIn_{m}");
|
||||
|
||||
yield return new TestCaseData("Like", "%pattern%", typeof(LikeExpression))
|
||||
.SetName("Like_{m}");
|
||||
|
||||
yield return new TestCaseData("Between", new object[] { 0, 5000 }, typeof(BetweenExpression))
|
||||
.SetName("Between_{m}");
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ComparisonExpressionTestCases))]
|
||||
public void ComparisonExpression_CreatesExpression(string expressionType, object testValue, Type expectedType)
|
||||
{
|
||||
// Arrange & Act
|
||||
BooleanExpression expression = expressionType switch
|
||||
{
|
||||
"GreaterThanOrEqual" => new GreaterThanOrEqualToExpression(_costColumnExp, (int)testValue),
|
||||
"LessThan" => new LessThanExpression(_revenueColumnExp, (int)testValue),
|
||||
"Equals" => new EqualToExpression(_nameColumnExp, (string)testValue),
|
||||
"NotIn" => new NotInExpression(_nameColumnExp, ((string[])testValue).Select(v => (Expression)v).ToArray()),
|
||||
"Like" => new LikeExpression(_nameColumnExp, (string)testValue),
|
||||
"Between" => new BetweenExpression(_revenueColumnExp, (Expression)(int)((object[])testValue)[0], (Expression)(int)((object[])testValue)[1]),
|
||||
_ => throw new ArgumentException($"Unknown expression type: {expressionType}")
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.That(expression, Is.Not.Null);
|
||||
Assert.That(expression, Is.TypeOf(expectedType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.Visitors.Snowflake;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a test case for expression tests with arrange, act, and assert phases.
|
||||
/// </summary>
|
||||
public class ExpressionTestCase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or initializes the name of the test case.
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the dictionary of expressions to arrange for the test.
|
||||
/// </summary>
|
||||
public Dictionary<string, Expression> Arrange { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the action to execute during the act phase of the test.
|
||||
/// </summary>
|
||||
public Func<Dictionary<string, Expression>, CommandVisitor, object> Act { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or initializes the function to execute during the assert phase of the test that returns true if all assertions pass.
|
||||
/// </summary>
|
||||
public Func<object, bool> Assertions { get; init; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional;
|
||||
using Strata.SqlTools.SqlServer.ExpressionFactory.Query;
|
||||
using Strata.SqlTools.SqlBreakdown.Tests.RegisteredTables;
|
||||
using Strata.SqlTools.Visitors.Snowflake;
|
||||
using ExpressionFactoryBase = Strata.SqlTools.SqlServer.ExpressionFactory.ExpressionFactory;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Test implementation of ExpressionFactory for unit testing.
|
||||
/// Uses July 1 as the fiscal year start date.
|
||||
/// </summary>
|
||||
internal class TestExpressionFactory : ExpressionFactoryBase
|
||||
{
|
||||
protected override DateTime GetCurrentFiscalYearStart()
|
||||
{
|
||||
return new DateTime(DateTime.UtcNow.Year, 7, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
protected override RegisteredTableColumnExpression GetColumnExpression(int dataColumnId)
|
||||
{
|
||||
return RegisteredTableColumns.GetColumn(dataColumnId);
|
||||
}
|
||||
|
||||
// Public wrappers for testing protected methods
|
||||
public new BooleanExpression CreateBooleanExpression(Filter filter) => base.CreateBooleanExpression(filter);
|
||||
public static new Expression GetFiscalYearMonthExpression(Expression dateColumnExpr, int fiscalYearStartMonth, int fiscalYearStartDay)
|
||||
=> ExpressionFactoryBase.GetFiscalYearMonthExpression(dateColumnExpr, fiscalYearStartMonth, fiscalYearStartDay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for expression tests with common setup.
|
||||
/// </summary>
|
||||
public abstract class ExpressionTestsBase
|
||||
{
|
||||
protected RegisteredTableSource _table = null!;
|
||||
protected RegisteredTableColumnExpression _nameColumnExp = null!;
|
||||
protected RegisteredTableColumnExpression _revenueColumnExp = null!;
|
||||
protected RegisteredTableColumnExpression _costColumnExp = null!;
|
||||
protected RegisteredTableColumnExpression _dischargeDateColumnExp = null!;
|
||||
protected CommandVisitor _sqlVisitor = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_table = new RegisteredTableSource(101, "CLIENT_DSS", "FACT_PATIENT_ENCOUNTER_SUMMARY", "PES");
|
||||
_nameColumnExp = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", _table);
|
||||
_revenueColumnExp = new RegisteredTableColumnExpression(2, "NET_REVENUE", _table);
|
||||
_costColumnExp = new RegisteredTableColumnExpression(3, "COST", _table);
|
||||
_dischargeDateColumnExp = new RegisteredTableColumnExpression(4, "DISCHARGE_DATE", _table);
|
||||
_sqlVisitor = new CommandVisitor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an expression test case with arrange, act, and assert phases.
|
||||
/// </summary>
|
||||
/// <param name="testCase">The test case to execute.</param>
|
||||
protected void ExecuteExpressionTest(ExpressionTestCase testCase)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new CommandVisitor();
|
||||
var arrange = testCase.Arrange;
|
||||
|
||||
// Act
|
||||
var result = testCase.Act(arrange, visitor);
|
||||
|
||||
// Assert
|
||||
Assert.That(testCase.Assertions(result));
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class GenericColumnExpressionTests : ExpressionTestsBase
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GenericColumnExpressionTestCases()
|
||||
{
|
||||
var testCases = new[]
|
||||
{
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "WithArithmeticOperatorsAndLiterals_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["price"] = new GenericColumnExpression("Price", "Products"),
|
||||
["quantity"] = new GenericColumnExpression("Quantity", "Products")
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var price = columns["price"];
|
||||
var quantity = columns["quantity"];
|
||||
var priceWithTax = price * new NumberLiteralExpression(1.1m);
|
||||
var priceWithFee = price + new NumberLiteralExpression(10);
|
||||
var revenue = quantity * price;
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
["priceWithTax"] = priceWithTax.Accept(visitor),
|
||||
["priceWithFee"] = priceWithFee.Accept(visitor),
|
||||
["revenue"] = revenue.Accept(visitor)
|
||||
};
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var results = (Dictionary<string, string>)result;
|
||||
Assert.That(results["priceWithTax"], Does.Contain("Price"));
|
||||
Assert.That(results["priceWithTax"], Does.Contain("1.1"));
|
||||
Assert.That(results["priceWithFee"], Does.Contain("Price"));
|
||||
Assert.That(results["priceWithFee"], Does.Contain("10"));
|
||||
Assert.That(results["revenue"], Does.Contain("Quantity"));
|
||||
Assert.That(results["revenue"], Does.Contain("Price"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "WithComparisonOperators_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["price"] = new GenericColumnExpression("Price", "Products"),
|
||||
["category"] = new GenericColumnExpression("Category", "Products")
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var price = columns["price"];
|
||||
var category = columns["category"];
|
||||
var priceCondition = price > 100;
|
||||
var categoryCondition = category == "Electronics";
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
["priceCondition"] = priceCondition.Accept(visitor),
|
||||
["categoryCondition"] = categoryCondition.Accept(visitor)
|
||||
};
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var results = (Dictionary<string, string>)result;
|
||||
Assert.That(results["priceCondition"], Does.Contain("Price"));
|
||||
Assert.That(results["priceCondition"], Does.Contain("100"));
|
||||
Assert.That(results["categoryCondition"], Does.Contain("Category"));
|
||||
Assert.That(results["categoryCondition"], Does.Contain("Electronics"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "InQueryBreakdown_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["productId"] = new GenericColumnExpression("ProductID", "Products"),
|
||||
["price"] = new GenericColumnExpression("Price", "Products"),
|
||||
["category"] = new GenericColumnExpression("Category", "Products")
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var query = new Breakdowns.SqlServer.QueryBreakdown();
|
||||
query.FromClause.Clause = "Products";
|
||||
var productId = columns["productId"];
|
||||
var price = columns["price"];
|
||||
var category = columns["category"];
|
||||
query.AddSelectExpression(productId, "ID");
|
||||
query.AddSelectExpression(price * new NumberLiteralExpression(1.1m), "PriceWithTax");
|
||||
query.AddSelectExpression(price + new NumberLiteralExpression(10), "PriceWithFee");
|
||||
query.AddWhereExpression(price > 100);
|
||||
query.AddWhereExpression(category == "Electronics", null, "AND");
|
||||
return query.GetSql();
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("ProductID"));
|
||||
Assert.That(sql, Does.Contain("Price"));
|
||||
Assert.That(sql, Does.Contain("Category"));
|
||||
Assert.That(sql, Does.Contain("Products"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "ComplexArithmetic_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["quantity"] = new GenericColumnExpression("Quantity", "OrderDetails"),
|
||||
["unitPrice"] = new GenericColumnExpression("UnitPrice", "OrderDetails"),
|
||||
["discount"] = new GenericColumnExpression("Discount", "OrderDetails")
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var quantity = columns["quantity"];
|
||||
var unitPrice = columns["unitPrice"];
|
||||
var discount = columns["discount"];
|
||||
var totalRevenue = (quantity * unitPrice) * (new NumberLiteralExpression(1) - discount);
|
||||
return totalRevenue.Accept(visitor);
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var sql = (string)result;
|
||||
Assert.That(sql, Does.Contain("Quantity"));
|
||||
Assert.That(sql, Does.Contain("UnitPrice"));
|
||||
Assert.That(sql, Does.Contain("Discount"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "WithSchema_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["customerId"] = new GenericColumnExpression("CustomerID", "dbo", "Customers"),
|
||||
["orderDate"] = new GenericColumnExpression("OrderDate", "dbo", "Orders")
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
["customerResult"] = columns["customerId"].Accept(visitor),
|
||||
["orderResult"] = columns["orderDate"].Accept(visitor)
|
||||
};
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var results = (Dictionary<string, string>)result;
|
||||
Assert.That(results["customerResult"], Does.Contain("CustomerID"));
|
||||
Assert.That(results["orderResult"], Does.Contain("OrderDate"));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
new ExpressionTestCase
|
||||
{
|
||||
Name = "MultipleComparisonOperators_{m}",
|
||||
Arrange = new Dictionary<string, Expression>
|
||||
{
|
||||
["unitPrice"] = new GenericColumnExpression("UnitPrice", "Products"),
|
||||
["minPrice"] = new GenericColumnExpression("MinPrice", "PriceRanges"),
|
||||
["maxPrice"] = new GenericColumnExpression("MaxPrice", "PriceRanges")
|
||||
},
|
||||
Act = (columns, visitor) =>
|
||||
{
|
||||
var unitPrice = columns["unitPrice"];
|
||||
var minPrice = columns["minPrice"];
|
||||
var maxPrice = columns["maxPrice"];
|
||||
var lowerBound = unitPrice >= minPrice;
|
||||
var upperBound = unitPrice <= maxPrice;
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
["lowerBound"] = lowerBound.Accept(visitor),
|
||||
["upperBound"] = upperBound.Accept(visitor)
|
||||
};
|
||||
},
|
||||
Assertions = result =>
|
||||
{
|
||||
var results = (Dictionary<string, string>)result;
|
||||
Assert.That(results["lowerBound"], Does.Contain("UnitPrice"));
|
||||
Assert.That(results["lowerBound"], Does.Contain("MinPrice"));
|
||||
Assert.That(results["upperBound"], Does.Contain("UnitPrice"));
|
||||
Assert.That(results["upperBound"], Does.Contain("MaxPrice"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
{
|
||||
yield return new TestCaseData(testCase).SetName(testCase.Name);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GenericColumnExpressionTestCases))]
|
||||
public void GenericColumnExpression_GeneratesCorrectSql(ExpressionTestCase testCase)
|
||||
=> ExecuteExpressionTest(testCase);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
|
||||
|
||||
[TestFixture]
|
||||
public class SelectClauseColumnTests : ExpressionTestsBase
|
||||
{
|
||||
private static IEnumerable<TestCaseData> SelectClauseColumnTestCases()
|
||||
{
|
||||
yield return new TestCaseData("TableColumn", "foo_dept_name", "PES.DEPARTMENT_NAME AS foo_dept_name")
|
||||
.SetName("TableColumn_{m}");
|
||||
|
||||
yield return new TestCaseData("SumFunction", "sum_net_rev", "SUM(PES.NET_REVENUE) AS sum_net_rev")
|
||||
.SetName("WithSumFunction_{m}");
|
||||
|
||||
yield return new TestCaseData("Parameter", "my_var_col", ":FOOBAR AS my_var_col")
|
||||
.SetName("WithParameter_{m}");
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(SelectClauseColumnTestCases))]
|
||||
public void SelectClauseColumn_GeneratesCorrectSql(string columnType, string alias, string expectedSql)
|
||||
{
|
||||
// Arrange
|
||||
SelectClauseColumn column = columnType switch
|
||||
{
|
||||
"TableColumn" => SelectClauseColumn.TableColumn(_nameColumnExp, alias),
|
||||
"SumFunction" => new SelectClauseColumn(new SumFunction(_revenueColumnExp), alias),
|
||||
"Parameter" => new SelectClauseColumn(new ParameterExpression("FOOBAR"), alias),
|
||||
_ => throw new ArgumentException($"Unknown column type: {columnType}")
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = column.Accept(_sqlVisitor);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedSql));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||
using Strata.SqlTools.Extensions;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.Extensions;
|
||||
|
||||
[TestFixture]
|
||||
public class QueryBreakdownExtensionsTests
|
||||
{
|
||||
#region Basic Clause Tests
|
||||
|
||||
[Test]
|
||||
public void Select_SetsSelectClause()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act
|
||||
var result = query.Select("id, name");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(query));
|
||||
Assert.That(query.SelectClause.Clause, Is.EqualTo("id, name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void From_SetsFromClause()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act
|
||||
var result = query.From("users");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(query));
|
||||
Assert.That(query.FromClause.Clause, Is.EqualTo("users"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Where_SetsWhereClause()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act
|
||||
var result = query.Where("active = 1");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(query));
|
||||
Assert.That(query.WhereClause.Clause, Is.EqualTo("active = 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GroupBy_SetsGroupByClause()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act
|
||||
var result = query.GroupBy("department");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(query));
|
||||
Assert.That(query.GroupByClause.Clause, Is.EqualTo("department"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Having_SetsHavingClause()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act
|
||||
var result = query.Having("COUNT(*) > 5");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(query));
|
||||
Assert.That(query.HavingClause.Clause, Is.EqualTo("COUNT(*) > 5"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderBy_SetsOrderByClause()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act
|
||||
var result = query.OrderBy("name ASC");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(query));
|
||||
Assert.That(query.OrderByClause.Clause, Is.EqualTo("name ASC"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddWhere_AppendsToWhereClause()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown().Where("active = 1");
|
||||
|
||||
// Act
|
||||
var result = query.AddWhere("AND age > 18");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.SameAs(query));
|
||||
Assert.That(query.WhereClause.Clause, Does.Contain("active = 1"));
|
||||
Assert.That(query.WhereClause.Clause, Does.Contain("AND age > 18"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Method Chaining Tests
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_CanChainSelectFromWhere()
|
||||
{
|
||||
// Act
|
||||
var query = new QueryBreakdown()
|
||||
.Select("id, name")
|
||||
.From("users")
|
||||
.Where("active = 1");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.SelectClause.Clause, Is.EqualTo("id, name"));
|
||||
Assert.That(query.FromClause.Clause, Is.EqualTo("users"));
|
||||
Assert.That(query.WhereClause.Clause, Is.EqualTo("active = 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_CanChainAllClauses()
|
||||
{
|
||||
// Act
|
||||
var query = new QueryBreakdown()
|
||||
.Select("department, COUNT(*) as employee_count")
|
||||
.From("employees")
|
||||
.Where("salary > 50000")
|
||||
.GroupBy("department")
|
||||
.Having("COUNT(*) > 5")
|
||||
.OrderBy("employee_count DESC");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.SelectClause.Clause, Is.EqualTo("department, COUNT(*) as employee_count"));
|
||||
Assert.That(query.FromClause.Clause, Is.EqualTo("employees"));
|
||||
Assert.That(query.WhereClause.Clause, Is.EqualTo("salary > 50000"));
|
||||
Assert.That(query.GroupByClause.Clause, Is.EqualTo("department"));
|
||||
Assert.That(query.HavingClause.Clause, Is.EqualTo("COUNT(*) > 5"));
|
||||
Assert.That(query.OrderByClause.Clause, Is.EqualTo("employee_count DESC"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_GeneratesCorrectSQL()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.Select("id, name, email")
|
||||
.From("users")
|
||||
.Where("active = 1")
|
||||
.OrderBy("name ASC");
|
||||
|
||||
var sql = query.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(sql, Does.Contain("SELECT"));
|
||||
Assert.That(sql, Does.Contain("id, name, email"));
|
||||
Assert.That(sql, Does.Contain("FROM"));
|
||||
Assert.That(sql, Does.Contain("users"));
|
||||
Assert.That(sql, Does.Contain("WHERE"));
|
||||
Assert.That(sql, Does.Contain("active = 1"));
|
||||
Assert.That(sql, Does.Contain("ORDER BY"));
|
||||
Assert.That(sql, Does.Contain("name ASC"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region WithCte Tests
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithConfigureAction_AddsCtesToQuery()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.WithCte("active_users", cte => cte
|
||||
.Select("id, name")
|
||||
.From("users")
|
||||
.Where("active = 1"))
|
||||
.Select("*")
|
||||
.From("active_users");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.IsUsingWithClause, Is.True);
|
||||
Assert.That(query.WithClauses.Count, Is.EqualTo(1));
|
||||
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users"));
|
||||
Assert.That(query.SelectClause.Clause, Is.EqualTo("*"));
|
||||
Assert.That(query.FromClause.Clause, Is.EqualTo("active_users"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_GeneratesCorrectSQL()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.WithCte("active_users", cte => cte
|
||||
.Select("id, name")
|
||||
.From("users")
|
||||
.Where("active = 1"))
|
||||
.Select("*")
|
||||
.From("active_users");
|
||||
|
||||
var sql = query.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(sql, Does.Contain("WITH"));
|
||||
Assert.That(sql, Does.Contain("active_users AS"));
|
||||
Assert.That(sql, Does.Contain("SELECT"));
|
||||
Assert.That(sql, Does.Contain("id, name"));
|
||||
Assert.That(sql, Does.Contain("FROM"));
|
||||
Assert.That(sql, Does.Contain("users"));
|
||||
Assert.That(sql, Does.Contain("WHERE"));
|
||||
Assert.That(sql, Does.Contain("active = 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_SupportsMultipleCTEs()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.WithCte("active_users", cte => cte
|
||||
.Select("id, name")
|
||||
.From("users")
|
||||
.Where("active = 1"))
|
||||
.WithCte("recent_orders", cte => cte
|
||||
.Select("order_id, user_id, total")
|
||||
.From("orders")
|
||||
.Where("created_date > '2024-01-01'"))
|
||||
.Select("u.name, COUNT(o.order_id) as order_count")
|
||||
.From("active_users u LEFT JOIN recent_orders o ON u.id = o.user_id")
|
||||
.GroupBy("u.id, u.name");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.IsUsingWithClause, Is.True);
|
||||
Assert.That(query.WithClauses.Count, Is.EqualTo(2));
|
||||
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users"));
|
||||
Assert.That(query.WithClauses[1].TableName, Is.EqualTo("recent_orders"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithColumnList_AddsCtesWithColumns()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.WithCte("active_users", new[] { "id", "name", "email" }, cte => cte
|
||||
.Select("user_id, user_name, user_email")
|
||||
.From("users")
|
||||
.Where("status = 'active'"))
|
||||
.Select("*")
|
||||
.From("active_users");
|
||||
|
||||
var sql = query.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(query.IsUsingWithClause, Is.True);
|
||||
Assert.That(query.WithClauses.Count, Is.EqualTo(1));
|
||||
Assert.That(sql, Does.Contain("active_users (id, name, email)"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithIQueryBreakdown_AddsCte()
|
||||
{
|
||||
// Arrange
|
||||
var cteQuery = new QueryBreakdown()
|
||||
.Select("id, name")
|
||||
.From("users")
|
||||
.Where("active = 1");
|
||||
|
||||
// Act
|
||||
var query = new QueryBreakdown()
|
||||
.WithCte("active_users", cteQuery)
|
||||
.Select("*")
|
||||
.From("active_users");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.IsUsingWithClause, Is.True);
|
||||
Assert.That(query.WithClauses.Count, Is.EqualTo(1));
|
||||
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Complex Query Tests
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_ComplexQueryWithGrouping()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.Select("department, AVG(salary) as avg_salary, COUNT(*) as emp_count")
|
||||
.From("employees")
|
||||
.Where("hire_date >= '2020-01-01'")
|
||||
.GroupBy("department")
|
||||
.Having("COUNT(*) >= 10")
|
||||
.OrderBy("avg_salary DESC");
|
||||
|
||||
var sql = query.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(sql, Does.Contain("SELECT"));
|
||||
Assert.That(sql, Does.Contain("department, AVG(salary) as avg_salary, COUNT(*) as emp_count"));
|
||||
Assert.That(sql, Does.Contain("GROUP BY"));
|
||||
Assert.That(sql, Does.Contain("HAVING"));
|
||||
Assert.That(sql, Does.Contain("COUNT(*) >= 10"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_RealWorldExample_SalesSummaryReport()
|
||||
{
|
||||
// Arrange & Act - Build a sales summary report with CTE
|
||||
var query = new QueryBreakdown()
|
||||
.WithCte("monthly_sales", cte => cte
|
||||
.Select("YEAR(order_date) as year, MONTH(order_date) as month, SUM(total_amount) as total_sales")
|
||||
.From("orders")
|
||||
.Where("status = 'completed'")
|
||||
.GroupBy("YEAR(order_date), MONTH(order_date)"))
|
||||
.Select("year, month, total_sales, LAG(total_sales) OVER (ORDER BY year, month) as prev_month_sales")
|
||||
.From("monthly_sales")
|
||||
.OrderBy("year DESC, month DESC");
|
||||
|
||||
var sql = query.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(sql, Does.Contain("WITH"));
|
||||
Assert.That(sql, Does.Contain("monthly_sales AS"));
|
||||
Assert.That(sql, Does.Contain("LAG(total_sales)"));
|
||||
Assert.That(query.IsUsingWithClause, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_RealWorldExample_ActiveUserHierarchy()
|
||||
{
|
||||
// Arrange & Act - Build a hierarchical user/order report
|
||||
var query = new QueryBreakdown()
|
||||
.WithCte("active_users", cte => cte
|
||||
.Select("id, name, email")
|
||||
.From("users")
|
||||
.Where("active = 1 AND last_login > DATEADD(month, -3, GETDATE())"))
|
||||
.WithCte("user_orders", cte => cte
|
||||
.Select("u.id as user_id, u.name, COUNT(o.id) as order_count, SUM(o.total) as total_spent")
|
||||
.From("active_users u LEFT JOIN orders o ON u.id = o.user_id")
|
||||
.GroupBy("u.id, u.name"))
|
||||
.Select("name, order_count, total_spent, CASE WHEN total_spent > 1000 THEN 'VIP' ELSE 'Regular' END as customer_tier")
|
||||
.From("user_orders")
|
||||
.Where("order_count > 0")
|
||||
.OrderBy("total_spent DESC");
|
||||
|
||||
var sql = query.GetSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(query.WithClauses.Count, Is.EqualTo(2));
|
||||
Assert.That(sql, Does.Contain("active_users AS"));
|
||||
Assert.That(sql, Does.Contain("user_orders AS"));
|
||||
Assert.That(sql, Does.Contain("customer_tier"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Validation Tests
|
||||
|
||||
[Test]
|
||||
public void Select_WithNullQuery_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
QueryBreakdown? query = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => query!.Select("*"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void From_WithNullQuery_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
QueryBreakdown? query = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => query!.From("users"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Where_WithNullQuery_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
QueryBreakdown? query = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => query!.Where("active = 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithNullQuery_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
QueryBreakdown? query = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
query!.WithCte("test", cte => cte.Select("*").From("users")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithEmptyTableName_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
query.WithCte("", cte => cte.Select("*").From("users")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithNullConfigureAction_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
query.WithCte("test", (Action<QueryBreakdown>)null!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithColumnList_WithEmptyColumns_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
query.WithCte("test", Array.Empty<string>(), cte => cte.Select("*").From("users")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithCte_WithIQueryBreakdown_WithNullCteQuery_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
query.WithCte("test", (QueryBreakdown)null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edge Case Tests
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_CanOverwriteClauses()
|
||||
{
|
||||
// Arrange
|
||||
var query = new QueryBreakdown()
|
||||
.Select("*")
|
||||
.From("users")
|
||||
.Where("active = 1");
|
||||
|
||||
// Act - Overwrite clauses
|
||||
query.Select("id, name").Where("active = 0");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.SelectClause.Clause, Is.EqualTo("id, name"));
|
||||
Assert.That(query.WhereClause.Clause, Is.EqualTo("active = 0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_HandlesEmptyStrings()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.Select("")
|
||||
.From("")
|
||||
.Where("");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.SelectClause.Clause, Is.EqualTo(""));
|
||||
Assert.That(query.FromClause.Clause, Is.EqualTo(""));
|
||||
Assert.That(query.WhereClause.Clause, Is.EqualTo(""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FluentAPI_PreservesWhitespace()
|
||||
{
|
||||
// Arrange & Act
|
||||
var query = new QueryBreakdown()
|
||||
.Select(" id, name ")
|
||||
.From(" users ");
|
||||
|
||||
// Assert
|
||||
Assert.That(query.SelectClause.Clause, Is.EqualTo(" id, name "));
|
||||
Assert.That(query.FromClause.Clause, Is.EqualTo(" users "));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class JsonTokenReaderTests
|
||||
{
|
||||
[Test]
|
||||
public void JsonTokenReader_ReadsJsonTokensCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var jsonString = """
|
||||
{
|
||||
"foo": "bar",
|
||||
"myList": [1,2,3,4],
|
||||
"another": 12
|
||||
}
|
||||
""";
|
||||
var jsonBytes = Encoding.UTF8.GetBytes(jsonString);
|
||||
var reader = new Utf8JsonReader(jsonBytes);
|
||||
var tokens = new List<(JsonTokenType Type, string Value)>();
|
||||
|
||||
// Act
|
||||
while (reader.Read())
|
||||
{
|
||||
var tokenValue = Encoding.UTF8.GetString(reader.ValueSpan);
|
||||
tokens.Add((reader.TokenType, tokenValue));
|
||||
}
|
||||
|
||||
// Assert - Validate JSON shape and structure
|
||||
Assert.That(tokens, Is.Not.Empty);
|
||||
|
||||
// Validate JSON starts with StartObject and ends with EndObject
|
||||
Assert.That(tokens.Count, Is.GreaterThan(2));
|
||||
Assert.That(tokens[0].Type, Is.EqualTo(JsonTokenType.StartObject));
|
||||
Assert.That(tokens[^1].Type, Is.EqualTo(JsonTokenType.EndObject));
|
||||
Assert.That(tokens.Count(t => t.Type == JsonTokenType.PropertyName && t.Value == "foo"), Is.EqualTo(1));
|
||||
Assert.That(tokens.Count(t => t.Type == JsonTokenType.String && t.Value == "bar"), Is.EqualTo(1));
|
||||
Assert.That(tokens.Count(t => t.Type == JsonTokenType.PropertyName && t.Value == "myList"), Is.EqualTo(1));
|
||||
Assert.That(tokens.Count(t => t.Type == JsonTokenType.StartArray), Is.EqualTo(1));
|
||||
Assert.That(tokens.Count(t => t.Type == JsonTokenType.PropertyName && t.Value == "another"), Is.EqualTo(1));
|
||||
Assert.That(tokens.Count(t => t.Type == JsonTokenType.Number && t.Value == "12"), Is.EqualTo(1));
|
||||
|
||||
// Deep validation of JSON content structure
|
||||
var tokenIndex = 4;
|
||||
|
||||
// Token 4: [
|
||||
Assert.That(tokens[tokenIndex++].Type, Is.EqualTo(JsonTokenType.StartArray));
|
||||
|
||||
// Token 5-8: Array elements [1,2,3,4]
|
||||
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
|
||||
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("1"));
|
||||
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
|
||||
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("2"));
|
||||
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
|
||||
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("3"));
|
||||
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
|
||||
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("4"));
|
||||
|
||||
// Token 9: ]
|
||||
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.EndArray));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.RegisteredTables;
|
||||
|
||||
/// <summary>
|
||||
/// Helper utilities for retrieving registered table column expressions.
|
||||
/// Provides mock implementations for testing purposes.
|
||||
/// </summary>
|
||||
public static class RegisteredTableColumns
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a registered table column expression for the specified data column ID.
|
||||
/// Currently returns mock column definitions for testing purposes.
|
||||
/// </summary>
|
||||
/// <param name="dataColumnId">The data column identifier (1=DEPARTMENT_ID, 2=NAME, 3=REVENUE, 4=DISCHARGE_DATE).</param>
|
||||
/// <returns>A registered table column expression for the specified column ID.</returns>
|
||||
/// <remarks>This is a mock implementation and should be replaced with actual column lookup logic.</remarks>
|
||||
public static RegisteredTableColumnExpression GetColumn(int dataColumnId)
|
||||
{
|
||||
var tableSource = new RegisteredTableSource(1001, "FW", "DEPARTMENT", "DEPT");
|
||||
return dataColumnId switch
|
||||
{
|
||||
1 => new RegisteredTableColumnExpression(dataColumnId, "DEPARTMENT_ID", tableSource),
|
||||
2 => new RegisteredTableColumnExpression(dataColumnId, "NAME", tableSource),
|
||||
3 => new RegisteredTableColumnExpression(dataColumnId, "REVENUE", tableSource),
|
||||
4 => new RegisteredTableColumnExpression(dataColumnId, "DISCHARGE_DATE", tableSource),
|
||||
_ => new RegisteredTableColumnExpression(dataColumnId, "FOOBAR", tableSource)
|
||||
};
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+469
@@ -0,0 +1,469 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the SqlBreakdownCollection core functionality.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionCoreTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void Constructor_WithNoArguments_CreatesEmptyCollection()
|
||||
{
|
||||
// Arrange & Act
|
||||
var collection = new SqlBreakdownCollection();
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(0));
|
||||
Assert.That(collection.IsEmpty, Is.True);
|
||||
Assert.That(collection.Breakdowns, Is.Empty);
|
||||
Assert.That(collection.RawStatements, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithBreakdownList_InitializesCollection()
|
||||
{
|
||||
// Arrange
|
||||
var mockBreakdowns = new List<ISqlBreakdown>
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2")
|
||||
};
|
||||
|
||||
// Act
|
||||
var collection = new SqlBreakdownCollection(mockBreakdowns);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.IsEmpty, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_WithValidBreakdown_AddsToCollection()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var breakdown = new MockSqlBreakdown("SELECT * FROM Table1");
|
||||
|
||||
// Act
|
||||
collection.Add(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(1));
|
||||
Assert.That(collection.GetAt(0), Is.EqualTo(breakdown));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_WithNullBreakdown_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => collection.Add(null!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddRange_WithMultipleBreakdowns_AddsAllItems()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var breakdowns = new List<ISqlBreakdown>
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table3")
|
||||
};
|
||||
|
||||
// Act
|
||||
collection.AddRange(breakdowns);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddRange_WithNullCollection_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => collection.AddRange(null!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Remove_WithExistingBreakdown_RemovesItem()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown = new MockSqlBreakdown("SELECT * FROM Table1");
|
||||
var collection = new SqlBreakdownCollection(new[] { breakdown });
|
||||
|
||||
// Act
|
||||
var result = collection.Remove(breakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(collection.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Remove_WithNonExistingBreakdown_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
|
||||
var otherBreakdown = new MockSqlBreakdown("SELECT * FROM Table2");
|
||||
|
||||
// Act
|
||||
var result = collection.Remove(otherBreakdown);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
Assert.That(collection.Count, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clear_WithMultipleItems_RemovesAll()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection(new[]
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2")
|
||||
});
|
||||
|
||||
// Act
|
||||
collection.Clear();
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(0));
|
||||
Assert.That(collection.IsEmpty, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithGoSeparators_ParsesStatements()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
SELECT * FROM Table1
|
||||
GO
|
||||
SELECT * FROM Table2
|
||||
GO
|
||||
SELECT * FROM Table3
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.RawStatements[0], Does.Contain("Table1"));
|
||||
Assert.That(collection.RawStatements[1], Does.Contain("Table2"));
|
||||
Assert.That(collection.RawStatements[2], Does.Contain("Table3"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithMixedCaseGO_ParsesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
SELECT * FROM Table1
|
||||
go
|
||||
SELECT * FROM Table2
|
||||
GO
|
||||
SELECT * FROM Table3
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithGoAndWhitespace_ParsesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
SELECT * FROM Table1
|
||||
GO
|
||||
SELECT * FROM Table2
|
||||
GO
|
||||
SELECT * FROM Table3
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithEmptyStatements_FiltersOutEmptyValues()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
SELECT * FROM Table1
|
||||
GO
|
||||
GO
|
||||
|
||||
SELECT * FROM Table2
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.RawStatements.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.RawStatements[0], Does.Contain("Table1"));
|
||||
Assert.That(collection.RawStatements[1], Does.Contain("Table2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithNullInput_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => collection.ParseBatch(null!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCombinedSql_WithMultipleBreakdowns_CombinesSqlWithSeparator()
|
||||
{
|
||||
// Arrange
|
||||
var breakdowns = new[]
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2")
|
||||
};
|
||||
var collection = new SqlBreakdownCollection(breakdowns);
|
||||
|
||||
// Act
|
||||
var result = collection.GetCombinedSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Does.Contain("Table1"));
|
||||
Assert.That(result, Does.Contain("Table2"));
|
||||
Assert.That(result, Does.Contain("GO"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCombinedSql_WithEmptyCollection_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
|
||||
// Act
|
||||
var result = collection.GetCombinedSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCombinedSql_WithCustomSeparator_UsesCustomSeparator()
|
||||
{
|
||||
// Arrange
|
||||
var breakdowns = new[]
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2")
|
||||
};
|
||||
var collection = new SqlBreakdownCollection(breakdowns);
|
||||
|
||||
// Act
|
||||
var result = collection.GetCombinedSql(true, ";");
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Does.Contain(";"));
|
||||
Assert.That(result, Does.Not.Contain("GO"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetBatchSql_WithRawStatements_CombinesWithGO()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
collection.ParseBatch(@"
|
||||
SELECT * FROM Table1
|
||||
GO
|
||||
SELECT * FROM Table2
|
||||
");
|
||||
|
||||
// Act
|
||||
var result = collection.GetBatchSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Does.Contain("Table1"));
|
||||
Assert.That(result, Does.Contain("GO"));
|
||||
Assert.That(result, Does.Contain("Table2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetBatchSql_WithEmptyCollection_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
|
||||
// Act
|
||||
var result = collection.GetBatchSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Where_WithValidPredicate_FiltersBreakdowns()
|
||||
{
|
||||
// Arrange
|
||||
var breakdowns = new[]
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table3")
|
||||
};
|
||||
var collection = new SqlBreakdownCollection(breakdowns);
|
||||
|
||||
// Act
|
||||
var filtered = collection.Where(b => b.ToString()!.Contains("Table2")).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.That(filtered.Count, Is.EqualTo(1));
|
||||
Assert.That(filtered[0].ToString()!, Does.Contain("Table2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Select_WithValidSelector_ProjectsBreakdowns()
|
||||
{
|
||||
// Arrange
|
||||
var breakdowns = new[]
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2")
|
||||
};
|
||||
var collection = new SqlBreakdownCollection(breakdowns);
|
||||
|
||||
// Act
|
||||
var projected = collection.Select(b => b.ToString()!.Length).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.That(projected.Count, Is.EqualTo(2));
|
||||
Assert.That(projected[0], Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAt_WithValidIndex_ReturnsBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var breakdown1 = new MockSqlBreakdown("SELECT * FROM Table1");
|
||||
var breakdown2 = new MockSqlBreakdown("SELECT * FROM Table2");
|
||||
var collection = new SqlBreakdownCollection(new[] { breakdown1, breakdown2 });
|
||||
|
||||
// Act
|
||||
var result = collection.GetAt(1);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(breakdown2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAt_WithInvalidIndex_ThrowsArgumentOutOfRangeException()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => collection.GetAt(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FirstOrDefault_WithMatchingPredicate_ReturnsBreakdown()
|
||||
{
|
||||
// Arrange
|
||||
var breakdowns = new[]
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2")
|
||||
};
|
||||
var collection = new SqlBreakdownCollection(breakdowns);
|
||||
|
||||
// Act
|
||||
var result = collection.FirstOrDefault(b => b.ToString()!.Contains("Table2"));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result!.ToString(), Does.Contain("Table2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FirstOrDefault_WithNoMatch_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
|
||||
|
||||
// Act
|
||||
var result = collection.FirstOrDefault(b => b.ToString()!.Contains("NonExistent"));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetRawStatementAt_WithValidIndex_ReturnsStatement()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
collection.ParseBatch(@"
|
||||
SELECT * FROM Table1
|
||||
GO
|
||||
SELECT * FROM Table2
|
||||
");
|
||||
|
||||
// Act
|
||||
var result = collection.GetRawStatementAt(1);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Does.Contain("Table2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetRawStatementAt_WithInvalidIndex_ThrowsArgumentOutOfRangeException()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
collection.ParseBatch("SELECT * FROM Table1");
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => collection.GetRawStatementAt(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToString_ReturnsFormattedSql()
|
||||
{
|
||||
// Arrange
|
||||
var breakdowns = new[]
|
||||
{
|
||||
new MockSqlBreakdown("SELECT * FROM Table1"),
|
||||
new MockSqlBreakdown("SELECT * FROM Table2")
|
||||
};
|
||||
var collection = new SqlBreakdownCollection(breakdowns);
|
||||
|
||||
// Act
|
||||
var result = collection.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Does.Contain("Table1"));
|
||||
Assert.That(result, Does.Contain("Table2"));
|
||||
Assert.That(result, Does.Contain("GO"));
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with CREATE statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionCreateTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithCreateTableStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
CREATE TABLE Users (
|
||||
Id INT PRIMARY KEY,
|
||||
Name NVARCHAR(100),
|
||||
Email NVARCHAR(100)
|
||||
)
|
||||
GO
|
||||
CREATE TABLE Orders (
|
||||
OrderId INT PRIMARY KEY,
|
||||
UserId INT FOREIGN KEY REFERENCES Users(Id),
|
||||
OrderDate DATETIME
|
||||
)
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE TABLE Users"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("CREATE TABLE Orders"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithCreateIndexStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
CREATE INDEX idx_Users_Email ON Users(Email)
|
||||
GO
|
||||
CREATE UNIQUE INDEX idx_Orders_OrderDate ON Orders(OrderDate)
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE INDEX"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("UNIQUE"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithCreateProcedureStatement_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
CREATE PROCEDURE sp_GetUserById
|
||||
@UserId INT
|
||||
AS
|
||||
BEGIN
|
||||
SELECT * FROM Users WHERE Id = @UserId
|
||||
END
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE PROCEDURE"));
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with DELETE statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionDeleteTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithDeleteStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
DELETE FROM Orders WHERE OrderDate < '2019-01-01'
|
||||
GO
|
||||
DELETE FROM UserProfiles WHERE UserId NOT IN (SELECT Id FROM Users)
|
||||
GO
|
||||
DELETE FROM AuditLog WHERE LogDate < DATEADD(year, -2, GETDATE())
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DELETE FROM Orders"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("NOT IN"));
|
||||
Assert.That(collection.GetRawStatementAt(2), Does.Contain("DATEADD"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithDeleteFromSelectStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
DELETE o FROM Orders o
|
||||
WHERE NOT EXISTS (SELECT 1 FROM OrderItems oi WHERE oi.OrderId = o.OrderId)
|
||||
GO
|
||||
DELETE FROM Users
|
||||
WHERE Id IN (SELECT UserId FROM Orders WHERE OrderStatus = 'Cancelled')
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("EXISTS"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithDeleteWithJoinStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
DELETE Orders
|
||||
FROM Orders o
|
||||
INNER JOIN OrderStatuses os ON o.StatusId = os.Id
|
||||
WHERE os.StatusName = 'Archived'
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DELETE Orders"));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INNER JOIN"));
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with DROP statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionDropTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithDropTableStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
DROP TABLE IF EXISTS OrderItems
|
||||
GO
|
||||
DROP TABLE IF EXISTS Orders
|
||||
GO
|
||||
DROP TABLE IF EXISTS Users
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DROP TABLE"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("DROP TABLE"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithDropIndexAndProcedureStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
DROP INDEX IF EXISTS idx_Users_Email ON Users
|
||||
GO
|
||||
DROP PROCEDURE IF EXISTS sp_GetUserById
|
||||
GO
|
||||
DROP VIEW IF EXISTS vw_UserOrders
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DROP INDEX"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("PROCEDURE"));
|
||||
Assert.That(collection.GetRawStatementAt(2), Does.Contain("VIEW"));
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with INSERT statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionInsertTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithInsertStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
INSERT INTO Users (Id, Name, Email) VALUES (1, 'John Doe', 'john@example.com')
|
||||
GO
|
||||
INSERT INTO Users (Id, Name, Email) VALUES (2, 'Jane Smith', 'jane@example.com')
|
||||
GO
|
||||
INSERT INTO Users (Id, Name, Email) VALUES (3, 'Bob Johnson', 'bob@example.com')
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INSERT INTO Users"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Jane Smith"));
|
||||
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Bob Johnson"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithInsertSelectStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
INSERT INTO UsersArchive
|
||||
SELECT Id, Name, Email FROM Users WHERE CreatedDate < '2020-01-01'
|
||||
GO
|
||||
INSERT INTO OrdersBackup
|
||||
SELECT OrderId, UserId, OrderDate FROM Orders WHERE OrderDate < DATEADD(year, -1, GETDATE())
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INSERT INTO UsersArchive"));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("SELECT"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithBulkInsertStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
BULK INSERT Users FROM 'C:\data\users.csv'
|
||||
WITH (FIELDTERMINATOR=',', ROWTERMINATOR='\n')
|
||||
GO
|
||||
BULK INSERT Orders FROM 'C:\data\orders.csv'
|
||||
WITH (FIELDTERMINATOR=',', ROWTERMINATOR='\n')
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("BULK INSERT"));
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with mixed CRUD operations.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionMixedCrudTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithMixedCrudOperations_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
CREATE TABLE Users (
|
||||
Id INT PRIMARY KEY,
|
||||
Name NVARCHAR(100),
|
||||
Email NVARCHAR(100)
|
||||
)
|
||||
GO
|
||||
INSERT INTO Users (Id, Name, Email) VALUES (1, 'John Doe', 'john@example.com')
|
||||
GO
|
||||
INSERT INTO Users (Id, Name, Email) VALUES (2, 'Jane Smith', 'jane@example.com')
|
||||
GO
|
||||
SELECT * FROM Users WHERE Id = 1
|
||||
GO
|
||||
UPDATE Users SET Name = 'John Updated' WHERE Id = 1
|
||||
GO
|
||||
DELETE FROM Users WHERE Id = 2
|
||||
GO
|
||||
DROP TABLE Users
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(7));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE TABLE"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("INSERT"));
|
||||
Assert.That(collection.GetRawStatementAt(3), Does.Contain("SELECT"));
|
||||
Assert.That(collection.GetRawStatementAt(4), Does.Contain("UPDATE"));
|
||||
Assert.That(collection.GetRawStatementAt(5), Does.Contain("DELETE"));
|
||||
Assert.That(collection.GetRawStatementAt(6), Does.Contain("DROP"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithComplexMultipleStatements_ParsesAllStatements()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
CREATE TABLE Products (
|
||||
Id INT PRIMARY KEY,
|
||||
Name NVARCHAR(100),
|
||||
Price DECIMAL(10, 2),
|
||||
Quantity INT
|
||||
)
|
||||
GO
|
||||
INSERT INTO Products (Id, Name, Price, Quantity)
|
||||
SELECT Id, ProductName, BasePrice, StockQuantity
|
||||
FROM StagingProducts
|
||||
WHERE IsActive = 1
|
||||
GO
|
||||
UPDATE Products SET Price = Price * 1.1 WHERE Quantity < 10
|
||||
GO
|
||||
SELECT p.Name,
|
||||
COUNT(oi.Id) AS OrderCount,
|
||||
SUM(oi.Quantity) AS TotalSold
|
||||
FROM Products p
|
||||
LEFT JOIN OrderItems oi ON p.Id = oi.ProductId
|
||||
GROUP BY p.Id, p.Name
|
||||
HAVING SUM(oi.Quantity) > 100
|
||||
GO
|
||||
DELETE FROM Products WHERE Quantity = 0 AND LastUpdated < DATEADD(month, -6, GETDATE())
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(5));
|
||||
Assert.That(collection.RawStatements.Count, Is.EqualTo(5));
|
||||
foreach (var statement in collection.RawStatements)
|
||||
{
|
||||
Assert.That(statement, Is.Not.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with parameterized statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionParameterTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithParameterizedInsertStatements_PreservesParameters()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
INSERT INTO Users (Id, Name, Email) VALUES (@UserId, @UserName, @UserEmail)
|
||||
GO
|
||||
INSERT INTO Orders (OrderId, UserId, OrderDate) VALUES (@OrderId, @UserId, @OrderDate)
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@UserId"));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@UserEmail"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithParameterizedUpdateStatements_PreservesParameters()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
UPDATE Users SET Name = @Name, Email = @Email WHERE Id = @UserId
|
||||
GO
|
||||
UPDATE Orders SET Status = @Status, ModifiedDate = @ModifiedDate WHERE OrderId = @OrderId
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@Name"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("@Status"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCombinedSql_WithMixedCrudOperations_CombinesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
collection.ParseBatch(@"
|
||||
INSERT INTO Users (Id, Name) VALUES (1, 'John')
|
||||
GO
|
||||
UPDATE Users SET Name = 'Jane' WHERE Id = 1
|
||||
GO
|
||||
DELETE FROM Users WHERE Id = 1
|
||||
");
|
||||
|
||||
// Act
|
||||
var combined = collection.GetCombinedSql();
|
||||
|
||||
// Assert
|
||||
Assert.That(combined, Does.Contain("INSERT"));
|
||||
Assert.That(combined, Does.Contain("UPDATE"));
|
||||
Assert.That(combined, Does.Contain("DELETE"));
|
||||
Assert.That(combined, Does.Contain("GO"));
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with SELECT statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionSelectTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithComplexSelectStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
SELECT u.Id, u.Name, COUNT(o.OrderId) AS OrderCount
|
||||
FROM Users u
|
||||
LEFT JOIN Orders o ON u.Id = o.UserId
|
||||
WHERE u.CreatedDate > '2020-01-01'
|
||||
GROUP BY u.Id, u.Name
|
||||
HAVING COUNT(o.OrderId) > 0
|
||||
ORDER BY OrderCount DESC
|
||||
GO
|
||||
SELECT * FROM Orders WHERE OrderDate BETWEEN '2020-01-01' AND '2021-12-31'
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("LEFT JOIN"));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("GROUP BY"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("BETWEEN"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithSelectWithCommonTableExpression_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
WITH UserOrders AS (
|
||||
SELECT UserId, OrderId, OrderDate
|
||||
FROM Orders
|
||||
WHERE OrderDate > '2020-01-01'
|
||||
)
|
||||
SELECT u.Id, u.Name, uo.OrderId
|
||||
FROM Users u
|
||||
INNER JOIN UserOrders uo ON u.Id = uo.UserId
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("WITH UserOrders AS"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithSelectFromMultipleTables_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
SELECT u.Id, u.Name, o.OrderId, p.ProductName
|
||||
FROM Users u
|
||||
JOIN Orders o ON u.Id = o.UserId
|
||||
JOIN OrderItems oi ON o.OrderId = oi.OrderId
|
||||
JOIN Products p ON oi.ProductId = p.Id
|
||||
WHERE u.Country = 'USA'
|
||||
GO
|
||||
SELECT * FROM UserProfiles WHERE UserId IN (SELECT Id FROM Users)
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("JOIN"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("IN"));
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with custom separators.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionSeparatorTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithSemicolonSeparator_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
SELECT * FROM Users
|
||||
;
|
||||
SELECT * FROM Orders
|
||||
;
|
||||
SELECT * FROM Products
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql, ";");
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("Users"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Orders"));
|
||||
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Products"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithCustomSeparator_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
INSERT INTO Users VALUES (1, 'John')
|
||||
~~~
|
||||
INSERT INTO Users VALUES (2, 'Jane')
|
||||
~~~
|
||||
INSERT INTO Users VALUES (3, 'Bob')
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql, "~~~");
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
using System.Text;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for SqlBreakdownCollection tests containing shared test utilities and mock implementations.
|
||||
/// </summary>
|
||||
public class SqlBreakdownCollectionTestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Mock implementation of ISqlBreakdown for testing purposes.
|
||||
/// </summary>
|
||||
public class MockSqlBreakdown : ISqlBreakdown
|
||||
{
|
||||
private readonly string _sql;
|
||||
|
||||
public MockSqlBreakdown(string sql)
|
||||
{
|
||||
_sql = sql;
|
||||
SetupClauses = new List<string>();
|
||||
FinishClauses = new System.Collections.ArrayList();
|
||||
}
|
||||
|
||||
public string? RawSql { get; set; }
|
||||
public List<string> SetupClauses { get; set; }
|
||||
public bool IsUsingSetupClause => SetupClauses.Count > 0;
|
||||
public System.Collections.ArrayList FinishClauses { get; set; }
|
||||
public bool IsUsingFinishClause => FinishClauses.Count > 0;
|
||||
|
||||
public string GetSql(bool includeSetupFinish = true)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (includeSetupFinish)
|
||||
{
|
||||
foreach (string setup in SetupClauses)
|
||||
{
|
||||
sb.AppendLine(setup);
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(_sql);
|
||||
|
||||
if (includeSetupFinish)
|
||||
{
|
||||
foreach (string finish in FinishClauses)
|
||||
{
|
||||
sb.AppendLine(finish);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString() => GetSql();
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (MockSqlBreakdown)MemberwiseClone();
|
||||
clone.SetupClauses = new List<string>(SetupClauses);
|
||||
clone.FinishClauses = new System.Collections.ArrayList(FinishClauses);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with transaction statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionTransactionTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithTransactionStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
BEGIN TRANSACTION UpdatePrices
|
||||
UPDATE Products SET Price = Price * 1.1 WHERE Category = 'Electronics'
|
||||
UPDATE ProductPriceHistory SET NewPrice = Price FROM Products WHERE Products.Id = ProductPriceHistory.ProductId
|
||||
COMMIT TRANSACTION UpdatePrices
|
||||
GO
|
||||
BEGIN TRANSACTION DeleteOldOrders
|
||||
DELETE FROM OrderItems WHERE OrderId IN (SELECT OrderId FROM Orders WHERE OrderDate < '2018-01-01')
|
||||
DELETE FROM Orders WHERE OrderDate < '2018-01-01'
|
||||
COMMIT TRANSACTION DeleteOldOrders
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
||||
var statement = collection.GetRawStatementAt(0);
|
||||
Assert.That(statement, Does.Contain("BEGIN TRANSACTION"));
|
||||
Assert.That(statement, Does.Contain("COMMIT"));
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
|
||||
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for SqlBreakdownCollection with UPDATE statements.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SqlBreakdownCollectionUpdateTests : SqlBreakdownCollectionTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ParseBatch_WithUpdateStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
UPDATE Users SET Name = 'John Updated', ModifiedDate = GETDATE() WHERE Id = 1
|
||||
GO
|
||||
UPDATE Orders SET Status = 'Shipped' WHERE OrderDate < DATEADD(day, -30, GETDATE())
|
||||
GO
|
||||
UPDATE Products SET Price = Price * 1.1 WHERE Category = 'Electronics'
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(3));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE Users"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Status"));
|
||||
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Price * 1.1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithUpdateFromSelectStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
UPDATE u
|
||||
SET u.LastOrderDate = (SELECT MAX(OrderDate) FROM Orders WHERE UserId = u.Id)
|
||||
FROM Users u
|
||||
WHERE u.Id IN (SELECT DISTINCT UserId FROM Orders)
|
||||
GO
|
||||
UPDATE Products
|
||||
SET Quantity = Quantity - 1
|
||||
FROM OrderItems oi
|
||||
WHERE Products.Id = oi.ProductId AND oi.OrderId = 100
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.EqualTo(2));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE u"));
|
||||
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Quantity - 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseBatch_WithUpdateWithJoinStatements_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new SqlBreakdownCollection();
|
||||
var batchSql = @"
|
||||
UPDATE Orders
|
||||
SET OrderStatus = 'Processed'
|
||||
FROM Orders o
|
||||
JOIN OrderItems oi ON o.OrderId = oi.OrderId
|
||||
WHERE oi.Quantity > 50
|
||||
";
|
||||
|
||||
// Act
|
||||
collection.ParseBatch(batchSql);
|
||||
|
||||
// Assert
|
||||
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE Orders"));
|
||||
Assert.That(collection.GetRawStatementAt(0), Does.Contain("JOIN"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Strata.SqlTools.SqlBreakdown.Tests\**" />
|
||||
<EmbeddedResource Remove="Strata.SqlTools.SqlBreakdown.Tests\**" />
|
||||
<None Remove="Strata.SqlTools.SqlBreakdown.Tests\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Strata.SqlTools.SqlBreakdown\Strata.SqlTools.SqlBreakdown.csproj" />
|
||||
<ProjectReference Include="..\..\src\Strata.SqlTools.SqlServer\Strata.SqlTools.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\src\Strata.SqlTools.Snowflake\Strata.SqlTools.Snowflake.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user