Files
jdescopingtool/NEW/tests/JdeScoping.DataAccess.Tests/Extensions/TableValuedParameterExtensionsTests.cs
T
Joseph Doherty 26ff8d9b4f Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/),
new .NET 10 Blazor solution (NEW/), OpenSpec specifications,
documentation, and project configuration.
2026-01-02 07:43:29 -05:00

481 lines
14 KiB
C#

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;
/// <summary>
/// Unit tests for TableValuedParameterExtensions.
/// </summary>
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
/// <summary>
/// Extracts the underlying DataTable from a Dapper table-valued parameter.
/// Uses reflection to access internal fields across different Dapper versions.
/// </summary>
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
}