using System.Data; using JdeScoping.DataSync.Etl.Transformers; using NSubstitute; namespace JdeScoping.DataSync.Tests.Etl.Transformers; public class JdeDateTransformerTests { [Fact] public void FieldCount_ReducedByOne() { var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt"); var reader = transformer.Transform(source); Assert.Equal(3, reader.FieldCount); } [Fact] public void GetName_DateColumnRenamed_TimeColumnRemoved() { var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt"); var reader = transformer.Transform(source); Assert.Equal("Id", reader.GetName(0)); Assert.Equal("UpdatedAt", reader.GetName(1)); Assert.Equal("Name", reader.GetName(2)); } [Fact] public void GetValue_ParsesJulianDateAndTime() { // Julian date 124001 = Jan 1, 2024 (century digit 1 = 2000s, year 24, day 001) // Time 120000 = 12:00:00 var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); source.Read().Returns(true); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt"); var reader = transformer.Transform(source); reader.Read(); var expectedDate = new DateTime(2024, 1, 1, 12, 0, 0); Assert.Equal(expectedDate, reader.GetValue(1)); } [Fact] public void GetValue_NullDate_ReturnsDbNull() { var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, DBNull.Value, DBNull.Value, "Test" }); source.Read().Returns(true); source.IsDBNull(1).Returns(true); source.IsDBNull(2).Returns(true); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt"); var reader = transformer.Transform(source); reader.Read(); Assert.Equal(DBNull.Value, reader.GetValue(1)); } [Fact] public void GetFieldType_DateColumn_ReturnsDateTime() { var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt"); var reader = transformer.Transform(source); Assert.Equal(typeof(DateTime), reader.GetFieldType(1)); } [Fact] public void GetOrdinal_NewDateColumn_ReturnsCorrectOrdinal() { var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UpdatedAt"); var reader = transformer.Transform(source); Assert.Equal(1, reader.GetOrdinal("UpdatedAt")); } [Fact] public void ParseJdeDateTime_VariousDates() { // Test the static parsing method Assert.Equal(new DateTime(2024, 1, 1, 12, 0, 0), JdeDateTransformer.ParseJdeDateTime(124001m, 120000m)); Assert.Equal(new DateTime(2023, 12, 31, 23, 59, 59), JdeDateTransformer.ParseJdeDateTime(123365m, 235959m)); 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); } [Fact] public void OutputColumnConflictsWithExistingColumn_ThrowsInvalidOperationException() { // Arrange - "Name" column exists and we try to use it as output var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "Name"); // "Name" conflicts // Act & Assert var ex = Assert.Throws(() => transformer.Transform(source)); Assert.Contains("conflicts with existing column", ex.Message); } [Fact] public void OutputColumnMatchesDateColumn_Succeeds() { // Arrange - output column can match the date column name (it replaces it) var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "UPMJ"); // Same as date column // Act - should not throw var reader = transformer.Transform(source); // Assert Assert.Equal("UPMJ", reader.GetName(1)); // Still named UPMJ } [Fact] public void OutputColumnMatchesTimeColumn_Succeeds() { // Arrange - output column can match the time column name (it's removed anyway) var source = CreateMockReader(new[] { "Id", "UPMJ", "TDAY", "Name" }, new object[] { 1, 124001m, 120000m, "Test" }); var transformer = new JdeDateTransformer("UPMJ", "TDAY", "TDAY"); // Same as time column // Act - should not throw var reader = transformer.Transform(source); // Assert Assert.Equal(3, reader.FieldCount); // One column removed } private static IDataReader CreateMockReader(string[] columns, object[] values) { var reader = Substitute.For(); reader.FieldCount.Returns(columns.Length); for (int i = 0; i < columns.Length; i++) { var index = i; reader.GetName(index).Returns(columns[index]); reader.GetOrdinal(columns[index]).Returns(index); reader.GetValue(index).Returns(values[index]); reader.IsDBNull(index).Returns(values[index] == DBNull.Value); reader.GetFieldType(index).Returns(values[index]?.GetType() ?? typeof(object)); } return reader; } }