test(sql): Add comprehensive unit tests for breakdown classes and utilities

Introduces new unit tests for Snowflake and SQL Server-specific SQL breakdown
classes (DELETE, INSERT, UPDATE, PROCEDURE) and various general SQL utility
functions. This significantly increases test coverage, ensuring robustness
and correctness across different SQL dialects and helper logic.
This commit is contained in:
Thom Lamb
2026-06-15 08:16:22 -05:00
parent 9f9450dc6d
commit fbedea3941
13 changed files with 2575 additions and 0 deletions
+2
View File
@@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.SqlTools.SqlBreakdow
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.SqlTools.Query", "src\Strata.SqlTools.Query\Strata.SqlTools.Query.csproj", "{177F3799-6A91-477B-87CC-BCCE05B87E1C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests.Integration", "Tests.Integration", "{CFCF0678-C587-40E9-B0AC-A7082AEBF206}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -0,0 +1,172 @@
using Strata.SqlTools.Breakdowns.Snowflake;
namespace Strata.SqlTools.SqlBreakdown.Tests.Snowflake;
[TestFixture]
public class DeleteBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyClauses()
{
// Arrange & Act
var breakdown = new DeleteBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.IsUsingWhereClause, Is.False);
});
}
[Test]
public void Constructor_WithClauses_SetsProperties()
{
// Arrange & Act
var breakdown = new DeleteBreakdown("USERS", "ID = :ID");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("USERS"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("ID = :ID"));
Assert.That(breakdown.IsUsingWhereClause, Is.True);
});
}
#endregion
#region GetSql Tests
[Test]
public void GetSql_WithWhere_RendersDeleteFromWhere()
{
// Arrange - Snowflake overrides the formatter with simplified DELETE FROM syntax
var breakdown = new DeleteBreakdown("USERS", "ID = :ID");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("DELETE FROM"));
Assert.That(sql, Does.Contain("USERS"));
Assert.That(sql, Does.Contain("WHERE"));
Assert.That(sql, Does.Contain("ID = :ID"));
});
}
[Test]
public void GetSql_WithoutWhere_OmitsWhereKeyword()
{
// Arrange
var breakdown = new DeleteBreakdown("USERS", string.Empty);
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("DELETE FROM"));
Assert.That(sql, Does.Not.Contain("WHERE"));
});
}
#endregion
#region Parse Tests
[Test]
public void Parse_SnowflakeDelete_PopulatesClauses()
{
// Arrange
var input = "DELETE FROM USERS WHERE ID = 1";
// Act
var breakdown = DeleteBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("USERS"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("ID = 1"));
});
}
[Test]
public void Parse_DeleteWithoutWhere_LeavesWhereEmpty()
{
// Act
var breakdown = DeleteBreakdown.Parse("DELETE FROM USERS");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("USERS"));
Assert.That(breakdown.IsUsingWhereClause, Is.False);
});
}
[Test]
public void Parse_MicrosoftSqlMode_DelegatesToBase()
{
// Act - T-SQL DELETE with alias is supported through the base breakdown
var breakdown = DeleteBreakdown.Parse("DELETE FROM Users WHERE Id = 1", isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown, Is.InstanceOf<DeleteBreakdown>());
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Users"));
});
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => DeleteBreakdown.Parse(null!));
}
[Test]
public void Parse_NotADeleteStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => DeleteBreakdown.Parse("SELECT * FROM USERS"));
}
[Test]
public void TryParse_MicrosoftSqlMode_ReturnsSnowflakeInstance()
{
// Act
var success = DeleteBreakdown.TryParse("DELETE FROM Users WHERE Id = 1", out var breakdown, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown, Is.InstanceOf<DeleteBreakdown>());
});
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalse()
{
// Act
var success = DeleteBreakdown.TryParse("UPDATE USERS SET ID = 1", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("DELETE"));
});
}
#endregion
}
@@ -0,0 +1,151 @@
using Strata.SqlTools.Breakdowns.Snowflake;
namespace Strata.SqlTools.SqlBreakdown.Tests.Snowflake;
[TestFixture]
public class InsertBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyClauses()
{
// Arrange & Act
var breakdown = new InsertBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo(string.Empty));
});
}
[Test]
public void Constructor_WithClauses_SetsProperties()
{
// Arrange & Act
var breakdown = new InsertBreakdown("USERS", "ID, NAME", ":ID, :NAME");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("USERS"));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo("ID, NAME"));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo(":ID, :NAME"));
});
}
[Test]
public void Constructor_WithColumnNames_GeneratesColonPrefixedParameters()
{
// Arrange & Act - Snowflake uses :parameter syntax (vs SQL Server's @parameter)
var breakdown = new InsertBreakdown("USERS", new List<string> { "ID", "NAME" });
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo("[ID],[NAME]"));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo(":ID,:NAME"));
});
}
#endregion
#region Parse Tests
[Test]
public void Parse_SnowflakeInsert_PopulatesClauses()
{
// Arrange
var input = "INSERT INTO USERS (ID, NAME) VALUES (1, 'BOB')";
// Act
var breakdown = InsertBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("USERS"));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo("ID, NAME"));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo("1, 'BOB'"));
});
}
[Test]
public void Parse_MicrosoftSqlMode_DelegatesToBaseAndSucceeds()
{
// Arrange
var input = "INSERT INTO Users (Id) VALUES (1)";
// Act
var breakdown = InsertBreakdown.Parse(input, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown, Is.InstanceOf<InsertBreakdown>());
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo("Id"));
});
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => InsertBreakdown.Parse(" "));
}
[Test]
public void Parse_NotAnInsertStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => InsertBreakdown.Parse("SELECT * FROM USERS"));
}
[Test]
public void TryParse_ValidInsert_ReturnsTrue()
{
// Act
var success = InsertBreakdown.TryParse("INSERT INTO USERS (ID) VALUES (1)", out var breakdown);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown.TableName.Clause, Is.EqualTo("USERS"));
});
}
[Test]
public void TryParse_MicrosoftSqlMode_ReturnsSnowflakeInstance()
{
// Act
var success = InsertBreakdown.TryParse("INSERT INTO Users (Id) VALUES (1)", out var breakdown, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown, Is.InstanceOf<InsertBreakdown>());
});
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalse()
{
// Act
var success = InsertBreakdown.TryParse("DELETE FROM USERS", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("INSERT INTO"));
});
}
#endregion
}
@@ -0,0 +1,193 @@
using Strata.SqlTools.Breakdowns.Snowflake;
namespace Strata.SqlTools.SqlBreakdown.Tests.Snowflake;
[TestFixture]
public class ProcedureBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_WithName_SetsProcedureName()
{
// Arrange & Act
var breakdown = new ProcedureBreakdown("MY_PROC");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("MY_PROC"));
Assert.That(breakdown.IsUsingParameters, Is.False);
});
}
[Test]
public void Constructor_WithParameters_SetsParameters()
{
// Arrange
var parameters = new Dictionary<string, string> { ["@ID"] = "1" };
// Act
var breakdown = new ProcedureBreakdown("MY_PROC", parameters);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.IsUsingParameters, Is.True);
Assert.That(breakdown.Parameters["@ID"], Is.EqualTo("1"));
});
}
#endregion
#region GetSql Tests - Snowflake CALL Syntax
[Test]
public void GetSql_WithoutParameters_RendersCallWithEmptyParens()
{
// Arrange - Snowflake uses CALL proc() rather than EXEC proc
var breakdown = new ProcedureBreakdown("MY_PROC");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("CALL"));
Assert.That(sql, Does.Contain("MY_PROC"));
Assert.That(sql, Does.Contain("()"));
Assert.That(sql, Does.Not.Contain("EXEC"));
});
}
[Test]
public void GetSql_WithParameters_RendersNamedArrowSyntax()
{
// Arrange
var breakdown = new ProcedureBreakdown("MY_PROC");
breakdown.AddParameter("@ID", "1");
// Act - Snowflake named-parameter syntax strips @ and uses =>
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("CALL"));
Assert.That(sql, Does.Contain("ID => 1"));
});
}
#endregion
#region Parse Tests - Snowflake CALL
[Test]
public void Parse_CallWithoutParameters_PopulatesName()
{
// Act
var breakdown = ProcedureBreakdown.Parse("CALL MY_PROC()");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("MY_PROC"));
Assert.That(breakdown.IsUsingParameters, Is.False);
});
}
[Test]
public void Parse_CallWithNamedParameters_PopulatesParametersWithAtPrefix()
{
// Arrange
var input = "CALL MY_PROC(ID => 1, NAME => 'BOB')";
// Act
var breakdown = ProcedureBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert - parsed named params are stored with @ prefix for consistency
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("MY_PROC"));
Assert.That(breakdown.Parameters["@ID"], Is.EqualTo("1"));
Assert.That(breakdown.Parameters["@NAME"], Is.EqualTo("'BOB'"));
});
}
[Test]
public void Parse_CallWithPositionalParameters_GeneratesParamNames()
{
// Arrange - no => present, so values are treated positionally
var input = "CALL MY_PROC(1, 'BOB')";
// Act
var breakdown = ProcedureBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.Parameters["@param1"], Is.EqualTo("1"));
Assert.That(breakdown.Parameters["@param2"], Is.EqualTo("'BOB'"));
});
}
[Test]
public void Parse_MicrosoftSqlMode_AcceptsExecSyntax()
{
// Act - T-SQL EXEC is parsed via base-class delegation
var breakdown = ProcedureBreakdown.Parse("EXEC dbo.GetUser @Id = 1", isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown, Is.InstanceOf<ProcedureBreakdown>());
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("dbo.GetUser"));
Assert.That(breakdown.Parameters["@Id"], Is.EqualTo("1"));
});
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => ProcedureBreakdown.Parse(" "));
}
[Test]
public void Parse_NotACallStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => ProcedureBreakdown.Parse("SELECT * FROM USERS"));
}
[Test]
public void TryParse_ValidCall_ReturnsTrue()
{
// Act
var success = ProcedureBreakdown.TryParse("CALL MY_PROC()", out var breakdown);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("MY_PROC"));
});
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalseWithMessage()
{
// Act
var success = ProcedureBreakdown.TryParse("SELECT 1", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("CALL"));
});
}
#endregion
}
@@ -0,0 +1,161 @@
using Strata.SqlTools.Breakdowns.Snowflake;
namespace Strata.SqlTools.SqlBreakdown.Tests.Snowflake;
[TestFixture]
public class UpdateBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyClauses()
{
// Arrange & Act
var breakdown = new UpdateBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.SetClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.IsUsingWhereClause, Is.False);
});
}
[Test]
public void Constructor_WithClauses_SetsProperties()
{
// Arrange & Act
var breakdown = new UpdateBreakdown("USERS", "NAME = :NAME", "ID = :ID");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("USERS"));
Assert.That(breakdown.SetClause.Clause, Is.EqualTo("NAME = :NAME"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("ID = :ID"));
});
}
#endregion
#region GetSql Tests
[Test]
public void GetSql_RendersUpdateSetWhere()
{
// Arrange - Snowflake inherits the SQL Server UPDATE formatter
var breakdown = new UpdateBreakdown("USERS", "NAME = :NAME", "ID = :ID");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("UPDATE"));
Assert.That(sql, Does.Contain("USERS"));
Assert.That(sql, Does.Contain("SET"));
Assert.That(sql, Does.Contain("NAME = :NAME"));
Assert.That(sql, Does.Contain("WHERE"));
});
}
#endregion
#region Parse Tests
[Test]
public void Parse_SnowflakeUpdate_PopulatesClauses()
{
// Arrange
var input = "UPDATE USERS SET NAME = 'BOB' WHERE ID = 1";
// Act
var breakdown = UpdateBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("USERS"));
Assert.That(breakdown.SetClause.Clause, Is.EqualTo("NAME = 'BOB'"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("ID = 1"));
});
}
[Test]
public void Parse_UpdateWithFrom_PopulatesFromClause()
{
// Arrange
var input = "UPDATE T SET T.NAME = S.NAME FROM TARGET T INNER JOIN SOURCE S ON T.ID = S.ID WHERE S.ACTIVE = 1";
// Act
var breakdown = UpdateBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.IsUsingFromClause, Is.True);
Assert.That(breakdown.FromClause.Clause, Does.Contain("INNER JOIN SOURCE"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("S.ACTIVE = 1"));
});
}
[Test]
public void Parse_MicrosoftSqlMode_DelegatesToBase()
{
// Act
var breakdown = UpdateBreakdown.Parse("UPDATE Users SET Name = 'Bob' WHERE Id = 1", isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown, Is.InstanceOf<UpdateBreakdown>());
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
});
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => UpdateBreakdown.Parse(string.Empty));
}
[Test]
public void Parse_NotAnUpdateStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => UpdateBreakdown.Parse("SELECT * FROM USERS"));
}
[Test]
public void TryParse_MicrosoftSqlMode_ReturnsSnowflakeInstance()
{
// Act
var success = UpdateBreakdown.TryParse("UPDATE Users SET Name = 'Bob'", out var breakdown, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown, Is.InstanceOf<UpdateBreakdown>());
});
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalse()
{
// Act
var success = UpdateBreakdown.TryParse("DELETE FROM USERS", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("UPDATE"));
});
}
#endregion
}
@@ -0,0 +1,324 @@
using System.Collections;
using Strata.SqlTools.SqlBreakdown.Utilities;
namespace Strata.SqlTools.SqlBreakdown.Tests.Utilities;
[TestFixture]
public class ArrayUtilsTests
{
private static readonly Guid GuidA = new("11111111-1111-1111-1111-111111111111");
private static readonly Guid GuidB = new("22222222-2222-2222-2222-222222222222");
private static readonly Guid GuidC = new("33333333-3333-3333-3333-333333333333");
#region NewArray / Coalesce
[Test]
public void NewArray_ReturnsArrayOfValues()
{
// Act
var result = ArrayUtils.NewArray(1, 2, 3);
// Assert
Assert.That(result, Is.EqualTo(new[] { 1, 2, 3 }));
}
[Test]
public void Coalesce_ReturnsFirstNonNull()
{
// Act & Assert
Assert.That(ArrayUtils.Coalesce(null!, null!, "x", "y"), Is.EqualTo("x"));
}
[Test]
public void Coalesce_AllNull_ReturnsNull()
{
// Act & Assert
Assert.That(ArrayUtils.Coalesce(null!, null!), Is.Null);
}
#endregion
#region CSV -> Collection
[Test]
public void GetArrayListFromCsv_TrimsAndSplits()
{
// Act
var result = ArrayUtils.GetArrayListFromCsv("a, b, c");
// Assert
Assert.That(result, Is.EqualTo(new ArrayList { "a", "b", "c" }));
}
[Test]
public void GetArrayListFromCsv_Empty_ReturnsEmptyList()
{
// Act & Assert
Assert.That(ArrayUtils.GetArrayListFromCsv(string.Empty), Is.Empty);
}
[Test]
public void GetGenericListFromCsv_TrimsEntries()
{
// Act
var result = ArrayUtils.GetGenericListFromCsv("a, b ,c");
// Assert
Assert.That(result, Is.EqualTo(new List<string> { "a", "b", "c" }));
}
[Test]
public void GetGenericListFromCsv_Empty_ReturnsEmpty()
{
// Act & Assert
Assert.That(ArrayUtils.GetGenericListFromCsv(string.Empty), Is.Empty);
}
[Test]
public void GetIntegerListFromCsv_SkipsNonIntegers()
{
// Act
var result = ArrayUtils.GetIntegerListFromCsv("1,2,x,3");
// Assert
Assert.That(result, Is.EqualTo(new List<int> { 1, 2, 3 }));
}
[Test]
public void GetGuidListFromCsv_ParsesValidSkipsInvalid()
{
// Act
var result = ArrayUtils.GetGuidListFromCsv($"{GuidA},not-a-guid,{GuidB}");
// Assert
Assert.That(result, Is.EqualTo(new List<Guid> { GuidA, GuidB }));
}
[Test]
public void GetGuidListFromCsv_Empty_ReturnsEmpty()
{
// Act & Assert
Assert.That(ArrayUtils.GetGuidListFromCsv(string.Empty), Is.Empty);
}
[Test]
public void GetGuidArrayFromCsv_ParsesGuids()
{
// Act
var result = ArrayUtils.GetGuidArrayFromCsv($"{GuidA},{GuidB}");
// Assert
Assert.That(result, Is.EqualTo(new[] { GuidA, GuidB }));
}
#endregion
#region Collection -> CSV
[Test]
public void GetCsvFromArrayList_JoinsWithCommas()
{
// Act
var result = ArrayUtils.GetCsvFromArrayList(new ArrayList { "a", "b", "c" });
// Assert
Assert.That(result, Is.EqualTo("a,b,c"));
}
[Test]
public void GetCsvFromGenericList_JoinsWithCommas()
{
// Act & Assert
Assert.That(ArrayUtils.GetCsvFromGenericList(new List<string> { "a", "b" }), Is.EqualTo("a,b"));
}
[Test]
public void GetCsvFromGenericList_SingleQuotes_WrapsEachValue()
{
// Act
var result = ArrayUtils.GetCsvFromGenericList(new List<string> { "a", "b" }, ArrayUtils.QuoteType.Single);
// Assert
Assert.That(result, Is.EqualTo("'a','b'"));
}
[Test]
public void GetCsvFromGenericList_DoubleQuotes_WrapsEachValue()
{
// Act
var result = ArrayUtils.GetCsvFromGenericList(new List<string> { "a", "b" }, ArrayUtils.QuoteType.Double);
// Assert
Assert.That(result, Is.EqualTo("\"a\",\"b\""));
}
[Test]
public void GetCsvFromGenericList_NoneQuoteType_BehavesLikePlainJoin()
{
// Act & Assert
Assert.That(ArrayUtils.GetCsvFromGenericList(new List<string> { "a", "b" }, ArrayUtils.QuoteType.None), Is.EqualTo("a,b"));
}
[Test]
public void GetCsvFromGenericListOfGuids_Null_ReturnsEmpty()
{
// Act & Assert
Assert.That(ArrayUtils.GetCsvFromGenericListOfGuids(null!), Is.EqualTo(string.Empty));
}
[Test]
public void GetCsvFromGenericListOfGuids_NoQuotes_JoinsGuids()
{
// Act
var result = ArrayUtils.GetCsvFromGenericListOfGuids(new List<Guid> { GuidA, GuidB });
// Assert
Assert.That(result, Is.EqualTo($"{GuidA},{GuidB}"));
}
[Test]
public void GetCsvFromGenericListOfGuids_SingleQuotes_WrapsEachGuid()
{
// Act
var result = ArrayUtils.GetCsvFromGenericListOfGuids(new List<Guid> { GuidA }, ArrayUtils.QuoteType.Single);
// Assert
Assert.That(result, Is.EqualTo($"'{GuidA}'"));
}
#endregion
#region Encode
[Test]
public void Encode_NoInternalDelimiters_JoinsWithCommas()
{
// Act & Assert
Assert.That(ArrayUtils.Encode(new List<string> { "x", "y", "z" }), Is.EqualTo("x,y,z"));
}
[Test]
public void Encode_InternalComma_ReplacedWithIllegalCharacter()
{
// Arrange - internal commas are swapped for char(8) so they survive the comma join
const char illegal = (char)8;
// Act
var result = ArrayUtils.Encode(new List<string> { "a,b", "c" });
// Assert
Assert.That(result, Is.EqualTo($"a{illegal}b,c"));
}
#endregion
#region Guid List <-> String
[Test]
public void ConvertGuidStringToList_Empty_ReturnsEmpty()
{
// Act & Assert
Assert.That(ArrayUtils.ConvertGuidStringToList(string.Empty), Is.Empty);
}
[Test]
public void ConvertGuidStringToList_ParsesAll()
{
// Act
var result = ArrayUtils.ConvertGuidStringToList($"{GuidA},{GuidB}");
// Assert
Assert.That(result, Is.EqualTo(new List<Guid> { GuidA, GuidB }));
}
[Test]
public void ConvertToGuidArray_ParsesStrings()
{
// Act
var result = ArrayUtils.ConvertToGuidArray(new[] { GuidA.ToString(), GuidB.ToString() });
// Assert
Assert.That(result, Is.EqualTo(new[] { GuidA, GuidB }));
}
[Test]
public void ConvertToGuidList_ParsesStrings()
{
// Act
var result = ArrayUtils.ConvertToGuidList(new[] { GuidA.ToString() });
// Assert
Assert.That(result, Is.EqualTo(new List<Guid> { GuidA }));
}
[Test]
public void ConvertGuidListToString_Null_ReturnsEmpty()
{
// Act & Assert
Assert.That(ArrayUtils.ConvertGuidListToString(null!), Is.EqualTo(string.Empty));
}
[Test]
public void ConvertGuidListToString_NoSurround_JoinsWithCommas()
{
// Act & Assert
Assert.That(ArrayUtils.ConvertGuidListToString(new[] { GuidA, GuidB }), Is.EqualTo($"{GuidA},{GuidB}"));
}
[Test]
public void ConvertGuidListToString_WithSurroundChar_WrapsEachGuid()
{
// Act
var result = ArrayUtils.ConvertGuidListToString(new[] { GuidA }, "'");
// Assert
Assert.That(result, Is.EqualTo($"'{GuidA}'"));
}
[Test]
public void ConvertGuidListToStringList_ReturnsStringRepresentations()
{
// Act
var result = ArrayUtils.ConvertGuidListToStringList(new[] { GuidA, GuidB });
// Assert
Assert.That(result, Is.EqualTo(new List<string> { GuidA.ToString(), GuidB.ToString() }));
}
#endregion
#region Intersection
[Test]
public void GetGuidListsIntersection_ReturnsCommonGuids()
{
// Act
var result = ArrayUtils.GetGuidListsIntersection(
new List<Guid> { GuidA, GuidB },
new List<Guid> { GuidB, GuidC });
// Assert
Assert.That(result, Is.EqualTo(new List<Guid> { GuidB }));
}
[Test]
public void GetGuidListsIntersection_NoOverlap_ReturnsEmpty()
{
// Act
var result = ArrayUtils.GetGuidListsIntersection(
new List<Guid> { GuidA },
new List<Guid> { GuidB });
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void GetGuidListsIntersection_NullArguments_ReturnEmpty()
{
// Act & Assert
Assert.That(ArrayUtils.GetGuidListsIntersection(null!, null!), Is.Empty);
}
#endregion
}
@@ -0,0 +1,215 @@
using Strata.SqlTools.SqlBreakdown.Utilities;
namespace Strata.SqlTools.SqlBreakdown.Tests.Utilities;
[TestFixture]
public class GuidUtilsTests
{
private static readonly Guid Sample = new("0123abcd-4567-89ef-0123-456789abcdef");
#region TranslateGuid / UnTranslateGuid
[Test]
public void TranslateGuid_RemovesHyphensAndPrefixesWithG()
{
// Act
var result = GuidUtils.TranslateGuid(Sample);
Assert.Multiple(() =>
{
// Assert
Assert.That(result, Does.StartWith("g"));
Assert.That(result, Has.Length.EqualTo(33));
Assert.That(result, Does.Not.Contain("-"));
});
}
[Test]
public void UnTranslateGuid_RoundTripsTranslateGuid()
{
// Arrange
var translated = GuidUtils.TranslateGuid(Sample);
// Act & Assert
Assert.That(GuidUtils.UnTranslateGuid(translated), Is.EqualTo(Sample));
}
[Test]
public void UnTranslateGuid_AlreadyHyphenated_ParsesDirectly()
{
// Act & Assert
Assert.That(GuidUtils.UnTranslateGuid(Sample.ToString()), Is.EqualTo(Sample));
}
[TestCase("")] // null/empty guard
[TestCase("notprefixed")] // no hyphen, does not start with 'g'
[TestCase("gTOOSHORT")] // starts with 'g' but wrong length (not 33)
public void UnTranslateGuid_InvalidInputs_ReturnEmpty(string value)
{
// Act & Assert
Assert.That(GuidUtils.UnTranslateGuid(value), Is.EqualTo(Guid.Empty));
}
#endregion
#region SQL-safe column GUID
[Test]
public void GetSqlColumnSafeGuid_UppercasesAndReplacesHyphens()
{
// Act
var result = GuidUtils.GetSqlColumnSafeGuid(Sample);
Assert.Multiple(() =>
{
// Assert
Assert.That(result, Does.StartWith("G"));
Assert.That(result, Does.Not.Contain("-"));
Assert.That(result, Does.Contain("_"));
});
}
[Test]
public void GetGuidFromSqlColumnSafeGuid_RoundTrips()
{
// Arrange
var safe = GuidUtils.GetSqlColumnSafeGuid(Sample);
// Act & Assert
Assert.That(GuidUtils.GetGuidFromSqlColumnSafeGuid(safe), Is.EqualTo(Sample));
}
#endregion
#region TranslateTextToGuid
[Test]
public void TranslateTextToGuid_IsDeterministic()
{
// Act & Assert - same input must always yield the same GUID
Assert.That(GuidUtils.TranslateTextToGuid("hello world"), Is.EqualTo(GuidUtils.TranslateTextToGuid("hello world")));
}
[Test]
public void TranslateTextToGuid_EmptyInput_ReturnsEmpty()
{
// Act & Assert
Assert.That(GuidUtils.TranslateTextToGuid(string.Empty), Is.EqualTo(Guid.Empty));
}
[Test]
public void TranslateTextToGuid_DifferentInputs_GenerallyDiffer()
{
// Act & Assert
Assert.That(GuidUtils.TranslateTextToGuid("alpha"), Is.Not.EqualTo(GuidUtils.TranslateTextToGuid("beta")));
}
#endregion
#region Validation
[TestCase("0123abcd-4567-89ef-0123-456789abcdef", true)]
[TestCase("not a guid", false)]
[TestCase("", false)]
public void IsGuid_String_ValidatesCorrectly(string value, bool expected)
{
// Act & Assert
Assert.That(GuidUtils.IsGuid(value), Is.EqualTo(expected));
}
[Test]
public void IsGuid_Object_GuidInstance_ReturnsTrue()
{
// Act & Assert
Assert.That(GuidUtils.IsGuid((object)Sample), Is.True);
}
[Test]
public void IsGuid_Object_GuidString_ReturnsTrue()
{
// Act & Assert
Assert.That(GuidUtils.IsGuid((object)Sample.ToString()), Is.True);
}
[Test]
public void IsGuid_Object_NonGuid_ReturnsFalse()
{
// Act & Assert
Assert.That(GuidUtils.IsGuid((object)12345), Is.False);
}
#endregion
#region FindFirstGuid / GetGuids
[Test]
public void FindFirstGuid_EmbeddedGuid_ReturnsIt()
{
// Act
var result = GuidUtils.FindFirstGuid($"prefix {Sample} suffix");
// Assert
Assert.That(result, Is.EqualTo(Sample));
}
[Test]
public void FindFirstGuid_NoGuid_ReturnsEmpty()
{
// Act & Assert
Assert.That(GuidUtils.FindFirstGuid("nothing here"), Is.EqualTo(Guid.Empty));
}
[Test]
public void GetGuids_ReturnsAllEmbeddedGuids()
{
// Arrange
var second = new Guid("99999999-9999-9999-9999-999999999999");
// Act
var result = GuidUtils.GetGuids($"a {Sample} b {second} c");
// Assert
Assert.That(result, Has.Count.EqualTo(2));
}
#endregion
#region GetGuid
[Test]
public void GetGuid_ValidString_ParsesValue()
{
// Act & Assert
Assert.That(GuidUtils.GetGuid(Sample.ToString()), Is.EqualTo(Sample));
}
[Test]
public void GetGuid_InvalidString_ReturnsEmpty()
{
// Act & Assert
Assert.That(GuidUtils.GetGuid("nope"), Is.EqualTo(Guid.Empty));
}
[Test]
public void GetGuid_InvalidString_ReturnsProvidedDefault()
{
// Act & Assert
Assert.That(GuidUtils.GetGuid("nope", Sample), Is.EqualTo(Sample));
}
[Test]
public void GetGuid_NullObject_ReturnsEmpty()
{
// Act & Assert
Assert.That(GuidUtils.GetGuid((object)null!), Is.EqualTo(Guid.Empty));
}
[Test]
public void GetGuid_Object_ParsesToStringValue()
{
// Act & Assert
Assert.That(GuidUtils.GetGuid((object)Sample.ToString()), Is.EqualTo(Sample));
}
#endregion
}
@@ -0,0 +1,480 @@
using System.Data;
using Strata.SqlTools.SqlBreakdown.Enums.SQL;
using Strata.SqlTools.SqlBreakdown.Utilities;
namespace Strata.SqlTools.SqlBreakdown.Tests.Utilities;
[TestFixture]
public class SqlUtilsTests
{
#region String Manipulation
[Test]
public void StripColumnTableAlias_RemovesTableAliases()
{
// Act & Assert
Assert.That(SqlUtils.StripColumnTableAlias("a.Col1, b.Col2"), Is.EqualTo("Col1,Col2"));
}
[Test]
public void StripColumnTableAlias_NoAlias_LeavesColumnUntouched()
{
// Act & Assert
Assert.That(SqlUtils.StripColumnTableAlias("MyCol"), Is.EqualTo("MyCol"));
}
[TestCase("O'Brien", "O''Brien")]
[TestCase("", "")]
[TestCase("clean", "clean")]
public void EscapeInvalidCharacter_DoublesSingleQuotes(string input, string expected)
{
// Act & Assert
Assert.That(SqlUtils.EscapeInvalidCharacter(input), Is.EqualTo(expected));
}
[TestCase("Col", "[Col]")]
[TestCase("[Col]", "[Col]")]
[TestCase("[Col", "[Col]")]
[TestCase("Col]", "[Col]")]
public void AddBrackets_WrapsWhenMissing(string input, string expected)
{
// Act & Assert
Assert.That(SqlUtils.AddBrackets(input), Is.EqualTo(expected));
}
[Test]
public void RemoveBrackets_StripsSquareBrackets()
{
// Act & Assert
Assert.That(SqlUtils.RemoveBrackets("[Col]"), Is.EqualTo("Col"));
}
[Test]
public void GetSqlFriendlyName_RemovesInvalidCharactersByDefault()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlFriendlyName("a.b c"), Is.EqualTo("abc"));
}
[Test]
public void GetSqlFriendlyName_UsesReplacement()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlFriendlyName("a.b c", "_"), Is.EqualTo("a_b_c"));
}
#endregion
#region Object Name Handling
[Test]
public void GetSqlObjectExpression_BuildsBracketedExpression()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlObjectExpression("dbo", "MyTable"), Is.EqualTo("[dbo].[MyTable]"));
}
[Test]
public void GetSqlObjectExpression_StripsExistingBrackets()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlObjectExpression("[dbo]", "[MyTable]"), Is.EqualTo("[dbo].[MyTable]"));
}
[Test]
public void GetSqlObjectName_ExtractsObjectPortion()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlObjectName("[dbo].[MyTable]"), Is.EqualTo("MyTable"));
}
[Test]
public void GetSqlObjectName_NoDot_ReturnsInput()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlObjectName("MyTable"), Is.EqualTo("MyTable"));
}
[Test]
public void GetSqlObjectName_MultipleDots_Throws()
{
// Act & Assert
Assert.Throws<NotSupportedException>(() => SqlUtils.GetSqlObjectName("a.b.c"));
}
[Test]
public void GetSqlSchemaName_ExtractsSchema()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlSchemaName("data.DimClient"), Is.EqualTo("data"));
}
[Test]
public void GetSqlSchemaName_NoDot_ReturnsDbo()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlSchemaName("DimClient"), Is.EqualTo("dbo"));
}
[Test]
public void GetSqlSchemaName_MultipleDots_Throws()
{
// Act & Assert
Assert.Throws<NotSupportedException>(() => SqlUtils.GetSqlSchemaName("a.b.c"));
}
[TestCase("dbo", true)]
[TestCase("DBO", true)]
[TestCase("data", false)]
public void IsDefaultSchema_ChecksAgainstDbo(string schema, bool expected)
{
// Act & Assert
Assert.That(SqlUtils.IsDefaultSchema(schema), Is.EqualTo(expected));
}
#endregion
#region Data Type Conversions
[Test]
public void GetStringConversionLength_KnownTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetStringConversionLength(SqlDataType.Bit), Is.EqualTo(1));
Assert.That(SqlUtils.GetStringConversionLength(SqlDataType.Int), Is.EqualTo(int.MaxValue.ToString().Length));
Assert.That(SqlUtils.GetStringConversionLength(SqlDataType.UniqueIdentifier), Is.EqualTo(Guid.Empty.ToString().Length));
Assert.That(SqlUtils.GetStringConversionLength(SqlDataType.NVarChar), Is.EqualTo(SqlUtils.MAX_NVARCHAR_LENGTH));
});
}
[Test]
public void ConvertGuidToAlias_ProducesPrefixedUppercaseAlias()
{
// Arrange
var guid = new Guid("0123abcd-4567-89ef-0123-456789abcdef");
// Act
var result = SqlUtils.ConvertGuidToAlias(guid, "p");
Assert.Multiple(() =>
{
// Assert
Assert.That(result, Does.StartWith("p"));
Assert.That(result, Does.Not.Contain("-"));
Assert.That(result, Is.EqualTo("p0123ABCD456789EF0123456789ABCDEF"));
});
}
[Test]
public void IsFixedPrecision_DecimalIsNotFixed()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.IsFixedPrecision(SqlDataType.Decimal), Is.False);
Assert.That(SqlUtils.IsFixedPrecision(SqlDataType.Int), Is.True);
// IsFixedScale mirrors IsFixedPrecision
Assert.That(SqlUtils.IsFixedScale(SqlDataType.Decimal), Is.False);
Assert.That(SqlUtils.IsFixedScale(SqlDataType.Int), Is.True);
});
}
[Test]
public void IsFixedMaxLength_VariesByType()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.IsFixedMaxLength(SqlDataType.Int), Is.True);
Assert.That(SqlUtils.IsFixedMaxLength(SqlDataType.UniqueIdentifier), Is.True);
Assert.That(SqlUtils.IsFixedMaxLength(SqlDataType.VarChar), Is.False);
});
}
[Test]
public void GetFixedScale_KnownTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetFixedScale(SqlDataType.DateTime), Is.EqualTo(3));
Assert.That(SqlUtils.GetFixedScale(SqlDataType.Money), Is.EqualTo(4));
Assert.That(SqlUtils.GetFixedScale(SqlDataType.Int), Is.EqualTo(0));
});
}
[Test]
public void GetFixedScale_Decimal_Throws()
{
// Act & Assert
Assert.Throws<NotImplementedException>(() => SqlUtils.GetFixedScale(SqlDataType.Decimal));
}
[Test]
public void GetFixedPrecision_KnownTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetFixedPrecision(SqlDataType.BigInt), Is.EqualTo(19));
Assert.That(SqlUtils.GetFixedPrecision(SqlDataType.Int), Is.EqualTo(10));
Assert.That(SqlUtils.GetFixedPrecision(SqlDataType.Bit), Is.EqualTo(1));
});
}
[Test]
public void GetFixedMaxLength_KnownTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetFixedMaxLength(SqlDataType.Int), Is.EqualTo(4));
Assert.That(SqlUtils.GetFixedMaxLength(SqlDataType.UniqueIdentifier), Is.EqualTo(16));
Assert.That(SqlUtils.GetFixedMaxLength(SqlDataType.TinyInt), Is.EqualTo(1));
});
}
[Test]
public void GetFixedMaxLength_UnsupportedType_Throws()
{
// Act & Assert - VarChar has no fixed length
Assert.Throws<NotImplementedException>(() => SqlUtils.GetFixedMaxLength(SqlDataType.VarChar));
}
#endregion
#region Well-Known SQL Errors
[Test]
public void GetWellKnownSqlError_DuplicateKeyRow_IsUniqueKeyViolation()
{
// Arrange
var ex = new Exception("Cannot insert duplicate key row in object 'dbo.Users'.");
// Act & Assert
Assert.That(SqlUtils.GetWellKnownSqlError(ex), Is.EqualTo(SqlUtils.WellKnownSqlError.UniqueKeyViolation));
}
[Test]
public void GetWellKnownSqlError_ObjectAlreadyExists_IsDetected()
{
// Arrange
var ex = new Exception("There is already an object named 'Users' in the database.");
// Act & Assert
Assert.That(SqlUtils.GetWellKnownSqlError(ex), Is.EqualTo(SqlUtils.WellKnownSqlError.ObjectAlreadyExists));
}
[Test]
public void GetWellKnownSqlError_TruncateForeignKey_IsDetected()
{
// Arrange
var ex = new Exception("Cannot truncate table 'dbo.Orders' because it is being referenced by a FOREIGN KEY constraint.");
// Act & Assert
Assert.That(SqlUtils.GetWellKnownSqlError(ex), Is.EqualTo(SqlUtils.WellKnownSqlError.TruncateTableForeignKeyReferenceError));
}
[Test]
public void GetWellKnownSqlError_UnrecognizedMessage_IsUnknown()
{
// Act & Assert
Assert.That(SqlUtils.GetWellKnownSqlError(new Exception("Something else entirely")), Is.EqualTo(SqlUtils.WellKnownSqlError.Unknown));
}
#endregion
#region Data Type Helpers
[Test]
public void GetClientFriendlyDataTypeName_MapsCategories()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetClientFriendlyDataTypeName(SqlDataType.NVarChar), Is.EqualTo("Text"));
Assert.That(SqlUtils.GetClientFriendlyDataTypeName(SqlDataType.Int), Is.EqualTo("Whole Number"));
Assert.That(SqlUtils.GetClientFriendlyDataTypeName(SqlDataType.Decimal), Is.EqualTo("Decimal"));
Assert.That(SqlUtils.GetClientFriendlyDataTypeName(SqlDataType.DateTime), Is.EqualTo("Date"));
Assert.That(SqlUtils.GetClientFriendlyDataTypeName(SqlDataType.Money), Is.EqualTo("Dollars"));
});
}
[Test]
public void IdentityColumnTypes_ContainsIntegerTypes()
{
// Act
var types = SqlUtils.IdentityColumnTypes().ToList();
// Assert
Assert.That(types, Is.EquivalentTo(new[]
{
SqlDataType.BigInt, SqlDataType.TinyInt, SqlDataType.Int, SqlDataType.SmallInt
}));
}
[Test]
public void GetOrderBySqlDirectionString_MapsDirections()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetOrderBySqlDirectionString(SortDirection.Ascending), Is.EqualTo("ASC"));
Assert.That(SqlUtils.GetOrderBySqlDirectionString(SortDirection.Descending), Is.EqualTo("DESC"));
});
}
[Test]
public void GetSimpleDataType_MapsToCategories()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetSimpleDataType(SqlDataType.Int), Is.EqualTo(SimpleDataType.Numeric));
Assert.That(SqlUtils.GetSimpleDataType(SqlDataType.DateTime), Is.EqualTo(SimpleDataType.Date));
Assert.That(SqlUtils.GetSimpleDataType(SqlDataType.Bit), Is.EqualTo(SimpleDataType.Boolean));
Assert.That(SqlUtils.GetSimpleDataType(SqlDataType.UniqueIdentifier), Is.EqualTo(SimpleDataType.GUID));
Assert.That(SqlUtils.GetSimpleDataType(SqlDataType.NVarChar), Is.EqualTo(SimpleDataType.String));
});
}
[TestCase("1", true)]
[TestCase("0", true)]
[TestCase("yes", true)]
[TestCase("no", true)]
[TestCase("true", true)]
[TestCase("maybe", false)]
public void IsStringValueValidForSimpleDataType_Boolean(string value, bool expected)
{
// Act & Assert
Assert.That(SqlUtils.IsStringValueValidForSimpleDataType(SimpleDataType.Boolean, value), Is.EqualTo(expected));
}
[TestCase("3.14", true)]
[TestCase("100", true)]
[TestCase("abc", false)]
public void IsStringValueValidForSimpleDataType_Numeric(string value, bool expected)
{
// Act & Assert
Assert.That(SqlUtils.IsStringValueValidForSimpleDataType(SimpleDataType.Numeric, value), Is.EqualTo(expected));
}
[Test]
public void IsStringValueValidForSimpleDataType_GuidAndString()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.IsStringValueValidForSimpleDataType(SimpleDataType.GUID, Guid.NewGuid().ToString()), Is.True);
Assert.That(SqlUtils.IsStringValueValidForSimpleDataType(SimpleDataType.GUID, "nope"), Is.False);
// String accepts anything
Assert.That(SqlUtils.IsStringValueValidForSimpleDataType(SimpleDataType.String, "anything"), Is.True);
});
}
[Test]
public void GetValueType_MapsToClrTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetValueType(SqlDataType.Bit), Is.EqualTo(typeof(bool)));
Assert.That(SqlUtils.GetValueType(SqlDataType.DateTime), Is.EqualTo(typeof(DateTime)));
Assert.That(SqlUtils.GetValueType(SqlDataType.Int), Is.EqualTo(typeof(double)));
Assert.That(SqlUtils.GetValueType(SqlDataType.UniqueIdentifier), Is.EqualTo(typeof(Guid)));
Assert.That(SqlUtils.GetValueType(SqlDataType.NVarChar), Is.EqualTo(typeof(string)));
});
}
[Test]
public void GetSqlDataType_MapsFromSqlDbType()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetSqlDataType(SqlDbType.Int), Is.EqualTo(SqlDataType.Int));
Assert.That(SqlUtils.GetSqlDataType(SqlDbType.DateTime2), Is.EqualTo(SqlDataType.DateTime));
Assert.That(SqlUtils.GetSqlDataType(SqlDbType.UniqueIdentifier), Is.EqualTo(SqlDataType.UniqueIdentifier));
// Unmapped types fall back to NVarChar
Assert.That(SqlUtils.GetSqlDataType(SqlDbType.Variant), Is.EqualTo(SqlDataType.NVarChar));
});
}
#endregion
#region Default Values
[Test]
public void GetSqlDefaultValueDefinition_KnownTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetSqlDefaultValueDefinition(SqlDataType.Int), Is.EqualTo("(0)"));
Assert.That(SqlUtils.GetSqlDefaultValueDefinition(SqlDataType.VarChar), Is.EqualTo("('')"));
Assert.That(SqlUtils.GetSqlDefaultValueDefinition(SqlDataType.DateTime), Is.EqualTo("(getdate())"));
Assert.That(SqlUtils.GetSqlDefaultValueDefinition(SqlDataType.UniqueIdentifier), Is.EqualTo($"('{Guid.Empty}')"));
});
}
[Test]
public void GetSqlZeroValue_KnownTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetSqlZeroValue(SqlDataType.Int), Is.EqualTo(0));
Assert.That(SqlUtils.GetSqlZeroValue(SqlDataType.NVarChar), Is.EqualTo(string.Empty));
Assert.That(SqlUtils.GetSqlZeroValue(SqlDataType.DateTime), Is.EqualTo(new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)));
Assert.That(SqlUtils.GetSqlZeroValue(SqlDataType.UniqueIdentifier), Is.EqualTo(Guid.Empty));
});
}
[Test]
public void GetSqlDefaultValue_NullDefinition_ReturnsNull()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlDefaultValue(SqlDataType.Int, string.Empty), Is.Null);
}
[Test]
public void GetSqlDefaultValue_ParsesScalarTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetSqlDefaultValue(SqlDataType.Int, "(0)"), Is.EqualTo(0));
Assert.That(SqlUtils.GetSqlDefaultValue(SqlDataType.BigInt, "(123)"), Is.EqualTo(123L));
Assert.That(SqlUtils.GetSqlDefaultValue(SqlDataType.Bit, "(1)"), Is.EqualTo(true));
Assert.That(SqlUtils.GetSqlDefaultValue(SqlDataType.Bit, "(0)"), Is.EqualTo(false));
Assert.That(SqlUtils.GetSqlDefaultValue(SqlDataType.NVarChar, "('hello')"), Is.EqualTo("hello"));
});
}
[Test]
public void GetSqlDefaultValue_UniqueIdentifierZeroGuid_ReturnsEmpty()
{
// Act & Assert
Assert.That(SqlUtils.GetSqlDefaultValue(SqlDataType.UniqueIdentifier, "(dbo.ZeroGUID())"), Is.EqualTo(Guid.Empty));
}
#endregion
#region Join / Connection Strings
[Test]
public void GetSqlJoinTypeString_MapsJoinTypes()
{
Assert.Multiple(() =>
{
Assert.That(SqlUtils.GetSqlJoinTypeString(SqlJoinType.Left), Is.EqualTo("LEFT"));
Assert.That(SqlUtils.GetSqlJoinTypeString(SqlJoinType.Right), Is.EqualTo("RIGHT"));
Assert.That(SqlUtils.GetSqlJoinTypeString(SqlJoinType.Inner), Is.EqualTo("INNER"));
Assert.That(SqlUtils.GetSqlJoinTypeString(SqlJoinType.Cross), Is.EqualTo("CROSS"));
});
}
[Test]
public void GetSqlConnectionString_BuildsIntegratedSecurityConnection()
{
// Act
var result = SqlUtils.GetSqlConnectionString("MyServer", "MyDb");
Assert.Multiple(() =>
{
// Assert
Assert.That(result, Does.Contain("data source=MyServer;"));
Assert.That(result, Does.Contain("initial catalog=MyDb;"));
Assert.That(result, Does.Contain("Integrated Security=SSPI;"));
});
}
#endregion
}
@@ -0,0 +1,39 @@
using Strata.SqlTools.SqlBreakdown.Utilities;
namespace Strata.SqlTools.SqlBreakdown.Tests.Utilities;
[TestFixture]
public class StringUtilsTests
{
[TestCase('a', -842352705)]
[TestCase('z', -842352730)]
[TestCase('A', -842352673)]
[TestCase('Z', -842352698)]
[TestCase('0', -842352768)]
[TestCase('9', -842352759)]
[TestCase(' ', -842352737)]
[TestCase('-', -842352782)]
public void GetHashCode32Bit_KnownCharacters_ReturnsHardcodedHash(char c, int expected)
{
// Act & Assert
Assert.That(StringUtils.GetHashCode32Bit(c), Is.EqualTo(expected));
}
[Test]
public void GetHashCode32Bit_UnmappedCharacter_UsesConsistentFallback()
{
// Arrange - '!' is not in the hardcoded table
const char c = '!';
var expected = -842352737 - (int)c;
// Act & Assert
Assert.That(StringUtils.GetHashCode32Bit(c), Is.EqualTo(expected));
}
[Test]
public void GetHashCode32Bit_SameCharacter_IsDeterministic()
{
// Act & Assert - critical: TranslateTextToGuid relies on stable hashing
Assert.That(StringUtils.GetHashCode32Bit('q'), Is.EqualTo(StringUtils.GetHashCode32Bit('q')));
}
}
@@ -0,0 +1,198 @@
using Strata.SqlTools.Breakdowns.SqlServer;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer;
[TestFixture]
public class DeleteBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyClauses()
{
// Arrange & Act
var breakdown = new DeleteBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.DeleteClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.IsUsingWhereClause, Is.False);
});
}
[Test]
public void Constructor_WithClauses_SetsProperties()
{
// Arrange & Act
var breakdown = new DeleteBreakdown("Users", "Id = @Id");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("Id = @Id"));
Assert.That(breakdown.IsUsingWhereClause, Is.True);
});
}
#endregion
#region GetSql Tests
[Test]
public void GetSql_WithWhere_RendersDeleteFromWhere()
{
// Arrange
var breakdown = new DeleteBreakdown("Users", "Id = @Id");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("DELETE"));
Assert.That(sql, Does.Contain("FROM"));
Assert.That(sql, Does.Contain("Users"));
Assert.That(sql, Does.Contain("WHERE"));
Assert.That(sql, Does.Contain("Id = @Id"));
});
}
[Test]
public void GetSql_WithoutWhere_OmitsWhereKeyword()
{
// Arrange
var breakdown = new DeleteBreakdown("Users", string.Empty);
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("DELETE"));
Assert.That(sql, Does.Not.Contain("WHERE"));
});
}
[Test]
public void GetSql_WithDeleteClause_RendersAlias()
{
// Arrange
var breakdown = new DeleteBreakdown("Users u", "u.Id = 1")
{
DeleteClause = { Clause = "u" }
};
// Act
var sql = breakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("DELETE"));
Assert.That(sql, Does.Contain("u"));
}
#endregion
#region Parse Tests
[Test]
public void Parse_SimpleDeleteFrom_PopulatesClauses()
{
// Arrange
var input = "DELETE FROM Users WHERE Id = 1";
// Act
var breakdown = DeleteBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("Id = 1"));
});
}
[Test]
public void Parse_DeleteWithAlias_PopulatesDeleteAndFromClauses()
{
// Arrange - DELETE u FROM Users u WHERE ... uses the alias pattern
var input = "DELETE u FROM Users u WHERE u.Id = 1";
// Act
var breakdown = DeleteBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.DeleteClause.Clause, Is.EqualTo("u"));
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Users u"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("u.Id = 1"));
});
}
[Test]
public void Parse_DeleteWithoutWhere_LeavesWhereEmpty()
{
// Arrange
var input = "DELETE FROM Users";
// Act
var breakdown = DeleteBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.IsUsingWhereClause, Is.False);
});
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => DeleteBreakdown.Parse(null!));
}
[Test]
public void Parse_NotADeleteStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => DeleteBreakdown.Parse("SELECT * FROM Users"));
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalseWithMessage()
{
// Act
var success = DeleteBreakdown.TryParse("UPDATE Users SET Id = 1", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("DELETE"));
});
}
[Test]
public void TryParse_ValidDelete_ReturnsTrue()
{
// Act
var success = DeleteBreakdown.TryParse("DELETE FROM Users WHERE Id = 1", out var breakdown);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown.FromClause.Clause, Is.EqualTo("Users"));
});
}
#endregion
}
@@ -0,0 +1,209 @@
using Strata.SqlTools.Breakdowns.SqlServer;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer;
[TestFixture]
public class InsertBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyClauses()
{
// Arrange & Act
var breakdown = new InsertBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.SetupClauses, Is.Empty);
Assert.That(breakdown.FinishClauses, Is.Empty);
});
}
[Test]
public void Constructor_WithClauses_SetsProperties()
{
// Arrange & Act
var breakdown = new InsertBreakdown("Users", "Id, Name", "@Id, @Name");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo("Id, Name"));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo("@Id, @Name"));
});
}
[Test]
public void Constructor_WithClauses_ExtractsComments()
{
// Arrange & Act
var breakdown = new InsertBreakdown("Users /* target */", "Id, Name", "@Id, @Name");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.TableName.Comment, Does.Contain("target"));
});
}
[Test]
public void Constructor_WithColumnNames_GeneratesBracketedColumnsAndAtParameters()
{
// Arrange & Act
var breakdown = new InsertBreakdown("Users", new List<string> { "Id", "Name" });
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo("[Id],[Name]"));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo("@Id,@Name"));
});
}
#endregion
#region GetSql Tests
[Test]
public void GetSql_RendersInsertIntoTableColumnsValues()
{
// Arrange
var breakdown = new InsertBreakdown("Users", "Id, Name", "@Id, @Name");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("INSERT INTO"));
Assert.That(sql, Does.Contain("Users"));
Assert.That(sql, Does.Contain("Id, Name"));
Assert.That(sql, Does.Contain("VALUES"));
Assert.That(sql, Does.Contain("@Id, @Name"));
});
}
[Test]
public void ToString_ReturnsGeneratedSql()
{
// Arrange
var breakdown = new InsertBreakdown("Users", "Id", "@Id");
// Act & Assert
Assert.That(breakdown.ToString(), Does.Contain("INSERT INTO"));
}
#endregion
#region Parse Tests
[Test]
public void Parse_ValidInsert_PopulatesClauses()
{
// Arrange
var input = "INSERT INTO Users (Id, Name) VALUES (1, 'Bob')";
// Act
var breakdown = InsertBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.InsertIntoClause.Clause, Is.EqualTo("Id, Name"));
Assert.That(breakdown.ValuesClause.Clause, Is.EqualTo("1, 'Bob'"));
});
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => InsertBreakdown.Parse(" "));
}
[Test]
public void Parse_NotAnInsertStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => InsertBreakdown.Parse("SELECT * FROM Users"));
}
[Test]
public void TryParse_ValidInsert_ReturnsTrue()
{
// Act
var success = InsertBreakdown.TryParse("INSERT INTO Users (Id) VALUES (1)", out var breakdown);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
});
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalseWithMessage()
{
// Act
var success = InsertBreakdown.TryParse("UPDATE Users SET Id = 1", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("INSERT INTO"));
});
}
[Test]
public void TryParse_MalformedInsert_ReturnsFalse()
{
// Act - matches the INSERT INTO prefix but not the column/values grammar
var success = InsertBreakdown.TryParse("INSERT INTO Users", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public void GetSql_IncludeSetupFinish_RendersSetupAndFinishClauses()
{
// Arrange
var breakdown = new InsertBreakdown("Users", "Id", "@Id")
{
SetupClauses = { "SET NOCOUNT ON" },
FinishClauses = { "SELECT SCOPE_IDENTITY()" }
};
// Act
var withClauses = breakdown.GetSql(includeSetupFinish: true);
var withoutClauses = breakdown.GetSql(includeSetupFinish: false);
Assert.Multiple(() =>
{
// Assert
Assert.That(withClauses, Does.Contain("SET NOCOUNT ON"));
Assert.That(withClauses, Does.Contain("SELECT SCOPE_IDENTITY()"));
Assert.That(withClauses, Does.Contain("INSERT INTO"));
Assert.That(withoutClauses, Does.Not.Contain("SET NOCOUNT ON"));
Assert.That(withoutClauses, Does.Not.Contain("SELECT SCOPE_IDENTITY()"));
});
}
#endregion
}
@@ -0,0 +1,223 @@
using Strata.SqlTools.Breakdowns.SqlServer;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer;
[TestFixture]
public class ProcedureBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyState()
{
// Arrange & Act
var breakdown = new ProcedureBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.Parameters, Is.Empty);
Assert.That(breakdown.IsUsingParameters, Is.False);
});
}
[Test]
public void Constructor_WithName_SetsProcedureName()
{
// Arrange & Act
var breakdown = new ProcedureBreakdown("dbo.GetUsers");
// Assert
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("dbo.GetUsers"));
}
[Test]
public void Constructor_WithParameters_SetsParameters()
{
// Arrange
var parameters = new Dictionary<string, string> { ["@Id"] = "1" };
// Act
var breakdown = new ProcedureBreakdown("dbo.GetUser", parameters);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.IsUsingParameters, Is.True);
Assert.That(breakdown.Parameters["@Id"], Is.EqualTo("1"));
});
}
#endregion
#region AddParameter Tests
[Test]
public void AddParameter_WithoutAtPrefix_AddsAtPrefix()
{
// Arrange
var breakdown = new ProcedureBreakdown("dbo.GetUser");
// Act
breakdown.AddParameter("Id", "1");
// Assert
Assert.That(breakdown.Parameters.ContainsKey("@Id"), Is.True);
}
[Test]
public void AddParameter_WithAtPrefix_KeepsSinglePrefix()
{
// Arrange
var breakdown = new ProcedureBreakdown("dbo.GetUser");
// Act
breakdown.AddParameter("@Id", "1");
// Assert
Assert.That(breakdown.Parameters.ContainsKey("@Id"), Is.True);
}
[Test]
public void AddParameter_NullName_ThrowsArgumentNullException()
{
// Arrange
var breakdown = new ProcedureBreakdown("dbo.GetUser");
// Act & Assert
Assert.Throws<ArgumentNullException>(() => breakdown.AddParameter(" ", "1"));
}
#endregion
#region GetSql Tests
[Test]
public void GetSql_WithoutParameters_RendersExecName()
{
// Arrange
var breakdown = new ProcedureBreakdown("dbo.GetUsers");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("EXEC"));
Assert.That(sql, Does.Contain("dbo.GetUsers"));
});
}
[Test]
public void GetSql_WithParameters_RendersNamedAssignments()
{
// Arrange
var breakdown = new ProcedureBreakdown("dbo.GetUser");
breakdown.AddParameter("@Id", "1");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("EXEC"));
Assert.That(sql, Does.Contain("@Id = 1"));
});
}
#endregion
#region Parse Tests
[Test]
public void Parse_ExecWithoutParameters_PopulatesName()
{
// Arrange
var input = "EXEC dbo.GetUsers";
// Act
var breakdown = ProcedureBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("dbo.GetUsers"));
Assert.That(breakdown.IsUsingParameters, Is.False);
});
}
[Test]
public void Parse_ExecWithParameters_PopulatesParameters()
{
// Arrange
var input = "EXEC dbo.GetUser @Id = 1, @Active = 1";
// Act
var breakdown = ProcedureBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("dbo.GetUser"));
Assert.That(breakdown.Parameters["@Id"], Is.EqualTo("1"));
Assert.That(breakdown.Parameters["@Active"], Is.EqualTo("1"));
});
}
[Test]
public void Parse_ExecuteKeyword_IsAccepted()
{
// Act
var breakdown = ProcedureBreakdown.Parse("EXECUTE dbo.GetUsers");
// Assert
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("dbo.GetUsers"));
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => ProcedureBreakdown.Parse(" "));
}
[Test]
public void Parse_NotAnExecStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => ProcedureBreakdown.Parse("SELECT * FROM Users"));
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalseWithMessage()
{
// Act
var success = ProcedureBreakdown.TryParse("SELECT 1", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("EXEC"));
});
}
[Test]
public void TryParse_ValidExec_ReturnsTrue()
{
// Act
var success = ProcedureBreakdown.TryParse("EXEC dbo.GetUsers", out var breakdown);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown.ProcedureName.Clause, Is.EqualTo("dbo.GetUsers"));
});
}
#endregion
}
@@ -0,0 +1,208 @@
using Strata.SqlTools.Breakdowns.SqlServer;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer;
[TestFixture]
public class UpdateBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyClauses()
{
// Arrange & Act
var breakdown = new UpdateBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.SetClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.FromClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo(string.Empty));
Assert.That(breakdown.IsUsingFromClause, Is.False);
Assert.That(breakdown.IsUsingWhereClause, Is.False);
});
}
[Test]
public void Constructor_WithClauses_SetsProperties()
{
// Arrange & Act
var breakdown = new UpdateBreakdown("Users", "Name = @Name", "Id = @Id");
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.SetClause.Clause, Is.EqualTo("Name = @Name"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("Id = @Id"));
Assert.That(breakdown.IsUsingWhereClause, Is.True);
});
}
#endregion
#region GetSql Tests
[Test]
public void GetSql_WithWhere_RendersUpdateSetWhere()
{
// Arrange
var breakdown = new UpdateBreakdown("Users", "Name = @Name", "Id = @Id");
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("UPDATE"));
Assert.That(sql, Does.Contain("Users"));
Assert.That(sql, Does.Contain("SET"));
Assert.That(sql, Does.Contain("Name = @Name"));
Assert.That(sql, Does.Contain("WHERE"));
Assert.That(sql, Does.Contain("Id = @Id"));
});
}
[Test]
public void GetSql_WithoutWhere_OmitsWhereKeyword()
{
// Arrange
var breakdown = new UpdateBreakdown("Users", "Name = @Name", string.Empty);
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(sql, Does.Contain("UPDATE"));
Assert.That(sql, Does.Not.Contain("WHERE"));
});
}
[Test]
public void GetSql_WithFromClause_RendersFromSection()
{
// Arrange
var breakdown = new UpdateBreakdown("u", "u.Name = s.Name", "u.Id = s.Id")
{
FromClause = { Clause = "Users u INNER JOIN Staging s ON u.Id = s.Id" }
};
// Act
var sql = breakdown.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.IsUsingFromClause, Is.True);
Assert.That(sql, Does.Contain("FROM"));
Assert.That(sql, Does.Contain("INNER JOIN Staging"));
});
}
#endregion
#region Parse Tests
[Test]
public void Parse_SimpleUpdate_PopulatesClauses()
{
// Arrange
var input = "UPDATE Users SET Name = 'Bob' WHERE Id = 1";
// Act
var breakdown = UpdateBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
Assert.That(breakdown.SetClause.Clause, Is.EqualTo("Name = 'Bob'"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("Id = 1"));
});
}
[Test]
public void Parse_UpdateWithFrom_PopulatesFromClause()
{
// Arrange
var input = "UPDATE u SET u.Name = s.Name FROM Users u INNER JOIN Staging s ON u.Id = s.Id WHERE s.Active = 1";
// Act
var breakdown = UpdateBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.TableName.Clause, Is.EqualTo("u"));
Assert.That(breakdown.IsUsingFromClause, Is.True);
Assert.That(breakdown.FromClause.Clause, Does.Contain("INNER JOIN Staging"));
Assert.That(breakdown.WhereClause.Clause, Is.EqualTo("s.Active = 1"));
});
}
[Test]
public void Parse_UpdateWithoutWhere_LeavesWhereEmpty()
{
// Arrange
var input = "UPDATE Users SET Name = 'Bob'";
// Act
var breakdown = UpdateBreakdown.Parse(input);
Assert.Multiple(() =>
{
// Assert
Assert.That(breakdown.SetClause.Clause, Is.EqualTo("Name = 'Bob'"));
Assert.That(breakdown.IsUsingWhereClause, Is.False);
});
}
[Test]
public void Parse_NullOrWhitespace_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => UpdateBreakdown.Parse(string.Empty));
}
[Test]
public void Parse_NotAnUpdateStatement_ThrowsFormatException()
{
// Act & Assert
Assert.Throws<FormatException>(() => UpdateBreakdown.Parse("DELETE FROM Users"));
}
[Test]
public void TryParse_WrongPrefix_ReturnsFalseWithMessage()
{
// Act
var success = UpdateBreakdown.TryParse("SELECT * FROM Users", out _, out var error);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Does.Contain("UPDATE"));
});
}
[Test]
public void TryParse_ValidUpdate_ReturnsTrue()
{
// Act
var success = UpdateBreakdown.TryParse("UPDATE Users SET Name = 'Bob' WHERE Id = 1", out var breakdown);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(breakdown.TableName.Clause, Is.EqualTo("Users"));
});
}
#endregion
}