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; /// /// Unit tests for SqlKataSearchQueryBuilder. /// public sealed class SqlKataSearchQueryBuilderTests { private readonly SqlServerCompiler _compiler = new(); [Fact] public void BuildSearchQuery_WithEmptyFilters_ProducesMinimalQuery() { // Arrange var handlers = Array.Empty(); 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(); 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(); lowPriorityHandler.Priority.Returns(100); lowPriorityHandler.IsEnabled(Arg.Any()).Returns(true); lowPriorityHandler.Apply(Arg.Any(), Arg.Any()) .Returns(new FilterResult(["-- LOW PRIORITY SQL"], new Dictionary())); var highPriorityHandler = Substitute.For(); highPriorityHandler.Priority.Returns(1); highPriorityHandler.IsEnabled(Arg.Any()).Returns(true); highPriorityHandler.Apply(Arg.Any(), Arg.Any()) .Returns(new FilterResult(["-- HIGH PRIORITY SQL"], new Dictionary())); // 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(); enabledHandler.Priority.Returns(1); enabledHandler.IsEnabled(Arg.Any()).Returns(true); enabledHandler.Apply(Arg.Any(), Arg.Any()) .Returns(new FilterResult(["-- ENABLED"], new Dictionary())); var disabledHandler = Substitute.For(); disabledHandler.Priority.Returns(2); disabledHandler.IsEnabled(Arg.Any()).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(), Arg.Any()); } [Fact] public void BuildSearchQuery_WithTimespanFilter_IncludesStepFlagging() { // Arrange var handlers = Array.Empty(); 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(); 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(); 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"); } }