cc017aabfc
Pure path-split composition forest from streamed AttributeValueChanged: branch dedupe by accumulated prefix, ordinal child sort, post-order bad-quality roll-up, case-insensitive name-contains filter (keeps ancestors). BuildAlarmTree left as a NotImplementedException stub for DV-4. 16 unit tests cover structure + roll-up + filter.
207 lines
6.8 KiB
C#
207 lines
6.8 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Pure unit tests for <see cref="DebugTreeBuilder.BuildAttributeTree"/> — 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.
|
|
/// </summary>
|
|
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<AttributeValueChanged>(), null);
|
|
Assert.Empty(tree);
|
|
}
|
|
}
|