chore: initial git load of code space

This commit is contained in:
Thom Lamb
2026-05-12 08:52:33 -05:00
parent 9abada692f
commit 5e467bcc9c
384 changed files with 65960 additions and 2 deletions
@@ -0,0 +1,279 @@
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Logical;
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
using Strata.SqlTools.Markdown.Expressions;
namespace Strata.SqlTools.Markdown.Tests.Expressions;
[TestFixture]
public class ExpressionGeneratorTests
{
private ExpressionGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new ExpressionGenerator();
}
[Test]
public void GenerateMarkdown_WithSimpleExpression_GeneratesMarkdown()
{
// Arrange
var expression = new NumberLiteralExpression(42);
// Act
var result = _generator.GenerateMarkdown(expression);
// Assert
Assert.That(result, Does.Contain("## Expression Structure"));
Assert.That(result, Does.Contain("## Expression Type"));
Assert.That(result, Does.Contain("## Mermaid Diagram"));
Assert.That(result, Does.Contain("NumberLiteralExpression"));
}
[Test]
public void GenerateMarkdown_WithComparisonExpression_IncludesComparison()
{
// Arrange
Expression left = new NumberLiteralExpression(100);
Expression right = new NumberLiteralExpression(50);
var expression = left > right;
// Act
var result = _generator.GenerateMarkdown(expression, "Price Comparison");
// Assert
Assert.That(result, Does.Contain("# Price Comparison"));
Assert.That(result, Does.Contain("Comparison"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateMarkdown_WithArithmeticExpression_IncludesArithmetic()
{
// Arrange
Expression price = new NumberLiteralExpression(100);
Expression discount = new NumberLiteralExpression(0.1m);
var expression = price * (1 - discount);
// Act
var result = _generator.GenerateMarkdown(expression);
// Assert
Assert.That(result, Does.Contain("Arithmetic"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("graph TD"));
}
[Test]
public void GenerateMarkdown_WithLogicalExpression_IncludesLogic()
{
// Arrange
var expr1 = new NumberLiteralExpression(5) > new NumberLiteralExpression(3);
var expr2 = new NumberLiteralExpression(10) < new NumberLiteralExpression(20);
var expression = new AndExpression(expr1, expr2);
// Act
var result = _generator.GenerateMarkdown(expression);
// Assert
Assert.That(result, Does.Contain("AND"));
Assert.That(result, Does.Contain("mermaid"));
}
[Test]
public void GenerateMarkdown_WithTitle_IncludesTitle()
{
// Arrange
var expression = new StringLiteralExpression("test");
// Act
var result = _generator.GenerateMarkdown(expression, "Test Expression");
// Assert
Assert.That(result, Does.StartWith("# Test Expression"));
}
[Test]
public void GenerateMarkdown_ComplexExpression_GeneratesCompleteDoc()
{
// Arrange
Expression quantity = new NumberLiteralExpression(10);
Expression price = new NumberLiteralExpression(25.50m);
Expression discount = new NumberLiteralExpression(0.15m);
var expression = quantity * price * (1 - discount);
// Act
var result = _generator.GenerateMarkdown(expression, "Order Total");
// Assert
Assert.That(result, Does.Contain("# Order Total"));
Assert.That(result, Does.Contain("## Expression Structure"));
Assert.That(result, Does.Contain("## Expression Type"));
Assert.That(result, Does.Contain("## Mermaid Diagram"));
Assert.That(result, Does.Contain("## Mathematical Expression"));
Assert.That(result, Does.Contain("Arithmetic"));
Assert.That(result.Length, Is.GreaterThan(200));
}
[Test]
public void GenerateMathematicalExpression_WithArithmetic_GeneratesLatex()
{
// Arrange
Expression a = new NumberLiteralExpression(5);
Expression b = new NumberLiteralExpression(3);
var expression = a + b;
// Act
var result = ExpressionGenerator.GenerateMathematicalExpression(expression);
// Assert
Assert.That(result, Does.Contain("$$"));
Assert.That(result, Does.Contain("+"));
Assert.That(result, Does.Contain("5"));
Assert.That(result, Does.Contain("3"));
}
[Test]
public void GenerateMathematicalExpression_WithMultiplication_UsesTimesSymbol()
{
// Arrange
Expression x = new NumberLiteralExpression(10);
Expression y = new NumberLiteralExpression(20);
var expression = x * y;
// Act
var result = ExpressionGenerator.GenerateMathematicalExpression(expression);
// Assert
Assert.That(result, Does.Contain("\\times"));
}
[Test]
public void GenerateMathematicalExpression_WithComparison_UsesCorrectSymbol()
{
// Arrange
Expression a = new NumberLiteralExpression(10);
Expression b = new NumberLiteralExpression(20);
var expression = a >= b;
// Act
var result = ExpressionGenerator.GenerateMathematicalExpression(expression);
// Assert
Assert.That(result, Does.Contain("\\geq"));
}
[Test]
public void GenerateMathematicalExpression_WithLogicalAnd_UsesLandSymbol()
{
// Arrange
var expr1 = new NumberLiteralExpression(5) > new NumberLiteralExpression(3);
var expr2 = new NumberLiteralExpression(10) < new NumberLiteralExpression(20);
var expression = new AndExpression(expr1, expr2);
// Act
var result = ExpressionGenerator.GenerateMathematicalExpression(expression);
// Assert
Assert.That(result, Does.Contain("\\land"));
}
[Test]
public void GenerateMathematicalExpression_Inline_UsesSingleDollar()
{
// Arrange
var expression = new NumberLiteralExpression(42);
// Act
var result = ExpressionGenerator.GenerateMathematicalExpression(expression, inline: true);
// Assert
Assert.That(result, Does.StartWith("$"));
Assert.That(result, Does.EndWith("$"));
Assert.That(result, Does.Not.Contain("$$"));
}
[Test]
public void GenerateMathematicalExpression_Block_UsesDoubleDollar()
{
// Arrange
var expression = new NumberLiteralExpression(42);
// Act
var result = ExpressionGenerator.GenerateMathematicalExpression(expression, inline: false);
// Assert
Assert.That(result, Does.Contain("$$"));
}
[Test]
public void GenerateMathematicalExpression_ComplexArithmetic_GeneratesCorrectLatex()
{
// Arrange
Expression quantity = new NumberLiteralExpression(10);
Expression price = new NumberLiteralExpression(25);
Expression discount = new NumberLiteralExpression(0.1m);
var expression = quantity * price * (1 - discount);
// Act
var result = ExpressionGenerator.GenerateMathematicalExpression(expression);
// Assert
Assert.That(result, Does.Contain("\\times"));
Assert.That(result, Does.Contain("10"));
Assert.That(result, Does.Contain("25"));
Assert.That(result, Does.Contain("0.1"));
}
[Test]
public void GenerateRawMathematicalExpression_CauchySchwarzInequality_WithDollarFormat()
{
// Arrange
var latex = @"\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)";
// Act
var result = ExpressionGenerator.GenerateRawMathematicalExpression(latex, format: "dollar", inline: false);
// Assert
Assert.That(result, Does.StartWith("$$"));
Assert.That(result, Does.EndWith("$$"));
Assert.That(result, Does.Contain("\\sum_{k=1}^n"));
Assert.That(result, Does.Contain("a_k b_k"));
Assert.That(result, Does.Contain("\\leq"));
}
[Test]
public void GenerateRawMathematicalExpression_CauchySchwarzInequality_WithMathFormat()
{
// Arrange
var latex = @"\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)";
// Act
var result = ExpressionGenerator.GenerateRawMathematicalExpression(latex, format: "math");
// Assert
Assert.That(result, Does.StartWith("```math"));
Assert.That(result, Does.EndWith("```"));
Assert.That(result, Does.Contain("\\sum_{k=1}^n"));
Assert.That(result, Does.Contain("a_k b_k"));
Assert.That(result, Does.Contain("\\leq"));
}
[Test]
public void GenerateRawMathematicalExpression_Inline_UsesSingleDollar()
{
// Arrange
var latex = "E = mc^2";
// Act
var result = ExpressionGenerator.GenerateRawMathematicalExpression(latex, inline: true);
// Assert
Assert.That(result, Does.StartWith("$"));
Assert.That(result, Does.EndWith("$"));
Assert.That(result, Does.Not.Contain("```"));
Assert.That(result, Is.EqualTo("$E = mc^2$"));
}
}
@@ -0,0 +1,158 @@
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Logical;
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
using Strata.SqlTools.Markdown.Expressions;
namespace Strata.SqlTools.Markdown.Tests.Expressions;
[TestFixture]
public class SimpleExpressionGeneratorTests
{
private SimpleExpressionGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new SimpleExpressionGenerator();
}
[Test]
public void GenerateMarkdown_WithSimpleExpression_GeneratesSimpleDoc()
{
// Arrange
var expression = new NumberLiteralExpression(42);
// Act
var result = _generator.GenerateMarkdown(expression);
// Assert
Assert.That(result, Does.Contain("## Expression"));
Assert.That(result, Does.Contain("## Type Information"));
Assert.That(result, Does.Contain("## Description"));
Assert.That(result, Does.Contain("```sql"));
}
[Test]
public void GenerateMarkdown_WithTitle_IncludesTitle()
{
// Arrange
var expression = new StringLiteralExpression("test");
// Act
var result = _generator.GenerateMarkdown(expression, "Simple Expression");
// Assert
Assert.That(result, Does.StartWith("# Simple Expression"));
}
[Test]
public void GenerateMarkdown_IncludesDescription()
{
// Arrange
Expression left = new NumberLiteralExpression(100);
Expression right = new NumberLiteralExpression(50);
var expression = left == right;
// Act
var result = _generator.GenerateMarkdown(expression);
// Assert
Assert.That(result, Does.Contain("EqualToExpression"));
Assert.That(result, Does.Contain("expression"));
}
[Test]
public void GenerateComparisonTable_WithMultipleExpressions_GeneratesTable()
{
// Arrange
var expressions = new Dictionary<string, Expression>
{
["Price Filter"] = new NumberLiteralExpression(100) > new NumberLiteralExpression(50),
["Status Filter"] = new StringLiteralExpression("Active") == new StringLiteralExpression("Active"),
["Quantity Check"] = new NumberLiteralExpression(10) <= new NumberLiteralExpression(100)
};
// Act
var result = _generator.GenerateComparisonTable(expressions);
// Assert
Assert.That(result, Does.Contain("| Name | Expression | Type |"));
Assert.That(result, Does.Contain("|------|------------|------|"));
Assert.That(result, Does.Contain("Price Filter"));
Assert.That(result, Does.Contain("Status Filter"));
Assert.That(result, Does.Contain("Quantity Check"));
}
[Test]
public void GenerateComparisonTable_WithTitle_IncludesTitle()
{
// Arrange
var expressions = new Dictionary<string, Expression>
{
["Test"] = new NumberLiteralExpression(1)
};
// Act
var result = _generator.GenerateComparisonTable(expressions, "Comparison Table");
// Assert
Assert.That(result, Does.StartWith("# Comparison Table"));
}
[Test]
public void GenerateBulletList_WithExpressions_GeneratesList()
{
// Arrange
var expressions = new List<Expression>
{
new NumberLiteralExpression(100),
new StringLiteralExpression("Active"),
new NumberLiteralExpression(5) + new NumberLiteralExpression(10)
};
// Act
var result = _generator.GenerateBulletList(expressions);
// Assert
Assert.That(result, Does.Contain("- `100`"));
Assert.That(result, Does.Contain("- `'Active'`"));
Assert.That(result, Does.Contain("- `"));
Assert.That(result, Does.Contain("*NumberLiteralExpression*"));
}
[Test]
public void GenerateBulletList_WithTitle_IncludesTitle()
{
// Arrange
var expressions = new List<Expression>
{
new NumberLiteralExpression(1)
};
// Act
var result = _generator.GenerateBulletList(expressions, "Expression List");
// Assert
Assert.That(result, Does.StartWith("## Expression List"));
}
[Test]
public void GenerateComparisonTable_WithOrExpression_GeneratesTable()
{
// Arrange
var expr1 = new NumberLiteralExpression(1) > new NumberLiteralExpression(0);
var expr2 = new NumberLiteralExpression(2) > new NumberLiteralExpression(0);
var expressions = new Dictionary<string, Expression>
{
["Test"] = new OrExpression(expr1, expr2)
};
// Act
var result = _generator.GenerateComparisonTable(expressions);
// Assert
Assert.That(result, Does.Contain("Test"));
Assert.That(result, Does.Contain("OrExpression"));
Assert.That(result, Does.Contain("OR"));
}
}
@@ -0,0 +1,137 @@
using Strata.SqlTools.Breakdowns.LinqToSql;
using Strata.SqlTools.Markdown.LinqToSql;
namespace Strata.SqlTools.Markdown.Tests.LinqToSql;
[TestFixture]
public class QueryBreakdownGeneratorTests
{
private QueryBreakdownGenerator _generator = null!;
private TestDataContext _context = null!;
[SetUp]
public void Setup()
{
_generator = new QueryBreakdownGenerator();
_context = new TestDataContext();
}
[Test]
public void GenerateMermaidDiagram_SimpleLinqQuery_GeneratesValidMermaid()
{
// Arrange
var query = _context.Users.Where(u => u.Age > 21);
var breakdown = LinqQueryBreakdown.Analyze(query);
// Act
var result = _generator.GenerateMermaidDiagram(breakdown, "Simple User Query");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
Assert.That(result, Does.Contain("### Simple User Query"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateMethodChainDiagram_WithMethodCalls_ShowsChain()
{
// Arrange
var query = _context.Users
.Where(u => u.Age > 18)
.OrderBy(u => u.Name)
.Select(u => new { u.Id, u.Name });
var breakdown = LinqQueryBreakdown.Analyze(query);
// Act
var result = _generator.GenerateMethodChainDiagram(breakdown, "Method Chain");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart LR"));
Assert.That(result, Does.Contain("### Method Chain"));
Assert.That(result, Does.Contain("Where"));
Assert.That(result, Does.Contain("OrderBy"));
Assert.That(result, Does.Contain("Select"));
Assert.That(result, Does.Contain("IQueryable"));
Assert.That(result, Does.Contain("Result"));
}
[Test]
public void GenerateMethodChainDiagram_EmptyChain_ShowsBasicFlow()
{
// Arrange
var breakdown = new LinqQueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateMethodChainDiagram(breakdown);
// Assert
Assert.That(result, Does.Contain("IQueryable"));
Assert.That(result, Does.Contain("Result"));
}
[Test]
public void GenerateCombinedDiagram_IncludesBothDiagrams()
{
// Arrange
var query = _context.Users.Where(u => u.Age > 21).OrderBy(u => u.Name);
var breakdown = LinqQueryBreakdown.Analyze(query);
// Act
var result = _generator.GenerateCombinedDiagram(breakdown, "User Query Analysis");
// Assert
Assert.That(result, Does.Contain("## User Query Analysis"));
Assert.That(result, Does.Contain("### LINQ Method Chain"));
Assert.That(result, Does.Contain("### SQL Query Structure"));
Assert.That(result, Does.Contain("flowchart LR"));
Assert.That(result, Does.Contain("flowchart TD"));
}
[Test]
public void GenerateMermaidDiagram_WithProjection_ShowsSelectedFields()
{
// Arrange
var query = _context.Users.Select(u => new { u.Id, u.Name, u.Email });
var breakdown = LinqQueryBreakdown.Analyze(query);
// Act
var result = _generator.GenerateMermaidDiagram(breakdown);
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("```mermaid"));
}
[Test]
public void GenerateMermaidDiagram_NullTitle_GeneratesWithoutTitle()
{
// Arrange
var query = _context.Users;
var breakdown = LinqQueryBreakdown.Analyze(query);
// Act
var result = _generator.GenerateMermaidDiagram(breakdown, null);
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("```mermaid"));
}
}
// Test data context
public class TestDataContext
{
public IQueryable<User> Users => new List<User>().AsQueryable();
}
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public int Age { get; set; }
}
@@ -0,0 +1,112 @@
using Strata.SqlTools.Markdown.LinqToSql;
using Strata.SqlTools.Breakdowns.LinqToSql;
namespace Strata.SqlTools.Markdown.Tests.LinqToSql;
[TestFixture]
public class SqlStatementGeneratorTests
{
private SqlStatementGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new SqlStatementGenerator();
}
[Test]
public void GenerateLinqPipelineDiagram_BasicQuery_GeneratesSequenceDiagram()
{
// Arrange
var query = new LinqQueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateLinqPipelineDiagram(query, "User Query Pipeline");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("### User Query Pipeline"));
Assert.That(result, Does.Contain("LINQ Provider"));
Assert.That(result, Does.Contain("Expression Tree"));
Assert.That(result, Does.Contain("SQL Generator"));
Assert.That(result, Does.Contain("Database"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateLinqPipelineDiagram_WithWhere_ShowsPredicateProcessing()
{
// Arrange
var query = new LinqQueryBreakdown("Id, Name", "Users");
query.AddWhereClause("Age > 21");
// Act
var result = _generator.GenerateLinqPipelineDiagram(query);
// Assert
Assert.That(result, Does.Contain("Where Predicate"));
Assert.That(result, Does.Contain("Expression Tree"));
Assert.That(result, Does.Contain("SQL Generator"));
}
[Test]
public void GenerateLinqPipelineDiagram_WithProjection_ShowsSelectProcessing()
{
// Arrange
var query = new LinqQueryBreakdown("Id, Name, Email", "Users");
// Act
var result = _generator.GenerateLinqPipelineDiagram(query);
// Assert
Assert.That(result, Does.Contain("Select Projection"));
Assert.That(result, Does.Contain("LINQ Provider"));
Assert.That(result, Does.Contain("Database"));
}
[Test]
public void GenerateLinqPipelineDiagram_NullTitle_GeneratesWithoutTitle()
{
// Arrange
var query = new LinqQueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateLinqPipelineDiagram(query, null);
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("sequenceDiagram"));
}
[Test]
public void GenerateSequenceDiagram_DelegatesToBaseClass()
{
// Arrange
var query = new LinqQueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateSequenceDiagram(query, "Sequence Test");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("### Sequence Test"));
}
[Test]
public void GenerateEntityRelationshipDiagram_DelegatesToBaseClass()
{
// Arrange
var query = new LinqQueryBreakdown("Id, Name", "Users");
// Act
var result = _generator.GenerateEntityRelationshipDiagram(query, "ER Test");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("### ER Test"));
}
}
@@ -0,0 +1,319 @@
using Strata.SqlTools.Breakdowns.PostgreSql;
using Strata.SqlTools.Markdown.PostgreSql;
namespace Strata.SqlTools.Markdown.Tests.PostgreSql;
[TestFixture]
public class QueryBreakdownCollectionGeneratorTests
{
private QueryBreakdownCollection _collection = null!;
[SetUp]
public void Setup()
{
_collection = new QueryBreakdownCollection();
}
[Test]
public void GenerateCollectionReport_WithTitle_IncludesTitle()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name", "users"));
var title = "PostgreSQL Query Report";
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection, title);
// Assert
Assert.That(report, Does.Contain(title));
Assert.That(report, Does.Contain("# PostgreSQL Query Report"));
}
[Test]
public void GenerateCollectionReport_WithMultipleQueries_GeneratesCompleteReport()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name, email", "users"));
_collection.Add(new QueryBreakdown("order_id, amount", "orders"));
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Collection Summary"));
Assert.That(report, Does.Contain("Parameter Analysis"));
Assert.That(report, Does.Contain("Query Composition Report"));
}
[Test]
public void GenerateCollectionSummary_WithQueries_IncludesSummaryMetrics()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name, email", "users"));
_collection.Add(new QueryBreakdown("order_id, amount", "orders"));
// Act
var summary = QueryBreakdownCollectionGenerator.GenerateCollectionSummary(_collection);
// Assert
Assert.That(summary, Does.Contain("Collection Summary"));
Assert.That(summary, Does.Contain("Total Queries"));
Assert.That(summary, Does.Contain("2"));
}
[Test]
public void GenerateParameterAnalysis_NoParameters_IndicatesNoParameters()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name", "users"));
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateParameterAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Parameters"));
Assert.That(analysis, Does.Contain("No parameters"));
}
[Test]
public void GenerateParameterAnalysis_WithPositionalParameters_ShowsPostgreSqlSyntax()
{
// Arrange - PostgreSQL positional parameters $1, $2
var query = new QueryBreakdown("id, name", "users", "id = $1");
query.Parameters["1"] = 123;
_collection.Add(query);
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateParameterAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Parameter Analysis"));
Assert.That(analysis, Does.Contain("$1"));
Assert.That(analysis, Does.Contain("Parameter Dependency Diagram"));
}
[Test]
public void GenerateParameterAnalysis_WithNamedParameters_ShowsColonSyntax()
{
// Arrange - PostgreSQL named parameters with :
var query = new QueryBreakdown("id, name", "users", "id = :userId");
query.Parameters["userId"] = 123;
_collection.Add(query);
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateParameterAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Parameter Analysis"));
Assert.That(analysis, Does.Contain(":userId"));
Assert.That(analysis, Does.Contain("Parameter Dependency Diagram"));
}
[Test]
public void GenerateParameterDependencyDiagram_WithParameters_GeneratesMermaidDiagram()
{
// Arrange
var query1 = new QueryBreakdown("id, name", "users", "id = $1");
var query2 = new QueryBreakdown("order_id", "orders", "user_id = $1");
query1.Parameters["1"] = 123;
query2.Parameters["1"] = 123;
_collection.Add(query1);
_collection.Add(query2);
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateParameterDependencyDiagram(_collection);
// Assert
Assert.That(diagram, Does.Contain("```mermaid"));
Assert.That(diagram, Does.Contain("graph TD"));
Assert.That(diagram, Does.Contain("$1"));
Assert.That(diagram, Does.Contain("Query #0"));
Assert.That(diagram, Does.Contain("Query #1"));
Assert.That(diagram, Does.Contain("```"));
}
[Test]
public void GenerateQueryCompositionReport_WithQueries_IncludesAllQueryDetails()
{
// Arrange
var query = new QueryBreakdown("id, name", "users", "id > 10");
query.GroupByClause.Clause = "region";
query.OrderByClause.Clause = "name ASC";
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Query Composition Report"));
Assert.That(report, Does.Contain("Query #0"));
Assert.That(report, Does.Contain("SELECT Clause"));
Assert.That(report, Does.Contain("✓ Yes"));
Assert.That(report, Does.Contain("WHERE Clause"));
}
[Test]
public void GenerateQueryCompositionReport_WithCTEs_IncludesCTEList()
{
// Arrange
var query = new QueryBreakdown("id, name", "users");
var cteQuery = new QueryBreakdown("id, name", "archive_users");
query.AddWithClause("active_users", cteQuery);
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("CTEs Defined"));
Assert.That(report, Does.Contain("active_users"));
}
[Test]
public void GenerateQueryCompositionReport_WithParameters_IncludesParameterValues()
{
// Arrange - PostgreSQL positional parameters
var query = new QueryBreakdown("id, name", "users", "id = $1 AND status = $2");
query.Parameters["1"] = 123;
query.Parameters["2"] = "Active";
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Parameters Used"));
Assert.That(report, Does.Contain("$1"));
Assert.That(report, Does.Contain("$2"));
Assert.That(report, Does.Contain("123"));
Assert.That(report, Does.Contain("Active"));
}
[Test]
public void GenerateBatchFlowDiagram_WithQueries_GeneratesFlowDiagram()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name", "users"));
_collection.Add(new QueryBreakdown("order_id", "orders"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection);
// Assert
Assert.That(diagram, Does.Contain("```mermaid"));
Assert.That(diagram, Does.Contain("flowchart TD"));
Assert.That(diagram, Does.Contain("Batch Start"));
Assert.That(diagram, Does.Contain("Query 0"));
Assert.That(diagram, Does.Contain("Query 1"));
Assert.That(diagram, Does.Contain("Batch Complete"));
Assert.That(diagram, Does.Contain("```"));
}
[Test]
public void GenerateBatchFlowDiagram_WithTransaction_IncludesBeginCommit()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name", "users"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection, includeTransaction: true);
// Assert
Assert.That(diagram, Does.Contain("BEGIN"));
Assert.That(diagram, Does.Contain("COMMIT"));
}
[Test]
public void GenerateBatchFlowDiagram_WithQueries_ValidatesProperConnections()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name", "users"));
_collection.Add(new QueryBreakdown("order_id", "orders"));
_collection.Add(new QueryBreakdown("product_id", "products"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection);
// Assert
// Verify proper connections between nodes
Assert.That(diagram, Does.Contain("Start([Batch Start]) --> node0")); // Start connects to first node
Assert.That(diagram, Does.Contain("node0[\"Query 0\"] --> node1")); // Query 0 to Query 1
Assert.That(diagram, Does.Contain("node1[\"Query 1\"] --> node2")); // Query 1 to Query 2
Assert.That(diagram, Does.Contain("node2[\"Query 2\"] --> End")); // Last query connects to End
// Verify no dangling nodes
Assert.That(diagram, Does.Not.Contain("node3")); // No extra unconnected node
}
[Test]
public void GenerateBatchFlowDiagram_WithTransaction_ValidatesProperConnections()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name", "users"));
_collection.Add(new QueryBreakdown("order_id", "orders"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection, includeTransaction: true);
// Assert
// Verify proper transaction flow
Assert.That(diagram, Does.Contain("Start([Batch Start]) --> node0")); // Start connects to BEGIN
Assert.That(diagram, Does.Contain("node0[\"BEGIN\"]")); // BEGIN node
Assert.That(diagram, Does.Contain("node0 --> node1")); // BEGIN to first query
Assert.That(diagram, Does.Contain("node1[\"Query 0\"] --> node2")); // Query 0 to Query 1
Assert.That(diagram, Does.Contain("node2[\"Query 1\"] --> node3")); // Last query to COMMIT
Assert.That(diagram, Does.Contain("node3[\"COMMIT\"]")); // COMMIT node
Assert.That(diagram, Does.Contain("node3 --> End")); // COMMIT to End
}
[Test]
public void GenerateCollectionReport_EmptyCollection_GeneratesValidMarkdown()
{
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Collection Summary"));
Assert.That(report, Does.Contain("Parameter Analysis"));
Assert.That(report, Does.Contain("Total Queries"));
Assert.That(report, Does.Contain("0"));
}
[Test]
public void GenerateCollectionReport_WithMultipleQueries_ContainsAllSections()
{
// Arrange
_collection.Add(new QueryBreakdown("id, name", "users"));
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("## Collection Summary"));
Assert.That(report, Does.Contain("## Parameter Analysis"));
Assert.That(report, Does.Contain("## Query Composition Report"));
}
[Test]
public void GenerateBatchFlowDiagram_EmptyCollection_GeneratesValidDiagram()
{
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection);
// Assert
Assert.That(diagram, Does.Contain("```mermaid"));
Assert.That(diagram, Does.Contain("flowchart TD"));
Assert.That(diagram, Does.Contain("Batch Start"));
Assert.That(diagram, Does.Contain("Batch Complete"));
}
[Test]
public void GenerateBatchFlowDiagram_EmptyCollectionWithTransaction_IncludesTransactionNodes()
{
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection, includeTransaction: true);
// Assert
Assert.That(diagram, Does.Contain("BEGIN"));
Assert.That(diagram, Does.Contain("COMMIT"));
}
}
@@ -0,0 +1,308 @@
using Strata.SqlTools.Breakdowns.PostgreSql;
using Strata.SqlTools.Markdown.PostgreSql;
namespace Strata.SqlTools.Markdown.Tests.PostgreSql;
[TestFixture]
public class QueryBreakdownGeneratorTests
{
private QueryBreakdownGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new QueryBreakdownGenerator();
}
[Test]
public void GenerateMermaidDiagram_SimplePostgreSqlQuery_GeneratesValidMermaid()
{
// Arrange - Using PostgreSQL syntax with lowercase identifiers
var query = new QueryBreakdown("user_id, user_name", "users");
// Act
var result = _generator.GenerateMermaidDiagram(query, "Simple User Query");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
Assert.That(result, Does.Contain("### Simple User Query"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateMermaidDiagram_WithPositionalParameters_HandlesParameters()
{
// Arrange - PostgreSQL uses $1, $2 syntax for positional parameters
var query = QueryBreakdown.Parse(@"
SELECT user_id, user_name, status
FROM users
WHERE user_id = $1
AND status = $2
");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
}
[Test]
public void GenerateMermaidDiagram_WithNamedParameters_HandlesColonSyntax()
{
// Arrange - PostgreSQL also supports :parameter syntax
var query = QueryBreakdown.Parse(@"
SELECT user_id, user_name, email
FROM users
WHERE user_id = :userId
AND status = :status
");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
}
[Test]
public void GenerateMermaidDiagram_WithPostgreSqlCTE_ShowsWithClause()
{
// Arrange - PostgreSQL CTE with lowercase naming
var query = QueryBreakdown.Parse(@"
WITH active_users AS (
SELECT user_id, user_name
FROM users
WHERE status = 'ACTIVE'
)
SELECT u.user_id, u.user_name, COUNT(o.order_id) AS order_count
FROM active_users u
JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.user_name
");
// Act
var result = _generator.GenerateMermaidDiagram(query, "Users with Orders");
// Assert
Assert.That(result, Does.Contain("### Users with Orders"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
Assert.That(result, Does.Contain("GROUP BY"));
}
[Test]
public void GenerateMermaidDiagram_WithLimit_HandlesLimitClause()
{
// Arrange - PostgreSQL LIMIT clause
var query = QueryBreakdown.Parse(@"
SELECT user_id, user_name, created_at
FROM users
ORDER BY created_at DESC
LIMIT 10
");
// Act
var result = _generator.GenerateMermaidDiagram(query, "Recent Users");
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("ORDER BY"));
Assert.That(result, Does.Contain("```mermaid"));
}
[Test]
public void GenerateMermaidDiagram_WithLimitOffset_HandlesPagination()
{
// Arrange - PostgreSQL LIMIT/OFFSET for pagination
var query = QueryBreakdown.Parse(@"
SELECT product_id, product_name, price
FROM products
ORDER BY product_name
LIMIT 20 OFFSET 40
");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("ORDER BY"));
Assert.That(result, Does.Contain("FROM"));
}
[Test]
public void GenerateMermaidDiagram_WithDoubleQuotedIdentifiers_HandlesPostgreSqlQuoting()
{
// Arrange - PostgreSQL uses "identifier" for case-sensitive names
var query = QueryBreakdown.Parse(@"
SELECT ""userId"", ""userName"", ""emailAddress""
FROM ""Users""
WHERE ""status"" = 'ACTIVE'
");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("FROM"));
}
[Test]
public void GenerateMermaidDiagram_ComplexPostgreSqlQuery_GeneratesCompleteDiagram()
{
// Arrange - Complex PostgreSQL query with multiple clauses
var query = QueryBreakdown.Parse(@"
WITH sales_data AS (
SELECT
region,
product_id,
SUM(sales_amount) AS total_sales,
COUNT(*) AS order_count
FROM sales
WHERE sale_date >= $1 AND sale_date <= $2
GROUP BY region, product_id
)
SELECT
sd.region,
p.product_name,
sd.total_sales,
sd.order_count
FROM sales_data sd
JOIN products p ON sd.product_id = p.product_id
WHERE sd.total_sales > $3
ORDER BY sd.total_sales DESC
");
// Act
var result = _generator.GenerateMermaidDiagram(query, "Regional Sales Analysis");
// Assert
Assert.That(result, Does.Contain("### Regional Sales Analysis"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("ORDER BY"));
}
[Test]
public void GenerateMermaidDiagram_WithGroupByAndHaving_IncludesBothClauses()
{
// Arrange
var query = QueryBreakdown.Parse(@"
SELECT category, COUNT(*) AS product_count, AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING COUNT(*) > 10 AND AVG(price) < 100
");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("GROUP BY"));
Assert.That(result, Does.Contain("HAVING"));
}
[Test]
public void GenerateMermaidDiagram_NullTitle_GeneratesWithoutTitle()
{
// Arrange
var query = new QueryBreakdown("id", "orders");
// Act
var result = _generator.GenerateMermaidDiagram(query, null);
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
}
[Test]
public void GenerateMermaidDiagram_EmptyTitle_GeneratesWithoutTitle()
{
// Arrange
var query = new QueryBreakdown("order_id", "orders");
// Act
var result = _generator.GenerateMermaidDiagram(query, "");
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("SELECT"));
}
[Test]
public void GenerateMermaidDiagram_PostgreSqlTypeCasting_PreservesSyntax()
{
// Arrange - PostgreSQL :: casting syntax
var query = QueryBreakdown.Parse(@"
SELECT
order_id::VARCHAR AS order_code,
amount::DECIMAL(10,2) AS formatted_amount,
create_date::TIMESTAMP AS created_at
FROM orders
");
// Act
var result = _generator.GenerateMermaidDiagram(query, "Type Cast Example");
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("### Type Cast Example"));
}
[Test]
public void GenerateMermaidDiagram_PostgreSqlJsonOperators_HandlesJsonNotation()
{
// Arrange - PostgreSQL JSON operators (-> for JSON object, ->> for text)
var query = QueryBreakdown.Parse(@"
SELECT
json_data->>'name' AS name,
json_data->'address'->>'city' AS city,
(json_data->>'age')::INTEGER AS age
FROM user_json
");
// Act
var result = _generator.GenerateMermaidDiagram(query, "JSON Data");
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("```mermaid"));
}
[Test]
public void GenerateMermaidDiagram_WithDistinct_HandlesDistinctClause()
{
// Arrange - PostgreSQL DISTINCT
var query = QueryBreakdown.Parse(@"
SELECT DISTINCT region, product_category
FROM sales
ORDER BY region, product_category
");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("ORDER BY"));
}
}
@@ -0,0 +1,179 @@
using Strata.SqlTools.Breakdowns.PostgreSql;
using Strata.SqlTools.Markdown.PostgreSql;
namespace Strata.SqlTools.Markdown.Tests.PostgreSql;
[TestFixture]
public class SqlStatementGeneratorTests
{
private SqlStatementGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new SqlStatementGenerator();
}
[Test]
public void GenerateSequenceDiagram_PostgreSqlQuery_GeneratesValidDiagram()
{
// Arrange
var query = QueryBreakdown.Parse(@"
SELECT user_id, user_name, email
FROM users
WHERE status = 'ACTIVE'
");
// Act
var result = _generator.GenerateSequenceDiagram(query, "User Query Flow");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("### User Query Flow"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateSequenceDiagram_WithPostgreSqlCTE_ShowsMainStatement()
{
// Arrange
var query = QueryBreakdown.Parse(@"
WITH active_customers AS (
SELECT customer_id, customer_name
FROM customers
WHERE status = 'ACTIVE'
)
SELECT c.customer_id, c.customer_name, COUNT(o.order_id) AS order_count
FROM active_customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
");
// Act
var result = _generator.GenerateSequenceDiagram(query);
// Assert
Assert.That(result, Does.Contain("Execute Main Statement"));
Assert.That(result, Does.Contain("activate DB"));
Assert.That(result, Does.Contain("deactivate DB"));
}
[Test]
public void GenerateSequenceDiagram_ComplexPostgreSqlQuery_GeneratesSequenceDiagram()
{
// Arrange
var query = QueryBreakdown.Parse(@"
SELECT region, product, SUM(sales) AS total_sales
FROM sales_data
WHERE sale_date >= $1
GROUP BY region, product
HAVING SUM(sales) > 1000
ORDER BY total_sales DESC
");
// Act
var result = _generator.GenerateSequenceDiagram(query, "Sales Analysis");
// Assert
Assert.That(result, Does.Contain("### Sales Analysis"));
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("Execute Main Statement"));
Assert.That(result, Does.Contain("activate DB"));
Assert.That(result, Does.Contain("Return Results"));
}
[Test]
public void GenerateEntityRelationshipDiagram_PostgreSqlTables_GeneratesERDiagram()
{
// Arrange - PostgreSQL typically uses lowercase table names
var tables = new[] { "customers", "orders", "order_items", "products" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables, "E-Commerce Schema");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("### E-Commerce Schema"));
Assert.That(result, Does.Contain("customers"));
Assert.That(result, Does.Contain("orders"));
Assert.That(result, Does.Contain("order_items"));
Assert.That(result, Does.Contain("products"));
}
[Test]
public void GenerateEntityRelationshipDiagram_WithDoubleQuotedNames_HandlesQuotedIdentifiers()
{
// Arrange - PostgreSQL case-sensitive identifiers
var tables = new[] { "\"Customers\"", "\"Orders\"", "\"OrderItems\"" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables);
// Assert
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateEntityRelationshipDiagram_WithSchemaQualifiedNames_HandlesSchemaDotTable()
{
// Arrange - PostgreSQL schema.table qualified names
var tables = new[] { "public.users", "sales.orders", "inventory.products" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables);
// Assert
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateEntityRelationshipDiagram_SingleTable_GeneratesDiagram()
{
// Arrange
var tables = new[] { "users" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables);
// Assert
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("users"));
}
[Test]
public void GenerateSequenceDiagram_NullTitle_GeneratesWithoutTitle()
{
// Arrange
var query = new QueryBreakdown("order_id", "orders");
// Act
var result = _generator.GenerateSequenceDiagram(query, null);
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("sequenceDiagram"));
}
[Test]
public void GenerateSequenceDiagram_WithPositionalParameters_HandlesParametrizedQuery()
{
// Arrange - PostgreSQL positional parameters $1, $2
var query = QueryBreakdown.Parse(@"
SELECT product_id, product_name, price
FROM products
WHERE category_id = $1 AND price > $2
ORDER BY price DESC
");
// Act
var result = _generator.GenerateSequenceDiagram(query);
// Assert
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("Execute Main Statement"));
}
}
@@ -0,0 +1,327 @@
using Strata.SqlTools.Breakdowns.Snowflake;
using Strata.SqlTools.Markdown.Snowflake;
namespace Strata.SqlTools.Markdown.Tests.Snowflake;
[TestFixture]
public class QueryBreakdownCollectionGeneratorTests
{
private QueryBreakdownCollection _collection = null!;
[SetUp]
public void Setup()
{
_collection = new QueryBreakdownCollection();
}
[Test]
public void GenerateCollectionReport_WithTitle_IncludesTitle()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
var title = "Snowflake Query Report";
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection, title);
// Assert
Assert.That(report, Does.Contain(title));
Assert.That(report, Does.Contain("# Snowflake Query Report"));
}
[Test]
public void GenerateCollectionReport_WithMultipleQueries_GeneratesCompleteReport()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name, Email", "Users"));
_collection.Add(new QueryBreakdown("OrderID, Amount", "Orders"));
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Collection Summary"));
Assert.That(report, Does.Contain("Snowflake Features"));
Assert.That(report, Does.Contain("Parameter Analysis"));
Assert.That(report, Does.Contain("Query Composition Report"));
}
[Test]
public void GenerateCollectionSummary_WithQueries_IncludesSummaryMetrics()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name, Email", "Users"));
_collection.Add(new QueryBreakdown("OrderID, Amount", "Orders"));
// Act
var summary = QueryBreakdownCollectionGenerator.GenerateCollectionSummary(_collection);
// Assert
Assert.That(summary, Does.Contain("Collection Summary"));
Assert.That(summary, Does.Contain("Total Queries"));
Assert.That(summary, Does.Contain("2"));
}
[Test]
public void GenerateSnowflakeFeaturesAnalysis_WithStageReferences_ReportsUsage()
{
// Arrange
var stageQuery = new QueryBreakdown("$1, $2", "@mystage/data.csv");
_collection.Add(stageQuery);
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateSnowflakeFeaturesAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Snowflake Features"));
Assert.That(analysis, Does.Contain("Stage References"));
Assert.That(analysis, Does.Contain("✓ Yes"));
}
[Test]
public void GenerateSnowflakeFeaturesAnalysis_WithSemiStructuredData_ReportsUsage()
{
// Arrange
var jsonQuery = new QueryBreakdown("JSON_EXTRACT(data, '$.id')", "raw_data");
_collection.Add(jsonQuery);
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateSnowflakeFeaturesAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Semi-Structured Data"));
Assert.That(analysis, Does.Contain("✓ Yes"));
}
[Test]
public void GenerateParameterAnalysis_NoParameters_IndicatesNoParameters()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateParameterAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Parameters"));
Assert.That(analysis, Does.Contain("No parameters"));
}
[Test]
public void GenerateParameterAnalysis_WithParameters_ShowsSnowflakeSyntax()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users", "ID = :UserId");
query.Parameters["UserId"] = 123;
_collection.Add(query);
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateParameterAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Parameter Analysis"));
Assert.That(analysis, Does.Contain(":UserId"));
Assert.That(analysis, Does.Contain("@UserId")); // Snowflake supports both
Assert.That(analysis, Does.Contain("Parameter Dependency Diagram"));
}
[Test]
public void GenerateParameterDependencyDiagram_WithParameters_GeneratesMermaidDiagram()
{
// Arrange
var query1 = new QueryBreakdown("ID, Name", "Users", "ID = :UserId");
var query2 = new QueryBreakdown("OrderID", "Orders", "UserID = :UserId");
query1.Parameters["UserId"] = 123;
query2.Parameters["UserId"] = 123;
_collection.Add(query1);
_collection.Add(query2);
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateParameterDependencyDiagram(_collection);
// Assert
Assert.That(diagram, Does.Contain("```mermaid"));
Assert.That(diagram, Does.Contain("graph TD"));
Assert.That(diagram, Does.Contain(":UserId"));
Assert.That(diagram, Does.Contain("Query #0"));
Assert.That(diagram, Does.Contain("Query #1"));
Assert.That(diagram, Does.Contain("```"));
}
[Test]
public void GenerateQueryCompositionReport_WithQueries_IncludesAllQueryDetails()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users", "ID > 10");
query.GroupByClause.Clause = "Region";
query.OrderByClause.Clause = "Name ASC";
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Query Composition Report"));
Assert.That(report, Does.Contain("Query #0"));
Assert.That(report, Does.Contain("SELECT Clause"));
Assert.That(report, Does.Contain("✓ Yes"));
Assert.That(report, Does.Contain("WHERE Clause"));
}
[Test]
public void GenerateQueryCompositionReport_WithSnowflakeFeatures_IncludesFeatureInfo()
{
// Arrange
var jsonQuery = new QueryBreakdown("JSON_EXTRACT(data, '$.id')", "raw_data");
var stageQuery = new QueryBreakdown("$1, $2", "@mystage/data.csv");
_collection.Add(jsonQuery);
_collection.Add(stageQuery);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Snowflake Features"));
Assert.That(report, Does.Contain("Semi-Structured Data"));
Assert.That(report, Does.Contain("Stage References"));
}
[Test]
public void GenerateQueryCompositionReport_WithCTEs_IncludesCTEList()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users");
var cteQuery = new QueryBreakdown("ID, Name", "ArchiveUsers");
query.AddWithClause("ActiveUsers", cteQuery);
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("CTEs Defined"));
Assert.That(report, Does.Contain("ActiveUsers"));
}
[Test]
public void GenerateQueryCompositionReport_WithParameters_IncludesParameterValues()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users", "ID = :UserId AND Status = :Status");
query.Parameters["UserId"] = 123;
query.Parameters["Status"] = "Active";
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Parameters Used"));
Assert.That(report, Does.Contain(":UserId"));
Assert.That(report, Does.Contain(":Status"));
Assert.That(report, Does.Contain("123"));
Assert.That(report, Does.Contain("Active"));
}
[Test]
public void GenerateBatchFlowDiagram_WithQueries_GeneratesFlowDiagram()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
_collection.Add(new QueryBreakdown("OrderID", "Orders"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection);
// Assert
Assert.That(diagram, Does.Contain("```mermaid"));
Assert.That(diagram, Does.Contain("flowchart TD"));
Assert.That(diagram, Does.Contain("Batch Start"));
Assert.That(diagram, Does.Contain("Query 0"));
Assert.That(diagram, Does.Contain("Query 1"));
Assert.That(diagram, Does.Contain("Batch Complete"));
Assert.That(diagram, Does.Contain("```"));
}
[Test]
public void GenerateBatchFlowDiagram_WithSessionSetup_IncludesSessionSetup()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection, includeSessionSetup: true);
// Assert
Assert.That(diagram, Does.Contain("Session Setup"));
}
[Test]
public void GenerateBatchFlowDiagram_WithQueries_ValidatesProperConnections()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
_collection.Add(new QueryBreakdown("OrderID", "Orders"));
_collection.Add(new QueryBreakdown("ProductID", "Products"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection);
// Assert
// Verify proper connections between nodes
Assert.That(diagram, Does.Contain("Start([Batch Start]) --> node0")); // Start connects to first node
Assert.That(diagram, Does.Contain("node0[\"Query 0\"] --> node1")); // Query 0 to Query 1
Assert.That(diagram, Does.Contain("node1[\"Query 1\"] --> node2")); // Query 1 to Query 2
Assert.That(diagram, Does.Contain("node2[\"Query 2\"] --> End")); // Last query connects to End
// Verify no dangling nodes
Assert.That(diagram, Does.Not.Contain("node3")); // No extra unconnected node
}
[Test]
public void GenerateBatchFlowDiagram_WithSessionSetup_ValidatesProperConnections()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
_collection.Add(new QueryBreakdown("OrderID", "Orders"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection, includeSessionSetup: true);
// Assert
// Verify proper session setup flow
Assert.That(diagram, Does.Contain("Start([Batch Start]) --> node0")); // Start connects to Session Setup
Assert.That(diagram, Does.Contain("node0[\"Session Setup\"]")); // Session Setup node
Assert.That(diagram, Does.Contain("node0 --> node1")); // Session Setup to first query
Assert.That(diagram, Does.Contain("node1[\"Query 0\"] --> node2")); // Query 0 to Query 1
Assert.That(diagram, Does.Contain("node2[\"Query 1\"] --> End")); // Last query to End
}
[Test]
public void GenerateCollectionReport_EmptyCollection_GeneratesValidMarkdown()
{
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Collection Summary"));
Assert.That(report, Does.Contain("Snowflake Features"));
Assert.That(report, Does.Contain("Total Queries"));
Assert.That(report, Does.Contain("0"));
}
[Test]
public void GenerateCollectionReport_WithSnowflakeSpecific_ContainsAllSections()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("## Collection Summary"));
Assert.That(report, Does.Contain("## Snowflake Features"));
Assert.That(report, Does.Contain("## Parameter Analysis"));
Assert.That(report, Does.Contain("## Query Composition Report"));
}
}
@@ -0,0 +1,251 @@
using Strata.SqlTools.Breakdowns.Snowflake;
using Strata.SqlTools.Markdown.Snowflake;
namespace Strata.SqlTools.Markdown.Tests.Snowflake;
[TestFixture]
public class QueryBreakdownGeneratorTests
{
private QueryBreakdownGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new QueryBreakdownGenerator();
}
[Test]
public void GenerateMermaidDiagram_SimpleSnowflakeQuery_GeneratesValidMermaid()
{
// Arrange - Using Snowflake syntax with uppercase identifiers
var query = new QueryBreakdown("USER_ID, USER_NAME", "USERS");
// Act
var result = _generator.GenerateMermaidDiagram(query, "Simple User Query");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
Assert.That(result, Does.Contain("### Simple User Query"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateMermaidDiagram_WithSnowflakeColonParameters_HandlesParameters()
{
// Arrange - Snowflake uses :parameter syntax
var query = QueryBreakdown.Parse(@"
SELECT USER_ID, USER_NAME, STATUS
FROM USERS
WHERE USER_ID = :userId
AND STATUS = :status
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
}
[Test]
public void GenerateMermaidDiagram_WithSnowflakeCTE_ShowsWithClause()
{
// Arrange - Snowflake CTE with uppercase naming
var query = QueryBreakdown.Parse(@"
WITH ACTIVE_USERS AS (
SELECT USER_ID, USER_NAME
FROM USERS
WHERE STATUS = 'ACTIVE'
)
SELECT U.USER_ID, U.USER_NAME, COUNT(O.ORDER_ID) AS ORDER_COUNT
FROM ACTIVE_USERS U
JOIN ORDERS O ON U.USER_ID = O.USER_ID
GROUP BY U.USER_ID, U.USER_NAME
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query, "Users with Orders");
// Assert
Assert.That(result, Does.Contain("WITH Clause"));
Assert.That(result, Does.Contain("ACTIVE_USERS"));
Assert.That(result, Does.Contain("GROUP BY"));
}
[Test]
public void GenerateMermaidDiagram_WithSnowflakeQualify_HandlesQualifyClause()
{
// Arrange - Snowflake QUALIFY clause (window function filtering)
var query = QueryBreakdown.Parse(@"
SELECT
CUSTOMER_ID,
ORDER_DATE,
ORDER_AMOUNT,
ROW_NUMBER() OVER (PARTITION BY CUSTOMER_ID ORDER BY ORDER_DATE DESC) AS RN
FROM ORDERS
QUALIFY RN = 1
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query, "Latest Orders per Customer");
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("```mermaid"));
}
[Test]
public void GenerateMermaidDiagram_WithDoubleQuotedIdentifiers_HandlesSnowflakeQuoting()
{
// Arrange - Snowflake uses "identifier" for case-sensitive names
var query = QueryBreakdown.Parse(@"
SELECT ""userId"", ""userName"", ""emailAddress""
FROM ""Users""
WHERE ""status"" = 'ACTIVE'
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("FROM"));
}
[Test]
public void GenerateMermaidDiagram_ComplexSnowflakeQuery_GeneratesCompleteDiagram()
{
// Arrange - Complex Snowflake query with multiple clauses
var query = QueryBreakdown.Parse(@"
WITH SALES_DATA AS (
SELECT
REGION,
PRODUCT_ID,
SUM(SALES_AMOUNT) AS TOTAL_SALES,
COUNT(*) AS ORDER_COUNT
FROM SALES
WHERE SALE_DATE >= :startDate AND SALE_DATE <= :endDate
GROUP BY REGION, PRODUCT_ID
)
SELECT
SD.REGION,
P.PRODUCT_NAME,
SD.TOTAL_SALES,
SD.ORDER_COUNT
FROM SALES_DATA SD
JOIN PRODUCTS P ON SD.PRODUCT_ID = P.PRODUCT_ID
WHERE SD.TOTAL_SALES > :minSales
ORDER BY SD.TOTAL_SALES DESC
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query, "Regional Sales Analysis");
// Assert
Assert.That(result, Does.Contain("### Regional Sales Analysis"));
Assert.That(result, Does.Contain("WITH Clause"));
Assert.That(result, Does.Contain("SALES_DATA"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("ORDER BY"));
Assert.That(result, Does.Contain("flowchart TD"));
}
[Test]
public void GenerateMermaidDiagram_WithGroupByAndHaving_IncludesBothClauses()
{
// Arrange
var query = QueryBreakdown.Parse(@"
SELECT CATEGORY, COUNT(*) AS PRODUCT_COUNT, AVG(PRICE) AS AVG_PRICE
FROM PRODUCTS
GROUP BY CATEGORY
HAVING COUNT(*) > 10 AND AVG(PRICE) < 100
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("GROUP BY"));
Assert.That(result, Does.Contain("HAVING"));
}
[Test]
public void GenerateMermaidDiagram_NullTitle_GeneratesWithoutTitle()
{
// Arrange
var query = new QueryBreakdown("ID", "ORDERS");
// Act
var result = _generator.GenerateMermaidDiagram(query, null);
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
}
[Test]
public void GenerateMermaidDiagram_EmptyTitle_GeneratesWithoutTitle()
{
// Arrange
var query = new QueryBreakdown("ORDER_ID", "ORDERS");
// Act
var result = _generator.GenerateMermaidDiagram(query, "");
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("SELECT"));
}
[Test]
public void GenerateMermaidDiagram_SnowflakeTypeCasting_PreservesSyntax()
{
// Arrange - Snowflake :: casting syntax
var query = QueryBreakdown.Parse(@"
SELECT
ORDER_ID::VARCHAR AS ORDER_CODE,
AMOUNT::DECIMAL(10,2) AS FORMATTED_AMOUNT,
CREATE_DATE::TIMESTAMP AS CREATED_AT
FROM ORDERS
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query, "Type Cast Example");
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("### Type Cast Example"));
}
[Test]
public void GenerateMermaidDiagram_SnowflakeJsonPath_HandlesJsonNotation()
{
// Arrange - Snowflake JSON path notation (: for path access)
var query = QueryBreakdown.Parse(@"
SELECT
JSON_DATA:name::STRING AS NAME,
JSON_DATA:address.city::STRING AS CITY,
JSON_DATA:age::NUMBER AS AGE
FROM USER_JSON
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateMermaidDiagram(query, "JSON Data");
// Assert
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("```mermaid"));
}
}
@@ -0,0 +1,146 @@
using Strata.SqlTools.Breakdowns.Snowflake;
using Strata.SqlTools.Markdown.Snowflake;
namespace Strata.SqlTools.Markdown.Tests.Snowflake;
[TestFixture]
public class SqlStatementGeneratorTests
{
private SqlStatementGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new SqlStatementGenerator();
}
[Test]
public void GenerateSequenceDiagram_SnowflakeQuery_GeneratesValidDiagram()
{
// Arrange
var query = QueryBreakdown.Parse(@"
SELECT USER_ID, USER_NAME, EMAIL
FROM USERS
WHERE STATUS = 'ACTIVE'
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateSequenceDiagram(query, "User Query Flow");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("### User Query Flow"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateSequenceDiagram_WithSnowflakeCTE_ShowsMainStatement()
{
// Arrange
var query = QueryBreakdown.Parse(@"
WITH ACTIVE_CUSTOMERS AS (
SELECT CUSTOMER_ID, CUSTOMER_NAME
FROM CUSTOMERS
WHERE STATUS = 'ACTIVE'
)
SELECT C.CUSTOMER_ID, C.CUSTOMER_NAME, COUNT(O.ORDER_ID) AS ORDER_COUNT
FROM ACTIVE_CUSTOMERS C
LEFT JOIN ORDERS O ON C.CUSTOMER_ID = O.CUSTOMER_ID
GROUP BY C.CUSTOMER_ID, C.CUSTOMER_NAME
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateSequenceDiagram(query);
// Assert
Assert.That(result, Does.Contain("Execute Main Statement"));
Assert.That(result, Does.Contain("activate DB"));
Assert.That(result, Does.Contain("deactivate DB"));
}
[Test]
public void GenerateSequenceDiagram_ComplexSnowflakeQuery_GeneratesSequenceDiagram()
{
// Arrange
var query = QueryBreakdown.Parse(@"
SELECT REGION, PRODUCT, SUM(SALES) AS TOTAL_SALES
FROM SALES_DATA
WHERE SALE_DATE >= :startDate
GROUP BY REGION, PRODUCT
HAVING SUM(SALES) > 1000
ORDER BY TOTAL_SALES DESC
", isMicrosoftSql: false);
// Act
var result = _generator.GenerateSequenceDiagram(query, "Sales Analysis");
// Assert
Assert.That(result, Does.Contain("### Sales Analysis"));
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("Execute Main Statement"));
Assert.That(result, Does.Contain("activate DB"));
Assert.That(result, Does.Contain("Return Results"));
}
[Test]
public void GenerateEntityRelationshipDiagram_SnowflakeTables_GeneratesERDiagram()
{
// Arrange - Snowflake typically uses uppercase table names
var tables = new[] { "CUSTOMERS", "ORDERS", "ORDER_ITEMS", "PRODUCTS" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables, "E-Commerce Schema");
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("### E-Commerce Schema"));
Assert.That(result, Does.Contain("CUSTOMERS"));
Assert.That(result, Does.Contain("ORDERS"));
Assert.That(result, Does.Contain("ORDER_ITEMS"));
Assert.That(result, Does.Contain("PRODUCTS"));
}
[Test]
public void GenerateEntityRelationshipDiagram_WithDoubleQuotedNames_HandlesQuotedIdentifiers()
{
// Arrange - Snowflake case-sensitive identifiers
var tables = new[] { "\"customers\"", "\"orders\"", "\"orderItems\"" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables);
// Assert
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateEntityRelationshipDiagram_SingleTable_GeneratesDiagram()
{
// Arrange
var tables = new[] { "USERS" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables);
// Assert
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("USERS"));
}
[Test]
public void GenerateSequenceDiagram_NullTitle_GeneratesWithoutTitle()
{
// Arrange
var query = new QueryBreakdown("ORDER_ID", "ORDERS");
// Act
var result = _generator.GenerateSequenceDiagram(query, null);
// Assert
Assert.That(result, Does.Not.Contain("###"));
Assert.That(result, Does.Contain("sequenceDiagram"));
}
}
@@ -0,0 +1,285 @@
using Strata.SqlTools.Breakdowns.SqlServer;
using Strata.SqlTools.Markdown.SqlServer;
namespace Strata.SqlTools.Markdown.Tests.SqlServer;
[TestFixture]
public class QueryBreakdownCollectionGeneratorTests
{
private QueryBreakdownCollection _collection = null!;
[SetUp]
public void Setup()
{
_collection = new QueryBreakdownCollection();
}
[Test]
public void GenerateCollectionReport_WithTitle_IncludesTitle()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
var title = "User Query Report";
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection, title);
// Assert
Assert.That(report, Does.Contain(title));
Assert.That(report, Does.Contain("# User Query Report"));
}
[Test]
public void GenerateCollectionReport_WithMultipleQueries_GeneratesCompleteReport()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name, Email", "Users"));
_collection.Add(new QueryBreakdown("OrderID, Amount", "Orders"));
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Collection Summary"));
Assert.That(report, Does.Contain("Parameter Analysis"));
Assert.That(report, Does.Contain("Query Composition Report"));
Assert.That(report, Does.Contain("Total Queries"));
}
[Test]
public void GenerateCollectionSummary_WithQueries_IncludesSummaryMetrics()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name, Email", "Users"));
_collection.Add(new QueryBreakdown("OrderID, Amount", "Orders"));
// Act
var summary = QueryBreakdownCollectionGenerator.GenerateCollectionSummary(_collection);
// Assert
Assert.That(summary, Does.Contain("Collection Summary"));
Assert.That(summary, Does.Contain("Total Queries"));
Assert.That(summary, Does.Contain("2"));
Assert.That(summary, Does.Contain("Unique Tables"));
}
[Test]
public void GenerateParameterAnalysis_NoParameters_IndicatesNoParameters()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateParameterAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Parameters"));
Assert.That(analysis, Does.Contain("No parameters"));
}
[Test]
public void GenerateParameterAnalysis_WithParameters_IncludesParameterTable()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users", "ID = @UserId");
query.Parameters["UserId"] = 123;
_collection.Add(query);
// Act
var analysis = QueryBreakdownCollectionGenerator.GenerateParameterAnalysis(_collection);
// Assert
Assert.That(analysis, Does.Contain("Parameter Analysis"));
Assert.That(analysis, Does.Contain("@UserId"));
Assert.That(analysis, Does.Contain("Parameter Dependency Diagram"));
Assert.That(analysis, Does.Contain("```mermaid"));
}
[Test]
public void GenerateParameterDependencyDiagram_WithParameters_GeneratesMermaidDiagram()
{
// Arrange
var query1 = new QueryBreakdown("ID, Name", "Users", "ID = @UserId");
var query2 = new QueryBreakdown("OrderID", "Orders", "UserID = @UserId");
query1.Parameters["UserId"] = 123;
query2.Parameters["UserId"] = 123;
_collection.Add(query1);
_collection.Add(query2);
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateParameterDependencyDiagram(_collection);
// Assert
Assert.That(diagram, Does.Contain("```mermaid"));
Assert.That(diagram, Does.Contain("graph TD"));
Assert.That(diagram, Does.Contain("@UserId"));
Assert.That(diagram, Does.Contain("Query #0"));
Assert.That(diagram, Does.Contain("Query #1"));
Assert.That(diagram, Does.Contain("```"));
}
[Test]
public void GenerateQueryCompositionReport_WithQueries_IncludesAllQueryDetails()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users", "ID > 10");
query.GroupByClause.Clause = "Region";
query.OrderByClause.Clause = "Name ASC";
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Query Composition Report"));
Assert.That(report, Does.Contain("Query #0"));
Assert.That(report, Does.Contain("SELECT Clause"));
Assert.That(report, Does.Contain("✓ Yes"));
Assert.That(report, Does.Contain("WHERE Clause"));
Assert.That(report, Does.Contain("GROUP BY"));
Assert.That(report, Does.Contain("ORDER BY"));
}
[Test]
public void GenerateQueryCompositionReport_WithCTEs_IncludesCTEList()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users");
var cteQuery = new QueryBreakdown("ID, Name", "ArchiveUsers");
query.AddWithClause("ActiveUsers", cteQuery);
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("CTEs Defined"));
Assert.That(report, Does.Contain("ActiveUsers"));
}
[Test]
public void GenerateQueryCompositionReport_WithParameters_IncludesParameterValues()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users", "ID = @UserId AND Status = @Status");
query.Parameters["UserId"] = 123;
query.Parameters["Status"] = "Active";
_collection.Add(query);
// Act
var report = QueryBreakdownCollectionGenerator.GenerateQueryCompositionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Parameters Used"));
Assert.That(report, Does.Contain("@UserId"));
Assert.That(report, Does.Contain("@Status"));
Assert.That(report, Does.Contain("123"));
Assert.That(report, Does.Contain("Active"));
}
[Test]
public void GenerateBatchFlowDiagram_WithQueries_GeneratesFlowDiagram()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
_collection.Add(new QueryBreakdown("OrderID", "Orders"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection);
// Assert
Assert.That(diagram, Does.Contain("```mermaid"));
Assert.That(diagram, Does.Contain("flowchart TD"));
Assert.That(diagram, Does.Contain("Batch Start"));
Assert.That(diagram, Does.Contain("Query 0"));
Assert.That(diagram, Does.Contain("Query 1"));
Assert.That(diagram, Does.Contain("Batch Complete"));
Assert.That(diagram, Does.Contain("```"));
}
[Test]
public void GenerateBatchFlowDiagram_WithTransaction_IncludesTransactionStatements()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection, includeTransaction: true);
// Assert
Assert.That(diagram, Does.Contain("BEGIN TRANSACTION"));
Assert.That(diagram, Does.Contain("COMMIT TRANSACTION"));
}
[Test]
public void GenerateBatchFlowDiagram_WithQueries_ValidatesProperConnections()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
_collection.Add(new QueryBreakdown("OrderID", "Orders"));
_collection.Add(new QueryBreakdown("ProductID", "Products"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection);
// Assert
// Verify proper connections between nodes
Assert.That(diagram, Does.Contain("Start([Batch Start]) --> node0")); // Start connects to first node
Assert.That(diagram, Does.Contain("node0[\"Query 0\"] --> node1")); // Query 0 to Query 1
Assert.That(diagram, Does.Contain("node1[\"Query 1\"] --> node2")); // Query 1 to Query 2
Assert.That(diagram, Does.Contain("node2[\"Query 2\"] --> End")); // Last query connects to End
// Verify no dangling nodes
Assert.That(diagram, Does.Not.Contain("node3")); // No extra unconnected node
}
[Test]
public void GenerateBatchFlowDiagram_WithTransaction_ValidatesProperConnections()
{
// Arrange
_collection.Add(new QueryBreakdown("ID, Name", "Users"));
_collection.Add(new QueryBreakdown("OrderID", "Orders"));
// Act
var diagram = QueryBreakdownCollectionGenerator.GenerateBatchFlowDiagram(_collection, includeTransaction: true);
// Assert
// Verify proper transaction flow
Assert.That(diagram, Does.Contain("Start([Batch Start]) --> node0")); // Start connects to BEGIN TRANSACTION
Assert.That(diagram, Does.Contain("node0[\"BEGIN TRANSACTION\"]")); // BEGIN TRANSACTION node
Assert.That(diagram, Does.Contain("node0 --> node1")); // BEGIN to first query
Assert.That(diagram, Does.Contain("node1[\"Query 0\"] --> node2")); // Query 0 to Query 1
Assert.That(diagram, Does.Contain("node2[\"Query 1\"] --> node3")); // Query 1 to COMMIT
Assert.That(diagram, Does.Contain("node3[\"COMMIT TRANSACTION\"]")); // COMMIT node
Assert.That(diagram, Does.Contain("node3 --> End")); // COMMIT to End
}
[Test]
public void GenerateCollectionReport_EmptyCollection_GeneratesValidMarkdown()
{
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("Collection Summary"));
Assert.That(report, Does.Contain("Total Queries"));
Assert.That(report, Does.Contain("0"));
}
[Test]
public void GenerateCollectionReport_MultipleQueries_ContainsAllSections()
{
// Arrange
for (int i = 0; i < 3; i++)
{
_collection.Add(new QueryBreakdown($"Col{i}", $"Table{i}"));
}
// Act
var report = QueryBreakdownCollectionGenerator.GenerateCollectionReport(_collection);
// Assert
Assert.That(report, Does.Contain("## Collection Summary"));
Assert.That(report, Does.Contain("## Parameter Analysis"));
Assert.That(report, Does.Contain("## Query Composition Report"));
}
}
@@ -0,0 +1,154 @@
using Strata.SqlTools.Breakdowns.SqlServer;
using Strata.SqlTools.Markdown.SqlServer;
using Interfaces = Strata.SqlTools.SqlBreakdown.Interfaces;
namespace Strata.SqlTools.Markdown.Tests.SqlServer;
[TestFixture]
public class QueryBreakdownGeneratorTests
{
private QueryBreakdownGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new QueryBreakdownGenerator();
}
[Test]
public void GenerateMermaidDiagram_WithSimpleQuery_GeneratesFlowchart()
{
// Arrange
var query = new QueryBreakdown("ID, Name", "Users");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateMermaidDiagram_WithComplexQuery_IncludesAllClauses()
{
// Arrange
var query = new QueryBreakdown(
"u.ID, u.Name, COUNT(o.OrderID) as OrderCount",
"Users u JOIN Orders o ON u.ID = o.UserID",
"u.Active = 1"
);
query.GroupByClause.Clause = "u.ID, u.Name";
query.HavingClause.Clause = "COUNT(o.OrderID) > 5";
query.OrderByClause.Clause = "OrderCount DESC";
// Act
var result = _generator.GenerateMermaidDiagram(query, "User Orders Query");
// Assert
Assert.That(result, Does.Contain("### User Orders Query"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result, Does.Contain("FROM"));
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("GROUP BY"));
Assert.That(result, Does.Contain("HAVING"));
Assert.That(result, Does.Contain("ORDER BY"));
}
[Test]
public void GenerateMermaidDiagram_WithWhereClause_UsesDecisionShape()
{
// Arrange
var query = new QueryBreakdown("*", "Products", "Price > 100");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("WHERE"));
Assert.That(result, Does.Contain("{"));
Assert.That(result, Does.Contain("}"));
}
[Test]
public void GenerateMermaidDiagram_ParsedQuery_GeneratesDiagram()
{
// Arrange
var sql = @"
SELECT u.ID, u.Name, u.Email
FROM Users u
WHERE u.Active = 1
ORDER BY u.Name";
var query = QueryBreakdown.Parse(sql);
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Does.Contain("mermaid"));
Assert.That(result, Does.Contain("SELECT"));
Assert.That(result.Length, Is.GreaterThan(100));
}
[Test]
public void GenerateMermaidDiagram_WithTitle_IncludesTitle()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateMermaidDiagram(query, "Test Query");
// Assert
Assert.That(result, Does.StartWith("### Test Query"));
}
[Test]
public void GenerateMermaidDiagram_WithoutTitle_NoTitle()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Not.StartWith("###"));
Assert.That(result, Does.StartWith("```mermaid"));
}
[Test]
public void GenerateMermaidDiagram_WithLongClause_TruncatesText()
{
// Arrange
var longSelect = string.Join(", ", Enumerable.Range(1, 50).Select(i => $"Column{i}"));
var query = new QueryBreakdown(longSelect, "LargeTable");
// Act
var result = _generator.GenerateMermaidDiagram(query);
// Assert
Assert.That(result, Does.Contain("..."));
}
[Test]
public void GenerateMermaidDiagram_ISqlBreakdown_GeneratesSimpleDiagram()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
query.SetupClauses.Add("CREATE TEMP TABLE #Temp (ID INT)");
// Act
var result = _generator.GenerateMermaidDiagram((Interfaces.ISqlBreakdown)query);
// Assert
Assert.That(result, Does.Contain("mermaid"));
Assert.That(result, Does.Contain("flowchart TD"));
Assert.That(result, Does.Contain("Users"));
}
}
@@ -0,0 +1,151 @@
using Strata.SqlTools.Breakdowns.SqlServer;
using Strata.SqlTools.Markdown.SqlServer;
namespace Strata.SqlTools.Markdown.Tests.SqlServer;
[TestFixture]
[Ignore("These tests are designed to generate markdown files from SQL queries in the Queries directory. They are not meant to be run as part of regular unit testing, but can be executed manually when needed.")]
[Category("MarkdownGeneration")]
public class QueryMarkdownGenerationTests
{
private QueryBreakdownGenerator _generator = null!;
private string _queriesSourcePath = null!;
private string _markdownOutputPath = null!;
[SetUp]
public void Setup()
{
_generator = new QueryBreakdownGenerator();
// Get the solution root directory
var testDirectory = TestContext.CurrentContext.TestDirectory;
var solutionRoot = Directory.GetParent(testDirectory)?.Parent?.Parent?.Parent?.Parent?.FullName;
if (solutionRoot == null)
throw new InvalidOperationException("Could not determine solution root directory");
_queriesSourcePath = Path.Combine(solutionRoot, "Queries");
_markdownOutputPath = Path.Combine(solutionRoot, "docs", "queries");
// Ensure output directory exists
if (!Directory.Exists(_markdownOutputPath))
{
Directory.CreateDirectory(_markdownOutputPath);
}
}
[Test]
public void GenerateMarkdownForAllQueries_CreatesMarkdownFiles()
{
// Arrange
Assert.That(Directory.Exists(_queriesSourcePath), Is.True,
$"Queries directory not found at: {_queriesSourcePath}");
var sqlFiles = Directory.GetFiles(_queriesSourcePath, "*.sql");
Assert.That(sqlFiles, Is.Not.Empty, "No SQL files found in Queries directory");
int successCount = 0;
int skippedCount = 0;
// Act & Assert for each file
foreach (var sqlFile in sqlFiles)
{
var fileName = Path.GetFileNameWithoutExtension(sqlFile);
var sqlContent = File.ReadAllText(sqlFile);
try
{
// Parse the SQL query
var queryBreakdown = QueryBreakdown.Parse(sqlContent);
// Generate markdown with the filename as title
var markdown = _generator.GenerateMermaidDiagram(queryBreakdown, fileName);
// Verify markdown was generated
Assert.That(markdown, Is.Not.Null);
Assert.That(markdown, Does.Contain("```mermaid"));
Assert.That(markdown, Does.Contain(fileName));
// Write to output file
var outputFile = Path.Combine(_markdownOutputPath, $"{fileName}.md");
File.WriteAllText(outputFile, markdown);
// Verify file was created
Assert.That(File.Exists(outputFile), Is.True,
$"Markdown file was not created: {outputFile}");
TestContext.WriteLine($"Generated: {fileName}.md");
successCount++;
}
catch (FormatException ex)
{
// Skip files that can't be parsed (e.g., partial queries with only WITH clauses)
TestContext.WriteLine($"Skipped: {fileName}.sql - {ex.Message}");
skippedCount++;
}
}
TestContext.WriteLine($"Generated {successCount} markdown files, skipped {skippedCount} files in {_markdownOutputPath}");
Assert.That(successCount, Is.GreaterThan(0), "At least one markdown file should be generated");
}
[Test]
public void GenerateMarkdownForAllQueries_IncludesQuerySource()
{
// Arrange
var sqlFiles = Directory.GetFiles(_queriesSourcePath, "*.sql");
Assert.That(sqlFiles, Is.Not.Empty);
int successCount = 0;
// Act & Assert
foreach (var sqlFile in sqlFiles)
{
var fileName = Path.GetFileNameWithoutExtension(sqlFile);
var sqlContent = File.ReadAllText(sqlFile);
try
{
var queryBreakdown = QueryBreakdown.Parse(sqlContent);
var markdown = _generator.GenerateMermaidDiagram(queryBreakdown, fileName);
// Add source SQL to the markdown
var fullMarkdown = $"{markdown}\n\n## Source SQL\n\n```sql\n{sqlContent}\n```\n";
var outputFile = Path.Combine(_markdownOutputPath, $"{fileName}.md");
File.WriteAllText(outputFile, fullMarkdown);
// Verify the output includes both diagram and source
var writtenContent = File.ReadAllText(outputFile);
Assert.That(writtenContent, Does.Contain("```mermaid"));
Assert.That(writtenContent, Does.Contain("## Source SQL"));
Assert.That(writtenContent, Does.Contain(sqlContent));
successCount++;
}
catch (FormatException)
{
// Skip files that can't be parsed
TestContext.WriteLine($"Skipped: {fileName}.sql (could not parse)");
}
}
Assert.That(successCount, Is.GreaterThan(0), "At least one markdown file should be generated");
}
[Test]
public void AllQueriesDirectory_Exists()
{
// Verify the Queries directory exists
Assert.That(Directory.Exists(_queriesSourcePath), Is.True,
$"Queries directory should exist at: {_queriesSourcePath}");
}
[Test]
public void OutputDirectory_IsCreated()
{
// Verify the output directory was created
Assert.That(Directory.Exists(_markdownOutputPath), Is.True,
$"Output directory should exist at: {_markdownOutputPath}");
}
}
@@ -0,0 +1,124 @@
using Strata.SqlTools.Breakdowns.SqlServer;
using Strata.SqlTools.Markdown.SqlServer;
namespace Strata.SqlTools.Markdown.Tests.SqlServer;
[TestFixture]
public class SqlStatementGeneratorTests
{
private SqlStatementGenerator _generator = null!;
[SetUp]
public void Setup()
{
_generator = new SqlStatementGenerator();
}
[Test]
public void GenerateSequenceDiagram_WithSimpleQuery_GeneratesSequence()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateSequenceDiagram(query);
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("sequenceDiagram"));
Assert.That(result, Does.Contain("App->>DB"));
Assert.That(result, Does.Contain("DB-->>App"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateSequenceDiagram_WithSetupClauses_IncludesSetup()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
query.SetupClauses.Add("CREATE TEMP TABLE #Temp (ID INT)");
// Act
var result = _generator.GenerateSequenceDiagram(query);
// Assert
Assert.That(result, Does.Contain("Setup Clauses"));
Assert.That(result, Does.Contain("activate DB"));
Assert.That(result, Does.Contain("deactivate DB"));
}
[Test]
public void GenerateSequenceDiagram_WithFinishClauses_IncludesFinish()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
query.FinishClauses.Add("DROP TABLE #Temp");
// Act
var result = _generator.GenerateSequenceDiagram(query);
// Assert
Assert.That(result, Does.Contain("Finish Clauses"));
Assert.That(result, Does.Contain("Cleanup"));
}
[Test]
public void GenerateSequenceDiagram_WithTitle_IncludesTitle()
{
// Arrange
var query = new QueryBreakdown("*", "Users");
// Act
var result = _generator.GenerateSequenceDiagram(query, "Query Execution");
// Assert
Assert.That(result, Does.StartWith("### Query Execution"));
}
[Test]
public void GenerateEntityRelationshipDiagram_WithTables_GeneratesERDiagram()
{
// Arrange
var tables = new[] { "Users", "Orders", "Products" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables);
// Assert
Assert.That(result, Does.Contain("```mermaid"));
Assert.That(result, Does.Contain("erDiagram"));
Assert.That(result, Does.Contain("Users"));
Assert.That(result, Does.Contain("Orders"));
Assert.That(result, Does.Contain("Products"));
Assert.That(result, Does.Contain("```"));
}
[Test]
public void GenerateEntityRelationshipDiagram_WithTitle_IncludesTitle()
{
// Arrange
var tables = new[] { "Users", "Orders" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables, "Database Schema");
// Assert
Assert.That(result, Does.StartWith("### Database Schema"));
}
[Test]
public void GenerateEntityRelationshipDiagram_CleansTableNames()
{
// Arrange
var tables = new[] { "[dbo].[Users]", "schema.Orders" };
// Act
var result = _generator.GenerateEntityRelationshipDiagram(tables);
// Assert
Assert.That(result, Does.Contain("dbo_Users"));
Assert.That(result, Does.Contain("schema_Orders"));
Assert.That(result, Does.Not.Contain("["));
Assert.That(result, Does.Not.Contain("]"));
}
}
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</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" />
<ProjectReference Include="..\..\src\Strata.SqlTools.PostgreSql\Strata.SqlTools.PostgreSql.csproj" />
<ProjectReference Include="..\..\src\Strata.SqlTools.LinqToSql\Strata.SqlTools.LinqToSql.csproj" />
<ProjectReference Include="..\..\src\Strata.SqlTools.Markdown\Strata.SqlTools.Markdown.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
</Project>
@@ -0,0 +1 @@
global using NUnit.Framework;