fix(code-review): resolve Batch 2 open findings (AbCip, AbLegacy, Galaxy, FOCAS)
- Driver.AbCip.Contracts-001: parse 'writable' from TagConfig JSON (default true) instead of hardcoding - Driver.AbCip.Contracts-002/-003: Dt type comment; drop dead [Display]/[Range] annotations - Driver.AbCip.Contracts-004: dedicated AbCipEquipmentTagParser test class (+15) - Driver.AbCip-017: document Tick severity Low-fallback on Bad severity read - Driver.AbLegacy.Contracts-002/-003/-004: isArray-scalar remarks (+tests), MaxTagBytes/ForFamily docs - Driver.Galaxy.Browser-003 + Driver.Galaxy.Contracts-003: extract ResolveApiKey -> GalaxySecretRef (dedup) - Driver.Galaxy-019: cache buffered-interval only on Ok + ILogger warnings + ClassifyIntervalReply (+tests) - Driver.FOCAS.Contracts-002: thread WriteIdempotent through DiscoverAsync (+test)
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Dedicated unit tests for <see cref="AbCipEquipmentTagParser.TryParse"/>.
|
||||
/// Covers all distinct outcome branches: valid scalar, 1-element array, N-element array,
|
||||
/// degenerate array shapes, non-JSON input, non-object JSON, blank/missing tagPath,
|
||||
/// the <c>writable</c> field, and the <c>Structure</c> dataType path (Driver.AbCip.Contracts-004).
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public class AbCipEquipmentTagParserTests
|
||||
{
|
||||
// ── Happy-path scalar ────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Valid_scalar_round_trip_parses_all_fields()
|
||||
{
|
||||
var json = """{"deviceHostAddress":"ab://10.0.0.1/1,0","tagPath":"Motor.Speed","dataType":"Real"}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.Name.ShouldBe(json);
|
||||
def.TagPath.ShouldBe("Motor.Speed");
|
||||
def.DeviceHostAddress.ShouldBe("ab://10.0.0.1/1,0");
|
||||
def.DataType.ShouldBe(AbCipDataType.Real);
|
||||
def.Writable.ShouldBeTrue();
|
||||
def.IsArray.ShouldBeFalse();
|
||||
def.ElementCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
// ── Array shape ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void One_element_array_isArray_true_arrayLength_1_is_an_array_not_a_scalar()
|
||||
{
|
||||
var json = """{"tagPath":"Tags[0]","dataType":"DInt","isArray":true,"arrayLength":1}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.IsArray.ShouldBeTrue();
|
||||
def.ElementCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void N_element_array_isArray_true_arrayLength_N_parses_correctly()
|
||||
{
|
||||
var json = """{"tagPath":"Buf","dataType":"SInt","isArray":true,"arrayLength":8}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.IsArray.ShouldBeTrue();
|
||||
def.ElementCount.ShouldBe(8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsArray_true_arrayLength_0_is_canonical_scalar()
|
||||
{
|
||||
// Canonical rule: isArray:true AND arrayLength < 1 → scalar.
|
||||
var json = """{"tagPath":"PT_101","isArray":true,"arrayLength":0}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.IsArray.ShouldBeFalse();
|
||||
def.ElementCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsArray_true_arrayLength_absent_is_canonical_scalar()
|
||||
{
|
||||
// Canonical rule: isArray:true but arrayLength absent → scalar.
|
||||
var json = """{"tagPath":"PT_101","isArray":true}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.IsArray.ShouldBeFalse();
|
||||
def.ElementCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
// ── Rejection paths ──────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Non_JSON_input_returns_false()
|
||||
=> AbCipEquipmentTagParser.TryParse("not json at all", out _).ShouldBeFalse();
|
||||
|
||||
[Fact]
|
||||
public void Non_object_JSON_array_returns_false()
|
||||
=> AbCipEquipmentTagParser.TryParse("""["tagPath","foo"]""", out _).ShouldBeFalse();
|
||||
|
||||
[Fact]
|
||||
public void Non_object_JSON_string_returns_false()
|
||||
=> AbCipEquipmentTagParser.TryParse("\"Motor.Speed\"", out _).ShouldBeFalse();
|
||||
|
||||
[Fact]
|
||||
public void Missing_tagPath_returns_false()
|
||||
=> AbCipEquipmentTagParser.TryParse("""{"dataType":"DInt"}""", out _).ShouldBeFalse();
|
||||
|
||||
[Fact]
|
||||
public void Blank_tagPath_returns_false()
|
||||
=> AbCipEquipmentTagParser.TryParse("""{"tagPath":" "}""", out _).ShouldBeFalse();
|
||||
|
||||
[Fact]
|
||||
public void TagPath_as_number_returns_false()
|
||||
=> AbCipEquipmentTagParser.TryParse("""{"tagPath":42}""", out _).ShouldBeFalse();
|
||||
|
||||
// ── Writable field (Driver.AbCip.Contracts-001) ───────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Writable_false_is_honoured()
|
||||
{
|
||||
var json = """{"tagPath":"Sensor.Val","writable":false}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.Writable.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Writable_absent_defaults_to_true()
|
||||
{
|
||||
var json = """{"tagPath":"Sensor.Val"}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.Writable.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Writable_true_explicit_is_honoured()
|
||||
{
|
||||
var json = """{"tagPath":"Sensor.Val","writable":true}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.Writable.ShouldBeTrue();
|
||||
}
|
||||
|
||||
// ── Structure dataType (Driver.AbCip.Contracts-001 Structure concern) ─────────────────
|
||||
|
||||
/// <summary>
|
||||
/// A "dataType":"Structure" equipment-tag input is accepted and produces a Structure-typed
|
||||
/// definition with Members:null. The driver treats the tag path as a black-box dotted-path
|
||||
/// read (libplctag resolves the full path); UDT member declarations are not supported in the
|
||||
/// equipment-tag flow. This test documents current behaviour so a future change to reject
|
||||
/// Structure is a conscious choice.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Structure_dataType_is_accepted_with_null_Members_and_returns_true()
|
||||
{
|
||||
var json = """{"tagPath":"Motor","dataType":"Structure"}""";
|
||||
AbCipEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
|
||||
def!.DataType.ShouldBe(AbCipDataType.Structure);
|
||||
def.Members.ShouldBeNull();
|
||||
def.TagPath.ShouldBe("Motor");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user