using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers; using ZB.MOM.WW.OtOpcUa.Driver.FOCAS; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Pickers; /// /// Gitea #540. The previous edition of this file asserted /// Build("axis", 5) == "axis:5" for four "parameter groups" that are not FOCAS /// addressing spaces — it pinned the exact string the driver could not parse, which is why /// a fully-green suite coexisted with a picker whose default value was unusable. Every case /// here therefore asserts the address ROUND-TRIPS through the driver's parser, not merely /// that it has the expected shape. /// public sealed class FocasAddressBuilderTests { [Theory] [InlineData(FocasManualAddressKind.Pmc, "R", 100, null, "R100")] [InlineData(FocasManualAddressKind.Pmc, "X", 0, 0, "X0.0")] [InlineData(FocasManualAddressKind.Pmc, "F", 20, 3, "F20.3")] [InlineData(FocasManualAddressKind.Pmc, "r", 100, null, "R100")] // letter is normalised [InlineData(FocasManualAddressKind.Parameter, null, 1020, null, "PARAM:1020")] [InlineData(FocasManualAddressKind.Parameter, null, 1815, 0, "PARAM:1815/0")] [InlineData(FocasManualAddressKind.Macro, null, 500, null, "MACRO:500")] [InlineData(FocasManualAddressKind.Macro, null, 500, 3, "MACRO:500")] // macros have no bit form public void Build_produces_an_address_the_driver_can_parse( FocasManualAddressKind kind, string? letter, int number, int? bit, string expected) { var built = FocasAddressBuilder.Build(kind, letter, number, bit); built.ShouldBe(expected); FocasAddress.TryParse(built).ShouldNotBeNull(); } [Theory] [InlineData(FocasManualAddressKind.Pmc, "Z", 10, null)] // not a PMC area letter [InlineData(FocasManualAddressKind.Pmc, "", 10, null)] // no area chosen [InlineData(FocasManualAddressKind.Pmc, "R", 10, 8)] // PMC bits are 0-7 [InlineData(FocasManualAddressKind.Pmc, "R", -1, null)] // negative byte [InlineData(FocasManualAddressKind.Parameter, null, 1815, 32)] // parameter bits are 0-31 [InlineData(FocasManualAddressKind.Parameter, null, -5, null)] public void Build_returns_empty_rather_than_an_unusable_address( FocasManualAddressKind kind, string? letter, int number, int? bit) => FocasAddressBuilder.Build(kind, letter, number, bit).ShouldBeEmpty(); /// /// The contract that matters, stated over the whole offered surface rather than a sample: /// nothing the picker can compose is unreadable. Every PMC area the picker lists, at a /// whole-word address and at each legal bit. /// [Fact] public void Every_offered_pmc_area_composes_a_parseable_address() { foreach (var letter in FocasAddressBuilder.PmcLetters) { foreach (int? bit in new int?[] { null, 0, 7 }) { var built = FocasAddressBuilder.Build(FocasManualAddressKind.Pmc, letter, 12, bit); built.ShouldNotBeEmpty($"PMC area '{letter}' bit '{bit}' composed nothing"); FocasAddress.TryParse(built).ShouldNotBeNull($"'{built}' does not parse"); } } } // ---- Gitea #647: ALARM: selectors ---- [Theory] [InlineData("*", null, null, "ALARM:*")] [InlineData("*", 401, null, "ALARM:*")] // numbers apply to a type scope only [InlineData("OTHER", null, null, "ALARM:OTHER")] [InlineData("Servo", null, null, "ALARM:Servo")] [InlineData("Servo", 401, null, "ALARM:Servo/401")] [InlineData("Servo", 401, 453, "ALARM:Servo/401-453")] [InlineData("Servo", 401, 401, "ALARM:Servo/401")] [InlineData("T9", 750, 758, "ALARM:T9/750-758")] public void BuildAlarm_produces_a_selector_the_driver_can_parse(string scope, int? lo, int? hi, string expected) { var built = FocasAddressBuilder.BuildAlarm(scope, lo, hi); built.ShouldBe(expected); FocasAddress.TryParse(built)!.Kind.ShouldBe(FocasAreaKind.Alarm); } [Theory] [InlineData("Servo", 453, 401)] // reversed range [InlineData("Bogus", null, null)] // not a type [InlineData("", null, null)] public void BuildAlarm_returns_empty_rather_than_an_unusable_selector(string scope, int? lo, int? hi) => FocasAddressBuilder.BuildAlarm(scope, lo, hi).ShouldBeEmpty(); [Fact] public void Every_offered_alarm_scope_composes_a_parseable_selector() { foreach (var scope in FocasAddressBuilder.AlarmScopes) FocasAddress.TryParse(FocasAddressBuilder.BuildAlarm(scope, 1, 2)).ShouldNotBeNull(scope); } }