refactor(commons): consolidate List element-type/coercion into AttributeValueCodec; InstanceActor + CLI reuse it (#93)

This commit is contained in:
Joseph Doherty
2026-06-19 02:03:09 -04:00
parent 2935c41bf7
commit 47f5ca687c
4 changed files with 120 additions and 57 deletions
@@ -231,4 +231,56 @@ public class AttributeValueCodecTests
[InlineData(DataType.List, false)]
public void IsValidElementType_MatchesScalarSet(DataType t, bool expected) =>
Assert.Equal(expected, AttributeValueCodec.IsValidElementType(t));
// ── ElementClrType + CoerceEnumerable: the shared object-input coercion ──
// entry point reused by InstanceActor (live OPC UA arrays). Distinct from the
// string-input path in Decode (JSON elements): here elements arrive as boxed
// CLR values and are converted via Convert.ToXxx (or parsed when string).
[Theory]
[InlineData(DataType.String, typeof(string))]
[InlineData(DataType.Int32, typeof(int))]
[InlineData(DataType.Float, typeof(float))]
[InlineData(DataType.Double, typeof(double))]
[InlineData(DataType.Boolean, typeof(bool))]
[InlineData(DataType.DateTime, typeof(DateTime))]
public void ElementClrType_MapsEachScalar(DataType t, Type expected) =>
Assert.Equal(expected, AttributeValueCodec.ElementClrType(t));
[Fact]
public void ElementClrType_RejectsNonElementType() =>
Assert.Throws<FormatException>(() => AttributeValueCodec.ElementClrType(DataType.List));
[Fact]
public void CoerceEnumerable_BuildsTypedList()
{
var list = AttributeValueCodec.CoerceEnumerable(new object[] { 10, 20, 30 }, DataType.Int32);
var typed = Assert.IsType<List<int>>(list);
Assert.Equal(new[] { 10, 20, 30 }, typed);
}
[Fact]
public void CoerceEnumerable_ConvertsMixedClrElementTypes()
{
// Ints and numeric strings both coerce to List<double> via Convert/parse —
// the object-input behavior InstanceActor's MV-8 coercion depends on.
var list = AttributeValueCodec.CoerceEnumerable(new object[] { 1, "2.5", 3.75 }, DataType.Double);
var typed = Assert.IsType<List<double>>(list);
Assert.Equal(new[] { 1.0, 2.5, 3.75 }, typed);
}
[Fact]
public void CoerceEnumerable_DateTimeRoundtripsViaInvariantCulture()
{
var when = new DateTime(2026, 6, 19, 8, 30, 0, DateTimeKind.Utc);
var list = AttributeValueCodec.CoerceEnumerable(
new object[] { when.ToString("O", CultureInfo.InvariantCulture) }, DataType.DateTime);
var typed = Assert.IsType<List<DateTime>>(list);
Assert.Equal(when, typed[0]);
}
[Fact]
public void CoerceEnumerable_ThrowsOnUnsupportedElementType() =>
Assert.Throws<FormatException>(
() => AttributeValueCodec.CoerceEnumerable(new object[] { new byte[] { 1 } }, DataType.Binary));
}