feat(etl): add MapOrdinal and date validation with sentinel to JdeDateTransformer
- Add DefaultInvalidDateSentinel (1900-01-01) for invalid date handling - Add optional invalidDateSentinel constructor parameter - Add MapOrdinal override returning -1 for computed DateTime column - Add GetDataTypeName override returning "datetime" for computed column - Update ParseJdeDateTime with comprehensive validation: - Validate date is positive - Validate century (0 or 1) - Validate year (0-99) - Validate day of year (1-366 and respects leap year) - Validate time components (hours 0-23, minutes/seconds 0-59) - Add tests for all new functionality
This commit is contained in:
@@ -80,6 +80,166 @@ public class JdeDateTransformerTests
|
||||
Assert.Equal(new DateTime(1999, 6, 15, 0, 0, 0), JdeDateTransformer.ParseJdeDateTime(99166m, 0m));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapOrdinal_DateOutputColumn_ReturnsNegativeOne()
|
||||
{
|
||||
// Arrange
|
||||
var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt");
|
||||
var source = CreateMockReader(new[] { "UPMJ", "TDAY", "Other" }, new object[] { 124001m, 120000m, "Test" });
|
||||
transformer.Transform(source);
|
||||
|
||||
// Act - ordinal 0 is the computed DateTime column (UPMJ becomes UpdatedAt at position 0)
|
||||
var result = transformer.MapOrdinal(0, source);
|
||||
|
||||
// Assert - computed columns return -1
|
||||
Assert.Equal(-1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapOrdinal_NonComputedColumn_ReturnsSourceOrdinal()
|
||||
{
|
||||
// Arrange
|
||||
var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt");
|
||||
var source = CreateMockReader(new[] { "UPMJ", "TDAY", "Other" }, new object[] { 124001m, 120000m, "Test" });
|
||||
transformer.Transform(source);
|
||||
|
||||
// Act - ordinal 1 is "Other" which maps to source ordinal 2
|
||||
var result = transformer.MapOrdinal(1, source);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJdeDateTime_InvalidDate_ReturnsSentinel()
|
||||
{
|
||||
// Arrange
|
||||
var sentinel = new DateTime(1900, 1, 1);
|
||||
|
||||
// Act - 999999 is invalid (century 9 doesn't exist)
|
||||
var result = JdeDateTransformer.ParseJdeDateTime(999999m, 0m, sentinel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sentinel, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJdeDateTime_ZeroDate_ReturnsSentinel()
|
||||
{
|
||||
// Arrange
|
||||
var sentinel = new DateTime(1900, 1, 1);
|
||||
|
||||
// Act
|
||||
var result = JdeDateTransformer.ParseJdeDateTime(0m, 0m, sentinel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sentinel, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultInvalidDateSentinel_Is1900()
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal(new DateTime(1900, 1, 1), JdeDateTransformer.DefaultInvalidDateSentinel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDataTypeName_DateOutputColumn_ReturnsDatetime()
|
||||
{
|
||||
// Arrange
|
||||
var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt");
|
||||
var source = CreateMockReader(new[] { "UPMJ", "TDAY", "Other" }, new object[] { 124001m, 120000m, "Test" });
|
||||
source.GetDataTypeName(2).Returns("nvarchar");
|
||||
var reader = transformer.Transform(source);
|
||||
|
||||
// Act - ordinal 0 is the computed DateTime column
|
||||
var result = reader.GetDataTypeName(0);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("datetime", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDataTypeName_NonComputedColumn_DelegatesToSource()
|
||||
{
|
||||
// Arrange
|
||||
var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt");
|
||||
var source = CreateMockReader(new[] { "UPMJ", "TDAY", "Other" }, new object[] { 124001m, 120000m, "Test" });
|
||||
source.GetDataTypeName(2).Returns("nvarchar");
|
||||
var reader = transformer.Transform(source);
|
||||
|
||||
// Act - ordinal 1 is "Other" which maps to source ordinal 2
|
||||
var result = reader.GetDataTypeName(1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("nvarchar", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJdeDateTime_NegativeDate_ReturnsSentinel()
|
||||
{
|
||||
// Arrange
|
||||
var sentinel = new DateTime(1900, 1, 1);
|
||||
|
||||
// Act
|
||||
var result = JdeDateTransformer.ParseJdeDateTime(-100m, 0m, sentinel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sentinel, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJdeDateTime_InvalidDayOfYear_ReturnsSentinel()
|
||||
{
|
||||
// Arrange
|
||||
var sentinel = new DateTime(1900, 1, 1);
|
||||
|
||||
// Act - Day 400 doesn't exist
|
||||
var result = JdeDateTransformer.ParseJdeDateTime(124400m, 0m, sentinel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sentinel, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJdeDateTime_InvalidTime_ReturnsSentinel()
|
||||
{
|
||||
// Arrange
|
||||
var sentinel = new DateTime(1900, 1, 1);
|
||||
|
||||
// Act - Hour 25 doesn't exist
|
||||
var result = JdeDateTransformer.ParseJdeDateTime(124001m, 250000m, sentinel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sentinel, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJdeDateTime_LeapYearDay366_ReturnsValidDate()
|
||||
{
|
||||
// Arrange
|
||||
var sentinel = new DateTime(1900, 1, 1);
|
||||
|
||||
// Act - 2024 is a leap year, day 366 is valid
|
||||
var result = JdeDateTransformer.ParseJdeDateTime(124366m, 0m, sentinel);
|
||||
|
||||
// Assert - December 31, 2024
|
||||
Assert.Equal(new DateTime(2024, 12, 31), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseJdeDateTime_NonLeapYearDay366_ReturnsSentinel()
|
||||
{
|
||||
// Arrange
|
||||
var sentinel = new DateTime(1900, 1, 1);
|
||||
|
||||
// Act - 2023 is NOT a leap year, day 366 is invalid
|
||||
var result = JdeDateTransformer.ParseJdeDateTime(123366m, 0m, sentinel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(sentinel, result);
|
||||
}
|
||||
|
||||
private static IDataReader CreateMockReader(string[] columns, object[] values)
|
||||
{
|
||||
var reader = Substitute.For<IDataReader>();
|
||||
|
||||
Reference in New Issue
Block a user