Files
lmxopcua/tests/Core/ZB.MOM.WW.OtOpcUa.Commons.Tests/RawPathResolverTests.cs
T
Joseph Doherty e81ac352ed v3 B1-WP4/WP5: pipeline rewire to greenfield schema + dark address space
- ConfigComposer: snapshot v3 tables (RawFolders/TagGroups/UnsTagReferences),
  drop the retired Namespaces snapshot; RevisionHash follows the new blob.
- DeploymentArtifact: compute each raw tag's RawPath (RawFolder->Driver->Device->
  TagGroup ancestry via shared RawPathResolver); per-driver RawTags + merged
  DriverConfig+DeviceConfig config injected into DriverInstanceSpec via
  DriverDeviceConfigMerger; ParseComposition emits an empty EquipmentTags set
  (DARK until Batch 4); EquipmentNode driver/device hooks always null; cluster
  scoping attributes equipment by UNS line only.
- AddressSpaceComposer: rewritten to the v3 shape (no Namespace/NamespaceKind,
  no dropped Tag/Equipment fields); emits empty EquipmentTags (parity mirror).
- DriverHostActor: fan-out/write map tuple member FullName -> RawPath.
- Commons: new RawPathResolver (shared identity authority) + DriverDeviceConfigMerger
  (single/multi-device endpoint+RawTags merge).
- DraftValidator: v3 rules — raw-name charset (RawPaths.ValidateSegment),
  historized effective-tagname <=255, UNS effective-leaf uniqueness across
  UnsTagReference/VirtualTag/ScriptedAlarm; DraftSnapshot(+Factory) carry the new tables.
- AdminOperationsActor: tag-config inspection resolves driver via Device (no
  EquipmentId/DriverInstanceId on Tag).
- Tests: RawPathResolver/merger unit tests (Commons.Tests, green) +
  DeploymentArtifact RawPath/dark test (Runtime.Tests, awaits WP6 corpus fixes).
2026-07-15 20:45:24 -04:00

114 lines
4.4 KiB
C#

using System.Text.Json;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Commons.Tests;
/// <summary>
/// v3 (B1-WP4) identity — the shared <see cref="RawPathResolver"/> must build a raw tag's RawPath by
/// walking RawFolder → Driver → Device → TagGroup ancestry, and <see cref="DriverDeviceConfigMerger"/>
/// must fold the endpoint + RawTags into the single driver config. This is the byte-parity authority
/// both the deploy artifact and the draft validator compute against.
/// </summary>
public sealed class RawPathResolverTests
{
private static RawPathResolver Nested() => new(
folders: new Dictionary<string, (string?, string)>
{
["f1"] = (null, "Plant"),
["f2"] = ("f1", "Line1"),
},
drivers: new Dictionary<string, (string?, string)>
{
["drv1"] = ("f2", "Modbus1"),
["drvRoot"] = (null, "S7Root"),
},
devices: new Dictionary<string, (string, string)>
{
["dev1"] = ("drv1", "PLC-A"),
["devRoot"] = ("drvRoot", "PLC-B"),
},
groups: new Dictionary<string, (string?, string)>
{
["g1"] = (null, "Motors"),
["g2"] = ("g1", "M1"),
});
[Fact]
public void TryBuildTagPath_walks_folder_driver_device_group_ancestry()
{
Nested().TryBuildTagPath("dev1", "g2", "Speed")
.ShouldBe("Plant/Line1/Modbus1/PLC-A/Motors/M1/Speed");
}
[Fact]
public void TryBuildTagPath_device_root_tag_has_no_group_segments()
{
Nested().TryBuildTagPath("dev1", null, "Status")
.ShouldBe("Plant/Line1/Modbus1/PLC-A/Status");
}
[Fact]
public void TryBuildTagPath_cluster_root_driver_has_no_folder_segments()
{
Nested().TryBuildTagPath("devRoot", null, "Run")
.ShouldBe("S7Root/PLC-B/Run");
}
[Fact]
public void TryBuildTagPath_unknown_device_returns_null()
{
Nested().TryBuildTagPath("ghost", null, "X").ShouldBeNull();
}
[Fact]
public void TryBuildDriverPrefix_is_folders_plus_driver_name()
{
Nested().TryBuildDriverPrefix("drv1").ShouldBe("Plant/Line1/Modbus1");
}
[Fact]
public void Merge_single_device_folds_endpoint_up_and_injects_rawtags()
{
var merged = DriverDeviceConfigMerger.Merge(
driverConfigJson: "{\"UnitId\":3}",
devices: new[] { new DriverDeviceConfigMerger.DeviceRow("PLC-A", "{\"Host\":\"10.0.0.5\",\"Port\":502}") },
rawTags: new[] { new RawTagEntry("Plant/Modbus1/PLC-A/Speed", "{\"address\":\"40001\"}", true, "PLC-A") });
using var doc = JsonDocument.Parse(merged);
var root = doc.RootElement;
root.GetProperty("UnitId").GetInt32().ShouldBe(3);
root.GetProperty("Host").GetString().ShouldBe("10.0.0.5"); // single-device endpoint merged up
root.GetProperty("Port").GetInt32().ShouldBe(502);
var rawTags = root.GetProperty("RawTags").EnumerateArray().ToList();
rawTags.ShouldHaveSingleItem();
rawTags[0].GetProperty("RawPath").GetString().ShouldBe("Plant/Modbus1/PLC-A/Speed");
var devices = root.GetProperty("Devices").EnumerateArray().ToList();
devices.ShouldHaveSingleItem();
devices[0].GetProperty("DeviceName").GetString().ShouldBe("PLC-A");
}
[Fact]
public void Merge_multi_device_reconstructs_devices_array_with_names()
{
var merged = DriverDeviceConfigMerger.Merge(
driverConfigJson: "{\"Series\":\"0i\"}",
devices: new[]
{
new DriverDeviceConfigMerger.DeviceRow("PLC-A", "{\"HostAddress\":\"10.0.0.5:8193\"}"),
new DriverDeviceConfigMerger.DeviceRow("PLC-B", "{\"HostAddress\":\"10.0.0.6:8193\"}"),
},
rawTags: Array.Empty<RawTagEntry>());
using var doc = JsonDocument.Parse(merged);
var devices = doc.RootElement.GetProperty("Devices").EnumerateArray().ToList();
devices.Count.ShouldBe(2);
devices.Select(d => d.GetProperty("DeviceName").GetString()).ShouldBe(new[] { "PLC-A", "PLC-B" });
devices[0].GetProperty("HostAddress").GetString().ShouldBe("10.0.0.5:8193");
// Multiple devices: no top-level endpoint merge-up (per-device only).
doc.RootElement.TryGetProperty("HostAddress", out _).ShouldBeFalse();
}
}