From 69b83379d56e7e09ca90d1f63a0bd45dd76912e0 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 17 Jun 2026 15:09:01 -0400 Subject: [PATCH] test(dv-3): add 4-level roll-up + deep-leaf filter tests; return AsReadOnly; add caller-contract remark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 1 (Important): RollUp_FourLevelDeepBadQuality_ReachesRoot — proves bad quality at a 4-segment-deep leaf propagates HasBadQuality up every ancestor to the root. Fix 2 (Important): Filter_DeepLeafMatch_RetainsAllAncestorBranches — proves filtering on a terminal segment of a 3-level path retains all ancestor branches. Fix 3 (Minor): BuildAttributeTree now returns roots.AsReadOnly() so the returned IReadOnlyList reference is not a mutable list. Fix 4 (Minor): Added XML doc to BuildAttributeTree noting the caller-contract that at most one AttributeValueChanged per AttributeName should be passed. All 18 DebugTreeBuilder tests pass. --- .../Pages/Deployment/DebugTreeBuilder.cs | 9 +++++- .../Deployment/DebugTreeBuilderTests.cs | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugTreeBuilder.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugTreeBuilder.cs index e746d423..42dc7ec8 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugTreeBuilder.cs +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugTreeBuilder.cs @@ -21,6 +21,13 @@ public static class DebugTreeBuilder /// ). Null/empty/whitespace /// keeps everything; matching leaves carry along their ancestor branches. /// + /// + /// The caller is expected to pass at most one + /// per . The DebugView page + /// enforces this by keying a dictionary on the attribute name before calling this + /// method. Passing duplicate names would produce sibling leaves with identical + /// keys under the same parent branch. + /// public static IReadOnlyList BuildAttributeTree( IEnumerable attributes, string? filter) { @@ -71,7 +78,7 @@ public static class DebugTreeBuilder } SortAndRollUp(roots); - return roots; + return roots.AsReadOnly(); } /// diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugTreeBuilderTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugTreeBuilderTests.cs index 9ee4ce24..45aa2705 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugTreeBuilderTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugTreeBuilderTests.cs @@ -203,4 +203,33 @@ public class DebugTreeBuilderTests var tree = DebugTreeBuilder.BuildAttributeTree(Array.Empty(), null); Assert.Empty(tree); } + + [Fact] + public void RollUp_FourLevelDeepBadQuality_ReachesRoot() + { + var tree = DebugTreeBuilder.BuildAttributeTree( + new[] { Attr("A.B.C.D", quality: "Bad") }, null); + + var a = Assert.Single(tree); + Assert.True(a.HasBadQuality); + var b = Assert.Single(a.Children); + Assert.True(b.HasBadQuality); + var c = Assert.Single(b.Children); + Assert.True(c.HasBadQuality); + var d = Assert.Single(c.Children); + Assert.True(d.HasBadQuality); + } + + [Fact] + public void Filter_DeepLeafMatch_RetainsAllAncestorBranches() + { + var tree = DebugTreeBuilder.BuildAttributeTree( + new[] { Attr("Motor1.Compressor.Pump"), Attr("Motor1.Speed") }, "Pump"); + + var motor = Assert.Single(tree); + Assert.Equal("Motor1", motor.Key); + var compressor = Assert.Single(motor.Children); + Assert.Equal("Motor1.Compressor", compressor.Key); + Assert.Equal("Motor1.Compressor.Pump", Assert.Single(compressor.Children).Key); + } }