using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.FOCAS; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; /// /// The FIXED: address form added by Gitea #540, and the guarantee that adding it did not /// disturb the three forms that were already there. /// [Trait("Category", "Unit")] public sealed class FocasAddressFixedFormTests { [Theory] [InlineData("FIXED:Identity/SeriesNumber", "Identity/SeriesNumber")] [InlineData("FIXED:Axes/X/AbsolutePosition", "Axes/X/AbsolutePosition")] [InlineData("FIXED:Timers/PowerOnSeconds", "Timers/PowerOnSeconds")] [InlineData("FIXED:Spindle/S1/Load", "Spindle/S1/Load")] [InlineData("fixed:Program/Name", "Program/Name")] // prefix is case-insensitive [InlineData(" FIXED:Program/Name ", "Program/Name")] // outer whitespace tolerated public void Parses_a_fixed_tree_path(string input, string expectedPath) { var parsed = FocasAddress.TryParse(input); parsed.ShouldNotBeNull(); parsed.Kind.ShouldBe(FocasAreaKind.Fixed); parsed.FixedPath.ShouldBe(expectedPath); parsed.Canonical.ShouldBe("FIXED:" + expectedPath); } [Theory] [InlineData("FIXED:")] // no path [InlineData("FIXED: ")] [InlineData("FIXED:/Identity/Series")] // leading separator [InlineData("FIXED:Identity/Series/")] // trailing separator [InlineData("FIXED:Identity//Series")] // empty segment [InlineData("FIXED:Identity/ /Series")] // blank segment [InlineData("FIXED:MACRO:500")] // a colon would make the grammar ambiguous public void Rejects_a_path_that_is_not_addressable(string input) => FocasAddress.TryParse(input).ShouldBeNull(); /// /// The exact strings the defect produced. Each names a real fixed-tree field but carries the /// device host, which is the tag's Device row's job — so they must still be rejected rather /// than quietly accepted with the host treated as a path segment. /// [Theory] [InlineData("10.201.31.5:8193/Identity/SeriesNumber")] [InlineData("focas://10.201.31.5:8193/Axes/X/AbsolutePosition")] [InlineData("axis:5")] // the manual picker's old default public void Still_rejects_what_the_defect_authored(string input) => FocasAddress.TryParse(input).ShouldBeNull(); /// /// F is a valid PMC area letter, so the FIXED: prefix has to be tested before /// the PMC branch. Both forms must survive that ordering. /// [Fact] public void The_fixed_prefix_does_not_shadow_the_pmc_F_area() { FocasAddress.TryParse("F20.3")!.Kind.ShouldBe(FocasAreaKind.Pmc); FocasAddress.TryParse("F20.3")!.PmcLetter.ShouldBe("F"); FocasAddress.TryParse("FIXED:Program/Name")!.Kind.ShouldBe(FocasAreaKind.Fixed); } [Theory] [InlineData("R100")] [InlineData("X0.0")] [InlineData("PARAM:1020")] [InlineData("PARAM:1815/0")] [InlineData("MACRO:500")] public void The_pre_existing_forms_still_round_trip(string input) { var parsed = FocasAddress.TryParse(input); parsed.ShouldNotBeNull(); parsed.Kind.ShouldNotBe(FocasAreaKind.Fixed); parsed.Canonical.ShouldBe(input); } /// The one place the FIXED: spelling is built, so emitter and parser cannot drift. [Fact] public void ForFixedPath_produces_what_TryParse_accepts() { var built = FocasAddress.ForFixedPath("Identity/SeriesNumber"); built.ShouldBe("FIXED:Identity/SeriesNumber"); FocasAddress.TryParse(built)!.FixedPath.ShouldBe("Identity/SeriesNumber"); } }