test(core,config): FullName-semantics characterization for walker + draft gate (R2-11)
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"lastUpdated": "2026-07-12",
|
"lastUpdated": "2026-07-12",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{ "id": "T1", "subject": "Golden TagConfig corpus + compose→encode→decode parity characterization test (Runtime.Tests, green on unmodified tree)", "status": "completed", "blockedBy": [] },
|
{ "id": "T1", "subject": "Golden TagConfig corpus + compose→encode→decode parity characterization test (Runtime.Tests, green on unmodified tree)", "status": "completed", "blockedBy": [] },
|
||||||
{ "id": "T2", "subject": "FullName-semantics characterization for EquipmentNodeWalker (Core.Tests) + DraftValidator Galaxy rule (Configuration.Tests)", "status": "pending", "blockedBy": [] },
|
{ "id": "T2", "subject": "FullName-semantics characterization for EquipmentNodeWalker (Core.Tests) + DraftValidator Galaxy rule (Configuration.Tests)", "status": "completed", "blockedBy": [] },
|
||||||
{ "id": "T3", "subject": "TagConfigIntent + TagAlarmIntent in Commons/Types (TDD; port composer semantics verbatim + OpcUaServer ExtractTag* test tables)", "status": "pending", "blockedBy": [] },
|
{ "id": "T3", "subject": "TagConfigIntent + TagAlarmIntent in Commons/Types (TDD; port composer semantics verbatim + OpcUaServer ExtractTag* test tables)", "status": "pending", "blockedBy": [] },
|
||||||
{ "id": "T4", "subject": "DeviceConfigIntent (TryExtractHost/NormalizeHost) in Commons/Types (TDD)", "status": "pending", "blockedBy": [] },
|
{ "id": "T4", "subject": "DeviceConfigIntent (TryExtractHost/NormalizeHost) in Commons/Types (TDD)", "status": "pending", "blockedBy": [] },
|
||||||
{ "id": "T5", "subject": "AddressSpaceComposer swap: single TagConfigIntent.Parse per tag, delete 4 Extract* statics (P-1 parse-once)", "status": "pending", "blockedBy": ["T1", "T3"] },
|
{ "id": "T5", "subject": "AddressSpaceComposer swap: single TagConfigIntent.Parse per tag, delete 4 Extract* statics (P-1 parse-once)", "status": "pending", "blockedBy": ["T1", "T3"] },
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Validation;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Configuration.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Characterization net (R2-11) pinning the DraftValidator Galaxy rule's <em>explicit</em>-FullName
|
||||||
|
/// semantics across the golden TagConfig corpus BEFORE the private FullName reader is delegated to
|
||||||
|
/// <c>TagConfigIntent.Parse(...).ExplicitFullName</c>. Distinct from the walker/composer raw-blob
|
||||||
|
/// fallback: <c>GalaxyTagMissingReference</c> fires whenever the TagConfig has no usable
|
||||||
|
/// <em>explicit</em> string <c>FullName</c> — i.e. absent / non-string / whitespace / non-object /
|
||||||
|
/// malformed — and does NOT fire only when a non-blank string FullName is present. Must be green on
|
||||||
|
/// the unmodified tree and stay green through the swap.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DraftValidatorGalaxyFullNameCorpusTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
// Present, non-blank string FullName → rule does NOT fire
|
||||||
|
[InlineData("{\"FullName\":\"X.Y\"}", false)]
|
||||||
|
[InlineData("{\"FullName\":\"A.B\",\"region\":\"Coils\"}", false)]
|
||||||
|
// Absent / non-string / whitespace / non-object / malformed → rule FIRES
|
||||||
|
[InlineData("{}", true)]
|
||||||
|
[InlineData("{\"region\":\"HoldingRegisters\"}", true)]
|
||||||
|
[InlineData("{\"FullName\":123}", true)]
|
||||||
|
[InlineData("{\"FullName\":\" \"}", true)]
|
||||||
|
[InlineData("[1,2]", true)]
|
||||||
|
[InlineData("not json {", true)]
|
||||||
|
public void Galaxy_missing_reference_fires_iff_no_explicit_string_FullName(string tagConfig, bool shouldFire)
|
||||||
|
{
|
||||||
|
var draft = new DraftSnapshot
|
||||||
|
{
|
||||||
|
GenerationId = 1,
|
||||||
|
ClusterId = "c",
|
||||||
|
DriverInstances =
|
||||||
|
[
|
||||||
|
new DriverInstance
|
||||||
|
{
|
||||||
|
DriverInstanceId = "d-galaxy", ClusterId = "c", NamespaceId = "ns-1",
|
||||||
|
Name = "Galaxy", DriverType = "GalaxyMxGateway", DriverConfig = "{}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Tags =
|
||||||
|
[
|
||||||
|
new Tag
|
||||||
|
{
|
||||||
|
TagId = "tag-galaxytag", DriverInstanceId = "d-galaxy", EquipmentId = "eq-1",
|
||||||
|
Name = "galaxytag", FolderPath = null, DataType = "Float",
|
||||||
|
AccessLevel = TagAccessLevel.Read, TagConfig = tagConfig,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
var fired = DraftValidator.Validate(draft)
|
||||||
|
.Any(e => e.Code == "GalaxyTagMissingReference" && e.Context == "tag-galaxytag");
|
||||||
|
fired.ShouldBe(shouldFire);
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Core.OpcUa;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Core.Tests.OpcUa;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Characterization net (R2-11) pinning <see cref="EquipmentNodeWalker.ExtractFullName"/>'s
|
||||||
|
/// raw-blob-fallback semantics across the golden TagConfig corpus BEFORE the parser is delegated to
|
||||||
|
/// <c>TagConfigIntent.Parse</c>. The walker's FullName copy falls back to the raw blob whenever the
|
||||||
|
/// input is not a JSON object carrying a string <c>FullName</c> (blank / non-object / malformed /
|
||||||
|
/// absent / non-string). Whitespace + non-string-property values are returned verbatim (not trimmed).
|
||||||
|
/// Must be green on the unmodified tree and stay green through the swap.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class EquipmentNodeWalkerFullNameCorpusTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
// object with a string FullName → the FullName string (verbatim, incl. whitespace)
|
||||||
|
[InlineData("{\"FullName\":\"X.Y\"}", "X.Y")]
|
||||||
|
[InlineData("{\"FullName\":\" \"}", " ")]
|
||||||
|
[InlineData("{\"FullName\":\"A.B\",\"region\":\"Coils\",\"address\":5}", "A.B")]
|
||||||
|
// blank → raw blob (IsNullOrWhiteSpace short-circuit)
|
||||||
|
[InlineData(" ", " ")]
|
||||||
|
// non-object root → raw blob
|
||||||
|
[InlineData("[1,2]", "[1,2]")]
|
||||||
|
// malformed JSON → raw blob
|
||||||
|
[InlineData("not json {", "not json {")]
|
||||||
|
// object, FullName absent → raw blob
|
||||||
|
[InlineData("{\"region\":\"HoldingRegisters\",\"address\":5}", "{\"region\":\"HoldingRegisters\",\"address\":5}")]
|
||||||
|
// object, FullName present but non-string → raw blob
|
||||||
|
[InlineData("{\"FullName\":123}", "{\"FullName\":123}")]
|
||||||
|
public void ExtractFullName_falls_back_to_raw_blob_except_for_string_FullName(string tagConfig, string expected)
|
||||||
|
{
|
||||||
|
EquipmentNodeWalker.ExtractFullName(tagConfig).ShouldBe(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user