1179 lines
35 KiB
Plaintext
1179 lines
35 KiB
Plaintext
using Strata.SqlTools.Interfaces;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Strata.SqlTools.SqlBreakdown.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the SqlBreakdownCollection class.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class SqlBreakdownCollectionTests
|
|
{
|
|
[Test]
|
|
public void Constructor_WithNoArguments_CreatesEmptyCollection()
|
|
{
|
|
// Arrange & Act
|
|
var collection = new SqlBreakdownCollection();
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(0));
|
|
Assert.That(collection.IsEmpty, Is.True);
|
|
Assert.That(collection.Breakdowns, Is.Empty);
|
|
Assert.That(collection.RawStatements, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void Constructor_WithBreakdownList_InitializesCollection()
|
|
{
|
|
// Arrange
|
|
var mockBreakdowns = new List<ISqlBreakdown>
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2")
|
|
};
|
|
|
|
// Act
|
|
var collection = new SqlBreakdownCollection(mockBreakdowns);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.IsEmpty, Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void Add_WithValidBreakdown_AddsToCollection()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var breakdown = new MockSqlBreakdown("SELECT * FROM Table1");
|
|
|
|
// Act
|
|
collection.Add(breakdown);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(1));
|
|
Assert.That(collection.GetAt(0), Is.EqualTo(breakdown));
|
|
}
|
|
|
|
[Test]
|
|
public void Add_WithNullBreakdown_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => collection.Add(null!));
|
|
}
|
|
|
|
[Test]
|
|
public void AddRange_WithMultipleBreakdowns_AddsAllItems()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var breakdowns = new List<ISqlBreakdown>
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2"),
|
|
new MockSqlBreakdown("SELECT * FROM Table3")
|
|
};
|
|
|
|
// Act
|
|
collection.AddRange(breakdowns);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
}
|
|
|
|
[Test]
|
|
public void AddRange_WithNullCollection_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => collection.AddRange(null!));
|
|
}
|
|
|
|
[Test]
|
|
public void Remove_WithExistingBreakdown_RemovesItem()
|
|
{
|
|
// Arrange
|
|
var breakdown = new MockSqlBreakdown("SELECT * FROM Table1");
|
|
var collection = new SqlBreakdownCollection(new[] { breakdown });
|
|
|
|
// Act
|
|
var result = collection.Remove(breakdown);
|
|
|
|
// Assert
|
|
Assert.That(result, Is.True);
|
|
Assert.That(collection.Count, Is.EqualTo(0));
|
|
}
|
|
|
|
[Test]
|
|
public void Remove_WithNonExistingBreakdown_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
|
|
var otherBreakdown = new MockSqlBreakdown("SELECT * FROM Table2");
|
|
|
|
// Act
|
|
var result = collection.Remove(otherBreakdown);
|
|
|
|
// Assert
|
|
Assert.That(result, Is.False);
|
|
Assert.That(collection.Count, Is.EqualTo(1));
|
|
}
|
|
|
|
[Test]
|
|
public void Clear_WithMultipleItems_RemovesAll()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection(new[]
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2")
|
|
});
|
|
|
|
// Act
|
|
collection.Clear();
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(0));
|
|
Assert.That(collection.IsEmpty, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithGoSeparators_ParsesStatements()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
SELECT * FROM Table1
|
|
GO
|
|
SELECT * FROM Table2
|
|
GO
|
|
SELECT * FROM Table3
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
|
Assert.That(collection.RawStatements[0], Does.Contain("Table1"));
|
|
Assert.That(collection.RawStatements[1], Does.Contain("Table2"));
|
|
Assert.That(collection.RawStatements[2], Does.Contain("Table3"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithMixedCaseGO_ParsesCorrectly()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
SELECT * FROM Table1
|
|
go
|
|
SELECT * FROM Table2
|
|
GO
|
|
SELECT * FROM Table3
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithGoAndWhitespace_ParsesCorrectly()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
SELECT * FROM Table1
|
|
GO
|
|
SELECT * FROM Table2
|
|
GO
|
|
SELECT * FROM Table3
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithEmptyStatements_FiltersOutEmptyValues()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
SELECT * FROM Table1
|
|
GO
|
|
GO
|
|
|
|
SELECT * FROM Table2
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.RawStatements.Count, Is.EqualTo(2));
|
|
Assert.That(collection.RawStatements[0], Does.Contain("Table1"));
|
|
Assert.That(collection.RawStatements[1], Does.Contain("Table2"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithNullInput_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => collection.ParseBatch(null!));
|
|
}
|
|
|
|
[Test]
|
|
public void GetCombinedSql_WithMultipleBreakdowns_CombinesSqlWithSeparator()
|
|
{
|
|
// Arrange
|
|
var breakdowns = new[]
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2")
|
|
};
|
|
var collection = new SqlBreakdownCollection(breakdowns);
|
|
|
|
// Act
|
|
var result = collection.GetCombinedSql();
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("Table1"));
|
|
Assert.That(result, Does.Contain("Table2"));
|
|
Assert.That(result, Does.Contain("GO"));
|
|
}
|
|
|
|
[Test]
|
|
public void GetCombinedSql_WithEmptyCollection_ReturnsEmpty()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
|
|
// Act
|
|
var result = collection.GetCombinedSql();
|
|
|
|
// Assert
|
|
Assert.That(result, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void GetCombinedSql_WithCustomSeparator_UsesCustomSeparator()
|
|
{
|
|
// Arrange
|
|
var breakdowns = new[]
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2")
|
|
};
|
|
var collection = new SqlBreakdownCollection(breakdowns);
|
|
|
|
// Act
|
|
var result = collection.GetCombinedSql(true, ";");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain(";"));
|
|
Assert.That(result, Does.Not.Contain("GO"));
|
|
}
|
|
|
|
[Test]
|
|
public void GetBatchSql_WithRawStatements_CombinesWithGO()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
collection.ParseBatch(@"
|
|
SELECT * FROM Table1
|
|
GO
|
|
SELECT * FROM Table2
|
|
");
|
|
|
|
// Act
|
|
var result = collection.GetBatchSql();
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("Table1"));
|
|
Assert.That(result, Does.Contain("GO"));
|
|
Assert.That(result, Does.Contain("Table2"));
|
|
}
|
|
|
|
[Test]
|
|
public void GetBatchSql_WithEmptyCollection_ReturnsEmpty()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
|
|
// Act
|
|
var result = collection.GetBatchSql();
|
|
|
|
// Assert
|
|
Assert.That(result, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void Where_WithValidPredicate_FiltersBreakdowns()
|
|
{
|
|
// Arrange
|
|
var breakdowns = new[]
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2"),
|
|
new MockSqlBreakdown("SELECT * FROM Table3")
|
|
};
|
|
var collection = new SqlBreakdownCollection(breakdowns);
|
|
|
|
// Act
|
|
var filtered = collection.Where(b => b.ToString()!.Contains("Table2")).ToList();
|
|
|
|
// Assert
|
|
Assert.That(filtered.Count, Is.EqualTo(1));
|
|
Assert.That(filtered[0].ToString()!, Does.Contain("Table2"));
|
|
}
|
|
|
|
[Test]
|
|
public void Select_WithValidSelector_ProjectsBreakdowns()
|
|
{
|
|
// Arrange
|
|
var breakdowns = new[]
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2")
|
|
};
|
|
var collection = new SqlBreakdownCollection(breakdowns);
|
|
|
|
// Act
|
|
var projected = collection.Select(b => b.ToString()!.Length).ToList();
|
|
|
|
// Assert
|
|
Assert.That(projected.Count, Is.EqualTo(2));
|
|
Assert.That(projected[0], Is.GreaterThan(0));
|
|
}
|
|
|
|
[Test]
|
|
public void GetAt_WithValidIndex_ReturnsBreakdown()
|
|
{
|
|
// Arrange
|
|
var breakdown1 = new MockSqlBreakdown("SELECT * FROM Table1");
|
|
var breakdown2 = new MockSqlBreakdown("SELECT * FROM Table2");
|
|
var collection = new SqlBreakdownCollection(new[] { breakdown1, breakdown2 });
|
|
|
|
// Act
|
|
var result = collection.GetAt(1);
|
|
|
|
// Assert
|
|
Assert.That(result, Is.EqualTo(breakdown2));
|
|
}
|
|
|
|
[Test]
|
|
public void GetAt_WithInvalidIndex_ThrowsIndexOutOfRangeException()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
|
|
|
|
// Act & Assert
|
|
Assert.Throws<IndexOutOfRangeException>(() => collection.GetAt(5));
|
|
}
|
|
|
|
[Test]
|
|
public void FirstOrDefault_WithMatchingPredicate_ReturnsBreakdown()
|
|
{
|
|
// Arrange
|
|
var breakdowns = new[]
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2")
|
|
};
|
|
var collection = new SqlBreakdownCollection(breakdowns);
|
|
|
|
// Act
|
|
var result = collection.FirstOrDefault(b => b.ToString()!.Contains("Table2"));
|
|
|
|
// Assert
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result!.ToString(), Does.Contain("Table2"));
|
|
}
|
|
|
|
[Test]
|
|
public void FirstOrDefault_WithNoMatch_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
|
|
|
|
// Act
|
|
var result = collection.FirstOrDefault(b => b.ToString()!.Contains("NonExistent"));
|
|
|
|
// Assert
|
|
Assert.That(result, Is.Null);
|
|
}
|
|
|
|
[Test]
|
|
public void GetRawStatementAt_WithValidIndex_ReturnsStatement()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
collection.ParseBatch(@"
|
|
SELECT * FROM Table1
|
|
GO
|
|
SELECT * FROM Table2
|
|
");
|
|
|
|
// Act
|
|
var result = collection.GetRawStatementAt(1);
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("Table2"));
|
|
}
|
|
|
|
[Test]
|
|
public void GetRawStatementAt_WithInvalidIndex_ThrowsIndexOutOfRangeException()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
collection.ParseBatch("SELECT * FROM Table1");
|
|
|
|
// Act & Assert
|
|
Assert.Throws<IndexOutOfRangeException>(() => collection.GetRawStatementAt(5));
|
|
}
|
|
|
|
[Test]
|
|
public void ToString_ReturnsFormattedSql()
|
|
{
|
|
// Arrange
|
|
var breakdowns = new[]
|
|
{
|
|
new MockSqlBreakdown("SELECT * FROM Table1"),
|
|
new MockSqlBreakdown("SELECT * FROM Table2")
|
|
};
|
|
var collection = new SqlBreakdownCollection(breakdowns);
|
|
|
|
// Act
|
|
var result = collection.ToString();
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("Table1"));
|
|
Assert.That(result, Does.Contain("Table2"));
|
|
Assert.That(result, Does.Contain("GO"));
|
|
}
|
|
|
|
// ============================================================
|
|
// CRUD Tests - Complete SQL Statement Coverage
|
|
// ============================================================
|
|
|
|
#region CREATE Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithCreateTableStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
CREATE TABLE Users (
|
|
Id INT PRIMARY KEY,
|
|
Name NVARCHAR(100),
|
|
Email NVARCHAR(100)
|
|
)
|
|
GO
|
|
CREATE TABLE Orders (
|
|
OrderId INT PRIMARY KEY,
|
|
UserId INT FOREIGN KEY REFERENCES Users(Id),
|
|
OrderDate DATETIME
|
|
)
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE TABLE Users"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("CREATE TABLE Orders"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithCreateIndexStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
CREATE INDEX idx_Users_Email ON Users(Email)
|
|
GO
|
|
CREATE UNIQUE INDEX idx_Orders_OrderDate ON Orders(OrderDate)
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE INDEX"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("UNIQUE"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithCreateProcedureStatement_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
CREATE PROCEDURE sp_GetUserById
|
|
@UserId INT
|
|
AS
|
|
BEGIN
|
|
SELECT * FROM Users WHERE Id = @UserId
|
|
END
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE PROCEDURE"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region INSERT (CREATE Data) Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithInsertStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
INSERT INTO Users (Id, Name, Email) VALUES (1, 'John Doe', 'john@example.com')
|
|
GO
|
|
INSERT INTO Users (Id, Name, Email) VALUES (2, 'Jane Smith', 'jane@example.com')
|
|
GO
|
|
INSERT INTO Users (Id, Name, Email) VALUES (3, 'Bob Johnson', 'bob@example.com')
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INSERT INTO Users"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Jane Smith"));
|
|
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Bob Johnson"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithInsertSelectStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
INSERT INTO UsersArchive
|
|
SELECT Id, Name, Email FROM Users WHERE CreatedDate < '2020-01-01'
|
|
GO
|
|
INSERT INTO OrdersBackup
|
|
SELECT OrderId, UserId, OrderDate FROM Orders WHERE OrderDate < DATEADD(year, -1, GETDATE())
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INSERT INTO UsersArchive"));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("SELECT"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithBulkInsertStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
BULK INSERT Users FROM 'C:\data\users.csv'
|
|
WITH (FIELDTERMINATOR=',', ROWTERMINATOR='\n')
|
|
GO
|
|
BULK INSERT Orders FROM 'C:\data\orders.csv'
|
|
WITH (FIELDTERMINATOR=',', ROWTERMINATOR='\n')
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("BULK INSERT"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SELECT (READ) Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithComplexSelectStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
SELECT u.Id, u.Name, COUNT(o.OrderId) AS OrderCount
|
|
FROM Users u
|
|
LEFT JOIN Orders o ON u.Id = o.UserId
|
|
WHERE u.CreatedDate > '2020-01-01'
|
|
GROUP BY u.Id, u.Name
|
|
HAVING COUNT(o.OrderId) > 0
|
|
ORDER BY OrderCount DESC
|
|
GO
|
|
SELECT * FROM Orders WHERE OrderDate BETWEEN '2020-01-01' AND '2021-12-31'
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("LEFT JOIN"));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("GROUP BY"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("BETWEEN"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithSelectWithCommonTableExpression_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
WITH UserOrders AS (
|
|
SELECT UserId, OrderId, OrderDate
|
|
FROM Orders
|
|
WHERE OrderDate > '2020-01-01'
|
|
)
|
|
SELECT u.Id, u.Name, uo.OrderId
|
|
FROM Users u
|
|
INNER JOIN UserOrders uo ON u.Id = uo.UserId
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("WITH UserOrders AS"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithSelectFromMultipleTables_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
SELECT u.Id, u.Name, o.OrderId, p.ProductName
|
|
FROM Users u
|
|
JOIN Orders o ON u.Id = o.UserId
|
|
JOIN OrderItems oi ON o.OrderId = oi.OrderId
|
|
JOIN Products p ON oi.ProductId = p.Id
|
|
WHERE u.Country = 'USA'
|
|
GO
|
|
SELECT * FROM UserProfiles WHERE UserId IN (SELECT Id FROM Users)
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("JOIN"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("IN"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region UPDATE Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithUpdateStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
UPDATE Users SET Name = 'John Updated', ModifiedDate = GETDATE() WHERE Id = 1
|
|
GO
|
|
UPDATE Orders SET Status = 'Shipped' WHERE OrderDate < DATEADD(day, -30, GETDATE())
|
|
GO
|
|
UPDATE Products SET Price = Price * 1.1 WHERE Category = 'Electronics'
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE Users"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Status"));
|
|
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Price * 1.1"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithUpdateFromSelectStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
UPDATE u
|
|
SET u.LastOrderDate = (SELECT MAX(OrderDate) FROM Orders WHERE UserId = u.Id)
|
|
FROM Users u
|
|
WHERE u.Id IN (SELECT DISTINCT UserId FROM Orders)
|
|
GO
|
|
UPDATE Products
|
|
SET Quantity = Quantity - 1
|
|
FROM OrderItems oi
|
|
WHERE Products.Id = oi.ProductId AND oi.OrderId = 100
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE u"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Quantity - 1"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithUpdateWithJoinStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
UPDATE Orders
|
|
SET OrderStatus = 'Processed'
|
|
FROM Orders o
|
|
JOIN OrderItems oi ON o.OrderId = oi.OrderId
|
|
WHERE oi.Quantity > 50
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE Orders"));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("JOIN"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DELETE Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithDeleteStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
DELETE FROM Orders WHERE OrderDate < '2019-01-01'
|
|
GO
|
|
DELETE FROM UserProfiles WHERE UserId NOT IN (SELECT Id FROM Users)
|
|
GO
|
|
DELETE FROM AuditLog WHERE LogDate < DATEADD(year, -2, GETDATE())
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DELETE FROM Orders"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("NOT IN"));
|
|
Assert.That(collection.GetRawStatementAt(2), Does.Contain("DATEADD"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithDeleteFromSelectStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
DELETE o FROM Orders o
|
|
WHERE NOT EXISTS (SELECT 1 FROM OrderItems oi WHERE oi.OrderId = o.OrderId)
|
|
GO
|
|
DELETE FROM Users
|
|
WHERE Id IN (SELECT UserId FROM Orders WHERE OrderStatus = 'Cancelled')
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("EXISTS"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithDeleteWithJoinStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
DELETE Orders
|
|
FROM Orders o
|
|
INNER JOIN OrderStatuses os ON o.StatusId = os.Id
|
|
WHERE os.StatusName = 'Archived'
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DELETE Orders"));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INNER JOIN"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DROP Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithDropTableStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
DROP TABLE IF EXISTS OrderItems
|
|
GO
|
|
DROP TABLE IF EXISTS Orders
|
|
GO
|
|
DROP TABLE IF EXISTS Users
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DROP TABLE"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("DROP TABLE"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithDropIndexAndProcedureStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
DROP INDEX IF EXISTS idx_Users_Email ON Users
|
|
GO
|
|
DROP PROCEDURE IF EXISTS sp_GetUserById
|
|
GO
|
|
DROP VIEW IF EXISTS vw_UserOrders
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DROP INDEX"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("PROCEDURE"));
|
|
Assert.That(collection.GetRawStatementAt(2), Does.Contain("VIEW"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Mixed CRUD Operations Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithMixedCrudOperations_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
CREATE TABLE Users (
|
|
Id INT PRIMARY KEY,
|
|
Name NVARCHAR(100),
|
|
Email NVARCHAR(100)
|
|
)
|
|
GO
|
|
INSERT INTO Users (Id, Name, Email) VALUES (1, 'John Doe', 'john@example.com')
|
|
GO
|
|
INSERT INTO Users (Id, Name, Email) VALUES (2, 'Jane Smith', 'jane@example.com')
|
|
GO
|
|
SELECT * FROM Users WHERE Id = 1
|
|
GO
|
|
UPDATE Users SET Name = 'John Updated' WHERE Id = 1
|
|
GO
|
|
DELETE FROM Users WHERE Id = 2
|
|
GO
|
|
DROP TABLE Users
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(7));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE TABLE"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("INSERT"));
|
|
Assert.That(collection.GetRawStatementAt(3), Does.Contain("SELECT"));
|
|
Assert.That(collection.GetRawStatementAt(4), Does.Contain("UPDATE"));
|
|
Assert.That(collection.GetRawStatementAt(5), Does.Contain("DELETE"));
|
|
Assert.That(collection.GetRawStatementAt(6), Does.Contain("DROP"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithComplexMultipleStatements_ParsesAllStatements()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
CREATE TABLE Products (
|
|
Id INT PRIMARY KEY,
|
|
Name NVARCHAR(100),
|
|
Price DECIMAL(10, 2),
|
|
Quantity INT
|
|
)
|
|
GO
|
|
INSERT INTO Products (Id, Name, Price, Quantity)
|
|
SELECT Id, ProductName, BasePrice, StockQuantity
|
|
FROM StagingProducts
|
|
WHERE IsActive = 1
|
|
GO
|
|
UPDATE Products SET Price = Price * 1.1 WHERE Quantity < 10
|
|
GO
|
|
SELECT p.Name,
|
|
COUNT(oi.Id) AS OrderCount,
|
|
SUM(oi.Quantity) AS TotalSold
|
|
FROM Products p
|
|
LEFT JOIN OrderItems oi ON p.Id = oi.ProductId
|
|
GROUP BY p.Id, p.Name
|
|
HAVING SUM(oi.Quantity) > 100
|
|
GO
|
|
DELETE FROM Products WHERE Quantity = 0 AND LastUpdated < DATEADD(month, -6, GETDATE())
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(5));
|
|
Assert.That(collection.RawStatements.Count, Is.EqualTo(5));
|
|
foreach (var statement in collection.RawStatements)
|
|
{
|
|
Assert.That(statement, Is.Not.Empty);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Transaction Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithTransactionStatements_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
BEGIN TRANSACTION UpdatePrices
|
|
UPDATE Products SET Price = Price * 1.1 WHERE Category = 'Electronics'
|
|
UPDATE ProductPriceHistory SET NewPrice = Price FROM Products WHERE Products.Id = ProductPriceHistory.ProductId
|
|
COMMIT TRANSACTION UpdatePrices
|
|
GO
|
|
BEGIN TRANSACTION DeleteOldOrders
|
|
DELETE FROM OrderItems WHERE OrderId IN (SELECT OrderId FROM Orders WHERE OrderDate < '2018-01-01')
|
|
DELETE FROM Orders WHERE OrderDate < '2018-01-01'
|
|
COMMIT TRANSACTION DeleteOldOrders
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
|
|
var statement = collection.GetRawStatementAt(0);
|
|
Assert.That(statement, Does.Contain("BEGIN TRANSACTION"));
|
|
Assert.That(statement, Does.Contain("COMMIT"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Custom Separator Tests
|
|
|
|
[Test]
|
|
public void ParseBatch_WithSemicolonSeparator_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
SELECT * FROM Users
|
|
;
|
|
SELECT * FROM Orders
|
|
;
|
|
SELECT * FROM Products
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql, ";");
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("Users"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Orders"));
|
|
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Products"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithCustomSeparator_ParsesSuccessfully()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
INSERT INTO Users VALUES (1, 'John')
|
|
~~~
|
|
INSERT INTO Users VALUES (2, 'Jane')
|
|
~~~
|
|
INSERT INTO Users VALUES (3, 'Bob')
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql, "~~~");
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(3));
|
|
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Parameter Tests with CRUD
|
|
|
|
[Test]
|
|
public void ParseBatch_WithParameterizedInsertStatements_PreservesParameters()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
INSERT INTO Users (Id, Name, Email) VALUES (@UserId, @UserName, @UserEmail)
|
|
GO
|
|
INSERT INTO Orders (OrderId, UserId, OrderDate) VALUES (@OrderId, @UserId, @OrderDate)
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@UserId"));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@UserEmail"));
|
|
}
|
|
|
|
[Test]
|
|
public void ParseBatch_WithParameterizedUpdateStatements_PreservesParameters()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
var batchSql = @"
|
|
UPDATE Users SET Name = @Name, Email = @Email WHERE Id = @UserId
|
|
GO
|
|
UPDATE Orders SET Status = @Status, ModifiedDate = @ModifiedDate WHERE OrderId = @OrderId
|
|
";
|
|
|
|
// Act
|
|
collection.ParseBatch(batchSql);
|
|
|
|
// Assert
|
|
Assert.That(collection.Count, Is.EqualTo(2));
|
|
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@Name"));
|
|
Assert.That(collection.GetRawStatementAt(1), Does.Contain("@Status"));
|
|
}
|
|
|
|
[Test]
|
|
public void GetCombinedSql_WithMixedCrudOperations_CombinesCorrectly()
|
|
{
|
|
// Arrange
|
|
var collection = new SqlBreakdownCollection();
|
|
collection.ParseBatch(@"
|
|
INSERT INTO Users (Id, Name) VALUES (1, 'John')
|
|
GO
|
|
UPDATE Users SET Name = 'Jane' WHERE Id = 1
|
|
GO
|
|
DELETE FROM Users WHERE Id = 1
|
|
");
|
|
|
|
// Act
|
|
var combined = collection.GetCombinedSql();
|
|
|
|
// Assert
|
|
Assert.That(combined, Does.Contain("INSERT"));
|
|
Assert.That(combined, Does.Contain("UPDATE"));
|
|
Assert.That(combined, Does.Contain("DELETE"));
|
|
Assert.That(combined, Does.Contain("GO"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Mock implementation of ISqlBreakdown for testing purposes.
|
|
/// </summary>
|
|
public class MockSqlBreakdown : ISqlBreakdown
|
|
{
|
|
private readonly string _sql;
|
|
|
|
public MockSqlBreakdown(string sql)
|
|
{
|
|
_sql = sql;
|
|
SetupClauses = new List<string>();
|
|
FinishClauses = new System.Collections.ArrayList();
|
|
}
|
|
|
|
public string? RawSql { get; set; }
|
|
public List<string> SetupClauses { get; set; }
|
|
public bool IsUsingSetupClause => SetupClauses.Count > 0;
|
|
public System.Collections.ArrayList FinishClauses { get; set; }
|
|
public bool IsUsingFinishClause => FinishClauses.Count > 0;
|
|
|
|
public string GetSql(bool includeSetupFinish = true)
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (includeSetupFinish)
|
|
{
|
|
foreach (string setup in SetupClauses)
|
|
{
|
|
sb.AppendLine(setup);
|
|
}
|
|
}
|
|
|
|
sb.Append(_sql);
|
|
|
|
if (includeSetupFinish)
|
|
{
|
|
foreach (string finish in FinishClauses)
|
|
{
|
|
sb.AppendLine(finish);
|
|
}
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public override string ToString() => GetSql();
|
|
|
|
public object Clone()
|
|
{
|
|
var clone = (MockSqlBreakdown)MemberwiseClone();
|
|
clone.SetupClauses = new List<string>(SetupClauses);
|
|
clone.FinishClauses = new System.Collections.ArrayList(FinishClauses);
|
|
return clone;
|
|
}
|
|
}
|
|
}
|