15da8d4f0d
Task 6: CapturedTree/CapturedNode model + CapturingAddressSpaceBuilder — an in-memory
IAddressSpaceBuilder that records a driver's DiscoverAsync stream (folder nesting,
leaf FullName as node id, alarm mark, node-cap truncation, no-op alarm sink).
Task 7: CapturedTreeBrowseSession — serves the captured snapshot through IBrowseSession
(Root/Expand/Attributes from memory, truncation marker, shared-immutable-tree dispose).
13 unit tests green.
71 lines
2.7 KiB
C#
71 lines
2.7 KiB
C#
using Shouldly;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Commons.Tests.Browsing;
|
|
|
|
public class CapturingAddressSpaceBuilderTests
|
|
{
|
|
private static DriverAttributeInfo Attr(string fullName, bool isAlarm = false) =>
|
|
new(fullName, DriverDataType.Float64, false, null, SecurityClassification.Operate, false, IsAlarm: isAlarm);
|
|
|
|
[Fact]
|
|
public void CapturesNestedFoldersAndLeaves()
|
|
{
|
|
var b = new CapturingAddressSpaceBuilder(nodeCap: 100);
|
|
var plc = b.Folder("Plc1", "PLC 1");
|
|
plc.Variable("Speed", "Speed", Attr("Program:Main.Speed"));
|
|
var udt = plc.Folder("Motor", "Motor");
|
|
udt.Variable("Amps", "Amps", Attr("Program:Main.Motor.Amps"));
|
|
|
|
var tree = b.Build();
|
|
tree.Truncated.ShouldBeFalse();
|
|
tree.Root.Children.Count.ShouldBe(1);
|
|
var plcNode = tree.Root.Children[0];
|
|
plcNode.Id.ShouldBe("/Plc1");
|
|
plcNode.IsLeaf.ShouldBeFalse();
|
|
plcNode.Children.Select(c => c.Id).ShouldBe(new[] { "Program:Main.Speed", "/Plc1/Motor" });
|
|
tree.ById["Program:Main.Motor.Amps"].Attribute!.FullName.ShouldBe("Program:Main.Motor.Amps");
|
|
}
|
|
|
|
[Fact]
|
|
public void LeafNodeIdIsTheDriverFullName()
|
|
{
|
|
var b = new CapturingAddressSpaceBuilder(100);
|
|
var handle = b.Variable("T", "T", Attr("Device.Tag1"));
|
|
handle.FullReference.ShouldBe("Device.Tag1");
|
|
b.Build().ById["Device.Tag1"].IsLeaf.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void NodeCapTruncatesInsteadOfGrowing()
|
|
{
|
|
var b = new CapturingAddressSpaceBuilder(nodeCap: 3);
|
|
for (var i = 0; i < 10; i++) b.Variable($"T{i}", $"T{i}", Attr($"Dev.T{i}"));
|
|
var tree = b.Build();
|
|
tree.Truncated.ShouldBeTrue();
|
|
tree.Count.ShouldBe(3);
|
|
}
|
|
|
|
[Fact]
|
|
public void MarkAsAlarmConditionRecordsAlarmAndReturnsNoopSink()
|
|
{
|
|
var b = new CapturingAddressSpaceBuilder(100);
|
|
var h = b.Variable("A", "A", Attr("Dev.Alarm1"));
|
|
var sink = h.MarkAsAlarmCondition(new AlarmConditionInfo("A", AlarmSeverity.High, null));
|
|
sink.ShouldNotBeNull();
|
|
Should.NotThrow(() => sink.OnTransition(null!)); // no-op — browse never delivers transitions
|
|
b.Build().ById["Dev.Alarm1"].IsAlarmMarked.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void AddPropertyAttachesToCurrentScope()
|
|
{
|
|
var b = new CapturingAddressSpaceBuilder(100);
|
|
var f = b.Folder("Dev", "Dev");
|
|
f.AddProperty("Model", DriverDataType.String, "1756-L83E");
|
|
b.Build().ById["/Dev"].Properties.ShouldContain(p => p.Name == "Model" && (string?)p.Value == "1756-L83E");
|
|
}
|
|
}
|