using Opc.Ua; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Client.CLI.Helpers; namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests; public class NodeIdParserTests { /// Verifies that Parse returns null for null input. [Fact] public void Parse_NullInput_ReturnsNull() { NodeIdParser.Parse(null).ShouldBeNull(); } /// Verifies that Parse returns null for empty string input. [Fact] public void Parse_EmptyString_ReturnsNull() { NodeIdParser.Parse("").ShouldBeNull(); } /// Verifies that Parse returns null for whitespace-only input. [Fact] public void Parse_WhitespaceOnly_ReturnsNull() { NodeIdParser.Parse(" ").ShouldBeNull(); } /// Verifies that Parse correctly parses standard NodeId format (ns=2;s=MyNode). [Fact] public void Parse_StandardStringFormat_ReturnsNodeId() { var result = NodeIdParser.Parse("ns=2;s=MyNode"); result.ShouldNotBeNull(); result.Value.NamespaceIndex.ShouldBe((ushort)2); result.Value.IdentifierAsString.ShouldBe("MyNode"); } /// Verifies that Parse correctly parses numeric NodeId format (i=85). [Fact] public void Parse_NumericFormat_ReturnsNodeId() { var result = NodeIdParser.Parse("i=85"); result.ShouldNotBeNull(); result.Value.IdType.ShouldBe(IdType.Numeric); } /// Verifies that Parse treats bare numeric input as namespace 0 numeric NodeId. [Fact] public void Parse_BareNumeric_ReturnsNamespace0NumericNodeId() { var result = NodeIdParser.Parse("85"); result.ShouldNotBeNull(); result.Value.NamespaceIndex.ShouldBe((ushort)0); result.Value.TryGetValue(out uint identifier).ShouldBeTrue(); identifier.ShouldBe(85u); } /// Verifies that Parse trims whitespace padding from input. [Fact] public void Parse_WithWhitespacePadding_Trims() { var result = NodeIdParser.Parse(" ns=2;s=MyNode "); result.ShouldNotBeNull(); result.Value.IdentifierAsString.ShouldBe("MyNode"); } /// Verifies that Parse throws FormatException for invalid input. [Fact] public void Parse_InvalidFormat_ThrowsFormatException() { Should.Throw(() => NodeIdParser.Parse("not-a-node-id")); } /// Verifies that ParseRequired throws ArgumentException for null input. [Fact] public void ParseRequired_NullInput_ThrowsArgumentException() { Should.Throw(() => NodeIdParser.ParseRequired(null)); } /// Verifies that ParseRequired throws ArgumentException for empty input. [Fact] public void ParseRequired_EmptyInput_ThrowsArgumentException() { Should.Throw(() => NodeIdParser.ParseRequired("")); } /// Verifies that ParseRequired returns a NodeId for valid input. [Fact] public void ParseRequired_ValidInput_ReturnsNodeId() { var result = NodeIdParser.ParseRequired("ns=2;s=TestNode"); result.IsNull.ShouldBeFalse(); result.IdentifierAsString.ShouldBe("TestNode"); } [Fact] public void Parse_OpaqueIdentifier_PreservesBytes() { var node = NodeIdParser.ParseRequired("ns=3;b=AQID"); node.IdType.ShouldBe(IdType.Opaque); node.NamespaceIndex.ShouldBe((ushort)3); node.TryGetValue(out ByteString bytes).ShouldBeTrue(); bytes.ToArray().ShouldBe(new byte[] { 1, 2, 3 }); } [Fact] public void Parse_StringIdentifierContainingEncodedText_StaysLiteral() { var node = NodeIdParser.ParseRequired("ns=4;s=ns=2;b=AQID"); node.IdType.ShouldBe(IdType.String); node.NamespaceIndex.ShouldBe((ushort)4); node.TryGetValue(out string? identifier).ShouldBeTrue(); identifier.ShouldBe("ns=2;b=AQID"); } [Theory] [InlineData("ns=bad;i=1")] [InlineData("ns=2;b=invalid!")] [InlineData("ns=2;i=invalid")] public void Parse_InvalidEncodedIdentifier_IsRejected(string input) { Should.Throw(() => NodeIdParser.ParseRequired(input)); } }