Files
UA-.NETStandard/tests/Opc.Ua.InformationModel.Tests/MethodCallTests.cs
2026-07-16 06:25:54 +02:00

588 lines
24 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ========================================================================
* 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.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.Client.TestFramework;
namespace Opc.Ua.InformationModel.Tests
{
/// <summary>
/// compliance tests for Method Service Set – Call.
/// </summary>
[TestFixture]
[Category("Conformance")]
[Category("MethodCall")]
public class MethodCallTests : TestFixture
{
[Description("Call Methods_Void with no arguments. Expect Good status.")]
[Test]
public async Task MethodCall001CallVoidMethodAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Void", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = default
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Void method call should return Good.");
}
[Description("Call Methods_Add with (1.5f, 2u). Expect output 3.5f.")]
[Test]
public async Task MethodCall002CallAddMethodAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Add", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new(1.5f),
new((uint)2)
}.ToArrayOf()
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Add method call should return Good.");
Assert.That(response.Results[0].OutputArguments.Count, Is.GreaterThan(0),
"Add method should return output arguments.");
float result = (float)response.Results[0].OutputArguments[0];
Assert.That(result, Is.EqualTo(3.5f).Within(0.001f),
"Add(1.5, 2) should return 3.5.");
}
[Description("Call Methods_Hello with \"World\". Expect \"hello World\".")]
[Test]
public async Task MethodCall003CallHelloMethodAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Hello", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("World")
}.ToArrayOf()
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Hello method call should return Good.");
Assert.That(response.Results[0].OutputArguments.Count, Is.GreaterThan(0),
"Hello method should return output arguments.");
string result = (string)response.Results[0].OutputArguments[0];
Assert.That(result, Is.EqualTo("hello World"),
"Hello('World') should return 'hello World'.");
}
[Description("Call Methods_Multiply with appropriate arguments.")]
[Test]
public async Task MethodCall004CallMultiplyMethodAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Multiply", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new((short)3),
new((ushort)4)
}.ToArrayOf()
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Multiply method call should return Good.");
Assert.That(response.Results[0].OutputArguments.Count, Is.GreaterThan(0),
"Multiply method should return output arguments.");
}
[Description("Call Void and Hello in a single request. Both should return Good.")]
[Test]
public async Task MethodCall005CallMultipleMethodsInOneRequestAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId voidMethodId = ToNodeId(
new ExpandedNodeId("Methods_Void", Constants.ReferenceServerNamespaceUri));
NodeId helloMethodId = ToNodeId(
new ExpandedNodeId("Methods_Hello", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = voidMethodId,
InputArguments = default
},
new() {
ObjectId = objectId,
MethodId = helloMethodId,
InputArguments = new Variant[]
{
new("Test")
}.ToArrayOf()
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(2));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Void method should return Good.");
Assert.That(StatusCode.IsGood(response.Results[1].StatusCode), Is.True,
"Hello method should return Good.");
}
[Description("Call Methods_Output which has output arguments only.")]
[Test]
public async Task MethodCall006CallOutputOnlyMethodAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Output", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = default
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Output-only method call should return Good.");
Assert.That(response.Results[0].OutputArguments.Count, Is.GreaterThan(0),
"Output-only method should return output arguments.");
}
[Description("Call Methods_Input which has input arguments only.")]
[Test]
public async Task MethodCall007CallInputOnlyMethodAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Input", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("TestInput")
}.ToArrayOf()
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Input-only method call should return Good.");
}
[Description("Call a non-existent method. Expect BadNodeIdUnknown or BadMethodInvalid.")]
[Test]
public async Task MethodCallErr001CallNonExistentMethodAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = Constants.InvalidNodeId,
InputArguments = default
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True,
"Calling a non-existent method should return a Bad status.");
}
[Description("Call Methods_Void with wrong ObjectId. Expect BadMethodInvalid or similar Bad status.")]
[Test]
public async Task MethodCallErr002CallWithWrongObjectIdAsync()
{
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Void", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = ObjectIds.Server,
MethodId = methodId,
InputArguments = default
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True,
"Calling method with wrong ObjectId should return a Bad status.");
}
[Description("Call Methods_Add with only 1 argument. Expect BadArgumentsMissing or BadInvalidArgument.")]
[Test]
public async Task MethodCallErr003CallWithMissingArgumentsAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Add", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new(1.5f)
}.ToArrayOf()
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True,
"Calling Add with missing arguments should return a Bad status.");
}
[Description("Call Methods_Void with unexpected arguments. Expect BadInvalidArgument or BadTooManyArguments.")]
[Test]
public async Task MethodCallErr004CallWithTooManyArgumentsAsync()
{
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(
new ExpandedNodeId("Methods_Void", Constants.ReferenceServerNamespaceUri));
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("unexpected")
}.ToArrayOf()
}
}.ToArrayOf(),
CancellationToken.None).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True,
"Calling Void method with arguments should return a Bad status.");
}
[Description("Call GetMonitoredItems on Server with an active subscription.")]
[Test]
public async Task MethodCall008VerifyMethodNodeClassIsMethodAsync()
{
CancellationToken ct = CancellationToken.None;
// Create a subscription
CreateSubscriptionResponse subResp = await Session.CreateSubscriptionAsync(
null, 1000, 100, 10, 0, true, 0, ct).ConfigureAwait(false);
uint subscriptionId = subResp.SubscriptionId;
try
{
// Add monitored items
CreateMonitoredItemsResponse createItems = await Session.CreateMonitoredItemsAsync(
null, subscriptionId, TimestampsToReturn.Both,
new MonitoredItemCreateRequest[]
{
new() {
ItemToMonitor = new ReadValueId
{
NodeId = ToNodeId(Constants.ScalarStaticInt32),
AttributeId = Attributes.Value
},
MonitoringMode = MonitoringMode.Reporting,
RequestedParameters = new MonitoringParameters
{
ClientHandle = 1,
SamplingInterval = 1000,
QueueSize = 1,
DiscardOldest = true
}
},
new() {
ItemToMonitor = new ReadValueId
{
NodeId = ToNodeId(Constants.ScalarStaticDouble),
AttributeId = Attributes.Value
},
MonitoringMode = MonitoringMode.Reporting,
RequestedParameters = new MonitoringParameters
{
ClientHandle = 2,
SamplingInterval = 1000,
QueueSize = 1,
DiscardOldest = true
}
}
}.ToArrayOf(), ct).ConfigureAwait(false);
Assert.That(StatusCode.IsGood(createItems.Results[0].StatusCode), Is.True);
Assert.That(StatusCode.IsGood(createItems.Results[1].StatusCode), Is.True);
// Call GetMonitoredItems
CallResponse callResponse = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = ObjectIds.Server,
MethodId = MethodIds.Server_GetMonitoredItems,
InputArguments = new Variant[]
{
new(subscriptionId)
}.ToArrayOf()
}
}.ToArrayOf(), ct).ConfigureAwait(false);
Assert.That(callResponse.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(callResponse.Results[0].StatusCode), Is.True,
"GetMonitoredItems should return Good.");
Assert.That(callResponse.Results[0].OutputArguments.Count, Is.EqualTo(2),
"GetMonitoredItems returns server handles and client handles.");
}
finally
{
await Session.DeleteSubscriptionsAsync(
null, new uint[] { subscriptionId }.ToArrayOf(), ct).ConfigureAwait(false);
}
}
[Description("Call a method that has IN parameters only.")]
[Test]
public async Task MethodCallInputOnlyAsync()
{
CancellationToken ct = CancellationToken.None;
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(Constants.MethodInput);
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("TestInput")
}.ToArrayOf()
}
}.ToArrayOf(), ct).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True,
"Input-only method call should return Good.");
}
[Description("Call the same method multiple times in a single Call request.")]
[Test]
public async Task MethodCallSameMethodMultipleTimesAsync()
{
CancellationToken ct = CancellationToken.None;
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(Constants.MethodHello);
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("First")
}.ToArrayOf()
},
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("Second")
}.ToArrayOf()
},
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("Third")
}.ToArrayOf()
}
}.ToArrayOf(), ct).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(3));
for (int i = 0; i < 3; i++)
{
Assert.That(StatusCode.IsGood(response.Results[i].StatusCode), Is.True,
$"Call {i + 1} should return Good.");
}
}
[Description("Call with an invalid Object NodeId.")]
[Test]
public async Task MethodCallErrInvalidObjectNodeIdAsync()
{
CancellationToken ct = CancellationToken.None;
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = Constants.InvalidNodeId,
MethodId = ToNodeId(Constants.MethodVoid)
}
}.ToArrayOf(), ct).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True,
"Invalid Object NodeId should return Bad status.");
}
[Description("Call with valid object but invalid Method NodeId.")]
[Test]
public async Task MethodCallErrInvalidMethodNodeIdAsync()
{
CancellationToken ct = CancellationToken.None;
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = ToNodeId(Constants.MethodsFolder),
MethodId = Constants.InvalidNodeId
}
}.ToArrayOf(), ct).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True,
"Invalid Method NodeId should return Bad status.");
}
[Description("Call method with wrong data type for input arguments.")]
[Test]
public async Task MethodCallErrWrongArgumentTypesAsync()
{
CancellationToken ct = CancellationToken.None;
NodeId objectId = ToNodeId(Constants.MethodsFolder);
NodeId methodId = ToNodeId(Constants.MethodMultiply);
CallResponse response = await Session.CallAsync(
null,
new CallMethodRequest[]
{
new() {
ObjectId = objectId,
MethodId = methodId,
InputArguments = new Variant[]
{
new("not_a_number"),
new("also_not_a_number")
}.ToArrayOf()
}
}.ToArrayOf(), ct).ConfigureAwait(false);
Assert.That(response.Results.Count, Is.EqualTo(1));
Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True,
"Wrong argument types should return Bad status.");
}
}
}