test(dv-3): add 4-level roll-up + deep-leaf filter tests; return AsReadOnly; add caller-contract remark

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<DebugTreeNode> reference is not a mutable list.

Fix 4 (Minor): Added <remarks> XML doc to BuildAttributeTree noting the caller-contract
that at most one AttributeValueChanged per AttributeName should be passed.

All 18 DebugTreeBuilder tests pass.
This commit is contained in:
Joseph Doherty
2026-06-17 15:09:01 -04:00
parent b5347faf44
commit 69b83379d5
2 changed files with 37 additions and 1 deletions
@@ -203,4 +203,33 @@ public class DebugTreeBuilderTests
var tree = DebugTreeBuilder.BuildAttributeTree(Array.Empty<AttributeValueChanged>(), 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);
}
}