refactor(data-access): update ISearchQueryBuilder to use SearchId only

- Change interface methods to accept int searchId instead of SearchModel
- Update SqlKataSearchQueryBuilder to generate SQL using extraction functions
- SQL now calls dbo.fn_GetSearchWorkOrders(@SearchId) etc instead of TVPs
- Update SearchProcessor to pass model.Id to query builder
- Update tests for new method signatures
This commit is contained in:
Joseph Doherty
2026-01-06 14:08:47 -05:00
parent 7508001be1
commit 6074424524
4 changed files with 341 additions and 344 deletions
@@ -1,9 +1,5 @@
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;
@@ -18,221 +14,173 @@ public sealed class SqlKataSearchQueryBuilderTests
private readonly SqlServerCompiler _compiler = new();
[Fact]
public void BuildSearchQuery_WithEmptyFilters_ProducesMinimalQuery()
public void BuildSearchQuery_WithSearchId_ProducesValidQuery()
{
// Arrange
var handlers = Array.Empty<IFilterHandler>();
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
var model = new SearchModel();
var builder = new SqlKataSearchQueryBuilder(_compiler);
var searchId = 123;
// Act
var result = builder.BuildSearchQuery(model);
var result = builder.BuildSearchQuery(searchId);
// Assert
result.ShouldNotBeNull();
result.Sql.ShouldNotBeNullOrEmpty();
result.TempTableSetupSql.ShouldNotBeEmpty();
result.Parameters.ShouldContainKey("SearchId");
result.Parameters["SearchId"].ShouldBe(searchId);
}
// Should contain temp table creation
[Fact]
public void BuildSearchQuery_ContainsTempTableCreation()
{
// Arrange
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildSearchQuery(1);
// Assert
var setupSql = string.Join("\n", result.TempTableSetupSql);
setupSql.ShouldContain("#Temp_WO");
setupSql.ShouldContain("CREATE TABLE");
}
[Fact]
public void BuildSearchQuery_WithEmptyFilters_ResultSqlContainsSelect()
public void BuildSearchQuery_ContainsValidationCall()
{
// Arrange
var handlers = Array.Empty<IFilterHandler>();
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
var model = new SearchModel();
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildSearchQuery(model);
var result = builder.BuildSearchQuery(1);
// Assert
var setupSql = string.Join("\n", result.TempTableSetupSql);
setupSql.ShouldContain("usp_ValidateSearchCriteria");
setupSql.ShouldContain("@SearchId");
}
[Fact]
public void BuildSearchQuery_UsesExtractionFunctions()
{
// Arrange
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildSearchQuery(1);
// Assert
var setupSql = string.Join("\n", result.TempTableSetupSql);
// Should use extraction functions instead of TVPs
setupSql.ShouldContain("fn_GetSearchWorkOrders(@SearchId)");
setupSql.ShouldContain("fn_GetSearchItemNumbers(@SearchId)");
setupSql.ShouldContain("fn_GetSearchProfitCenters(@SearchId)");
setupSql.ShouldContain("fn_GetSearchWorkCenters(@SearchId)");
setupSql.ShouldContain("fn_GetSearchOperatorIDs(@SearchId)");
setupSql.ShouldContain("fn_GetSearchComponentLots(@SearchId)");
setupSql.ShouldContain("fn_GetSearchPartOperations(@SearchId)");
setupSql.ShouldContain("fn_GetSearchMinimumDt(@SearchId)");
setupSql.ShouldContain("fn_GetSearchMaximumDt(@SearchId)");
}
[Fact]
public void BuildSearchQuery_CreatesFilterTempTables()
{
// Arrange
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildSearchQuery(1);
// Assert
var setupSql = string.Join("\n", result.TempTableSetupSql);
// Should create filter temp tables
setupSql.ShouldContain("#P_WorkOrders");
setupSql.ShouldContain("#P_ItemNumbers");
setupSql.ShouldContain("#P_WorkCenters");
setupSql.ShouldContain("#P_OperatorIDs");
setupSql.ShouldContain("#P_ComponentLots");
setupSql.ShouldContain("#P_PartOperations");
}
[Fact]
public void BuildSearchQuery_ResultSqlContainsSelect()
{
// Arrange
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildSearchQuery(1);
// Assert
result.Sql.ShouldContain("SELECT");
result.Sql.ShouldContain("WorkOrderNumber");
}
[Fact]
public void BuildSearchQuery_WithSingleFilter_ProducesCorrectStructure()
public void BuildSearchQuery_ContainsStepFlaggingQuery()
{
// 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 }
]
};
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildSearchQuery(model);
var result = builder.BuildSearchQuery(1);
// 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");
setupSql.ShouldContain("MERGE");
}
[Fact]
public void BuildMisQuery_ReturnsValidResult()
{
// Arrange
var handlers = Array.Empty<IFilterHandler>();
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
var model = new SearchModel();
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildMisQuery(model);
var result = builder.BuildMisQuery(1);
// Assert
result.ShouldNotBeNull();
result.Sql.ShouldNotBeNullOrEmpty();
result.Sql.ShouldContain("#TempMisData");
result.Parameters.ShouldContainKey("SearchId");
}
[Fact]
public void BuildMisNonMatchQuery_ReturnsValidResult()
{
// Arrange
var handlers = Array.Empty<IFilterHandler>();
var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
var model = new SearchModel();
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result = builder.BuildMisNonMatchQuery(model);
var result = builder.BuildMisNonMatchQuery(1);
// Assert
result.ShouldNotBeNull();
result.Sql.ShouldNotBeNullOrEmpty();
result.Sql.ShouldContain("WasJobStepAdded");
result.Sql.ShouldContain("MatchedJobStepNumber");
result.Parameters.ShouldContainKey("SearchId");
}
[Fact]
public void BuildSearchQuery_DifferentSearchIds_ProduceDifferentParameters()
{
// Arrange
var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
var result1 = builder.BuildSearchQuery(100);
var result2 = builder.BuildSearchQuery(200);
// Assert
result1.Parameters["SearchId"].ShouldBe(100);
result2.Parameters["SearchId"].ShouldBe(200);
}
}