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 }