fix(commons): resolve Commons-013,014 — integral JSON index handling, distinguish Malformed vs Legacy OPC UA config
This commit is contained in:
@@ -168,6 +168,47 @@ public class OpcUaEndpointConfigSerializerTests
|
||||
Assert.True(result.IsLegacy);
|
||||
}
|
||||
|
||||
// ── Commons-014 regression: a corrupt typed row must not be mislabelled Legacy ──
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_TypedShapeWithInvalidEnum_ReportsMalformedNotLegacy()
|
||||
{
|
||||
// The row IS the current typed shape (it has endpointUrl) but an enum-valued
|
||||
// field holds an unrecognised string. Typed deserialization throws JsonException;
|
||||
// it must NOT fall through to the legacy path and be reported as Legacy.
|
||||
var json = """{"endpointUrl":"opc.tcp://x:4840","securityMode":"NotARealMode"}""";
|
||||
|
||||
var result = OpcUaEndpointConfigSerializer.Deserialize(json);
|
||||
|
||||
Assert.Equal(OpcUaConfigParseStatus.Malformed, result.Status);
|
||||
Assert.False(result.IsLegacy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_TypedShapeWithWrongTypeField_ReportsMalformedNotLegacy()
|
||||
{
|
||||
// endpointUrl present (typed shape) but a numeric field holds a non-numeric token.
|
||||
var json = """{"endpointUrl":"opc.tcp://x:4840","sessionTimeoutMs":"not-a-number"}""";
|
||||
|
||||
var result = OpcUaEndpointConfigSerializer.Deserialize(json);
|
||||
|
||||
Assert.Equal(OpcUaConfigParseStatus.Malformed, result.Status);
|
||||
Assert.False(result.IsLegacy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_ValidTypedRow_StillReportsTyped()
|
||||
{
|
||||
// Guard: a clean typed row is still classified Typed after the Commons-014 fix.
|
||||
var json = """{"endpointUrl":"opc.tcp://x:4840","securityMode":"sign"}""";
|
||||
|
||||
var result = OpcUaEndpointConfigSerializer.Deserialize(json);
|
||||
|
||||
Assert.Equal(OpcUaConfigParseStatus.Typed, result.Status);
|
||||
Assert.Equal("opc.tcp://x:4840", result.Config.EndpointUrl);
|
||||
Assert.Equal(OpcUaSecurityMode.Sign, result.Config.SecurityMode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_TwoElementDeconstruction_StillWorks()
|
||||
{
|
||||
|
||||
@@ -144,4 +144,48 @@ public class DynamicJsonElementTests
|
||||
Assert.False(strWrapper.TryConvert(convBinder, out var result));
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
// ── Commons-013 regression: integral index values other than int must work ──
|
||||
|
||||
[Fact]
|
||||
public void IndexAccess_WithLongIndex_Works()
|
||||
{
|
||||
// DynamicJsonElement.Wrap surfaces JSON numbers as long; an index computed
|
||||
// from a wrapped JSON number (obj.items[obj.count - 1]) arrives as a long.
|
||||
dynamic obj = Wrap("""{ "items": [ "a", "b", "c" ] }""");
|
||||
long idx = 1L;
|
||||
|
||||
Assert.Equal("b", obj.items[idx]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexAccess_WithIndexDerivedFromWrappedJsonNumber_Works()
|
||||
{
|
||||
// The exact failing case from Commons-013: count is a wrapped JSON number
|
||||
// (unwrapped as long), so count - 1 is a long.
|
||||
dynamic obj = Wrap("""{ "items": [ "a", "b", "c" ], "count": 3 }""");
|
||||
|
||||
Assert.Equal("c", obj.items[obj.count - 1]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData((byte)0, "a")]
|
||||
[InlineData((short)1, "b")]
|
||||
public void IndexAccess_WithWideningIntegralIndex_Works(object index, string expected)
|
||||
{
|
||||
dynamic obj = Wrap("""{ "items": [ "a", "b", "c" ] }""");
|
||||
|
||||
Assert.Equal(expected, obj.items[index]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexAccess_WithLongIndexOutOfRange_Throws()
|
||||
{
|
||||
// An out-of-range long index is still rejected (binder surfaces the error).
|
||||
dynamic obj = Wrap("""{ "items": [ "a" ] }""");
|
||||
long idx = 5L;
|
||||
|
||||
Assert.Throws<Microsoft.CSharp.RuntimeBinder.RuntimeBinderException>(
|
||||
() => { var _ = obj.items[idx]; });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user