506ba5c61d
Add virtual methods to DataTransformerBase for GetBytes, GetChars, GetData, and GetDataTypeName that properly handle computed columns by throwing NotSupportedException when MapOrdinal returns -1. Update TransformingDataReader to delegate these methods to the transformer instead of directly to the source reader.
387 lines
12 KiB
C#
387 lines
12 KiB
C#
using System.Data;
|
|
using JdeScoping.DataSync.Etl.Transformers;
|
|
using NSubstitute;
|
|
|
|
namespace JdeScoping.DataSync.Tests.Etl.Transformers;
|
|
|
|
public class TransformingDataReaderTests
|
|
{
|
|
[Fact]
|
|
public void Read_DelegatesToSourceReader()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.Read().Returns(true, true, false);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.True(reader.Read());
|
|
Assert.True(reader.Read());
|
|
Assert.False(reader.Read());
|
|
}
|
|
|
|
[Fact]
|
|
public void FieldCount_DelegatesToTransformer()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(5);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal(5, reader.FieldCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetName_DelegatesToTransformer()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.GetName(0).Returns("OriginalName");
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal("OriginalName", reader.GetName(0));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_DelegatesToTransformer()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.GetValue(0).Returns("TestValue");
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal("TestValue", reader.GetValue(0));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetOrdinal_DelegatesToTransformer()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.GetOrdinal("TestField").Returns(3);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal(3, reader.GetOrdinal("TestField"));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetFieldType_DelegatesToTransformer()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.GetFieldType(0).Returns(typeof(string));
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal(typeof(string), reader.GetFieldType(0));
|
|
}
|
|
|
|
[Fact]
|
|
public void IsDBNull_DelegatesToTransformer()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.IsDBNull(0).Returns(true);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.True(reader.IsDBNull(0));
|
|
}
|
|
|
|
[Fact]
|
|
public void Indexer_ByOrdinal_UsesGetValue()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.GetValue(1).Returns("IndexedValue");
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal("IndexedValue", reader[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Indexer_ByName_UsesGetOrdinalAndGetValue()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.GetOrdinal("TestColumn").Returns(2);
|
|
source.GetValue(2).Returns("NamedValue");
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal("NamedValue", reader["TestColumn"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValues_PopulatesArrayWithTransformedValues()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(3);
|
|
source.GetValue(0).Returns("Value0");
|
|
source.GetValue(1).Returns("Value1");
|
|
source.GetValue(2).Returns("Value2");
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
var values = new object[3];
|
|
var count = reader.GetValues(values);
|
|
|
|
Assert.Equal(3, count);
|
|
Assert.Equal("Value0", values[0]);
|
|
Assert.Equal("Value1", values[1]);
|
|
Assert.Equal("Value2", values[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Dispose_DisposesSourceReader()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
reader.Dispose();
|
|
|
|
source.Received(1).Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Close_ClosesSourceReader()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
reader.Close();
|
|
|
|
source.Received(1).Close();
|
|
}
|
|
|
|
[Fact]
|
|
public void NextResult_DelegatesToSourceReader()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.NextResult().Returns(true);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.True(reader.NextResult());
|
|
}
|
|
|
|
[Fact]
|
|
public void Depth_DelegatesToSourceReader()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.Depth.Returns(2);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal(2, reader.Depth);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsClosed_DelegatesToSourceReader()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.IsClosed.Returns(true);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.True(reader.IsClosed);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecordsAffected_DelegatesToSourceReader()
|
|
{
|
|
var source = Substitute.For<IDataReader>();
|
|
source.RecordsAffected.Returns(42);
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
var reader = transformer.Transform(source);
|
|
|
|
Assert.Equal(42, reader.RecordsAffected);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transform_ThrowsOnNullSource()
|
|
{
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
Assert.Throws<ArgumentNullException>(() => transformer.Transform(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void TransformerName_ReturnsExpectedName()
|
|
{
|
|
var transformer = new PassThroughTransformer();
|
|
|
|
Assert.Equal("PassThrough", transformer.TransformerName);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetBytes_ComputedColumn_ThrowsNotSupportedException()
|
|
{
|
|
// Arrange - transformer that returns -1 for ordinal 0 (computed)
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("Computed");
|
|
source.GetName(1).Returns("B");
|
|
var transformer = new ComputedColumnTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<NotSupportedException>(() =>
|
|
reader.GetBytes(0, 0, null, 0, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetChars_ComputedColumn_ThrowsNotSupportedException()
|
|
{
|
|
// Arrange - transformer that returns -1 for ordinal 0 (computed)
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("Computed");
|
|
source.GetName(1).Returns("B");
|
|
var transformer = new ComputedColumnTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<NotSupportedException>(() =>
|
|
reader.GetChars(0, 0, null, 0, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetData_ComputedColumn_ThrowsNotSupportedException()
|
|
{
|
|
// Arrange - transformer that returns -1 for ordinal 0 (computed)
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("Computed");
|
|
source.GetName(1).Returns("B");
|
|
var transformer = new ComputedColumnTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<NotSupportedException>(() =>
|
|
reader.GetData(0));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetDataTypeName_ComputedColumn_ThrowsNotSupportedException()
|
|
{
|
|
// Arrange - transformer that returns -1 for ordinal 0 (computed)
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("Computed");
|
|
source.GetName(1).Returns("B");
|
|
var transformer = new ComputedColumnTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<NotSupportedException>(() =>
|
|
reader.GetDataTypeName(0));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetBytes_NonComputedColumn_DelegatesToSource()
|
|
{
|
|
// Arrange
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("A");
|
|
source.GetName(1).Returns("B");
|
|
var expectedBytes = new byte[] { 1, 2, 3 };
|
|
source.GetBytes(1, 0, Arg.Any<byte[]?>(), 0, 3).Returns(3L);
|
|
var transformer = new PassThroughTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act
|
|
var buffer = new byte[3];
|
|
var result = reader.GetBytes(1, 0, buffer, 0, 3);
|
|
|
|
// Assert
|
|
Assert.Equal(3L, result);
|
|
source.Received(1).GetBytes(1, 0, buffer, 0, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetChars_NonComputedColumn_DelegatesToSource()
|
|
{
|
|
// Arrange
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("A");
|
|
source.GetName(1).Returns("B");
|
|
source.GetChars(1, 0, Arg.Any<char[]?>(), 0, 3).Returns(3L);
|
|
var transformer = new PassThroughTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act
|
|
var buffer = new char[3];
|
|
var result = reader.GetChars(1, 0, buffer, 0, 3);
|
|
|
|
// Assert
|
|
Assert.Equal(3L, result);
|
|
source.Received(1).GetChars(1, 0, buffer, 0, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetData_NonComputedColumn_DelegatesToSource()
|
|
{
|
|
// Arrange
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("A");
|
|
source.GetName(1).Returns("B");
|
|
var nestedReader = Substitute.For<IDataReader>();
|
|
source.GetData(1).Returns(nestedReader);
|
|
var transformer = new PassThroughTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act
|
|
var result = reader.GetData(1);
|
|
|
|
// Assert
|
|
Assert.Same(nestedReader, result);
|
|
source.Received(1).GetData(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetDataTypeName_NonComputedColumn_DelegatesToSource()
|
|
{
|
|
// Arrange
|
|
var source = Substitute.For<IDataReader>();
|
|
source.FieldCount.Returns(2);
|
|
source.GetName(0).Returns("A");
|
|
source.GetName(1).Returns("B");
|
|
source.GetDataTypeName(1).Returns("nvarchar");
|
|
var transformer = new PassThroughTransformer();
|
|
var reader = transformer.Transform(source);
|
|
|
|
// Act
|
|
var result = reader.GetDataTypeName(1);
|
|
|
|
// Assert
|
|
Assert.Equal("nvarchar", result);
|
|
source.Received(1).GetDataTypeName(1);
|
|
}
|
|
|
|
private class PassThroughTransformer : DataTransformerBase
|
|
{
|
|
public override string TransformerName => "PassThrough";
|
|
}
|
|
|
|
private class ComputedColumnTransformer : DataTransformerBase
|
|
{
|
|
public override string TransformerName => "Computed";
|
|
public override int MapOrdinal(int ordinal, IDataReader source) => ordinal == 0 ? -1 : ordinal;
|
|
}
|
|
}
|