Files
mxaccessgw/src/MxGateway.Worker.Tests/Conversion/VariantConverterTests.cs
T
Joseph Doherty 371bcb3f91 Resolve Worker.Tests-008..015 code-review findings
Worker.Tests-008: moved the misplaced WorkerLogRedactor test out of
VariantConverterTests into Bootstrap/WorkerLogRedactorTests.

Worker.Tests-009: renamed 46 snake_case alarm-test methods to PascalCase
Method_Scenario_Expectation.

Worker.Tests-010: replaced a weak Assert.Contains with an exact assertion
against the real diagnostic message and corrected the XML doc.

Worker.Tests-011: renamed and re-documented a cancellation test that
overstated what it proved.

Worker.Tests-012: added an oversized-frame (MessageTooLarge) test; renamed
the mislabeled zero-length-payload test.

Worker.Tests-013: removed the fixed-100ms ThrowIfCompletedAsync helper; the
caller now races runTask deterministically.

Worker.Tests-014: consolidated duplicated test fakes/helpers
(FakeRuntimeSession, NoopComApartmentInitializer, NoopEventSink, frame
helpers) into a shared TestSupport namespace.

Worker.Tests-015: added MxAccessEventQueue coverage for drain-all (maxEvents
0), empty-queue drain, and enqueue-after-fault.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:59:07 -04:00

213 lines
8.9 KiB
C#

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();
/// <summary>Verifies that supported scalar types are converted with correct data type and value kind.</summary>
/// <param name="value">Scalar value to convert.</param>
/// <param name="expectedDataType">Expected MxDataType of the converted value.</param>
/// <param name="expectedKind">Expected KindOneofCase of the converted value.</param>
[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));
}
/// <summary>Verifies that DateTime values are converted to protobuf timestamps.</summary>
[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);
}
/// <summary>Verifies that file time values with expected time data type are converted to protobuf timestamps.</summary>
[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);
}
/// <summary>
/// Worker-010 regression: a 32-bit <see cref="uint"/> with an expected
/// data type of <see cref="MxDataType.Time"/> 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.
/// </summary>
[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);
}
/// <summary>Verifies that null-like values preserve their null semantics and variant type.</summary>
/// <param name="value">Null-like value to convert.</param>
/// <param name="expectedVariantType">Expected variant type string.</param>
[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);
}
/// <summary>Verifies that supported array types are converted with correct element type and dimensions.</summary>
[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);
}
/// <summary>Verifies that multidimensional arrays preserve rank and dimension information.</summary>
[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);
}
/// <summary>Verifies that file time arrays with expected time data type are converted to timestamp arrays.</summary>
[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);
}
/// <summary>Verifies that unknown scalar types preserve raw value and diagnostic metadata.</summary>
[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);
}
/// <summary>Verifies that unknown array types preserve raw values and diagnostic metadata.</summary>
[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);
}
/// <summary>Fake unsupported variant type for testing unknown type handling.</summary>
private sealed class UnsupportedVariant
{
private readonly string _value;
/// <summary>Initializes a new instance of the UnsupportedVariant class.</summary>
/// <param name="value">The opaque value.</param>
public UnsupportedVariant(string value)
{
_value = value;
}
/// <inheritdoc />
public override string ToString()
{
return _value;
}
}
}