/* ========================================================================
* 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
];
}
}