using Google.Protobuf.WellKnownTypes; using Grpc.Core; using ZB.MOM.WW.MxGateway.Contracts.Proto; using ZB.MOM.WW.MxGateway.Server.Sessions; namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Sessions; public sealed class SparseArrayExpanderTests { private static MxValue SparseValue( MxDataType elementType, uint totalLength, params (uint Index, MxValue Value)[] elements) { MxSparseArray sparse = new() { ElementDataType = elementType, TotalLength = totalLength, }; foreach ((uint index, MxValue value) in elements) { sparse.Elements.Add(new MxSparseElement { Index = index, Value = value }); } return new MxValue { SparseArrayValue = sparse }; } [Fact] public void Expand_Int32_FillsDefaultsAndSetsElement() { MxValue value = SparseValue( MxDataType.Integer, 4, (1, new MxValue { Int32Value = 7 })); SparseArrayExpander.Expand(value); Assert.Equal(MxValue.KindOneofCase.ArrayValue, value.KindCase); Assert.Equal(MxDataType.Integer, value.ArrayValue.ElementDataType); Assert.Equal(new uint[] { 4 }, value.ArrayValue.Dimensions); Assert.Equal(MxArray.ValuesOneofCase.Int32Values, value.ArrayValue.ValuesCase); Assert.Equal(new[] { 0, 7, 0, 0 }, value.ArrayValue.Int32Values.Values); } [Fact] public void Expand_Boolean_EmptyElements_AllDefaultFalse() { MxValue value = SparseValue(MxDataType.Boolean, 3); SparseArrayExpander.Expand(value); Assert.Equal(MxArray.ValuesOneofCase.BoolValues, value.ArrayValue.ValuesCase); Assert.Equal(new[] { false, false, false }, value.ArrayValue.BoolValues.Values); } [Fact] public void Expand_ZeroTotalLength_Throws() { MxValue value = SparseValue(MxDataType.Integer, 0); RpcException ex = Assert.Throws(() => SparseArrayExpander.Expand(value)); Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode); } [Fact] public void Expand_IndexOutOfRange_Throws() { MxValue value = SparseValue( MxDataType.Integer, 2, (5, new MxValue { Int32Value = 1 })); RpcException ex = Assert.Throws(() => SparseArrayExpander.Expand(value)); Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode); } [Fact] public void Expand_DuplicateIndex_Throws() { MxValue value = SparseValue( MxDataType.Integer, 4, (1, new MxValue { Int32Value = 1 }), (1, new MxValue { Int32Value = 2 })); RpcException ex = Assert.Throws(() => SparseArrayExpander.Expand(value)); Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode); } [Fact] public void Expand_UnsupportedElementType_Throws() { MxValue value = SparseValue(MxDataType.Unspecified, 2); RpcException ex = Assert.Throws(() => SparseArrayExpander.Expand(value)); Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode); } [Fact] public void Expand_ElementValueKindMismatch_Throws() { MxValue value = SparseValue( MxDataType.Integer, 2, (0, new MxValue { StringValue = "nope" })); RpcException ex = Assert.Throws(() => SparseArrayExpander.Expand(value)); Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode); } [Fact] public void Expand_String_FillsEmptyStringDefault() { MxValue value = SparseValue( MxDataType.String, 2, (0, new MxValue { StringValue = "a" })); SparseArrayExpander.Expand(value); Assert.Equal(MxArray.ValuesOneofCase.StringValues, value.ArrayValue.ValuesCase); Assert.Equal(new[] { "a", string.Empty }, value.ArrayValue.StringValues.Values); } [Fact] public void Expand_Time_FillsEpochDefault() { MxValue value = SparseValue( MxDataType.Time, 2, (1, new MxValue { TimestampValue = new Timestamp { Seconds = 5 } })); SparseArrayExpander.Expand(value); Assert.Equal(MxArray.ValuesOneofCase.TimestampValues, value.ArrayValue.ValuesCase); Assert.Equal(2, value.ArrayValue.TimestampValues.Values.Count); Assert.Equal(0, value.ArrayValue.TimestampValues.Values[0].Seconds); Assert.Equal(0, value.ArrayValue.TimestampValues.Values[0].Nanos); Assert.Equal(5, value.ArrayValue.TimestampValues.Values[1].Seconds); } [Fact] public void Expand_Double_HappyPath() { MxValue value = SparseValue( MxDataType.Double, 3, (2, new MxValue { DoubleValue = 1.5 })); SparseArrayExpander.Expand(value); Assert.Equal(MxArray.ValuesOneofCase.DoubleValues, value.ArrayValue.ValuesCase); Assert.Equal(new[] { 0d, 0d, 1.5 }, value.ArrayValue.DoubleValues.Values); } [Fact] public void Expand_Float_HappyPath() { MxValue value = SparseValue( MxDataType.Float, 2, (0, new MxValue { FloatValue = 2.5f })); SparseArrayExpander.Expand(value); Assert.Equal(MxArray.ValuesOneofCase.FloatValues, value.ArrayValue.ValuesCase); Assert.Equal(new[] { 2.5f, 0f }, value.ArrayValue.FloatValues.Values); } [Fact] public void Expand_Int64_WhenElementIsInt64() { MxValue value = SparseValue( MxDataType.Integer, 3, (2, new MxValue { Int64Value = 9_000_000_000L })); SparseArrayExpander.Expand(value); Assert.Equal(MxArray.ValuesOneofCase.Int64Values, value.ArrayValue.ValuesCase); Assert.Equal(new[] { 0L, 0L, 9_000_000_000L }, value.ArrayValue.Int64Values.Values); } [Fact] public void Expand_NonSparseValue_NoOps() { MxValue value = new() { Int32Value = 42 }; SparseArrayExpander.Expand(value); Assert.Equal(MxValue.KindOneofCase.Int32Value, value.KindCase); Assert.Equal(42, value.Int32Value); } [Fact] public void Expand_NullValue_ThrowsArgumentNull() { Assert.Throws(() => SparseArrayExpander.Expand(null!)); } [Fact] public void Expand_TotalLengthExceedsMaxArrayLength_Throws() { MxValue value = SparseValue(MxDataType.Integer, 2_147_483_648u); RpcException ex = Assert.Throws(() => SparseArrayExpander.Expand(value)); Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode); } }