using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.FOCAS; using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.Commands; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli.Tests; /// /// Covers across every FOCAS atomic type. /// Driver.FOCAS.Cli-001: malformed numeric input must surface as a friendly /// , not a raw /// / stack trace. /// [Trait("Category", "Unit")] public sealed class WriteCommandParseValueTests { [Theory] [InlineData("true", true)] [InlineData("0", false)] [InlineData("yes", true)] [InlineData("OFF", false)] public void ParseValue_Bit_accepts_common_boolean_aliases(string raw, bool expected) { WriteCommand.ParseValue(raw, FocasDataType.Bit).ShouldBe(expected); } [Fact] public void ParseValue_Bit_rejects_garbage_as_CommandException() { Should.Throw( () => WriteCommand.ParseValue("maybe", FocasDataType.Bit)); } [Fact] public void ParseValue_Byte_signed_range() { // FocasDataType.Byte is signed (PMC byte read returns int8). WriteCommand.ParseValue("-128", FocasDataType.Byte).ShouldBe((sbyte)-128); WriteCommand.ParseValue("127", FocasDataType.Byte).ShouldBe((sbyte)127); } [Fact] public void ParseValue_Int16_signed_range() { WriteCommand.ParseValue("-32768", FocasDataType.Int16).ShouldBe(short.MinValue); WriteCommand.ParseValue("32767", FocasDataType.Int16).ShouldBe(short.MaxValue); } [Fact] public void ParseValue_Int32_parses_negative() { WriteCommand.ParseValue("-2147483648", FocasDataType.Int32).ShouldBe(int.MinValue); } [Fact] public void ParseValue_Float32_invariant_culture() { WriteCommand.ParseValue("3.14", FocasDataType.Float32).ShouldBe(3.14f); } [Fact] public void ParseValue_Float64_higher_precision() { WriteCommand.ParseValue("2.718281828", FocasDataType.Float64).ShouldBeOfType(); } [Fact] public void ParseValue_String_passthrough() { WriteCommand.ParseValue("hello fanuc", FocasDataType.String).ShouldBe("hello fanuc"); } // Driver.FOCAS.Cli-001: malformed input must produce a CommandException (a clean // one-line CliFx error), NOT a raw FormatException stack trace. Previously the raw // BCL parser exceptions leaked, contradicting how the Bit path already handled bad // boolean input. [Theory] [InlineData("xyz", FocasDataType.Byte)] [InlineData("xyz", FocasDataType.Int16)] [InlineData("xyz", FocasDataType.Int32)] [InlineData("not-a-number", FocasDataType.Float32)] [InlineData("also-bad", FocasDataType.Float64)] public void ParseValue_non_numeric_for_numeric_types_throws_CommandException( string raw, FocasDataType type) { Should.Throw( () => WriteCommand.ParseValue(raw, type)); } // OverflowException from out-of-range input must also surface as CommandException. [Theory] [InlineData("128", FocasDataType.Byte)] // sbyte max + 1 [InlineData("-129", FocasDataType.Byte)] // sbyte min - 1 [InlineData("32768", FocasDataType.Int16)] // short max + 1 [InlineData("9999999999", FocasDataType.Int32)] // > int max public void ParseValue_overflow_for_numeric_types_throws_CommandException( string raw, FocasDataType type) { Should.Throw( () => WriteCommand.ParseValue(raw, type)); } [Fact] public void ParseValue_CommandException_message_names_the_type_and_value() { var ex = Should.Throw( () => WriteCommand.ParseValue("xyz", FocasDataType.Int16)); ex.Message.ShouldContain("xyz"); ex.Message.ShouldContain("Int16"); } [Theory] [InlineData("R100", FocasDataType.Int16, "R100:Int16")] [InlineData("X0.0", FocasDataType.Bit, "X0.0:Bit")] [InlineData("PARAM:1815/0", FocasDataType.Int32, "PARAM:1815/0:Int32")] [InlineData("MACRO:500", FocasDataType.Float64, "MACRO:500:Float64")] public void SynthesiseTagName_preserves_FOCAS_address_verbatim( string address, FocasDataType type, string expected) { ReadCommand.SynthesiseTagName(address, type).ShouldBe(expected); } }