26ff8d9b4f
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
239 lines
8.3 KiB
C#
239 lines
8.3 KiB
C#
using JdeScoping.DataAccess.FilterHandlers;
|
|
using JdeScoping.DataAccess.Interfaces;
|
|
using JdeScoping.DataAccess.Models;
|
|
using JdeScoping.DataAccess.Models.FilterEntries;
|
|
using JdeScoping.DataAccess.QueryBuilders;
|
|
using NSubstitute;
|
|
using Shouldly;
|
|
using SqlKata.Compilers;
|
|
using Xunit;
|
|
|
|
namespace JdeScoping.DataAccess.Tests.QueryBuilders;
|
|
|
|
/// <summary>
|
|
/// Unit tests for SqlKataSearchQueryBuilder.
|
|
/// </summary>
|
|
public sealed class SqlKataSearchQueryBuilderTests
|
|
{
|
|
private readonly SqlServerCompiler _compiler = new();
|
|
|
|
[Fact]
|
|
public void BuildSearchQuery_WithEmptyFilters_ProducesMinimalQuery()
|
|
{
|
|
// Arrange
|
|
var handlers = Array.Empty<IFilterHandler>();
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel();
|
|
|
|
// Act
|
|
var result = builder.BuildSearchQuery(model);
|
|
|
|
// Assert
|
|
result.ShouldNotBeNull();
|
|
result.Sql.ShouldNotBeNullOrEmpty();
|
|
result.TempTableSetupSql.ShouldNotBeEmpty();
|
|
|
|
// Should contain temp table creation
|
|
var setupSql = string.Join("\n", result.TempTableSetupSql);
|
|
setupSql.ShouldContain("#Temp_WO");
|
|
setupSql.ShouldContain("CREATE TABLE");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildSearchQuery_WithEmptyFilters_ResultSqlContainsSelect()
|
|
{
|
|
// Arrange
|
|
var handlers = Array.Empty<IFilterHandler>();
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel();
|
|
|
|
// Act
|
|
var result = builder.BuildSearchQuery(model);
|
|
|
|
// Assert
|
|
result.Sql.ShouldContain("SELECT");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildSearchQuery_WithSingleFilter_ProducesCorrectStructure()
|
|
{
|
|
// Arrange
|
|
var workOrderHandler = new WorkOrderFilterHandler();
|
|
var handlers = new IFilterHandler[] { workOrderHandler };
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel
|
|
{
|
|
WorkOrderFilter =
|
|
[
|
|
new WorkOrderFilterEntry { WorkOrderNumber = 12345 }
|
|
]
|
|
};
|
|
|
|
// Act
|
|
var result = builder.BuildSearchQuery(model);
|
|
|
|
// Assert
|
|
result.ShouldNotBeNull();
|
|
result.TempTableSetupSql.ShouldNotBeEmpty();
|
|
result.Parameters.ShouldContainKey("p_WorkOrderFilter");
|
|
|
|
var setupSql = string.Join("\n", result.TempTableSetupSql);
|
|
// Should have temp table creation and work order merge
|
|
setupSql.ShouldContain("#Temp_WO");
|
|
setupSql.ShouldContain("MERGE");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildSearchQuery_WithMultipleFilters_CombinesCorrectly()
|
|
{
|
|
// Arrange
|
|
var workOrderHandler = new WorkOrderFilterHandler();
|
|
var itemNumberHandler = new ItemNumberFilterHandler();
|
|
var handlers = new IFilterHandler[] { workOrderHandler, itemNumberHandler };
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel
|
|
{
|
|
WorkOrderFilter =
|
|
[
|
|
new WorkOrderFilterEntry { WorkOrderNumber = 12345 }
|
|
],
|
|
ItemNumberFilter =
|
|
[
|
|
new ItemNumberFilterEntry { ItemNumber = "ABC123" }
|
|
]
|
|
};
|
|
|
|
// Act
|
|
var result = builder.BuildSearchQuery(model);
|
|
|
|
// Assert
|
|
result.ShouldNotBeNull();
|
|
result.Parameters.ShouldContainKey("p_WorkOrderFilter");
|
|
result.Parameters.ShouldContainKey("p_ItemNumberFilter");
|
|
|
|
var setupSql = string.Join("\n", result.TempTableSetupSql);
|
|
setupSql.ShouldContain("#Temp_WO");
|
|
setupSql.ShouldContain("#P_ItemNumbers");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildSearchQuery_HandlersAreAppliedInPriorityOrder()
|
|
{
|
|
// Arrange
|
|
var lowPriorityHandler = Substitute.For<IFilterHandler>();
|
|
lowPriorityHandler.Priority.Returns(100);
|
|
lowPriorityHandler.IsEnabled(Arg.Any<SearchModel>()).Returns(true);
|
|
lowPriorityHandler.Apply(Arg.Any<SearchModel>(), Arg.Any<SqlServerCompiler>())
|
|
.Returns(new FilterResult(["-- LOW PRIORITY SQL"], new Dictionary<string, object>()));
|
|
|
|
var highPriorityHandler = Substitute.For<IFilterHandler>();
|
|
highPriorityHandler.Priority.Returns(1);
|
|
highPriorityHandler.IsEnabled(Arg.Any<SearchModel>()).Returns(true);
|
|
highPriorityHandler.Apply(Arg.Any<SearchModel>(), Arg.Any<SqlServerCompiler>())
|
|
.Returns(new FilterResult(["-- HIGH PRIORITY SQL"], new Dictionary<string, object>()));
|
|
|
|
// Pass handlers in reverse priority order to verify sorting
|
|
var handlers = new[] { lowPriorityHandler, highPriorityHandler };
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel();
|
|
|
|
// Act
|
|
var result = builder.BuildSearchQuery(model);
|
|
|
|
// Assert
|
|
var setupSql = string.Join("\n", result.TempTableSetupSql);
|
|
var highIndex = setupSql.IndexOf("-- HIGH PRIORITY SQL", StringComparison.Ordinal);
|
|
var lowIndex = setupSql.IndexOf("-- LOW PRIORITY SQL", StringComparison.Ordinal);
|
|
|
|
highIndex.ShouldBeGreaterThan(-1);
|
|
lowIndex.ShouldBeGreaterThan(-1);
|
|
highIndex.ShouldBeLessThan(lowIndex);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildSearchQuery_DisabledHandlersAreSkipped()
|
|
{
|
|
// Arrange
|
|
var enabledHandler = Substitute.For<IFilterHandler>();
|
|
enabledHandler.Priority.Returns(1);
|
|
enabledHandler.IsEnabled(Arg.Any<SearchModel>()).Returns(true);
|
|
enabledHandler.Apply(Arg.Any<SearchModel>(), Arg.Any<SqlServerCompiler>())
|
|
.Returns(new FilterResult(["-- ENABLED"], new Dictionary<string, object>()));
|
|
|
|
var disabledHandler = Substitute.For<IFilterHandler>();
|
|
disabledHandler.Priority.Returns(2);
|
|
disabledHandler.IsEnabled(Arg.Any<SearchModel>()).Returns(false);
|
|
|
|
var handlers = new[] { enabledHandler, disabledHandler };
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel();
|
|
|
|
// Act
|
|
var result = builder.BuildSearchQuery(model);
|
|
|
|
// Assert
|
|
var setupSql = string.Join("\n", result.TempTableSetupSql);
|
|
setupSql.ShouldContain("-- ENABLED");
|
|
|
|
// Apply should never be called on disabled handler
|
|
disabledHandler.DidNotReceive().Apply(Arg.Any<SearchModel>(), Arg.Any<SqlServerCompiler>());
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildSearchQuery_WithTimespanFilter_IncludesStepFlagging()
|
|
{
|
|
// Arrange
|
|
var handlers = Array.Empty<IFilterHandler>();
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel
|
|
{
|
|
MinimumDt = DateTime.Now.AddDays(-30),
|
|
MaximumDt = DateTime.Now
|
|
};
|
|
|
|
// Act
|
|
var result = builder.BuildSearchQuery(model);
|
|
|
|
// Assert
|
|
// When ShouldSearchSteps returns true, step flagging query is added
|
|
var setupSql = string.Join("\n", result.TempTableSetupSql);
|
|
setupSql.ShouldContain("LU_WO");
|
|
setupSql.ShouldContain("Flagged");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildMisQuery_ReturnsValidResult()
|
|
{
|
|
// Arrange
|
|
var handlers = Array.Empty<IFilterHandler>();
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel();
|
|
|
|
// Act
|
|
var result = builder.BuildMisQuery(model);
|
|
|
|
// Assert
|
|
result.ShouldNotBeNull();
|
|
result.Sql.ShouldNotBeNullOrEmpty();
|
|
result.Sql.ShouldContain("#TempMisData");
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildMisNonMatchQuery_ReturnsValidResult()
|
|
{
|
|
// Arrange
|
|
var handlers = Array.Empty<IFilterHandler>();
|
|
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
|
|
var model = new SearchModel();
|
|
|
|
// Act
|
|
var result = builder.BuildMisNonMatchQuery(model);
|
|
|
|
// Assert
|
|
result.ShouldNotBeNull();
|
|
result.Sql.ShouldNotBeNullOrEmpty();
|
|
result.Sql.ShouldContain("WasJobStepAdded");
|
|
result.Sql.ShouldContain("MatchedJobStepNumber");
|
|
}
|
|
}
|