using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Runtime.Drivers; using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers; [Trait("Category", "Unit")] public sealed class DiscoveredNodeMapperTests { private static DiscoveredNode Node(string[] path, string name, string fullRef, DriverDataType dt = DriverDataType.Float64, bool writable = false) => new(path, name, name, fullRef, dt, false, null, writable, false); [Fact] public void Maps_under_equipment_collapsing_single_device_folder() { var nodes = new[] { Node(["FOCAS", "10.0.0.5:8193", "Identity"], "SeriesNumber", "10.0.0.5:8193/Identity/SeriesNumber", DriverDataType.String), Node(["FOCAS", "10.0.0.5:8193", "Axes", "X"], "AbsolutePosition", "10.0.0.5:8193/Axes/X/AbsolutePosition"), }; var result = DiscoveredNodeMapper.Map("EQ-1", nodes, authoredRefs: new HashSet()); result.Variables.Select(v => v.NodeId).ShouldBe(new[] { "EQ-1/FOCAS/Identity/SeriesNumber", "EQ-1/FOCAS/Axes/X/AbsolutePosition", }, ignoreOrder: true); result.Folders.Select(f => f.NodeId).ShouldContain("EQ-1/FOCAS/Axes/X"); result.Folders.First(f => f.NodeId == "EQ-1/FOCAS/Axes/X").ParentNodeId.ShouldBe("EQ-1/FOCAS/Axes"); result.RoutingByRef["10.0.0.5:8193/Identity/SeriesNumber"].ShouldBe("EQ-1/FOCAS/Identity/SeriesNumber"); result.Variables.First(v => v.NodeId.EndsWith("SeriesNumber")).Writable.ShouldBeFalse(); } [Fact] public void Dedups_authored_refs() { var nodes = new[] { Node(["FOCAS", "10.0.0.5:8193"], "parts-count", "parts-count"), Node(["FOCAS", "10.0.0.5:8193", "Identity"], "SeriesNumber", "10.0.0.5:8193/Identity/SeriesNumber", DriverDataType.String), }; var result = DiscoveredNodeMapper.Map("EQ-1", nodes, authoredRefs: new HashSet { "parts-count" }); result.Variables.ShouldHaveSingleItem(); result.Variables[0].NodeId.ShouldBe("EQ-1/FOCAS/Identity/SeriesNumber"); } [Fact] public void Does_not_collapse_when_two_devices_present() { var nodes = new[] { Node(["FOCAS", "10.0.0.5:8193", "Identity"], "SeriesNumber", "a", DriverDataType.String), Node(["FOCAS", "10.0.0.6:8193", "Identity"], "SeriesNumber", "b", DriverDataType.String), }; var result = DiscoveredNodeMapper.Map("EQ-1", nodes, authoredRefs: new HashSet()); result.Variables.Select(v => v.NodeId).ShouldBe(new[] { "EQ-1/FOCAS/10.0.0.5:8193/Identity/SeriesNumber", "EQ-1/FOCAS/10.0.0.6:8193/Identity/SeriesNumber", }, ignoreOrder: true); } [Theory] // Mirror OtOpcUaNodeManager.ResolveBuiltInDataType's accepted string set: Float32 -> "Float", // Float64 -> "Double", Reference (Galaxy attr ref encoded as a string) -> "String". The pass-through // members must keep their enum name so the node manager resolves them to the matching built-in type. [InlineData(DriverDataType.Float64, "Double")] [InlineData(DriverDataType.Float32, "Float")] [InlineData(DriverDataType.Reference, "String")] [InlineData(DriverDataType.Boolean, "Boolean")] [InlineData(DriverDataType.Int16, "Int16")] [InlineData(DriverDataType.Int32, "Int32")] [InlineData(DriverDataType.Int64, "Int64")] [InlineData(DriverDataType.UInt16, "UInt16")] [InlineData(DriverDataType.UInt32, "UInt32")] [InlineData(DriverDataType.UInt64, "UInt64")] [InlineData(DriverDataType.String, "String")] [InlineData(DriverDataType.DateTime, "DateTime")] public void DataType_maps_to_node_manager_builtin_string(DriverDataType dt, string expected) { var nodes = new[] { Node(["FOCAS", "10.0.0.5:8193", "Identity"], "Value", "10.0.0.5:8193/Identity/Value", dt) }; var result = DiscoveredNodeMapper.Map("EQ-1", nodes, authoredRefs: new HashSet()); result.Variables.ShouldHaveSingleItem(); result.Variables[0].DataType.ShouldBe(expected); } }