using System.Data; using System.Reflection; using Dapper; using JdeScoping.DataAccess.Extensions; using JdeScoping.DataAccess.Models; using JdeScoping.DataAccess.Models.FilterEntries; using Shouldly; using Xunit; namespace JdeScoping.DataAccess.Tests.Extensions; /// /// Unit tests for TableValuedParameterExtensions. /// public sealed class TableValuedParameterExtensionsTests { #region CreateWorkOrderFilterParameter Tests [Fact] public void CreateWorkOrderFilterParameter_ProducesCorrectSchema() { // Arrange var model = new SearchModel { WorkOrderFilter = [ new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ABC" } ] }; // Act var param = model.CreateWorkOrderFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.ShouldNotBeNull(); dataTable.Columns.Count.ShouldBe(1); dataTable.Columns.Contains("WorkOrderNumber").ShouldBeTrue(); dataTable.Columns["WorkOrderNumber"]!.DataType.ShouldBe(typeof(long)); } [Fact] public void CreateWorkOrderFilterParameter_WithEmptyCollection_ProducesEmptyDataTable() { // Arrange var model = new SearchModel { WorkOrderFilter = [] }; // Act var param = model.CreateWorkOrderFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.ShouldNotBeNull(); dataTable.Rows.Count.ShouldBe(0); } [Fact] public void CreateWorkOrderFilterParameter_PopulatesCorrectData() { // Arrange var model = new SearchModel { WorkOrderFilter = [ new WorkOrderFilterEntry { WorkOrderNumber = 12345 }, new WorkOrderFilterEntry { WorkOrderNumber = 67890 } ] }; // Act var param = model.CreateWorkOrderFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(2); dataTable.Rows[0]["WorkOrderNumber"].ShouldBe(12345L); dataTable.Rows[1]["WorkOrderNumber"].ShouldBe(67890L); } #endregion #region CreateItemNumberFilterParameter Tests [Fact] public void CreateItemNumberFilterParameter_ProducesCorrectSchema() { // Arrange var model = new SearchModel { ItemNumberFilter = [ new ItemNumberFilterEntry { ItemNumber = "ABC123" } ] }; // Act var param = model.CreateItemNumberFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.ShouldNotBeNull(); dataTable.Columns.Count.ShouldBe(1); dataTable.Columns.Contains("ItemNumber").ShouldBeTrue(); dataTable.Columns["ItemNumber"]!.DataType.ShouldBe(typeof(string)); } [Fact] public void CreateItemNumberFilterParameter_WithEmptyCollection_ProducesEmptyDataTable() { // Arrange var model = new SearchModel { ItemNumberFilter = [] }; // Act var param = model.CreateItemNumberFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(0); } #endregion #region CreateProfitCenterFilterParameter Tests [Fact] public void CreateProfitCenterFilterParameter_ProducesCorrectSchema() { // Arrange var model = new SearchModel { ProfitCenterFilter = [ new ProfitCenterFilterEntry { Code = "PC001" } ] }; // Act var param = model.CreateProfitCenterFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Columns.Count.ShouldBe(1); dataTable.Columns.Contains("Code").ShouldBeTrue(); dataTable.Columns["Code"]!.DataType.ShouldBe(typeof(string)); } [Fact] public void CreateProfitCenterFilterParameter_WithEmptyCollection_ProducesEmptyDataTable() { // Arrange var model = new SearchModel { ProfitCenterFilter = [] }; // Act var param = model.CreateProfitCenterFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(0); } #endregion #region CreateWorkCenterFilterParameter Tests [Fact] public void CreateWorkCenterFilterParameter_ProducesCorrectSchema() { // Arrange var model = new SearchModel { WorkCenterFilter = [ new WorkCenterFilterEntry { Code = "WC001" } ] }; // Act var param = model.CreateWorkCenterFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Columns.Count.ShouldBe(1); dataTable.Columns.Contains("Code").ShouldBeTrue(); dataTable.Columns["Code"]!.DataType.ShouldBe(typeof(string)); } [Fact] public void CreateWorkCenterFilterParameter_WithEmptyCollection_ProducesEmptyDataTable() { // Arrange var model = new SearchModel { WorkCenterFilter = [] }; // Act var param = model.CreateWorkCenterFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(0); } #endregion #region CreateComponentLotFilterParameter Tests [Fact] public void CreateComponentLotFilterParameter_ProducesCorrectSchema() { // Arrange var model = new SearchModel { ComponentLotFilter = [ new ComponentLotFilterEntry { LotNumber = "LOT001", ItemNumber = "ITEM001" } ] }; // Act var param = model.CreateComponentLotFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Columns.Count.ShouldBe(2); dataTable.Columns.Contains("ComponentLotNumber").ShouldBeTrue(); dataTable.Columns.Contains("ItemNumber").ShouldBeTrue(); dataTable.Columns["ComponentLotNumber"]!.DataType.ShouldBe(typeof(string)); dataTable.Columns["ItemNumber"]!.DataType.ShouldBe(typeof(string)); } [Fact] public void CreateComponentLotFilterParameter_WithEmptyCollection_ProducesEmptyDataTable() { // Arrange var model = new SearchModel { ComponentLotFilter = [] }; // Act var param = model.CreateComponentLotFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(0); } [Fact] public void CreateComponentLotFilterParameter_PopulatesCorrectData() { // Arrange var model = new SearchModel { ComponentLotFilter = [ new ComponentLotFilterEntry { LotNumber = "LOT001", ItemNumber = "ITEM001" }, new ComponentLotFilterEntry { LotNumber = "LOT002", ItemNumber = "ITEM002" } ] }; // Act var param = model.CreateComponentLotFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(2); dataTable.Rows[0]["ComponentLotNumber"].ShouldBe("LOT001"); dataTable.Rows[0]["ItemNumber"].ShouldBe("ITEM001"); } #endregion #region CreateOperatorFilterParameter Tests [Fact] public void CreateOperatorFilterParameter_ProducesCorrectSchema() { // Arrange var model = new SearchModel { OperatorFilter = [ new OperatorFilterEntry { UserId = "USER01", AddressNumber = 123 } ] }; // Act var param = model.CreateOperatorFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Columns.Count.ShouldBe(1); dataTable.Columns.Contains("UserName").ShouldBeTrue(); dataTable.Columns["UserName"]!.DataType.ShouldBe(typeof(string)); } [Fact] public void CreateOperatorFilterParameter_WithEmptyCollection_ProducesEmptyDataTable() { // Arrange var model = new SearchModel { OperatorFilter = [] }; // Act var param = model.CreateOperatorFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(0); } #endregion #region CreateItemOperationMisFilterParameter Tests [Fact] public void CreateItemOperationMisFilterParameter_ProducesCorrectSchema() { // Arrange var model = new SearchModel { ItemOperationMisFilter = [ new ItemOperationMisFilterEntry { ItemNumber = "ITEM001", OperationNumber = "010", MisNumber = "MIS001", MisRevision = "A" } ] }; // Act var param = model.CreateItemOperationMisFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Columns.Count.ShouldBe(4); dataTable.Columns.Contains("ItemNumber").ShouldBeTrue(); dataTable.Columns.Contains("OperationNumber").ShouldBeTrue(); dataTable.Columns.Contains("MisNumber").ShouldBeTrue(); dataTable.Columns.Contains("MisRevision").ShouldBeTrue(); dataTable.Columns["ItemNumber"]!.DataType.ShouldBe(typeof(string)); dataTable.Columns["OperationNumber"]!.DataType.ShouldBe(typeof(string)); dataTable.Columns["MisNumber"]!.DataType.ShouldBe(typeof(string)); dataTable.Columns["MisRevision"]!.DataType.ShouldBe(typeof(string)); } [Fact] public void CreateItemOperationMisFilterParameter_WithEmptyCollection_ProducesEmptyDataTable() { // Arrange var model = new SearchModel { ItemOperationMisFilter = [] }; // Act var param = model.CreateItemOperationMisFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(0); } [Fact] public void CreateItemOperationMisFilterParameter_PopulatesCorrectData() { // Arrange var model = new SearchModel { ItemOperationMisFilter = [ new ItemOperationMisFilterEntry { ItemNumber = "ITEM001", OperationNumber = "010", MisNumber = "MIS001", MisRevision = "A" } ] }; // Act var param = model.CreateItemOperationMisFilterParameter(); var dataTable = ExtractDataTable(param); // Assert dataTable.Rows.Count.ShouldBe(1); dataTable.Rows[0]["ItemNumber"].ShouldBe("ITEM001"); dataTable.Rows[0]["OperationNumber"].ShouldBe("010"); dataTable.Rows[0]["MisNumber"].ShouldBe("MIS001"); dataTable.Rows[0]["MisRevision"].ShouldBe("A"); } #endregion #region Helper Methods /// /// Extracts the underlying DataTable from a Dapper table-valued parameter. /// Uses reflection to access internal fields across different Dapper versions. /// private static DataTable ExtractDataTable(SqlMapper.ICustomQueryParameter param) { // The TableValuedParameter wraps a DataTable - try multiple field/property names // across different Dapper versions var type = param.GetType(); var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; // Try field names used in different Dapper versions var fieldNames = new[] { "_table", "table", "Table", "_dataTable", "dataTable" }; foreach (var fieldName in fieldNames) { var field = type.GetField(fieldName, bindingFlags); if (field != null && field.FieldType == typeof(DataTable)) { var value = field.GetValue(param); if (value is DataTable dt) return dt; } } // Try property names var propertyNames = new[] { "Table", "DataTable", "table", "_table" }; foreach (var propName in propertyNames) { var prop = type.GetProperty(propName, bindingFlags); if (prop != null && prop.PropertyType == typeof(DataTable)) { var value = prop.GetValue(param); if (value is DataTable dt) return dt; } } // Last resort: scan all fields foreach (var field in type.GetFields(bindingFlags)) { if (field.FieldType == typeof(DataTable)) { var value = field.GetValue(param); if (value is DataTable dt) return dt; } } // Scan all properties foreach (var prop in type.GetProperties(bindingFlags)) { if (prop.PropertyType == typeof(DataTable)) { var value = prop.GetValue(param); if (value is DataTable dt) return dt; } } throw new InvalidOperationException( $"Could not extract DataTable from {type.FullName}. " + $"Fields: {string.Join(", ", type.GetFields(bindingFlags).Select(f => f.Name))}. " + $"Properties: {string.Join(", ", type.GetProperties(bindingFlags).Select(p => p.Name))}"); } #endregion }