// Gitea #797: this test USED to pin the denial (an Equipment grant could not authorize an opaque scripted // alarm id; only a Cluster grant could). The owner binding fixed that deliberately, so it now pins the fixed // behaviour: the owning equipment's grant allows, the sibling equipment's grant denies, a cluster grant still // allows. The SDK 1.5 baseline probe under scripts/validation/opcua-scripted-alarm-baseline records the // pre-fix denial on the original application commit and is unaffected. using Opc.Ua; using Opc.Ua.Client; 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; using static ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.ServerEventRig; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests; public sealed class Sdk2OpaqueScriptedScopeBaselineTests { [Fact] public async Task Opaque_scripted_ID_is_allowed_by_its_equipment_grant_denied_by_a_sibling_grant_and_allowed_by_a_cluster_grant() { var ct = TestContext.Current.CancellationToken; await using var rig = await StartAsync("BaselineScriptedScope", encrypted: false, ct); rig.KeyRoles = []; const NodePermissions permissions = NodePermissions.ReadOnly | NodePermissions.AlarmAcknowledge | NodePermissions.MethodCall; NodeAcl Narrow(string equipment) => new() { NodeAclId = $"narrow-{equipment}", ClusterId = ServerEventRig.Cluster, Principal = KeyPrincipal, ScopeKind = NodeAclScopeKind.Equipment, ScopeId = $"eq-{equipment}", PermissionFlags = permissions, }; // Sibling first: a grant on EqB must not authorize EqA's alarm. rig.Enforce(true, [Key()], Narrow(EqB)); rig.MaterialiseScripted(); rig.Nm.ScriptedAlarmOwnerOf(ScriptedAlarmId).ShouldBe(EqA); var routed = 0; rig.Nm.AlarmCommandRouter = _ => routed++; using var session = await rig.ConnectAsync(anonymous: false, ct); rig.Project(false, new AlarmConditionSnapshot(true, false, false, true, AlarmShelvingKind.Unshelved, 500, "scope")); var condition = rig.Nm.TryGetAlarmCondition(ScriptedAlarmId, AddressSpaceRealm.Uns).ShouldNotBeNull(); condition.NodeId.IdentifierAsString.ShouldBe(ScriptedAlarmId, "the opaque NodeId is unchanged"); var eventId = condition.EventId.ShouldNotBeNull().Value; var denied = await Should.ThrowAsync(() => session.CallAsync(condition.NodeId, MethodIds.AcknowledgeableConditionType_Acknowledge, ct, new Variant[] { Variant.From(eventId), Variant.From(new LocalizedText("sibling")) })); denied.StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied); routed.ShouldBe(0); // The owning equipment's narrow grant now authorizes the same opaque id (#797 fixed). rig.Enforce(true, [Key()], Narrow(EqA)); await session.CallAsync(condition.NodeId, MethodIds.AcknowledgeableConditionType_Acknowledge, ct, new Variant[] { Variant.From(eventId), Variant.From(new LocalizedText("owner")) }); routed.ShouldBe(1); // The existing cluster contract is preserved. rig.Enforce(true, [Key()], ClusterGrant(KeyPrincipal, "cluster-positive", permissions)); await session.CallAsync(condition.NodeId, MethodIds.AcknowledgeableConditionType_Acknowledge, ct, new Variant[] { Variant.From(condition.EventId!.Value), Variant.From(new LocalizedText("cluster")) }); routed.ShouldBe(2); await session.CloseAsync(ct); } }