using System; using Google.Protobuf; using MxGateway.Contracts.Proto; using MxGateway.Worker.Bootstrap; using MxGateway.Worker.Conversion; using ProtobufTimestamp = Google.Protobuf.WellKnownTypes.Timestamp; namespace MxGateway.Worker.Tests.Conversion; public sealed class VariantConverterTests { private readonly VariantConverter _converter = new(); [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)); } [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); } [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); } [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); } [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); } [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); } [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); } [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); } [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); } [Fact] public void Redactor_WithCredentialBearingValueFields_RedactsBeforeLogging() { Assert.Equal(WorkerLogRedactor.RedactedValue, WorkerLogRedactor.RedactValue("credential_value", "secret")); Assert.Equal(WorkerLogRedactor.RedactedValue, WorkerLogRedactor.RedactValue("password_value", "secret")); Assert.Equal(WorkerLogRedactor.RedactedValue, WorkerLogRedactor.RedactValue("secured_write_token", "secret")); } private sealed class UnsupportedVariant { private readonly string _value; public UnsupportedVariant(string value) { _value = value; } public override string ToString() { return _value; } } }