Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/Sdk2AlarmBrowseCompatibilityTests.cs
T

72 lines
4.0 KiB
C#

using Opc.Ua;
using Opc.Ua.Client;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests;
/// <summary>Preserves baseline alarm discovery through hierarchical folder browses in both application realms.</summary>
public sealed class Sdk2AlarmBrowseCompatibilityTests
{
[Theory]
[InlineData("OffNormalAlarm", AddressSpaceRealm.Raw)]
[InlineData("DiscreteAlarm", AddressSpaceRealm.Raw)]
[InlineData("AlarmCondition", AddressSpaceRealm.Raw)]
[InlineData("OffNormalAlarm", AddressSpaceRealm.Uns)]
[InlineData("DiscreteAlarm", AddressSpaceRealm.Uns)]
[InlineData("AlarmCondition", AddressSpaceRealm.Uns)]
public async Task Alarm_is_a_hierarchical_component_with_the_same_inverse_parent_and_object_type(
string type, AddressSpaceRealm realm)
{
var ct = TestContext.Current.CancellationToken;
await using var rig = await ServerEventRig.StartAsync("AlarmBrowseCompatibility", false, ct);
rig.Enforce(false, [ServerEventRig.Key()]);
rig.Sink.EnsureFolder("browse-parent", null, "Parent", realm);
rig.Sink.MaterialiseAlarmCondition("browse-alarm", "browse-parent", "Alarm", type, 500, realm,
isNative: realm == AddressSpaceRealm.Raw);
using var session = await rig.ConnectAsync(true, ct);
var uri = realm == AddressSpaceRealm.Raw ? V3NodeIds.RawNamespaceUri : V3NodeIds.UnsNamespaceUri;
var ns = session.NamespaceUris.GetIndex(uri);
ns.ShouldBeGreaterThan(0);
var parent = new NodeId("browse-parent", (ushort)ns);
var alarm = new NodeId("browse-alarm", (ushort)ns);
var expectedType = type switch
{
"OffNormalAlarm" => ObjectTypeIds.OffNormalAlarmType,
"DiscreteAlarm" => ObjectTypeIds.DiscreteAlarmType,
_ => ObjectTypeIds.AlarmConditionType,
};
var children = await BrowseAsync(session, parent, ReferenceTypeIds.HierarchicalReferences, BrowseDirection.Forward, true, ct);
var discovered = children.SingleOrDefault(reference => ExpandedNodeId.ToNodeId(reference.NodeId, session.NamespaceUris) == alarm);
discovered.ShouldNotBeNull("the baseline folder browse exposes every materialised alarm object");
discovered.ReferenceTypeId.ShouldBe(ReferenceTypeIds.HasComponent);
discovered.IsForward.ShouldBeTrue();
discovered.NodeClass.ShouldBe(NodeClass.Object);
ExpandedNodeId.ToNodeId(discovered.TypeDefinition, session.NamespaceUris).ShouldBe(expectedType);
var components = await BrowseAsync(session, parent, ReferenceTypeIds.HasComponent, BrowseDirection.Forward, false, ct);
components.ShouldContain(reference => ExpandedNodeId.ToNodeId(reference.NodeId, session.NamespaceUris) == alarm);
var inverse = await BrowseAsync(session, alarm, ReferenceTypeIds.HasComponent, BrowseDirection.Inverse, false, ct);
inverse.ShouldContain(reference => !reference.IsForward
&& ExpandedNodeId.ToNodeId(reference.NodeId, session.NamespaceUris) == parent);
var types = await BrowseAsync(session, alarm, ReferenceTypeIds.HasTypeDefinition, BrowseDirection.Forward, false, ct);
types.ShouldContain(reference => ExpandedNodeId.ToNodeId(reference.NodeId, session.NamespaceUris) == expectedType);
await session.CloseAsync(ct);
}
private static async Task<IReadOnlyList<ReferenceDescription>> BrowseAsync(ISession session, NodeId node,
NodeId reference, BrowseDirection direction, bool subtypes, CancellationToken ct)
{
var result = (await session.BrowseAsync(null, null, 0, [new BrowseDescription
{
NodeId = node, ReferenceTypeId = reference, BrowseDirection = direction, IncludeSubtypes = subtypes,
ResultMask = (uint)BrowseResultMask.All,
}], ct)).Results[0];
result.StatusCode.ShouldBe(StatusCodes.Good);
result.ContinuationPoint.IsEmpty.ShouldBeTrue();
return result.References.ToList();
}
}