feat(core-abstractions): TagConfigJson shared strict-capable field readers (R2-11, 05/CONV-2)

This commit is contained in:
Joseph Doherty
2026-07-13 10:53:13 -04:00
parent 9c94d51bf4
commit a9b7a5002a
3 changed files with 180 additions and 1 deletions
@@ -0,0 +1,79 @@
using System.Text.Json;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests;
/// <summary>
/// Pins the shared strict-capable TagConfig field readers (R2-11, 05/CONV-2): the
/// <see cref="TagConfigJson.TryReadEnum{TEnum}"/> Absent/Valid/Invalid matrix (case-insensitive),
/// <see cref="TagConfigJson.ReadEnumOrDefault{TEnum}"/> lenient equivalence to the retired copies,
/// <see cref="TagConfigJson.ReadWritable"/> explicit-false-only semantics, and
/// <see cref="TagConfigJson.DescribeInvalidEnum{TEnum}"/> messages.
/// </summary>
public sealed class TagConfigJsonTests
{
/// <summary>Sample enum for the reader matrix (public so it can be an <c>[InlineData]</c> arg).</summary>
public enum Sample { Alpha, Beta, Gamma }
private static JsonElement Root(string json) => JsonDocument.Parse(json).RootElement;
[Theory]
[InlineData("{\"k\":\"Beta\"}", JsonEnumRead.Valid, Sample.Beta)]
[InlineData("{\"k\":\"beta\"}", JsonEnumRead.Valid, Sample.Beta)] // case-insensitive
[InlineData("{\"k\":\"GAMMA\"}", JsonEnumRead.Valid, Sample.Gamma)]
[InlineData("{\"k\":\"Deltaa\"}", JsonEnumRead.Invalid, default(Sample))] // typo → Invalid
[InlineData("{}", JsonEnumRead.Absent, default(Sample))] // absent
[InlineData("{\"k\":123}", JsonEnumRead.Absent, default(Sample))] // non-string → Absent (lenient)
[InlineData("{\"k\":null}", JsonEnumRead.Absent, default(Sample))]
public void TryReadEnum_matrix(string json, JsonEnumRead expected, Sample expectedValue)
{
var outcome = TagConfigJson.TryReadEnum<Sample>(Root(json), "k", out var v);
outcome.ShouldBe(expected);
v.ShouldBe(expectedValue);
}
[Theory]
[InlineData("{\"k\":\"Gamma\"}", Sample.Gamma)] // valid → parsed
[InlineData("{\"k\":\"typo\"}", Sample.Alpha)] // invalid → fallback
[InlineData("{}", Sample.Alpha)] // absent → fallback
[InlineData("{\"k\":42}", Sample.Alpha)] // non-string → fallback
public void ReadEnumOrDefault_is_lenient(string json, Sample expected)
{
TagConfigJson.ReadEnumOrDefault(Root(json), "k", Sample.Alpha).ShouldBe(expected);
}
[Theory]
[InlineData("{\"writable\":false}", true, false)] // explicit false → false
[InlineData("{\"writable\":true}", true, true)] // explicit true → default
[InlineData("{}", true, true)] // absent → default
[InlineData("{\"writable\":\"no\"}", true, true)] // non-bool → default
[InlineData("{}", false, false)] // absent, default false
[InlineData("{\"writable\":false}", false, false)] // explicit false, default false
public void ReadWritable_explicit_false_only(string json, bool dflt, bool expected)
{
TagConfigJson.ReadWritable(Root(json), dflt).ShouldBe(expected);
}
[Fact]
public void DescribeInvalidEnum_names_field_value_and_valid_members()
{
var msg = TagConfigJson.DescribeInvalidEnum<Sample>(Root("{\"dataType\":\"Betaa\"}"), "dataType");
msg.ShouldNotBeNull();
msg.ShouldContain("Betaa");
msg.ShouldContain("dataType");
msg.ShouldContain("Alpha");
msg.ShouldContain("Beta");
msg.ShouldContain("Gamma");
}
[Theory]
[InlineData("{\"dataType\":\"Beta\"}")] // valid → null
[InlineData("{}")] // absent → null
[InlineData("{\"dataType\":5}")] // non-string → null
public void DescribeInvalidEnum_null_when_absent_or_valid(string json)
{
TagConfigJson.DescribeInvalidEnum<Sample>(Root(json), "dataType").ShouldBeNull();
}
}