/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Moq; using NUnit.Framework; namespace Opc.Ua.Server.Tests { /// /// Focused tests for the permission gate added to /// (closes the /// permission bypass in the legacy /// EventManager.ReportEvent(IFilterTarget, IList<IEventMonitoredItem>) /// overload). /// [TestFixture] [Category("EventManager")] [SetCulture("en-us")] [SetUICulture("en-us")] [Parallelizable] public class EventManagerReportEventAsyncTests { [TestCase(false)] [TestCase(true)] public async Task OneFailingReceiverDoesNotDiscardLaterEventsAsync(bool permissionFailure) { var owner = new Mock(); owner.Setup(value => value.ValidateEventRolePermissionsAsync( It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new ValueTask(ServiceResult.Good)); var failing = new Mock(); var following = new Mock(); if (permissionFailure) { owner.Setup(value => value.ValidateEventRolePermissionsAsync( failing.Object, It.IsAny(), It.IsAny())) .ThrowsAsync(new InvalidOperationException("permission lookup failed")); } else { failing.Setup(value => value.QueueEvent(It.IsAny())) .Callback(filter => throw new ArgumentNullException(nameof(filter))); } var occurrence = new BaseEventState(null); await EventManager.ReportEventAsync(occurrence, owner.Object, [failing.Object, following.Object]) .ConfigureAwait(false); following.Verify(value => value.QueueEvent(occurrence), Times.Once); } [Test] public void ReportEventCancellationStillStopsDispatch() { var owner = new Mock(); var receiver = new Mock(); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); Assert.ThrowsAsync(async () => await EventManager.ReportEventAsync( new BaseEventState(null), owner.Object, [receiver.Object], cancellation.Token) .ConfigureAwait(false)); receiver.Verify(value => value.QueueEvent(It.IsAny()), Times.Never); } /// /// When the node manager grants ReceiveEvents, the event is /// queued on every supplied item. /// [Test] public async Task ReportEventAsync_WithPermission_QueuesToAllItems() { var nodeManagerMock = new Mock(); nodeManagerMock .Setup(m => m.ValidateEventRolePermissionsAsync( It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new ValueTask(ServiceResult.Good)); var item1 = new Mock(); item1.Setup(m => m.Id).Returns(1u); var item2 = new Mock(); item2.Setup(m => m.Id).Returns(2u); IList receivers = [item1.Object, item2.Object]; var ev = new BaseEventState(null); await EventManager.ReportEventAsync(ev, nodeManagerMock.Object, receivers).ConfigureAwait(false); item1.Verify(m => m.QueueEvent(ev), Times.Once); item2.Verify(m => m.QueueEvent(ev), Times.Once); } /// /// When the node manager denies ReceiveEvents, the event is /// dropped silently — no item receives a queue call. /// [Test] public async Task ReportEventAsync_WithoutPermission_DropsEvent() { var nodeManagerMock = new Mock(); nodeManagerMock .Setup(m => m.ValidateEventRolePermissionsAsync( It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new ValueTask(new ServiceResult(StatusCodes.BadUserAccessDenied))); var item = new Mock(); item.Setup(m => m.Id).Returns(1u); IList receivers = [item.Object]; var ev = new BaseEventState(null); await EventManager.ReportEventAsync(ev, nodeManagerMock.Object, receivers).ConfigureAwait(false); item.Verify(m => m.QueueEvent(It.IsAny()), Times.Never); } /// /// The gate is evaluated per item, so a mixed-permission set still /// delivers to the permitted item only. /// [Test] public async Task ReportEventAsync_MixedPermissions_DeliversOnlyToPermittedItem() { var permittedItem = new Mock(); permittedItem.Setup(m => m.Id).Returns(1u); var deniedItem = new Mock(); deniedItem.Setup(m => m.Id).Returns(2u); var nodeManagerMock = new Mock(); nodeManagerMock .Setup(m => m.ValidateEventRolePermissionsAsync( permittedItem.Object, It.IsAny(), It.IsAny())) .Returns(new ValueTask(ServiceResult.Good)); nodeManagerMock .Setup(m => m.ValidateEventRolePermissionsAsync( deniedItem.Object, It.IsAny(), It.IsAny())) .Returns(new ValueTask(new ServiceResult(StatusCodes.BadUserAccessDenied))); IList receivers = [permittedItem.Object, deniedItem.Object]; var ev = new BaseEventState(null); await EventManager.ReportEventAsync(ev, nodeManagerMock.Object, receivers).ConfigureAwait(false); permittedItem.Verify(m => m.QueueEvent(ev), Times.Once); deniedItem.Verify(m => m.QueueEvent(It.IsAny()), Times.Never); } /// /// An Uncertain verdict is not a denial, so the event must still be delivered. /// [Test] public async Task ReportEventAsync_WithUncertainPermission_StillQueuesEventAsync( [ValueSource(nameof(UncertainStatusCodes))] StatusCode statusCode) { var nodeManagerMock = new Mock(); nodeManagerMock .Setup(m => m.ValidateEventRolePermissionsAsync( It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new ValueTask(new ServiceResult(statusCode))); var item = new Mock(); item.Setup(m => m.Id).Returns(1u); IList receivers = [item.Object]; var ev = new BaseEventState(null); await EventManager.ReportEventAsync(ev, nodeManagerMock.Object, receivers).ConfigureAwait(false); Assert.That(StatusCode.IsUncertain(statusCode), Is.True); item.Verify(m => m.QueueEvent(ev), Times.Once); } private static StatusCode[] UncertainStatusCodes => [ StatusCodes.UncertainNotAllNodesAvailable, StatusCodes.UncertainReferenceOutOfServer ]; } }