mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 15:11:53 +08:00
507 lines
18 KiB
C#
507 lines
18 KiB
C#
/* ========================================================================
|
|
* 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 NUnit.Framework;
|
|
using Opc.Ua.PubSub.PublishedData;
|
|
using Opc.Ua.Tests;
|
|
|
|
namespace Opc.Ua.PubSub.Tests.PublishedData
|
|
{
|
|
[TestFixture]
|
|
[Category("DataCollector")]
|
|
[SetCulture("en-us")]
|
|
[SetUICulture("en-us")]
|
|
[Parallelizable]
|
|
public class DataCollectorAdditionalTests
|
|
{
|
|
private static DataCollector CreateCollector(IUaPubSubDataStore dataStore = null)
|
|
{
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
return new DataCollector(dataStore ?? new UaPubSubDataStore(), telemetry);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate returns true when DataSetMetaData is default-initialized
|
|
/// </summary>
|
|
[Test]
|
|
public void ValidateReturnsTrueWhenDataSetIsDefaultInitialized()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "Test",
|
|
DataSetSource = ExtensionObject.Null
|
|
};
|
|
bool result = collector.ValidatePublishedDataSet(pds);
|
|
Assert.That(result, Is.True);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate returns false when PublishedData count mismatches Fields count
|
|
/// </summary>
|
|
[Test]
|
|
public void ValidateReturnsFalseWhenCountMismatch()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "Test",
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = "Test",
|
|
Fields = [
|
|
new FieldMetaData { Name = "F1", BuiltInType = (byte)BuiltInType.Int32 },
|
|
new FieldMetaData { Name = "F2", BuiltInType = (byte)BuiltInType.Int32 }
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [
|
|
new PublishedVariableDataType()
|
|
]
|
|
})
|
|
};
|
|
bool result = collector.ValidatePublishedDataSet(pds);
|
|
Assert.That(result, Is.False);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate throws on null publishedDataSet
|
|
/// </summary>
|
|
[Test]
|
|
public void ValidateThrowsOnNull()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
Assert.That(() => collector.ValidatePublishedDataSet(null), Throws.TypeOf<ArgumentException>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// AddPublishedDataSet throws on null
|
|
/// </summary>
|
|
[Test]
|
|
public void AddPublishedDataSetThrowsOnNull()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
Assert.That(() => collector.AddPublishedDataSet(null), Throws.TypeOf<ArgumentException>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// RemovePublishedDataSet throws on null
|
|
/// </summary>
|
|
[Test]
|
|
public void RemovePublishedDataSetThrowsOnNull()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
Assert.That(() => collector.RemovePublishedDataSet(null), Throws.TypeOf<ArgumentException>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// GetPublishedDataSet throws on null name
|
|
/// </summary>
|
|
[Test]
|
|
public void GetPublishedDataSetThrowsOnNullName()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
Assert.That(() => collector.GetPublishedDataSet(null), Throws.TypeOf<ArgumentException>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// GetPublishedDataSet returns null for unknown name
|
|
/// </summary>
|
|
[Test]
|
|
public void GetPublishedDataSetReturnsNullForUnknownName()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
PublishedDataSetDataType result = collector.GetPublishedDataSet("NonExistent");
|
|
Assert.That(result, Is.Null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// CollectData returns null for unregistered dataset
|
|
/// </summary>
|
|
[Test]
|
|
public void CollectDataReturnsNullForUnknownDataSet()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
DataSet result = collector.CollectData("NonExistent");
|
|
Assert.That(result, Is.Null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// CollectData returns null when DataSetSource is null (IsNull)
|
|
/// </summary>
|
|
[Test]
|
|
public void CollectDataReturnsNullWhenDataSetSourceIsNull()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
PublishedDataSetDataType pds = CreateValidPds("Test", ExtensionObject.Null, BuiltInType.Int32);
|
|
collector.AddPublishedDataSet(pds);
|
|
|
|
DataSet result = collector.CollectData("Test");
|
|
Assert.That(result, Is.Null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// AddPublishedDataSet with mismatched counts logs error and does not add
|
|
/// </summary>
|
|
[Test]
|
|
public void AddInvalidPublishedDataSetDoesNotAdd()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "Invalid",
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = "Invalid",
|
|
Fields = [
|
|
new FieldMetaData { Name = "F1", BuiltInType = (byte)BuiltInType.Int32 },
|
|
new FieldMetaData { Name = "F2", BuiltInType = (byte)BuiltInType.Int32 }
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [new PublishedVariableDataType()]
|
|
})
|
|
};
|
|
collector.AddPublishedDataSet(pds);
|
|
|
|
PublishedDataSetDataType found = collector.GetPublishedDataSet("Invalid");
|
|
Assert.That(found, Is.Null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// CollectData with extension field fallback
|
|
/// </summary>
|
|
[Test]
|
|
public void CollectDataUsesExtensionFieldWhenDataValueIsNull()
|
|
{
|
|
var dataStore = new UaPubSubDataStore();
|
|
DataCollector collector = CreateCollector(dataStore);
|
|
|
|
var extensionFieldName = new QualifiedName("ExtField1");
|
|
var extensionField = new KeyValuePair
|
|
{
|
|
Key = extensionFieldName,
|
|
Value = new Variant(99)
|
|
};
|
|
|
|
var pubVar = new PublishedVariableDataType
|
|
{
|
|
SubstituteValue = new Variant(extensionFieldName)
|
|
};
|
|
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "ExtTest",
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = "ExtTest",
|
|
Fields = [
|
|
new FieldMetaData
|
|
{
|
|
Name = "F1",
|
|
BuiltInType = (byte)BuiltInType.Int32,
|
|
ValueRank = ValueRanks.Scalar
|
|
}
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [pubVar]
|
|
}),
|
|
ExtensionFields = [extensionField]
|
|
};
|
|
|
|
collector.AddPublishedDataSet(pds);
|
|
DataSet result = collector.CollectData("ExtTest");
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result.Fields, Has.Length.EqualTo(1));
|
|
Assert.That(result.Fields[0].Value.WrappedValue.AsBoxedObject(), Is.EqualTo(99));
|
|
}
|
|
|
|
/// <summary>
|
|
/// CollectData with no matching extension field produces Bad status
|
|
/// </summary>
|
|
[Test]
|
|
public void CollectDataProducesBadWhenNoValueAndNoExtensionField()
|
|
{
|
|
var dataStore = new UaPubSubDataStore();
|
|
DataCollector collector = CreateCollector(dataStore);
|
|
|
|
var pubVar = new PublishedVariableDataType();
|
|
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "BadTest",
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = "BadTest",
|
|
Fields = [
|
|
new FieldMetaData
|
|
{
|
|
Name = "F1",
|
|
BuiltInType = (byte)BuiltInType.Int32,
|
|
ValueRank = ValueRanks.Scalar
|
|
}
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [pubVar]
|
|
})
|
|
};
|
|
|
|
collector.AddPublishedDataSet(pds);
|
|
DataSet result = collector.CollectData("BadTest");
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result.Fields[0].Value.StatusCode, Is.EqualTo(StatusCodes.Bad));
|
|
}
|
|
|
|
/// <summary>
|
|
/// CollectData with SubstituteValue on bad status from store
|
|
/// </summary>
|
|
[Test]
|
|
public void CollectDataUsesSubstituteValueOnBadStatus()
|
|
{
|
|
var dataStore = new UaPubSubDataStore();
|
|
var nodeId = new NodeId(100, 2);
|
|
dataStore.WritePublishedDataItem(nodeId, Attributes.Value, DataValue.FromStatusCode(StatusCodes.Bad));
|
|
DataCollector collector = CreateCollector(dataStore);
|
|
|
|
var pubVar = new PublishedVariableDataType
|
|
{
|
|
PublishedVariable = nodeId,
|
|
AttributeId = Attributes.Value,
|
|
SubstituteValue = new Variant(42)
|
|
};
|
|
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "SubTest",
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = "SubTest",
|
|
Fields = [
|
|
new FieldMetaData
|
|
{
|
|
Name = "F1",
|
|
BuiltInType = (byte)BuiltInType.Int32,
|
|
ValueRank = ValueRanks.Scalar
|
|
}
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [pubVar]
|
|
})
|
|
};
|
|
|
|
collector.AddPublishedDataSet(pds);
|
|
DataSet result = collector.CollectData("SubTest");
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result.Fields[0].Value.WrappedValue.AsBoxedObject(), Is.EqualTo(42));
|
|
Assert.That(
|
|
result.Fields[0].Value.StatusCode,
|
|
Is.EqualTo(StatusCodes.UncertainSubstituteValue));
|
|
}
|
|
|
|
/// <summary>
|
|
/// CollectData with string truncation
|
|
/// </summary>
|
|
[Test]
|
|
public void CollectDataTruncatesStringToMaxStringLength()
|
|
{
|
|
var dataStore = new UaPubSubDataStore();
|
|
var nodeId = new NodeId(200, 2);
|
|
dataStore.WritePublishedDataItem(
|
|
nodeId,
|
|
Attributes.Value,
|
|
new DataValue(new Variant("Hello World Long String")));
|
|
DataCollector collector = CreateCollector(dataStore);
|
|
|
|
var pubVar = new PublishedVariableDataType
|
|
{
|
|
PublishedVariable = nodeId,
|
|
AttributeId = Attributes.Value
|
|
};
|
|
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "TruncTest",
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = "TruncTest",
|
|
Fields = [
|
|
new FieldMetaData
|
|
{
|
|
Name = "F1",
|
|
BuiltInType = (byte)BuiltInType.String,
|
|
ValueRank = ValueRanks.Scalar,
|
|
MaxStringLength = 5
|
|
}
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [pubVar]
|
|
})
|
|
};
|
|
|
|
collector.AddPublishedDataSet(pds);
|
|
DataSet result = collector.CollectData("TruncTest");
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
string value = result.Fields[0].Value.WrappedValue.ToString();
|
|
Assert.That(value, Has.Length.EqualTo(5));
|
|
}
|
|
|
|
/// <summary>
|
|
/// CollectData with ByteString truncation
|
|
/// </summary>
|
|
[Test]
|
|
public void CollectDataTruncatesByteStringToMaxStringLength()
|
|
{
|
|
var dataStore = new UaPubSubDataStore();
|
|
var nodeId = new NodeId(201, 2);
|
|
dataStore.WritePublishedDataItem(
|
|
nodeId,
|
|
Attributes.Value,
|
|
new DataValue(Variant.From(ByteString.From(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }))));
|
|
DataCollector collector = CreateCollector(dataStore);
|
|
|
|
var pubVar = new PublishedVariableDataType
|
|
{
|
|
PublishedVariable = nodeId,
|
|
AttributeId = Attributes.Value
|
|
};
|
|
|
|
var pds = new PublishedDataSetDataType
|
|
{
|
|
Name = "ByteTrunc",
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = "ByteTrunc",
|
|
Fields = [
|
|
new FieldMetaData
|
|
{
|
|
Name = "F1",
|
|
BuiltInType = (byte)BuiltInType.ByteString,
|
|
ValueRank = ValueRanks.Scalar,
|
|
MaxStringLength = 3
|
|
}
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [pubVar]
|
|
})
|
|
};
|
|
|
|
collector.AddPublishedDataSet(pds);
|
|
DataSet result = collector.CollectData("ByteTrunc");
|
|
|
|
Assert.That(result, Is.Not.Null);
|
|
Assert.That(result.Fields[0].Value.WrappedValue.TryGetValue(out ByteString bs), Is.True);
|
|
Assert.That(bs.Length, Is.EqualTo(3));
|
|
}
|
|
|
|
/// <summary>
|
|
/// RemovePublishedDataSet removes correctly
|
|
/// </summary>
|
|
[Test]
|
|
public void RemovePublishedDataSetSucceeds()
|
|
{
|
|
DataCollector collector = CreateCollector();
|
|
PublishedDataSetDataType pds = CreateValidPds("RemoveTest", BuiltInType.Int32);
|
|
collector.AddPublishedDataSet(pds);
|
|
|
|
Assert.That(collector.GetPublishedDataSet("RemoveTest"), Is.Not.Null);
|
|
|
|
collector.RemovePublishedDataSet(pds);
|
|
Assert.That(collector.GetPublishedDataSet("RemoveTest"), Is.Null);
|
|
}
|
|
|
|
private static PublishedDataSetDataType CreateValidPds(
|
|
string name,
|
|
ExtensionObject dataSetSource,
|
|
BuiltInType builtInType)
|
|
{
|
|
return new PublishedDataSetDataType
|
|
{
|
|
Name = name,
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = name,
|
|
Fields = [
|
|
new FieldMetaData
|
|
{
|
|
Name = "F1",
|
|
BuiltInType = (byte)builtInType,
|
|
ValueRank = ValueRanks.Scalar
|
|
}
|
|
]
|
|
},
|
|
DataSetSource = dataSetSource
|
|
};
|
|
}
|
|
|
|
private static PublishedDataSetDataType CreateValidPds(
|
|
string name,
|
|
BuiltInType builtInType)
|
|
{
|
|
var pubVar = new PublishedVariableDataType();
|
|
return new PublishedDataSetDataType
|
|
{
|
|
Name = name,
|
|
DataSetMetaData = new DataSetMetaDataType
|
|
{
|
|
Name = name,
|
|
Fields = [
|
|
new FieldMetaData
|
|
{
|
|
Name = "F1",
|
|
BuiltInType = (byte)builtInType,
|
|
ValueRank = ValueRanks.Scalar
|
|
}
|
|
]
|
|
},
|
|
DataSetSource = new ExtensionObject(new PublishedDataItemsDataType
|
|
{
|
|
PublishedData = [pubVar]
|
|
})
|
|
};
|
|
}
|
|
}
|
|
}
|