diff --git a/NEW/tests/JdeScoping.Database.Tests/Functions/SimpleTableFunctionTests.cs b/NEW/tests/JdeScoping.Database.Tests/Functions/SimpleTableFunctionTests.cs new file mode 100644 index 0000000..b2c44d0 --- /dev/null +++ b/NEW/tests/JdeScoping.Database.Tests/Functions/SimpleTableFunctionTests.cs @@ -0,0 +1,618 @@ +using Dapper; +using FluentAssertions; +using JdeScoping.Core.Models.Search; +using JdeScoping.Database.Tests.Infrastructure; + +namespace JdeScoping.Database.Tests.Functions; + +/// +/// Tests for simple table extraction functions that return single-column result sets. +/// These inline TVFs extract arrays from Search.Criteria JSON. +/// +[Collection("DatabaseTests")] +public class SimpleTableFunctionTests : DatabaseTestBase +{ + #region fn_GetSearchWorkOrders Tests + + [Fact] + public async Task fn_GetSearchWorkOrders_ValidArray_ReturnsAllValues() + { + // Arrange + var criteria = new SearchCriteria { WorkOrderNumbers = [12345, 67890, 11111] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEquivalentTo([12345L, 67890L, 11111L]); + } + + [Fact] + public async Task fn_GetSearchWorkOrders_EmptyArray_ReturnsEmpty() + { + // Arrange + var criteria = new SearchCriteria { WorkOrderNumbers = [] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkOrders_MissingProperty_ReturnsEmpty() + { + // Arrange - criteria without WorkOrderNumbers property set (uses default empty list) + var searchId = await InsertTestSearchWithRawCriteriaAsync("{\"MinimumDt\":null}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkOrders_SearchNotFound_ReturnsEmpty() + { + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = 99999 }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkOrders_NullCriteria_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync(null); + + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkOrders_InvalidJson_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync("not valid json"); + + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkOrders_BadTypeValues_FilteredOut() + { + // Arrange - include null values that should be filtered out + // Note: OPENJSON...WITH BIGINT will filter out NULLs via WHERE clause, + // but will throw conversion error on incompatible types like strings. + // This test verifies NULL values are properly filtered. + var searchId = await InsertTestSearchWithRawCriteriaAsync( + "{\"WorkOrderNumbers\":[12345, null, 67890]}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = searchId }); + + // Assert - null values should be filtered out + results.Should().BeEquivalentTo([12345L, 67890L]); + } + + #endregion + + #region fn_GetSearchItemNumbers Tests + + [Fact] + public async Task fn_GetSearchItemNumbers_ValidArray_ReturnsAllValues() + { + // Arrange + var criteria = new SearchCriteria { ItemNumbers = ["ITEM001", "ITEM002", "ITEM003"] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEquivalentTo(["ITEM001", "ITEM002", "ITEM003"]); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_EmptyArray_ReturnsEmpty() + { + // Arrange + var criteria = new SearchCriteria { ItemNumbers = [] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_MissingProperty_ReturnsEmpty() + { + // Arrange - criteria without ItemNumbers property + var searchId = await InsertTestSearchWithRawCriteriaAsync("{\"MinimumDt\":null}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_SearchNotFound_ReturnsEmpty() + { + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = 99999 }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_NullCriteria_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync(null); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_InvalidJson_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync("not valid json"); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_BadTypeValues_FilteredOut() + { + // Arrange - include null values that should be filtered out + var searchId = await InsertTestSearchWithRawCriteriaAsync( + "{\"ItemNumbers\":[\"ITEM001\", null, \"ITEM002\"]}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert - null values should be filtered out + results.Should().BeEquivalentTo(["ITEM001", "ITEM002"]); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_SpecialCharacters_ReturnsCorrectly() + { + // Arrange + var criteria = new SearchCriteria { ItemNumbers = ["ITEM-001", "ITEM_002", "ITEM.003"] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEquivalentTo(["ITEM-001", "ITEM_002", "ITEM.003"]); + } + + #endregion + + #region fn_GetSearchProfitCenters Tests + + [Fact] + public async Task fn_GetSearchProfitCenters_ValidArray_ReturnsAllValues() + { + // Arrange + var criteria = new SearchCriteria { ProfitCenters = ["PC01", "PC02", "PC03"] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchProfitCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEquivalentTo(["PC01", "PC02", "PC03"]); + } + + [Fact] + public async Task fn_GetSearchProfitCenters_EmptyArray_ReturnsEmpty() + { + // Arrange + var criteria = new SearchCriteria { ProfitCenters = [] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchProfitCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchProfitCenters_MissingProperty_ReturnsEmpty() + { + // Arrange - criteria without ProfitCenters property + var searchId = await InsertTestSearchWithRawCriteriaAsync("{\"MinimumDt\":null}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchProfitCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchProfitCenters_SearchNotFound_ReturnsEmpty() + { + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchProfitCenters(@SearchId)", + new { SearchId = 99999 }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchProfitCenters_NullCriteria_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync(null); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchProfitCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchProfitCenters_InvalidJson_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync("not valid json"); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchProfitCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchProfitCenters_BadTypeValues_FilteredOut() + { + // Arrange - include null values that should be filtered out + var searchId = await InsertTestSearchWithRawCriteriaAsync( + "{\"ProfitCenters\":[\"PC01\", null, \"PC02\"]}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchProfitCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert - null values should be filtered out + results.Should().BeEquivalentTo(["PC01", "PC02"]); + } + + #endregion + + #region fn_GetSearchWorkCenters Tests + + [Fact] + public async Task fn_GetSearchWorkCenters_ValidArray_ReturnsAllValues() + { + // Arrange + var criteria = new SearchCriteria { WorkCenters = ["WC001", "WC002", "WC003"] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchWorkCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEquivalentTo(["WC001", "WC002", "WC003"]); + } + + [Fact] + public async Task fn_GetSearchWorkCenters_EmptyArray_ReturnsEmpty() + { + // Arrange + var criteria = new SearchCriteria { WorkCenters = [] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchWorkCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkCenters_MissingProperty_ReturnsEmpty() + { + // Arrange - criteria without WorkCenters property + var searchId = await InsertTestSearchWithRawCriteriaAsync("{\"MinimumDt\":null}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchWorkCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkCenters_SearchNotFound_ReturnsEmpty() + { + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchWorkCenters(@SearchId)", + new { SearchId = 99999 }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkCenters_NullCriteria_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync(null); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchWorkCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkCenters_InvalidJson_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync("not valid json"); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchWorkCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchWorkCenters_BadTypeValues_FilteredOut() + { + // Arrange - include null values that should be filtered out + var searchId = await InsertTestSearchWithRawCriteriaAsync( + "{\"WorkCenters\":[\"WC001\", null, \"WC002\"]}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT Code FROM dbo.fn_GetSearchWorkCenters(@SearchId)", + new { SearchId = searchId }); + + // Assert - null values should be filtered out + results.Should().BeEquivalentTo(["WC001", "WC002"]); + } + + #endregion + + #region fn_GetSearchOperatorIDs Tests + + [Fact] + public async Task fn_GetSearchOperatorIDs_ValidArray_ReturnsAllValues() + { + // Arrange + var criteria = new SearchCriteria { OperatorIDs = ["OP001", "OP002", "OP003"] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT OperatorID FROM dbo.fn_GetSearchOperatorIDs(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEquivalentTo(["OP001", "OP002", "OP003"]); + } + + [Fact] + public async Task fn_GetSearchOperatorIDs_EmptyArray_ReturnsEmpty() + { + // Arrange + var criteria = new SearchCriteria { OperatorIDs = [] }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT OperatorID FROM dbo.fn_GetSearchOperatorIDs(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchOperatorIDs_MissingProperty_ReturnsEmpty() + { + // Arrange - criteria without OperatorIDs property + var searchId = await InsertTestSearchWithRawCriteriaAsync("{\"MinimumDt\":null}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT OperatorID FROM dbo.fn_GetSearchOperatorIDs(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchOperatorIDs_SearchNotFound_ReturnsEmpty() + { + // Act + var results = await Connection.QueryAsync( + "SELECT OperatorID FROM dbo.fn_GetSearchOperatorIDs(@SearchId)", + new { SearchId = 99999 }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchOperatorIDs_NullCriteria_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync(null); + + // Act + var results = await Connection.QueryAsync( + "SELECT OperatorID FROM dbo.fn_GetSearchOperatorIDs(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchOperatorIDs_InvalidJson_ReturnsEmpty() + { + // Arrange + var searchId = await InsertTestSearchWithRawCriteriaAsync("not valid json"); + + // Act + var results = await Connection.QueryAsync( + "SELECT OperatorID FROM dbo.fn_GetSearchOperatorIDs(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().BeEmpty(); + } + + [Fact] + public async Task fn_GetSearchOperatorIDs_BadTypeValues_FilteredOut() + { + // Arrange - include null values that should be filtered out + var searchId = await InsertTestSearchWithRawCriteriaAsync( + "{\"OperatorIDs\":[\"OP001\", null, \"OP002\"]}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT OperatorID FROM dbo.fn_GetSearchOperatorIDs(@SearchId)", + new { SearchId = searchId }); + + // Assert - null values should be filtered out + results.Should().BeEquivalentTo(["OP001", "OP002"]); + } + + #endregion + + #region Additional Edge Case Tests + + [Fact] + public async Task fn_GetSearchWorkOrders_LargeArray_ReturnsAll() + { + // Arrange + var workOrders = Enumerable.Range(1, 1000).Select(i => (long)i).ToList(); + var criteria = new SearchCriteria { WorkOrderNumbers = workOrders }; + var searchId = await InsertTestSearchAsync(criteria); + + // Act + var results = await Connection.QueryAsync( + "SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId)", + new { SearchId = searchId }); + + // Assert + results.Should().HaveCount(1000); + results.Should().BeEquivalentTo(workOrders); + } + + [Fact] + public async Task fn_GetSearchItemNumbers_LongValues_Truncated() + { + // Arrange - ItemNumber column is VARCHAR(128) + var longValue = new string('A', 200); + var searchId = await InsertTestSearchWithRawCriteriaAsync( + $"{{\"ItemNumbers\":[\"{longValue}\"]}}"); + + // Act + var results = await Connection.QueryAsync( + "SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId)", + new { SearchId = searchId }); + + // Assert - value should be truncated to 128 characters + results.Should().HaveCount(1); + results.First().Should().HaveLength(128); + } + + #endregion +}