26ff8d9b4f
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
481 lines
14 KiB
C#
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
|
|
}
|