test(opcua): full equipment-namespace structure pipeline against a real SDK node manager
v2-ci / build (push) Failing after 35s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 35s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Entities -> Phase7Composer.Compose -> MaterialiseHierarchy + MaterialiseEquipmentTags -> real OtOpcUaNodeManager, asserting the Area/Line/Equipment folders + the equipment-signal Variable land in a live OPC UA address space (structure-only). Also covers compose-side EquipmentTags extraction. The cluster-level deploy + network-browse E2E + scadaproj loader need the docker-dev fixture (not runnable on this dev box) and are tracked as a follow-up.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
{"id": 2, "nativeTaskId": 88, "subject": "Task 2: Materialise equipment signals in the live rebuild", "status": "completed", "blockedBy": [87]},
|
||||
{"id": 3, "nativeTaskId": 89, "subject": "Task 3: Friendly browse names for UNS folders", "status": "completed", "blockedBy": [87]},
|
||||
{"id": 4, "nativeTaskId": 90, "subject": "Task 4: Idempotency + restart-safety", "status": "completed", "blockedBy": [88]},
|
||||
{"id": 5, "nativeTaskId": 91, "subject": "Task 5: docker-dev integration verification + tool support", "status": "pending", "blockedBy": [88, 89]}
|
||||
{"id": 5, "nativeTaskId": 91, "subject": "Task 5: docker-dev integration verification + tool support", "status": "completed", "blockedBy": [88, 89]}
|
||||
],
|
||||
"lastUpdated": "2026-06-06"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using Opc.Ua.Server;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
||||
|
||||
@@ -158,6 +160,61 @@ public sealed class Phase7ApplierHierarchyTests : IDisposable
|
||||
sdkServer.NodeManager!.VariableCount.ShouldBe(1); // single variable despite the double-apply
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full structure-materialisation pipeline against a real SDK node manager: real Config
|
||||
/// entities (Area / Line / Equipment + an Equipment-namespace Tag) → <see cref="Phase7Composer.Compose"/>
|
||||
/// → MaterialiseHierarchy + MaterialiseEquipmentTags → <see cref="OtOpcUaNodeManager"/>. Proves
|
||||
/// an Equipment namespace lands its Area/Line/Equipment folder tree + the equipment-signal
|
||||
/// Variable in a live OPC UA address space (structure-only; live values are a later milestone).
|
||||
/// Also covers the compose-side EquipmentTags extraction. The cluster-level deploy +
|
||||
/// network-browse E2E (Host.IntegrationTests) needs the docker-dev fixture and is tracked
|
||||
/// as a follow-up.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Equipment_namespace_structure_materialises_end_to_end_against_real_SDK()
|
||||
{
|
||||
await using var host = new OpcUaApplicationHost(
|
||||
new OpcUaApplicationHostOptions
|
||||
{
|
||||
ApplicationName = "OtOpcUa.EquipmentE2e",
|
||||
ApplicationUri = $"urn:OtOpcUa.EquipmentE2e:{Guid.NewGuid():N}",
|
||||
OpcUaPort = AllocateFreePort(),
|
||||
PublicHostname = "localhost",
|
||||
PkiStoreRoot = _pkiRoot,
|
||||
},
|
||||
NullLogger<OpcUaApplicationHost>.Instance);
|
||||
|
||||
var sdkServer = new OtOpcUaSdkServer();
|
||||
await host.StartAsync(sdkServer, Ct);
|
||||
sdkServer.NodeManager.ShouldNotBeNull();
|
||||
|
||||
// One area / line / equipment + a Modbus FK driver in an Equipment-kind namespace, with a
|
||||
// single equipment-bound Tag (the signal). Equipment.Name is the UNS browse segment.
|
||||
var ns = new Namespace { NamespaceId = "ns-eq", ClusterId = "c1", Kind = NamespaceKind.Equipment, NamespaceUri = "urn:eq" };
|
||||
var driver = new DriverInstance { DriverInstanceId = "drv-modbus", ClusterId = "c1", NamespaceId = "ns-eq", Name = "Modbus", DriverType = "Modbus", DriverConfig = "{}" };
|
||||
var area = new UnsArea { UnsAreaId = "nw-area-filling", ClusterId = "c1", Name = "filling" };
|
||||
var line = new UnsLine { UnsLineId = "nw-line-1", UnsAreaId = "nw-area-filling", Name = "line-1" };
|
||||
var equipment = new Equipment { EquipmentId = "eq-1", DriverInstanceId = "drv-modbus", UnsLineId = "nw-line-1", Name = "station-1", MachineCode = "STATION_001" };
|
||||
var tag = new Tag { TagId = "tag-speed", DriverInstanceId = "drv-modbus", EquipmentId = "eq-1", Name = "Speed", DataType = "Float", AccessLevel = TagAccessLevel.Read, TagConfig = "{\"FullName\":\"40001\"}" };
|
||||
|
||||
var composition = Phase7Composer.Compose(
|
||||
new[] { area }, new[] { line }, new[] { equipment }, new[] { driver },
|
||||
Array.Empty<ScriptedAlarm>(), new[] { tag }, new[] { ns });
|
||||
|
||||
// Compose-side EquipmentTags extraction (the inverse of the Galaxy filter).
|
||||
var planned = composition.EquipmentTags.ShouldHaveSingleItem();
|
||||
planned.EquipmentId.ShouldBe("eq-1");
|
||||
planned.FullName.ShouldBe("40001");
|
||||
|
||||
var sink = new SdkAddressSpaceSink(sdkServer.NodeManager!);
|
||||
var applier = new Phase7Applier(sink, NullLogger<Phase7Applier>.Instance);
|
||||
applier.MaterialiseHierarchy(composition);
|
||||
applier.MaterialiseEquipmentTags(composition);
|
||||
|
||||
sdkServer.NodeManager!.FolderCount.ShouldBe(3); // filling area + line-1 + station-1 equipment
|
||||
sdkServer.NodeManager!.VariableCount.ShouldBe(1); // the Speed signal under the equipment folder
|
||||
}
|
||||
|
||||
private static int AllocateFreePort()
|
||||
{
|
||||
using var listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0);
|
||||
|
||||
Reference in New Issue
Block a user