/* ======================================================================== * Copyright (c) 2005-2026 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.Globalization; using System.IO; using System.Linq; using System.Xml.Linq; using NUnit.Framework; using Opc.Ua.Machinery.Result; using Opc.Ua.MachineTool; namespace Opc.Ua.GMS.Tests { /// /// Exercises generated factories and inherited mandatory/default-optional structures at public model seams. /// [TestFixture] [Category("GMS")] public sealed class ModelGenerationTests { /// /// GMS uses the existing dependency types rather than regenerating their declarations in its assembly. /// [Test] public void GeneratedInheritanceUsesDependencyAssemblies() { Assert.Multiple(() => { Assert.That(typeof(GMSState).BaseType, Is.EqualTo(typeof(MachineToolState))); Assert.That(typeof(GMSIdentificationState).BaseType, Is.EqualTo(typeof(MachineToolIdentificationState))); Assert.That(typeof(GMSEquipmentState).BaseType, Is.EqualTo(typeof(EquipmentState))); Assert.That(typeof(GMSMonitoringState).BaseType, Is.EqualTo(typeof(MonitoringState))); Assert.That(typeof(GMSJobState).BaseType, Is.EqualTo(typeof(ProductionJobState))); Assert.That(typeof(GMSPartState).BaseType, Is.EqualTo(typeof(ProductionPartState))); Assert.That(typeof(SensorState).BaseType, Is.EqualTo(typeof(ToolState))); Assert.That(typeof(MultiSensorState).BaseType, Is.EqualTo(typeof(MultiToolState))); Assert.That(typeof(GMSResultManagementState).BaseType, Is.EqualTo(typeof(ResultManagementState))); Assert.That(typeof(GMSResultState).BaseType, Is.EqualTo(typeof(ResultState))); Assert.That(typeof(IntermediateResultEventState).BaseType, Is.EqualTo(typeof(ResultReadyEventState))); Assert.That(typeof(GMSState).Assembly, Is.Not.SameAs(typeof(MachineToolState).Assembly)); Assert.That(typeof(GMSResultState).Assembly, Is.Not.SameAs(typeof(ResultState).Assembly)); Assert.That(Namespaces.GMS, Is.EqualTo("http://opcfoundation.org/UA/GMS/")); Assert.That(GmsModel.NamespaceUri, Is.EqualTo(Namespaces.GMS)); Assert.That(GmsModel.Version, Is.EqualTo("1.0.0")); Assert.That(GmsModel.ReferenceEdition, Is.EqualTo("1.0.1")); Assert.That(GmsModel.SourceCommit, Is.EqualTo("96a08945425d0295edb3607d2a38f6841ef44046")); Assert.That(GmsModel.NormalizedNodeSetSha256, Is.EqualTo("9E510C01A2671B732E5EB27561B06A5BC040BB219412884D0A3AFF3C55B88EE0")); }); } /// /// Every published object type has a typed, reflection-free factory at its canonical numeric identity. /// [TestCase(1001u, typeof(IntermediateResultEventState))] [TestCase(1002u, typeof(GMSState))] [TestCase(1003u, typeof(GMSEquipmentState))] [TestCase(1004u, typeof(GMSMonitoringState))] [TestCase(1005u, typeof(CorrectionState))] [TestCase(1006u, typeof(ToolMonitoringState))] [TestCase(1007u, typeof(SensorState))] [TestCase(1008u, typeof(GMSResultManagementState))] [TestCase(1009u, typeof(CharacteristicState))] [TestCase(1010u, typeof(SensorWarningAlarmState))] [TestCase(1011u, typeof(GMSIdentificationState))] [TestCase(1012u, typeof(MultiSensorState))] [TestCase(1014u, typeof(LoadingMonitoringState))] [TestCase(1015u, typeof(SensorExchangeRackState))] [TestCase(1016u, typeof(GMSJobState))] [TestCase(1017u, typeof(GMSPartState))] [TestCase(1018u, typeof(RotaryTableState))] [TestCase(1019u, typeof(CalibrationPrognosisState))] [TestCase(1022u, typeof(TipExchangeRackState))] public void ObjectFactoriesRetainCanonicalTypes(uint identifier, Type expectedType) { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); var factory = new NodeStateFactory(); factory.AddGmsTypeSystem(); var typeId = NodeId.Create(identifier, GmsModel.NamespaceUri, context.NamespaceUris); var browseName = new QualifiedName("GmsComponent", 1); NodeState? created = factory.CreateInstance( context, null, NodeClass.Object, browseName, ReferenceTypeIds.HasComponent, typeId); Assert.That(created, Is.TypeOf(expectedType)); Assert.That(((BaseObjectState)created!).TypeDefinitionId, Is.EqualTo(typeId)); } /// /// The three GMS variable types retain their typed factory and underlying wire data types. /// [TestCase(2002u, typeof(AdditionalSensorState), 26u, "http://opcfoundation.org/UA/")] [TestCase(2003u, typeof(CatalogState), 26u, "http://opcfoundation.org/UA/")] [TestCase(2004u, typeof(GMSResultState), 3008u, "http://opcfoundation.org/UA/Machinery/Result/")] public void VariableFactoriesRetainCanonicalTypes( uint identifier, Type expectedType, uint dataTypeId, string dataTypeNamespace) { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); var factory = new NodeStateFactory(); factory.AddGmsTypeSystem(); var typeId = NodeId.Create(identifier, GmsModel.NamespaceUri, context.NamespaceUris); NodeState? created = factory.CreateInstance( context, null, NodeClass.Variable, new QualifiedName("Value", 1), ReferenceTypeIds.HasComponent, typeId); Assert.That(created, Is.TypeOf(expectedType)); var variable = (BaseVariableState)created!; Assert.Multiple(() => { Assert.That(variable.TypeDefinitionId, Is.EqualTo(typeId)); Assert.That(variable.DataType, Is.EqualTo(NodeId.Create(dataTypeId, dataTypeNamespace, context.NamespaceUris))); }); } /// /// The default factory retains required inherited production, monitoring, identification and result structure. /// [TestCase(false)] [TestCase(true)] public void GmsFactoryRetainsMandatoryInheritedStructure(bool reverseNamespaces) { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: reverseNamespaces); GMSState machine = context.CreateInstanceOfGMSType(); var identification = ModelTestContext.RequireChild( context, machine, "Identification", Di.Namespaces.OpcUaDi); var equipment = ModelTestContext.RequireChild( context, machine, "Equipment", MachineTool.Namespaces.MachineTool); var monitoring = ModelTestContext.RequireChild( context, machine, "Monitoring", MachineTool.Namespaces.MachineTool); var operation = ModelTestContext.RequireChild( context, monitoring, "MachineTool", MachineTool.Namespaces.MachineTool); var mode = ModelTestContext.RequireChild( context, operation, "OperationMode", MachineTool.Namespaces.MachineTool); var production = ModelTestContext.RequireChild( context, machine, "Production", MachineTool.Namespaces.MachineTool); var active = ModelTestContext.RequireChild( context, production, "ActiveProgram", MachineTool.Namespaces.MachineTool); var state = ModelTestContext.RequireChild( context, active, "State", MachineTool.Namespaces.MachineTool); var current = ModelTestContext.RequireChild( context, state, "CurrentState", Opc.Ua.Namespaces.OpcUa); var result = ModelTestContext.RequireChild( context, machine, "ResultManagement", GmsModel.NamespaceUri); ModelTestContext.RequireChild( context, machine, "Notification", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild>( context, identification, "Manufacturer", Di.Namespaces.OpcUaDi); ModelTestContext.RequireChild>( context, identification, "ProductInstanceUri", Di.Namespaces.OpcUaDi); ModelTestContext.RequireChild>( context, identification, "SerialNumber", Di.Namespaces.OpcUaDi); ModelTestContext.RequireChild( context, active, "Name", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild>( context, active, "NumberInList", Opc.Ua.Namespaces.OpcUa); ModelTestContext.RequireChild>(context, current, "Id", Opc.Ua.Namespaces.OpcUa); ModelTestContext.RequireChild>(context, current, "Number", Opc.Ua.Namespaces.OpcUa); Assert.Multiple(() => { Assert.That(machine.TypeDefinitionId, Is.EqualTo(NodeId.Create(1002, GmsModel.NamespaceUri, context.NamespaceUris))); Assert.That(identification.ReferenceTypeId, Is.EqualTo(ReferenceTypeIds.HasAddIn)); Assert.That(mode.DataType, Is.EqualTo(ExpandedNodeId.ToNodeId( MachineTool.DataTypeIds.MachineOperationMode, context.NamespaceUris))); Assert.That(result.TypeDefinitionId, Is.EqualTo(NodeId.Create(1008, GmsModel.NamespaceUri, context.NamespaceUris))); Assert.That(result.ReferenceTypeId, Is.EqualTo(ReferenceTypeIds.HasComponent)); Assert.That(result.ResultTransfer, Is.Null); Assert.That(result.GetLatestResult, Is.Null); Assert.That(result.CorrectionsFolder, Is.Null); Assert.That(identification.Workspace, Is.Null); Assert.That(equipment.Tools, Is.Null); Assert.That(equipment.Accessories, Is.Null); Assert.That(monitoring.LoadingMonitoring, Is.Null); Assert.That(monitoring.ToolMonitoring, Is.Null); }); } /// /// Sensor factories specialize real tools and preserve mandatory tool-life and classification structures. /// [Test] public void SensorFactoryPreservesToolInheritanceAndOptionalQualification() { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); SensorState sensor = context.CreateInstanceOfSensorType(); var life = ModelTestContext.RequireChild( context, sensor, "ToolLife", MachineTool.Namespaces.MachineTool); var classification = ModelTestContext.RequireChild( context, sensor, "Class", GmsModel.NamespaceUri); ModelTestContext.RequireChild>( context, sensor, "ControlIdentifier1", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, sensor, "Locked", MachineTool.Namespaces.MachineTool); Assert.Multiple(() => { Assert.That(classification.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.UInteger)); Assert.That(life.FindChild(context, new QualifiedName( "Qualified", context.NamespaceUris.GetIndexOrAppend(GmsModel.NamespaceUri))), Is.Null); Assert.That(sensor.Alignment, Is.Null); Assert.That(sensor.IsQualifiedStatus, Is.Null); }); sensor.AddAlignment(context).AddIsQualifiedStatus(context).AddAxes(context); sensor.Alignment!.Value = ToolAlignmentState.Indexed; sensor.IsQualifiedStatus!.Value = ToolIsQualifiedStatus.Imprecise; sensor.Axes!.Value = ["A", "B"]; Assert.Multiple(() => { Assert.That(sensor.Alignment.DataType, Is.EqualTo(NodeId.Create(3004, GmsModel.NamespaceUri, context.NamespaceUris))); Assert.That(sensor.IsQualifiedStatus.DataType, Is.EqualTo(NodeId.Create(3002, GmsModel.NamespaceUri, context.NamespaceUris))); Assert.That(sensor.Axes.ValueRank, Is.EqualTo(ValueRanks.OneDimension)); }); } /// /// A MultiSensor's inherited placeholder creates the specialized sensor with required nested tool members. /// [Test] public void MultiSensorPlaceholderCreatesSensorRatherThanBaseTool() { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); MultiSensorState multi = context.CreateInstanceOfMultiSensorType(); var browseName = new QualifiedName("ProbeHead", 1); SensorState sensor = multi.AddTool_Placeholder(context, browseName); Assert.Multiple(() => { Assert.That(multi.FindChild(context, browseName), Is.SameAs(sensor)); Assert.That(sensor.Parent, Is.SameAs(multi)); Assert.That(sensor.TypeDefinitionId, Is.EqualTo(NodeId.Create(1007, GmsModel.NamespaceUri, context.NamespaceUris))); Assert.That(sensor.Class, Is.Not.Null); Assert.That(sensor.ToolLife, Is.Not.Null); Assert.That(sensor.ControlIdentifier1, Is.Not.Null); }); } /// /// The Qualified declaration retains published defaults; selecting the CU is a runtime responsibility. /// [Test] public void QualifiedDeclarationPreservesCanonicalDefaultsAndRequiredProperties() { SystemContext context = ModelTestContext.CreateSystemContext(); Dictionary nodes = ModelTestContext.IndexNodes( context, new NodeStateCollection().AddGmsTypeSystem(context)); var qualified = (ToolLifeState)nodes[NodeId.Create( 6341, GmsModel.NamespaceUri, context.NamespaceUris)]; Assert.Multiple(() => { Assert.That(qualified.Indication!.Value, Is.EqualTo(ToolLifeIndication.Time)); Assert.That(qualified.IsCountingUp!.Value, Is.True, "The XML and Table 17 say true; the QualifiedToolLife CU instead requires false at runtime."); Assert.That(qualified.EngineeringUnits, Is.Not.Null); Assert.That(qualified.TypeDefinitionId, Is.EqualTo(ExpandedNodeId.ToNodeId( MachineTool.VariableTypeIds.ToolLifeType, context.NamespaceUris))); Assert.That(qualified.ModellingRuleId, Is.EqualTo(Opc.Ua.ObjectIds.ModellingRule_Optional)); Assert.That(qualified.Indication.BrowseName.NamespaceIndex, Is.EqualTo(context.NamespaceUris.GetIndex(MachineTool.Namespaces.MachineTool))); }); } /// /// GMS monitoring adds optional typed loading and tool monitoring without replacing required operation data. /// [Test] public void OptionalMonitoringIncludesInheritedMandatoryMembers() { SystemContext context = ModelTestContext.CreateSystemContext(); GMSMonitoringState monitoring = context.CreateInstanceOfGMSMonitoringType(); monitoring.AddLoadingMonitoring(context).AddToolMonitoring(context); var load = ModelTestContext.RequireChild( context, monitoring, "LoadingMonitoring", GmsModel.NamespaceUri); var tool = ModelTestContext.RequireChild( context, monitoring, "ToolMonitoring", GmsModel.NamespaceUri); var status = ModelTestContext.RequireChild( context, load, "LoadStatus", GmsModel.NamespaceUri); ModelTestContext.RequireChild( context, load, "Name", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, tool, "Name", MachineTool.Namespaces.MachineTool); Assert.Multiple(() => { Assert.That(status.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.UInteger)); Assert.That(load.IsInLoadingPosition, Is.Null); Assert.That(tool.ActiveTool, Is.Null); }); tool.AddActiveTool(context); Assert.That(tool.ActiveTool!.ValueRank, Is.EqualTo(ValueRanks.OneDimension)); } /// /// GMS jobs and parts retain the older MachineTool production model while adding measurement metadata. /// [Test] public void ProductionJobsAndPartsRetainInheritedMandatoryStateAndStatistics() { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); GMSJobState job = context.CreateInstanceOfGMSJobType(); GMSPartState part = context.CreateInstanceOfGMSPartType(); ModelTestContext.RequireChild>( context, job, "Identifier", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, job, "RunsCompleted", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, job, "RunsPlanned", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, job, "ProductionPrograms", MachineTool.Namespaces.MachineTool); var state = ModelTestContext.RequireChild( context, job, "State", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, state, "CurrentState", Opc.Ua.Namespaces.OpcUa); ModelTestContext.RequireChild( context, part, "Name", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, part, "PartQuality", MachineTool.Namespaces.MachineTool); ModelTestContext.RequireChild( context, part, "ProcessIrregularity", MachineTool.Namespaces.MachineTool); Assert.Multiple(() => { Assert.That(job.MeasurementReason, Is.Null); Assert.That(part.Operator, Is.Null); Assert.That(part.ProcessParameter, Is.Null); }); job.AddMeasurementReason(context).AddRemainingTime(context); job.MeasurementReason!.Value = MeasurementReasonEnum.AuditMeasurement; part.AddOperator(context).AddProcessParameter(context); Assert.Multiple(() => { Assert.That(job.MeasurementReason.DataType, Is.EqualTo(NodeId.Create(3009, GmsModel.NamespaceUri, context.NamespaceUris))); Assert.That(job.RemainingTime!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Duration)); Assert.That(part.Operator!.CatalogName, Is.Not.Null); Assert.That(part.Operator.CatalogEntry, Is.Not.Null); Assert.That(part.ProcessParameter!.ValueRank, Is.EqualTo(ValueRanks.OneDimension)); }); } /// /// The real inherited statistics type remains available without inventing the undefined GMS statistics type. /// [Test] public void GmsRetainsOptionalMachineToolProductionStatistics() { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); GMSState machine = context.CreateInstanceOfGMSType(); ProductionState production = machine.Production!; Assert.That(production.Statistics, Is.Null); production.AddStatistics(context); var statistics = ModelTestContext.RequireChild( context, production, "Statistics", MachineTool.Namespaces.MachineTool); Assert.That(statistics.PartsProducedInLifetime, Is.Null); statistics.AddPartsProducedInLifetime(context); statistics.PartsProducedInLifetime!.Value = 42; Assert.Multiple(() => { Assert.That(statistics.TypeDefinitionId, Is.EqualTo(NodeId.Create(20, MachineTool.Namespaces.MachineTool, context.NamespaceUris))); Assert.That(statistics.PartsProducedInLifetime.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.UInt32)); Assert.That(statistics.PartsProducedInLifetime.Value, Is.EqualTo(42)); Assert.That(statistics.PartsProducedInLifetime.BrowseName.NamespaceIndex, Is.EqualTo(context.NamespaceUris.GetIndex(MachineTool.Namespaces.MachineTool))); }); } /// /// Accessories and prognoses preserve their DI/Machinery identification and required calibration data. /// [Test] public void AccessoryAndCalibrationFactoriesPreserveMandatoryInheritedMembers() { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); RotaryTableState rotary = context.CreateInstanceOfRotaryTableType(); SensorExchangeRackState sensorRack = context.CreateInstanceOfSensorExchangeRackType(); TipExchangeRackState tipRack = context.CreateInstanceOfTipExchangeRackType(); foreach (BaseObjectState accessory in new BaseObjectState[] { rotary, sensorRack, tipRack }) { var identification = ModelTestContext.RequireChild( context, accessory, "Identification", Di.Namespaces.OpcUaDi); ModelTestContext.RequireChild>( context, identification, "Manufacturer", Di.Namespaces.OpcUaDi); Assert.That(identification.ReferenceTypeId, Is.EqualTo(ReferenceTypeIds.HasAddIn)); } CalibrationPrognosisState calibration = context.CreateInstanceOfCalibrationPrognosisType(); Assert.Multiple(() => { Assert.That(rotary.IsIntegrated, Is.Not.Null); Assert.That(rotary.NumberOfAxes!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Byte)); Assert.That(sensorRack.IsAvailable, Is.Not.Null); Assert.That(tipRack.IsAvailable, Is.Not.Null); Assert.That(calibration.Calibrated!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Boolean)); Assert.That(calibration.DateOfCalibration!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.UtcTime)); Assert.That(calibration.PredictedTime!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.UtcTime)); Assert.That(calibration.CalibrationCertificate, Is.Null); }); calibration.AddCalibrationCertificate(context).AddCalibrationInterval(context); Assert.Multiple(() => { Assert.That(calibration.CalibrationCertificate!.ValueRank, Is.EqualTo(ValueRanks.OneDimension)); Assert.That(calibration.CalibrationInterval!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Duration)); }); } /// /// Specialized result/event/alarm factories keep Machinery Result inheritance and GMS-specific members. /// [Test] public void ResultEventAndAlarmFactoriesPreserveTheirTypedExtensions() { SystemContext context = ModelTestContext.CreateSystemContext(reverseNamespaces: true); GMSResultState result = context.CreateInstanceOfGMSResultType(); IntermediateResultEventState intermediate = context.CreateInstanceOfIntermediateResultEventType(); SensorWarningAlarmState alarm = context.CreateInstanceOfSensorWarningAlarmType(); Assert.Multiple(() => { Assert.That(result.ResultMetaData, Is.Not.Null); Assert.That(result.UsedTools, Is.Null); Assert.That(result.ValueRank, Is.EqualTo(ValueRanks.Scalar)); Assert.That(intermediate.Result, Is.Not.Null); Assert.That(intermediate.EventId, Is.Not.Null); Assert.That(alarm.ErrorCode!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.String)); Assert.That(alarm.ActiveState, Is.Not.Null); Assert.That(alarm.InputNode, Is.Not.Null); }); result.AddUsedTools(context); result.UsedTools!.Value = ["sensor-17", "sensor-18"]; var browseName = new QualifiedName("Diameter", 1); CharacteristicState characteristic = intermediate.AddCharacteristics_Placeholder(context, browseName); characteristic.AddResultValue(context).AddNominal(context).AddUpperToleranceLimit(context); Assert.Multiple(() => { Assert.That(intermediate.FindChild(context, browseName), Is.SameAs(characteristic)); Assert.That(result.UsedTools.ValueRank, Is.EqualTo(ValueRanks.OneDimension)); Assert.That(characteristic.ResultValue!.EngineeringUnits, Is.Not.Null); Assert.That(characteristic.UpperToleranceLimit!.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Double)); }); } /// /// Every released GMS enumeration registers exact symbols and rejects unknown values and names. /// [Test] public void EnumFactoriesPreserveReleasedValuesAndRejectInvalidSymbols() { ServiceMessageContext context = ModelTestContext.CreateMessageContext(); XElement root = XDocument.Load(Path.Combine( TestContext.CurrentContext.TestDirectory, "Model", "opc.ua.gms.nodeset2.xml")).Root!; XNamespace ns = "http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"; XElement[] enums = [ .. root.Elements(ns + "UADataType").Where(node => node.Element(ns + "Definition")!.Elements(ns + "Field") .Any(field => field.Attribute("Value") != null)) ]; Assert.That(enums, Has.Length.EqualTo(4)); foreach (XElement definition in enums) { uint identifier = uint.Parse( ((string)definition.Attribute("NodeId")!)[7..], CultureInfo.InvariantCulture); Assert.That(context.Factory.TryGetEnumeratedType( new ExpandedNodeId(identifier, GmsModel.NamespaceUri), out IEnumeratedType? type), Is.True); foreach (XElement field in definition.Element(ns + "Definition")!.Elements(ns + "Field")) { int value = (int)field.Attribute("Value")!; string name = (string)field.Attribute("Name")!; Assert.That(type!.TryGetSymbol(value, out string? symbol), Is.True); Assert.That(symbol, Is.EqualTo(name)); Assert.That(type.TryGetValue(name, out int actual), Is.True); Assert.That(actual, Is.EqualTo(value)); } Assert.Multiple(() => { Assert.That(type!.TryGetSymbol(-1, out _), Is.False); Assert.That(type.TryGetSymbol(int.MaxValue, out _), Is.False); Assert.That(type.TryGetValue("NotAReleasedValue", out _), Is.False); }); } } } }