using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.Types; namespace ZB.MOM.WW.OtOpcUa.Commons.Tests; /// /// The v3 identity authority (): segment validation, slash-joined build, /// combine/split/leaf/parent, and the case-sensitive ordinal comparer. Every identity seam in v3 /// keys on the strings this helper produces, so the separator + segment charset contract is pinned /// here in exactly one place. /// public sealed class RawPathsTests { // ---- constants ---- [Fact] public void Separator_is_forward_slash() { RawPaths.Separator.ShouldBe('/'); RawPaths.SeparatorString.ShouldBe("/"); } [Fact] public void Comparer_is_ordinal_case_sensitive() { RawPaths.Comparer.ShouldBe(StringComparer.Ordinal); RawPaths.Comparer.Equals("Line/Tag", "line/tag").ShouldBeFalse(); RawPaths.Comparer.Equals("Line/Tag", "Line/Tag").ShouldBeTrue(); } // ---- ValidateSegment / IsValidSegment ---- [Theory] [InlineData("Modbus")] [InlineData("Device_001")] [InlineData("Tag.Name")] // dots are legal inside a segment [InlineData("a")] [InlineData("Has Space")] // interior whitespace is legal; only leading/trailing is not public void ValidateSegment_accepts_legal_segments(string segment) { RawPaths.ValidateSegment(segment).ShouldBeNull(); RawPaths.IsValidSegment(segment).ShouldBeTrue(); } [Theory] [InlineData(null)] [InlineData("")] public void ValidateSegment_rejects_empty(string? segment) { RawPaths.ValidateSegment(segment).ShouldNotBeNull(); RawPaths.IsValidSegment(segment).ShouldBeFalse(); } [Theory] [InlineData("a/b")] [InlineData("/leading")] [InlineData("trailing/")] public void ValidateSegment_rejects_separator(string segment) { RawPaths.ValidateSegment(segment).ShouldNotBeNull(); RawPaths.ValidateSegment(segment)!.ShouldContain("/"); RawPaths.IsValidSegment(segment).ShouldBeFalse(); } [Theory] [InlineData(" leading")] [InlineData("trailing ")] [InlineData("\ttab")] [InlineData("nl\n")] public void ValidateSegment_rejects_edge_whitespace(string segment) { RawPaths.ValidateSegment(segment).ShouldNotBeNull(); RawPaths.IsValidSegment(segment).ShouldBeFalse(); } // ---- Build ---- [Fact] public void Build_joins_multiple_segments_with_separator() { RawPaths.Build("Folder", "Modbus", "Dev", "Group", "Tag") .ShouldBe("Folder/Modbus/Dev/Group/Tag"); } [Fact] public void Build_single_segment_is_the_segment() { RawPaths.Build("Solo").ShouldBe("Solo"); } [Fact] public void Build_enumerable_overload_joins() { RawPaths.Build(new List { "A", "B" }).ShouldBe("A/B"); } [Fact] public void Build_empty_list_throws() { Should.Throw(() => RawPaths.Build(Array.Empty())); } [Fact] public void Build_null_throws() { Should.Throw(() => RawPaths.Build((IEnumerable)null!)); } [Fact] public void Build_invalid_segment_throws_naming_the_index() { var ex = Should.Throw(() => RawPaths.Build("ok", "bad/seg", "also-ok")); ex.Message.ShouldContain("index 1"); } // ---- Combine ---- [Fact] public void Combine_blank_parent_yields_child_alone() { RawPaths.Combine(null, "Root").ShouldBe("Root"); RawPaths.Combine("", "Root").ShouldBe("Root"); } [Fact] public void Combine_appends_child_under_parent() { RawPaths.Combine("A/B", "C").ShouldBe("A/B/C"); } [Fact] public void Combine_invalid_child_throws() { Should.Throw(() => RawPaths.Combine("A/B", "c/d")); Should.Throw(() => RawPaths.Combine("A/B", "")); } // ---- Split ---- [Fact] public void Split_returns_ordered_segments() { RawPaths.Split("A/B/C").ShouldBe(new[] { "A", "B", "C" }); } [Fact] public void Split_single_segment_is_one_element() { RawPaths.Split("Solo").ShouldBe(new[] { "Solo" }); } // ---- Leaf ---- [Fact] public void Leaf_of_multi_segment_is_last() { RawPaths.Leaf("A/B/C").ShouldBe("C"); } [Fact] public void Leaf_of_single_segment_is_itself() { RawPaths.Leaf("Solo").ShouldBe("Solo"); } // ---- TryParent ---- [Fact] public void TryParent_of_multi_segment_returns_parent() { RawPaths.TryParent("A/B/C", out var parent).ShouldBeTrue(); parent.ShouldBe("A/B"); } [Fact] public void TryParent_of_two_segment_returns_root() { RawPaths.TryParent("A/B", out var parent).ShouldBeTrue(); parent.ShouldBe("A"); } [Fact] public void TryParent_of_single_segment_returns_false() { RawPaths.TryParent("Solo", out var parent).ShouldBeFalse(); parent.ShouldBe(string.Empty); } // ---- round trips ---- [Fact] public void Build_then_Split_round_trips() { var segs = new[] { "Folder", "Sub", "Modbus", "Dev", "Tag" }; RawPaths.Split(RawPaths.Build(segs)).ShouldBe(segs); } [Fact] public void Combine_then_Leaf_and_Parent_are_consistent() { var path = RawPaths.Combine("A/B", "C"); RawPaths.Leaf(path).ShouldBe("C"); RawPaths.TryParent(path, out var parent).ShouldBeTrue(); parent.ShouldBe("A/B"); } }