using System; using Google.Protobuf; using MxGateway.Contracts.Proto; using MxGateway.Worker.Conversion; using ProtobufTimestamp = Google.Protobuf.WellKnownTypes.Timestamp; namespace MxGateway.Worker.Tests.Conversion; public sealed class VariantConverterTests { private readonly VariantConverter _converter = new(); /// Verifies that supported scalar types are converted with correct data type and value kind. /// Scalar value to convert. /// Expected MxDataType of the converted value. /// Expected KindOneofCase of the converted value. [Theory] [InlineData(true, MxDataType.Boolean, MxValue.KindOneofCase.BoolValue)] [InlineData(42, MxDataType.Integer, MxValue.KindOneofCase.Int32Value)] [InlineData(42L, MxDataType.Integer, MxValue.KindOneofCase.Int64Value)] [InlineData(1.25f, MxDataType.Float, MxValue.KindOneofCase.FloatValue)] [InlineData(2.5d, MxDataType.Double, MxValue.KindOneofCase.DoubleValue)] [InlineData("value", MxDataType.String, MxValue.KindOneofCase.StringValue)] public void Convert_WithSupportedScalar_ProjectsTypedValue( object value, MxDataType expectedDataType, MxValue.KindOneofCase expectedKind) { MxValue converted = _converter.Convert(value); Assert.Equal(expectedDataType, converted.DataType); Assert.Equal(expectedKind, converted.KindCase); Assert.False(string.IsNullOrWhiteSpace(converted.VariantType)); } /// Verifies that DateTime values are converted to protobuf timestamps. [Fact] public void Convert_WithDateTime_ProjectsTimestamp() { DateTime dateTime = new(2026, 4, 26, 17, 45, 0, DateTimeKind.Utc); MxValue converted = _converter.Convert(dateTime); Assert.Equal(MxDataType.Time, converted.DataType); Assert.Equal(ProtobufTimestamp.FromDateTime(dateTime), converted.TimestampValue); Assert.Equal("VT_DATE", converted.VariantType); } /// Verifies that file time values with expected time data type are converted to protobuf timestamps. [Fact] public void Convert_WithFileTimeAndExpectedTime_ProjectsTimestamp() { DateTime dateTime = new(2026, 4, 26, 17, 45, 0, DateTimeKind.Utc); MxValue converted = _converter.Convert(dateTime.ToFileTimeUtc(), MxDataType.Time); Assert.Equal(MxDataType.Time, converted.DataType); Assert.Equal(ProtobufTimestamp.FromDateTime(dateTime), converted.TimestampValue); Assert.Equal("VT_I8", converted.VariantType); } /// /// Worker-010 regression: a 32-bit with an expected /// data type of must not be projected as a /// Windows FILETIME. A uint can only hold the low 32 bits of a FILETIME, /// which would silently render as a near-1601 timestamp; the converter /// must fall through to an integer projection instead. /// [Fact] public void Convert_WithUInt32AndExpectedTime_DoesNotProjectFileTime() { const uint value = 123456789u; MxValue converted = _converter.Convert(value, MxDataType.Time); Assert.Equal(MxDataType.Integer, converted.DataType); Assert.Equal(MxValue.KindOneofCase.Int64Value, converted.KindCase); Assert.Equal(value, converted.Int64Value); Assert.Equal("VT_UI4", converted.VariantType); } /// Verifies that null-like values preserve their null semantics and variant type. /// Null-like value to convert. /// Expected variant type string. [Theory] [InlineData(null, "VT_EMPTY")] [InlineData(typeof(DBNull), "VT_NULL")] public void Convert_WithNullLikeValue_PreservesNull( object? value, string expectedVariantType) { object? actualValue = value is System.Type ? DBNull.Value : value; MxValue converted = _converter.Convert(actualValue); Assert.True(converted.IsNull); Assert.Equal(MxDataType.NoData, converted.DataType); Assert.Equal(expectedVariantType, converted.VariantType); Assert.Equal(MxValue.KindOneofCase.None, converted.KindCase); } /// Verifies that supported array types are converted with correct element type and dimensions. [Fact] public void ConvertArray_WithSupportedArrays_ProjectsTypedValuesAndDimensions() { MxValue bools = _converter.Convert(new[] { true, false }); MxValue ints = _converter.Convert(new[] { 1, 2, 3 }); MxValue floats = _converter.Convert(new[] { 1.25f, 2.5f }); MxValue doubles = _converter.Convert(new[] { 1.25d, 2.5d }); MxValue strings = _converter.Convert(new[] { "one", "two" }); MxValue times = _converter.Convert(new[] { new DateTime(2026, 4, 26, 17, 45, 0, DateTimeKind.Utc), new DateTime(2026, 4, 26, 17, 46, 0, DateTimeKind.Utc), }); Assert.Equal(new[] { true, false }, bools.ArrayValue.BoolValues.Values); Assert.Equal(new[] { 1, 2, 3 }, ints.ArrayValue.Int32Values.Values); Assert.Equal(new[] { 1.25f, 2.5f }, floats.ArrayValue.FloatValues.Values); Assert.Equal(new[] { 1.25d, 2.5d }, doubles.ArrayValue.DoubleValues.Values); Assert.Equal(new[] { "one", "two" }, strings.ArrayValue.StringValues.Values); Assert.Equal(2, times.ArrayValue.TimestampValues.Values.Count); Assert.Equal(new uint[] { 2 }, bools.ArrayValue.Dimensions); Assert.Equal(MxDataType.Boolean, bools.ArrayValue.ElementDataType); } /// Verifies that multidimensional arrays preserve rank and dimension information. [Fact] public void ConvertArray_WithMultidimensionalArray_PreservesRankAndDimensions() { int[,] values = { { 1, 2, 3 }, { 4, 5, 6 }, }; MxValue converted = _converter.Convert(values); Assert.Equal(new uint[] { 2, 3 }, converted.ArrayValue.Dimensions); Assert.Equal(new[] { 1, 2, 3, 4, 5, 6 }, converted.ArrayValue.Int32Values.Values); } /// Verifies that file time arrays with expected time data type are converted to timestamp arrays. [Fact] public void ConvertArray_WithExpectedTimeAndFileTimeValues_ProjectsTimestampArray() { DateTime first = new(2026, 4, 26, 17, 45, 0, DateTimeKind.Utc); DateTime second = new(2026, 4, 26, 17, 46, 0, DateTimeKind.Utc); MxValue converted = _converter.Convert( new[] { first.ToFileTimeUtc(), second.ToFileTimeUtc() }, MxDataType.Time); Assert.Equal(MxDataType.Time, converted.ArrayValue.ElementDataType); Assert.Equal( new[] { ProtobufTimestamp.FromDateTime(first), ProtobufTimestamp.FromDateTime(second) }, converted.ArrayValue.TimestampValues.Values); } /// Verifies that unknown scalar types preserve raw value and diagnostic metadata. [Fact] public void Convert_WithUnknownScalar_PreservesRawMetadata() { UnsupportedVariant value = new("opaque"); MxValue converted = _converter.Convert(value); Assert.Equal(MxDataType.Unknown, converted.DataType); Assert.Equal(MxValue.KindOneofCase.RawValue, converted.KindCase); Assert.Contains(typeof(UnsupportedVariant).FullName!, converted.VariantType); Assert.Contains(typeof(UnsupportedVariant).FullName!, converted.RawDiagnostic); Assert.Equal(ByteString.CopyFromUtf8("opaque"), converted.RawValue); } /// Verifies that unknown array types preserve raw values and diagnostic metadata. [Fact] public void ConvertArray_WithUnknownArray_PreservesRawMetadata() { UnsupportedVariant[] values = [ new("first"), new("second"), ]; MxValue converted = _converter.Convert(values); Assert.Equal(MxDataType.Unknown, converted.ArrayValue.ElementDataType); Assert.Equal(MxArray.ValuesOneofCase.RawValues, converted.ArrayValue.ValuesCase); Assert.Equal(new uint[] { 2 }, converted.ArrayValue.Dimensions); Assert.Equal("first", converted.ArrayValue.RawValues.Values[0].ToStringUtf8()); Assert.Contains(typeof(UnsupportedVariant).FullName!, converted.ArrayValue.RawDiagnostic); } /// Fake unsupported variant type for testing unknown type handling. private sealed class UnsupportedVariant { private readonly string _value; /// Initializes a new instance of the UnsupportedVariant class. /// The opaque value. public UnsupportedVariant(string value) { _value = value; } /// public override string ToString() { return _value; } } }