using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Deployment; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Deployment; /// /// Pure unit tests for — node /// model derivation, branch dedupe, child sort, bad-quality roll-up, and the /// name-contains filter. No bUnit / DI required; the builder is a pure function. /// public class DebugTreeBuilderTests { private static AttributeValueChanged Attr(string name, object? value = null, string quality = "Good") => new("Inst", name, name, value, quality, DateTimeOffset.UtcNow); [Fact] public void RootLevelAttribute_BecomesSingleLeaf() { var tree = DebugTreeBuilder.BuildAttributeTree(new[] { Attr("Speed") }, null); var node = Assert.Single(tree); Assert.Equal("Speed", node.Key); Assert.Equal("Speed", node.Segment); Assert.False(node.HasChildren); Assert.NotNull(node.Attribute); Assert.Equal("Speed", node.Attribute!.AttributeName); } [Fact] public void TwoSiblingLeaves_ShareSingleBranch_SortedBySegment() { // Provided out of order to prove children are sorted ordinally. var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Temp"), Attr("Motor1.Speed") }, null); var branch = Assert.Single(tree); Assert.Equal("Motor1", branch.Key); Assert.Equal("Motor1", branch.Segment); Assert.True(branch.HasChildren); Assert.Null(branch.Attribute); Assert.Equal(2, branch.Children.Count); Assert.Equal("Motor1.Speed", branch.Children[0].Key); Assert.Equal("Speed", branch.Children[0].Segment); Assert.Equal("Motor1.Temp", branch.Children[1].Key); Assert.Equal("Temp", branch.Children[1].Segment); } [Fact] public void DeepPath_NestsBranchesByAccumulatedPrefix() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Compressor.Pump") }, null); var motor = Assert.Single(tree); Assert.Equal("Motor1", motor.Key); Assert.True(motor.HasChildren); var compressor = Assert.Single(motor.Children); Assert.Equal("Motor1.Compressor", compressor.Key); Assert.Equal("Compressor", compressor.Segment); Assert.True(compressor.HasChildren); Assert.Null(compressor.Attribute); var pump = Assert.Single(compressor.Children); Assert.Equal("Motor1.Compressor.Pump", pump.Key); Assert.Equal("Pump", pump.Segment); Assert.False(pump.HasChildren); Assert.NotNull(pump.Attribute); } [Fact] public void BranchDedupe_SharedPrefixAcrossDeepPaths() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Compressor.Pump"), Attr("Motor1.Compressor.Valve"), Attr("Motor1.Speed"), }, null); var motor = Assert.Single(tree); Assert.Equal("Motor1", motor.Key); // Two direct children: "Compressor" (branch) and "Speed" (leaf), sorted. Assert.Equal(2, motor.Children.Count); Assert.Equal("Motor1.Compressor", motor.Children[0].Key); Assert.Equal("Motor1.Speed", motor.Children[1].Key); var compressor = motor.Children[0]; Assert.Equal(2, compressor.Children.Count); Assert.Equal("Motor1.Compressor.Pump", compressor.Children[0].Key); Assert.Equal("Motor1.Compressor.Valve", compressor.Children[1].Key); } [Fact] public void RollUp_BadQualityDescendant_MarksAllAncestorBranches() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Compressor.Pump", quality: "Bad"), Attr("Motor1.Speed", quality: "Good"), }, null); var motor = Assert.Single(tree); Assert.True(motor.HasBadQuality); var compressor = motor.Children.Single(c => c.Key == "Motor1.Compressor"); Assert.True(compressor.HasBadQuality); } [Fact] public void RollUp_AllGood_LeavesBranchHasBadQualityFalse() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Speed"), Attr("Motor1.Temp") }, null); var motor = Assert.Single(tree); Assert.False(motor.HasBadQuality); } [Theory] [InlineData("Uncertain")] [InlineData("Bad")] public void RollUp_NonGoodQuality_CountsAsBad(string quality) { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Speed", quality: quality) }, null); Assert.True(Assert.Single(tree).HasBadQuality); } [Fact] public void Filter_KeepsOnlyMatchingLeaves_AndTheirAncestors() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Temp"), Attr("Motor1.Speed"), Attr("Motor2.Pressure"), }, "temp"); // Only Motor1.Temp matches → just the Motor1 branch with one Temp leaf. var motor = Assert.Single(tree); Assert.Equal("Motor1", motor.Key); var leaf = Assert.Single(motor.Children); Assert.Equal("Motor1.Temp", leaf.Key); } [Fact] public void Filter_IsCaseInsensitive() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Temp"), Attr("Motor1.Speed") }, "TEMP"); var motor = Assert.Single(tree); Assert.Equal("Motor1.Temp", Assert.Single(motor.Children).Key); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public void Filter_EmptyOrWhitespace_ReturnsFullForest(string? filter) { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Temp"), Attr("Motor2.Pressure") }, filter); Assert.Equal(2, tree.Count); Assert.Equal("Motor1", tree[0].Key); Assert.Equal("Motor2", tree[1].Key); } [Fact] public void Filter_NoMatches_ReturnsEmptyForest() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Motor1.Temp") }, "nonexistent"); Assert.Empty(tree); } [Fact] public void TopLevelForest_SortedBySegment() { var tree = DebugTreeBuilder.BuildAttributeTree( new[] { Attr("Zeta"), Attr("Alpha"), Attr("Motor1.Speed") }, null); Assert.Equal(3, tree.Count); Assert.Equal("Alpha", tree[0].Key); Assert.Equal("Motor1", tree[1].Key); Assert.Equal("Zeta", tree[2].Key); } [Fact] public void EmptyInput_ReturnsEmptyForest() { var tree = DebugTreeBuilder.BuildAttributeTree(Array.Empty(), null); Assert.Empty(tree); } }