/* ======================================================================== * 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.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using Opc.Ua.Client.TestFramework; namespace Opc.Ua.InformationModel.Tests { /// /// compliance tests for Attribute Service Set – Read. /// Based on test scripts: Attribute Read 001–011 and Err tests. /// [TestFixture] [Category("Conformance")] [Category("AttributeRead")] public class AttributeReadTests : TestFixture { [Description("Read Value from a single valid node. Expect StatusCode Good.")] [Test] public async Task AttributeRead001SingleNodeValueAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Read of a valid node Value attribute should return Good."); } [Description("Read Value from multiple valid nodes. All should return Good.")] [Test] public async Task AttributeRead002MultipleNodesValueAsync() { var readValueIds = Constants.ScalarStaticNodes .Select(n => new ReadValueId { NodeId = ToNodeId(n), AttributeId = Attributes.Value }).ToArrayOf(); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, readValueIds, CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(readValueIds.Count)); for (int i = 0; i < response.Results.Count; i++) { Assert.That(StatusCode.IsGood(response.Results[i].StatusCode), Is.True, $"Read of node index {i} should return Good."); } } [Description("Read DisplayName attribute. Expect a non-null LocalizedText.")] [Test] public async Task AttributeRead003DisplayNameAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.DisplayName } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); LocalizedText displayName = response.Results[0].GetValue(default); Assert.That(displayName, Is.Not.Null, "DisplayName should be a non-null LocalizedText."); Assert.That(displayName.Text, Is.Not.Null.And.Not.Empty, "DisplayName text should not be empty."); } [Description("Read BrowseName attribute. Expect a non-null QualifiedName.")] [Test] public async Task AttributeRead004BrowseNameAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.BrowseName } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); QualifiedName browseName = response.Results[0].GetValue(default); Assert.That(browseName, Is.Not.Null, "BrowseName should be a non-null QualifiedName."); Assert.That(browseName.Name, Is.Not.Null.And.Not.Empty, "BrowseName name should not be empty."); } [Description("Read NodeClass attribute. Should return Variable for data nodes.")] [Test] public async Task AttributeRead006NodeClassAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.NodeClass } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); int nodeClass = response.Results[0].GetValue(0); Assert.That(nodeClass, Is.EqualTo((int)NodeClass.Variable), "NodeClass for a scalar static node should be Variable."); } [Description("Read DataType attribute. Should return a valid DataType NodeId.")] [Test] public async Task AttributeRead007DataTypeAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.DataType } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); NodeId dataType = response.Results[0].GetValue(default); Assert.That(dataType, Is.Not.Null, "DataType should not be null."); Assert.That(dataType, Is.Not.EqualTo(NodeId.Null), "DataType should not be Null NodeId."); } [Description("Read all standard attributes in a single call for a Variable node.")] [Test] public async Task AttributeRead008AllAttributesAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); uint[] attributeIds = [ Attributes.NodeId, Attributes.NodeClass, Attributes.BrowseName, Attributes.DisplayName, Attributes.Description, Attributes.WriteMask, Attributes.UserWriteMask, Attributes.DataType, Attributes.ValueRank, Attributes.Value, Attributes.AccessLevel, Attributes.UserAccessLevel ]; var readValueIds = attributeIds .Select(attrId => new ReadValueId { NodeId = nodeId, AttributeId = attrId }).ToArrayOf(); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, readValueIds, CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(attributeIds.Length)); // All standard variable attributes should be readable for (int i = 0; i < response.Results.Count; i++) { Assert.That(StatusCode.IsGood(response.Results[i].StatusCode), Is.True, $"AttributeId {attributeIds[i]} read should return Good."); } } [Description("Read with TimestampsToReturn=Source. Source timestamp should be present.")] [Test] public async Task AttributeRead009TimestampsSourceAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Source, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); // Server timestamp should be MinValue when only source is requested Assert.That(response.Results[0].ServerTimestamp, Is.EqualTo(DateTime.MinValue), "ServerTimestamp should be MinValue when TimestampsToReturn=Source."); } [Description("Read with TimestampsToReturn=Server. Server timestamp should be present.")] [Test] public async Task AttributeRead010TimestampsServerAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Server, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); // Source timestamp should be MinValue when only server is requested Assert.That(response.Results[0].SourceTimestamp, Is.EqualTo(DateTime.MinValue), "SourceTimestamp should be MinValue when TimestampsToReturn=Server."); } [Description("Read with MaxAge=0 (from device). Server must return a fresh value.")] [Test] public async Task AttributeRead011MaxAgeZeroAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Read with MaxAge=0 should still return Good."); } [Description("Read an invalid/non-existent NodeId. Expect BadNodeIdUnknown.")] [Test] public async Task AttributeReadErr001InvalidNodeIdAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(response.Results[0].StatusCode.Code, Is.EqualTo(StatusCodes.BadNodeIdUnknown), "Reading an invalid NodeId should return BadNodeIdUnknown."); } [Description("Read with an invalid AttributeId. Expect BadAttributeIdInvalid.")] [Test] public async Task AttributeReadErr002InvalidAttributeIdAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = 999 } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(response.Results[0].StatusCode.Code, Is.EqualTo(StatusCodes.BadAttributeIdInvalid), "Reading with invalid AttributeId should return BadAttributeIdInvalid."); } [Description("Read an attribute not valid for the node class. E.g., read InverseName from a Variable node.")] [Test] public async Task AttributeReadErr003AttributeNotValidForNodeClassAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.InverseName } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); // InverseName is not valid on a Variable node Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True, "Reading InverseName from a Variable node should return a Bad status."); } [Description("Read Value of an array node. Expect Good and an array value.")] [Test] public async Task AttributeRead012ReadArrayValueAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticArrayInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Read of an array node Value should return Good."); // Arrays might be returned as typed arrays (e.g. int[]) or the // value may not yet have been written; verify the Variant has a // declared type. Genuinely polymorphic across BuiltInType because // the array element type is unknown to this generic test. Assert.That(response.Results[0].WrappedValue.TypeInfo, Is.Not.Null, "Value of an array node should not be null."); } [Description("Read an array node with IndexRange=\"0\". Should return a single element.")] [Test] public async Task AttributeRead013ReadWithIndexRangeAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticArrayInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value, IndexRange = "0" } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Read with IndexRange='0' should return Good."); } [Description("Batch read of 5 different scalar nodes. All should return Good.")] [Test] public async Task AttributeRead014BatchReadMultipleNodesAsync() { ExpandedNodeId[] nodes = [ Constants.ScalarStaticBoolean, Constants.ScalarStaticInt32, Constants.ScalarStaticDouble, Constants.ScalarStaticString, Constants.ScalarStaticDateTime ]; var readValueIds = nodes .Select(n => new ReadValueId { NodeId = ToNodeId(n), AttributeId = Attributes.Value }).ToArrayOf(); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, readValueIds, CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(nodes.Length)); for (int i = 0; i < response.Results.Count; i++) { Assert.That(StatusCode.IsGood(response.Results[i].StatusCode), Is.True, $"Batch read node index {i} should return Good."); } } [Description("Read MinimumSamplingInterval from a variable node. Should return Good (or BadAttributeIdInvalid). If Good, value >= 0.")] [Test] public async Task AttributeRead016ReadMinimumSamplingIntervalAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.MinimumSamplingInterval } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); if (StatusCode.IsGood(response.Results[0].StatusCode)) { double samplingInterval = response.Results[0].GetValue(0); Assert.That(samplingInterval, Is.GreaterThanOrEqualTo(0), "MinimumSamplingInterval should be >= 0."); } else { Assert.That(response.Results[0].StatusCode.Code, Is.EqualTo(StatusCodes.BadAttributeIdInvalid), "If not Good, should be BadAttributeIdInvalid."); } } [Description("Read Historizing attribute from a variable node. Should return Good and a boolean value.")] [Test] public async Task AttributeRead017ReadHistorizingAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Historizing } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Historizing attribute should be readable."); // Value should be a boolean bool historizing = response.Results[0].GetValue(false); Assert.That(historizing, Is.InstanceOf()); } [Description("Read AccessLevel from a variable node. Should return Good and a non-zero value (at least readable).")] [Test] public async Task AttributeRead018ReadAccessLevelAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.AccessLevel } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "AccessLevel should be readable."); byte accessLevel = response.Results[0].GetValue(0); Assert.That(accessLevel, Is.Not.Zero, "AccessLevel should be non-zero (at least readable)."); } [Description("Read UserAccessLevel from a variable node. Should return Good and a byte value.")] [Test] public async Task AttributeRead019ReadUserAccessLevelAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.UserAccessLevel } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "UserAccessLevel should be readable."); byte userAccessLevel = response.Results[0].GetValue(0); Assert.That(userAccessLevel, Is.InstanceOf()); } [Description("Read ValueRank from a scalar variable. Should return Scalar (-1).")] [Test] public async Task AttributeRead020ReadValueRankAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.ValueRank } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "ValueRank should be readable."); int valueRank = response.Results[0].GetValue(0); Assert.That(valueRank, Is.EqualTo(ValueRanks.Scalar), "ValueRank for a scalar node should be Scalar (-1)."); } [Description("Read ValueRank from an array variable. Should return OneDimension (1).")] [Test] public async Task AttributeRead021ReadValueRankArrayAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticArrayInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.ValueRank } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "ValueRank should be readable for an array node."); int valueRank = response.Results[0].GetValue(0); Assert.That(valueRank, Is.EqualTo(ValueRanks.OneDimension), "ValueRank for an array node should be OneDimension (1)."); } [Description("Read all standard Object attributes from the Server object. All should return Good. NodeClass should be Object.")] [Test] public async Task AttributeRead022ReadAllAttributesOfObjectAsync() { uint[] attributeIds = [ Attributes.NodeId, Attributes.NodeClass, Attributes.BrowseName, Attributes.DisplayName, Attributes.Description, Attributes.WriteMask, Attributes.UserWriteMask, Attributes.EventNotifier ]; var readValueIds = attributeIds .Select(attrId => new ReadValueId { NodeId = ObjectIds.Server, AttributeId = attrId }).ToArrayOf(); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, readValueIds, CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(attributeIds.Length)); for (int i = 0; i < response.Results.Count; i++) { Assert.That(StatusCode.IsGood(response.Results[i].StatusCode), Is.True, $"Object attribute {attributeIds[i]} should return Good."); } // NodeClass (index 1) should be Object int nodeClass = response.Results[1].GetValue(0); Assert.That(nodeClass, Is.EqualTo((int)NodeClass.Object), "NodeClass of Server should be Object."); } [Description("Read all standard Variable attributes from a scalar variable node. All should return Good or at least not BadAttributeIdInvalid.")] [Test] public async Task AttributeRead023ReadAllAttributesOfVariableAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); uint[] attributeIds = [ Attributes.NodeId, Attributes.NodeClass, Attributes.BrowseName, Attributes.DisplayName, Attributes.Description, Attributes.WriteMask, Attributes.UserWriteMask, Attributes.Value, Attributes.DataType, Attributes.ValueRank, Attributes.ArrayDimensions, Attributes.AccessLevel, Attributes.UserAccessLevel, Attributes.MinimumSamplingInterval, Attributes.Historizing, Attributes.AccessLevelEx ]; var readValueIds = attributeIds .Select(attrId => new ReadValueId { NodeId = nodeId, AttributeId = attrId }).ToArrayOf(); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, readValueIds, CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(attributeIds.Length)); for (int i = 0; i < response.Results.Count; i++) { Assert.That(response.Results[i].StatusCode.Code, Is.Not.EqualTo(StatusCodes.BadAttributeIdInvalid), $"Variable attribute {attributeIds[i]} should not return BadAttributeIdInvalid."); } } [Description("Read all standard Method attributes from a method node. Browse the Methods folder to find a child method, then read its attributes.")] [Test] public async Task AttributeRead024ReadAllAttributesOfMethodAsync() { NodeId methodsFolderId = ToNodeId(Constants.MethodsFolder); // Browse forward to find a Method child BrowseResponse browseResponse = await Session.BrowseAsync( null, null, 0, new BrowseDescription[] { new() { NodeId = methodsFolderId, BrowseDirection = BrowseDirection.Forward, ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, IncludeSubtypes = true, NodeClassMask = (uint)NodeClass.Method, ResultMask = (uint)BrowseResultMask.All } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(browseResponse.Results.Count, Is.GreaterThan(0), "Browse should return results."); Assert.That(browseResponse.Results[0].References.Count, Is.GreaterThan(0), "Methods folder should contain at least one Method child."); var methodNodeId = ExpandedNodeId.ToNodeId( browseResponse.Results[0].References[0].NodeId, Session.NamespaceUris); uint[] attributeIds = [ Attributes.NodeId, Attributes.NodeClass, Attributes.BrowseName, Attributes.DisplayName, Attributes.Description, Attributes.WriteMask, Attributes.UserWriteMask, Attributes.Executable, Attributes.UserExecutable ]; var readValueIds = attributeIds .Select(attrId => new ReadValueId { NodeId = methodNodeId, AttributeId = attrId }).ToArrayOf(); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, readValueIds, CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(attributeIds.Length)); for (int i = 0; i < response.Results.Count; i++) { Assert.That(StatusCode.IsGood(response.Results[i].StatusCode), Is.True, $"Method attribute {attributeIds[i]} should return Good."); } } [Description("Read all standard ReferenceType attributes from Organizes. All should return Good.")] [Test] public async Task AttributeRead025ReadAllAttributesOfReferenceTypeAsync() { uint[] attributeIds = [ Attributes.NodeId, Attributes.NodeClass, Attributes.BrowseName, Attributes.DisplayName, Attributes.Description, Attributes.WriteMask, Attributes.UserWriteMask, Attributes.IsAbstract, Attributes.Symmetric, Attributes.InverseName ]; var readValueIds = attributeIds .Select(attrId => new ReadValueId { NodeId = ReferenceTypeIds.Organizes, AttributeId = attrId }).ToArrayOf(); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, readValueIds, CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(attributeIds.Length)); for (int i = 0; i < response.Results.Count; i++) { Assert.That(StatusCode.IsGood(response.Results[i].StatusCode), Is.True, $"ReferenceType attribute {attributeIds[i]} should return Good."); } } [Description("Read Description from the Server object. Should return Good. Value is a LocalizedText (may be null or empty).")] [Test] public async Task AttributeRead026ReadDescriptionAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ObjectIds.Server, AttributeId = Attributes.Description } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Description attribute should be readable from Server object."); } [Description("Read WriteMask from a variable node. Should return Good.")] [Test] public async Task AttributeRead027ReadWriteMaskAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.WriteMask } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "WriteMask attribute should be readable."); } [Description("Read UserWriteMask from a variable node. Should return Good.")] [Test] public async Task AttributeRead028ReadUserWriteMaskAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.UserWriteMask } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "UserWriteMask attribute should be readable."); } [Description("Read with TimestampsToReturn=Neither. Both timestamps should be MinValue.")] [Test] public async Task AttributeRead029TimestampsNoneAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Neither, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); Assert.That(response.Results[0].ServerTimestamp, Is.EqualTo(DateTime.MinValue), "ServerTimestamp should be MinValue when TimestampsToReturn=Neither."); Assert.That(response.Results[0].SourceTimestamp, Is.EqualTo(DateTime.MinValue), "SourceTimestamp should be MinValue when TimestampsToReturn=Neither."); } [Description("Read EventNotifier from the Server object. Should return Good. Value is a byte.")] [Test] public async Task AttributeRead030ReadEventNotifierAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ObjectIds.Server, AttributeId = Attributes.EventNotifier } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "EventNotifier should be readable from Server object."); byte eventNotifier = response.Results[0].GetValue(0); Assert.That(eventNotifier, Is.InstanceOf()); } [Description("Read Server_ServerStatus_CurrentTime. Should return Good and a DateTime close to now.")] [Test] public async Task AttributeRead031ReadServerStatusCurrentTimeAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = VariableIds.Server_ServerStatus_CurrentTime, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Server_ServerStatus_CurrentTime should return Good."); // CurrentTime is per spec a DateTime (UtcTime). Verify the wire // value parses as a DateTimeUtc and is non-min. Assert.That( response.Results[0].WrappedValue.TryGetValue(out DateTimeUtc serverTime), Is.True, "CurrentTime value should decode as DateTime."); Assert.That(serverTime.ToDateTime(), Is.Not.EqualTo(DateTime.MinValue), "CurrentTime should not be MinValue."); } [Description("Read Server_ServerArray. Should return Good and a string array with at least one entry.")] [Test] public async Task AttributeRead032ReadServerArrayAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = VariableIds.Server_ServerArray, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Server_ServerArray should return Good."); string[] serverArray = response.Results[0].GetValue(null); Assert.That(serverArray, Is.Not.Null, "ServerArray should not be null."); Assert.That(serverArray, Is.Not.Empty, "ServerArray should have at least one entry."); } [Description("Read Server_NamespaceArray. Should return Good and a string array. First entry should be \"http://opcfoundation.org/UA/\".")] [Test] public async Task AttributeRead033ReadNamespaceArrayAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = VariableIds.Server_NamespaceArray, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "Server_NamespaceArray should return Good."); string[] namespaceArray = response.Results[0].GetValue(null); Assert.That(namespaceArray, Is.Not.Null, "NamespaceArray should not be null."); Assert.That(namespaceArray, Is.Not.Empty, "NamespaceArray should have at least one entry."); Assert.That(namespaceArray[0], Is.EqualTo("http://opcfoundation.org/UA/"), "First namespace should be the OPC UA namespace."); } [Description("Read with NodeId.Null. Should return BadNodeIdUnknown or BadNodeIdInvalid.")] [Test] public async Task AttributeReadErr004ReadNullNodeIdAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = NodeId.Null, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(response.Results[0].StatusCode), Is.True, "Reading with NodeId.Null should return a Bad status."); Assert.That(response.Results[0].StatusCode.Code, Is.AnyOf(StatusCodes.BadNodeIdUnknown, StatusCodes.BadNodeIdInvalid), "Status should be BadNodeIdUnknown or BadNodeIdInvalid."); } [Description("Mix of valid and invalid nodes. First and third should be Good; second should be BadNodeIdUnknown.")] [Test] public async Task AttributeReadErr005MixOfValidAndInvalidNodesAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value }, new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value }, new() { NodeId = ToNodeId(Constants.ScalarStaticBoolean), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(3)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "First valid node should return Good."); Assert.That(response.Results[1].StatusCode.Code, Is.EqualTo(StatusCodes.BadNodeIdUnknown), "Invalid node should return BadNodeIdUnknown."); Assert.That(StatusCode.IsGood(response.Results[2].StatusCode), Is.True, "Third valid node should return Good."); } [Description("Read Value attribute from an Object node (Server). Should return BadAttributeIdInvalid.")] [Test] public async Task AttributeReadErr006ReadValueFromObjectNodeAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ObjectIds.Server, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(response.Results[0].StatusCode.Code, Is.EqualTo(StatusCodes.BadAttributeIdInvalid), "Reading Value from an Object node should return BadAttributeIdInvalid."); } [Description("Read Executable attribute from a Variable node. Should return BadAttributeIdInvalid.")] [Test] public async Task AttributeReadErr007ReadExecutableFromVariableAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Executable } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(response.Results[0].StatusCode.Code, Is.EqualTo(StatusCodes.BadAttributeIdInvalid), "Reading Executable from a Variable node should return BadAttributeIdInvalid."); } [Description("Read with DataEncoding=Default Binary should succeed.")] [Test] public async Task ReadWithDefaultBinaryEncodingAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value, DataEncoding = new QualifiedName("Default Binary") } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); // Simple types don't require encoding, so Good or BadDataEncodingUnsupported Assert.That( StatusCode.IsGood(response.Results[0].StatusCode) || response.Results[0].StatusCode.Code == StatusCodes.BadDataEncodingUnsupported || response.Results[0].StatusCode.Code == StatusCodes.BadDataEncodingInvalid, Is.True); } [Description("Read with DataEncoding=Default XML should succeed or return unsupported.")] [Test] public async Task ReadWithDefaultXmlEncodingAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value, DataEncoding = new QualifiedName("Default XML") } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That( StatusCode.IsGood(response.Results[0].StatusCode) || response.Results[0].StatusCode.Code == StatusCodes.BadDataEncodingUnsupported || response.Results[0].StatusCode.Code == StatusCodes.BadDataEncodingInvalid, Is.True); } [Description("Read with DataEncoding=Default JSON should succeed or return unsupported.")] [Test] public async Task ReadWithDefaultJsonEncodingAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value, DataEncoding = new QualifiedName("Default JSON") } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That( StatusCode.IsGood(response.Results[0].StatusCode) || response.Results[0].StatusCode.Code == StatusCodes.BadDataEncodingUnsupported || response.Results[0].StatusCode.Code == StatusCodes.BadDataEncodingInvalid, Is.True); } [Description("Read Value of a DataType node should return Null.")] [Test] public async Task ReadValueOfDataTypeNodeReturnsNullAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = DataTypeIds.Int32, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That( response.Results[0].StatusCode.Code, Is.EqualTo(StatusCodes.BadAttributeIdInvalid), "DataType nodes should not have Value attribute."); } [Description("Read Value of a ReferenceType node should return BadAttributeIdInvalid.")] [Test] public async Task ReadValueOfReferenceTypeNodeReturnsErrorAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ReferenceTypeIds.References, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That( response.Results[0].StatusCode.Code, Is.EqualTo(StatusCodes.BadAttributeIdInvalid), "ReferenceType nodes should not have Value attribute."); } [Description("Read all attributes of the Views folder.")] [Test] public async Task ReadAllAttributesOfViewsFolderAsync() { uint[] attrIds = [ Attributes.NodeId, Attributes.NodeClass, Attributes.BrowseName, Attributes.DisplayName, Attributes.Description, Attributes.WriteMask, Attributes.UserWriteMask, Attributes.EventNotifier ]; ReadValueId[] items = [.. attrIds.Select(a => new ReadValueId { NodeId = ObjectIds.ViewsFolder, AttributeId = a })]; ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, items.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(attrIds.Length)); // NodeId, NodeClass, BrowseName, DisplayName should all be Good for (int i = 0; i < 4; i++) { Assert.That( StatusCode.IsGood(response.Results[i].StatusCode), Is.True, $"Attribute {attrIds[i]} should be readable on Views folder."); } } [Description("Read array of structures (if available).")] [Test] public async Task ReadArrayVariableAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticArrayInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That( StatusCode.IsGood(response.Results[0].StatusCode) || StatusCode.IsUncertain(response.Results[0].StatusCode), Is.True); } [Description("Read nested structure (ServerStatusDataType on ServerStatus).")] [Test] public async Task ReadServerStatusStructureAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True, "ServerStatus should be readable."); Assert.That(response.Results[0].WrappedValue.IsNull, Is.False); } [Description("Read ExtensionObject value from ServerStatus (complex structure).")] [Test] public async Task ReadComplexStructureValueAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); DataValue dv = response.Results[0]; Assert.That(StatusCode.IsGood(dv.StatusCode), Is.True); Assert.That(dv.WrappedValue.IsNull, Is.False, "ServerStatus Value should not be null."); } [Test] public async Task ReadDisplayNameOfServerAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ObjectIds.Server, AttributeId = Attributes.DisplayName } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); LocalizedText displayName = response.Results[0].GetValue(default); Assert.That(displayName.Text, Is.EqualTo("Server")); } [Test] public async Task ReadDescriptionOfServerStatusAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = VariableIds.Server_ServerStatus, AttributeId = Attributes.Description } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); // Description may or may not be present; just verify no error Assert.That( StatusCode.IsGood(response.Results[0].StatusCode) || response.Results[0].StatusCode.Code == StatusCodes.Good, Is.True); } [Test] public async Task ReadDataTypeOfInt32VariableAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.DataType } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); NodeId dataType = response.Results[0].GetValue(default); Assert.That(dataType, Is.EqualTo(DataTypeIds.Int32)); } [Test] public async Task ReadAccessLevelOfInt32VariableAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.AccessLevel } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); byte accessLevel = response.Results[0].WrappedValue.GetByte(); Assert.That( (accessLevel & AccessLevels.CurrentRead) != 0, Is.True, "ScalarStaticInt32 should have CurrentRead access."); } [Test] public async Task ReadMinimumSamplingIntervalAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.MinimumSamplingInterval } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); // MinimumSamplingInterval may not be supported on all variables Assert.That( StatusCode.IsGood(response.Results[0].StatusCode) || response.Results[0].StatusCode.Code == StatusCodes.BadAttributeIdInvalid, Is.True); } [Test] public async Task ReadHistorizingAttributeAsync() { NodeId nodeId = ToNodeId(Constants.ScalarStaticInt32); ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = nodeId, AttributeId = Attributes.Historizing } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); bool historizing = response.Results[0].WrappedValue.GetBoolean(); // Historizing may be true if history support is enabled on the server Assert.That(historizing, Is.TypeOf(), "Historizing attribute should be a boolean value."); } [Test] public async Task ReadIsAbstractOnObjectTypeAsync() { ReadResponse response = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ObjectTypeIds.BaseObjectType, AttributeId = Attributes.IsAbstract } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(response.Results[0].StatusCode), Is.True); bool isAbstract = response.Results[0].WrappedValue.GetBoolean(); // BaseObjectType is not always abstract in all implementations Assert.That(isAbstract, Is.TypeOf(), "IsAbstract should be a Boolean value."); } [Description("Read a data value with TimestampsToReturn = BOTH.")] [Test] public async Task AttributeRead006ReadWithTimestampsBothAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Reads the BROWSENAME attribute of multiple valid nodes.")] [Test] public async Task AttributeRead011ReadBrowseNameMultipleNodesAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Reads the same valid attribute from the same valid node multiple times in the same call.")] [Test] public async Task AttributeRead013ReadSameAttributeMultipleTimesAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("MaxAge greater than Int32.")] [Test] public async Task AttributeRead016MaxAgeGreaterThanInt32Async() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Read array with indexRange retrieving elements 2–4 only.")] [Test] public async Task AttributeRead025ReadArrayIndexRange2To4Async() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Read array with indexRange retrieving the last 3 elements.")] [Test] public async Task AttributeRead026ReadArrayLastThreeElementsAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Read any attribute except Value; SourceTimestamp is null.")] [Test] public async Task AttributeRead028ReadNonValueAttributeAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Read Value of a multi-dimensional array for each data-type.")] [Test] public async Task AttributeRead030ReadMultiDimensionalArrayValueAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Read Value of multiple multi-dimensional array nodes.")] [Test] public async Task AttributeRead031ReadMultipleMultiDimensionalArraysAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("IndexRange reading a single element of a multi-dimensional array.")] [Test] public async Task AttributeRead032IndexRangeSingleElementMultiDimAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("IndexRange reading a range from a multi-dimensional array.")] [Test] public async Task AttributeRead033IndexRangeMultiDimAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("IndexRange reading last 3 elements of last dimension.")] [Test] public async Task AttributeRead034IndexRangeLastThreeMultiDimAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("IndexRange lower bound within array but exceeding upper bound.")] [Test] public async Task AttributeRead036IndexRangeExceedingUpperBoundAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = ToNodeId(Constants.ScalarStaticInt32), AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(readResponse.Results[0].StatusCode), Is.True); } [Description("Read multiple valid attributes and one invalid attribute.")] [Test] public async Task AttributeReadErr002ReadInvalidNodeIdAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Read the same invalid attribute from a valid node multiple times in the same call.")] [Test] public async Task AttributeReadErr003ReadSameInvalidAttributeMultipleTimesAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Read from a node id with invalid syntax.")] [Test] public async Task AttributeReadErr006ReadFromInvalidSyntaxNodeIdAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Read valid attributes from multiple non-existent nodes.")] [Test] public async Task AttributeReadErr009ReadFromNonExistentNodesAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Read valid attributes from nodes with invalid syntax.")] [Test] public async Task AttributeReadErr010ReadFromInvalidSyntaxNodesAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Specifies a null nodes array for reading.")] [Test] public async Task AttributeReadErr011NullNodesArrayAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("IndexRange outside the bounds of the array.")] [Test] public async Task AttributeReadErr012IndexRangeOutOfBoundsAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Invalid IndexRange \"-2:0\".")] [Test] public async Task AttributeReadErr013InvalidIndexRangeNegativeAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("MaxAge is a negative number.")] [Test] public async Task AttributeReadErr014NegativeMaxAgeAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("IndexRange on non-applicable attributes.")] [Test] public async Task AttributeReadErr015IndexRangeOnNonApplicableAttributeAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Read a node that is NOT readable.")] [Test] public async Task AttributeReadErr016ReadNonReadableNodeAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Read Value with invalid DataEncoding.")] [Test] public async Task AttributeReadErr017InvalidDataEncodingAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Invalid TimestampsToReturn value.")] [Test] public async Task AttributeReadErr019InvalidTimestampsToReturnAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Invalid IndexRange \"2-4\" (dash is invalid).")] [Test] public async Task AttributeReadErr022InvalidIndexRangeDashAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Invalid IndexRange \"2:2\" (not a range).")] [Test] public async Task AttributeReadErr023InvalidIndexRangeSameValueAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } [Description("Invalid IndexRange \"5:2\" (backwards).")] [Test] public async Task AttributeReadErr024InvalidIndexRangeBackwardsAsync() { ReadResponse readResponse = await Session.ReadAsync( null, 0, TimestampsToReturn.Both, new ReadValueId[] { new() { NodeId = Constants.InvalidNodeId, AttributeId = Attributes.Value } }.ToArrayOf(), CancellationToken.None).ConfigureAwait(false); Assert.That(readResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsBad(readResponse.Results[0].StatusCode), Is.True); } } }