using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect.IntegrationTests; /// /// An that records the tree DiscoverAsync streams into /// it, so discovery against a live Agent can be asserted on shape (which leaf landed under which /// folder, with which driver-side metadata) rather than on a flat list. /// /// /// A near-twin of the MTConnect unit suite's CapturingBuilder, restated here rather than /// shared: that type is internal to a different test assembly, and the two production /// capturing builders (Commons.Browsing / Runtime.Drivers) would drag the server /// stack into a driver integration project that deliberately references only the driver. /// internal sealed class DiscoveryCapture : IAddressSpaceBuilder { private readonly State _state; private readonly string _path; /// Creates the root scope a driver's DiscoverAsync is handed. public DiscoveryCapture() { _state = new State(); _path = string.Empty; } private DiscoveryCapture(State state, string path) { _state = state; _path = path; } /// Every folder streamed, with the slash-joined path it landed at. public IReadOnlyList Folders => _state.Folders; /// Every variable streamed, with the folder path it landed under. public IReadOnlyList Variables => _state.Variables; /// public IAddressSpaceBuilder Folder(string browseName, string displayName) { var path = _path.Length == 0 ? browseName : $"{_path}/{browseName}"; _state.Folders.Add(new CapturedFolder(path, _path, browseName, displayName)); return new DiscoveryCapture(_state, path); } /// public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo) { var captured = new CapturedVariable(_path, browseName, displayName, attributeInfo); _state.Variables.Add(captured); return new Handle(captured); } /// public void AddProperty(string browseName, DriverDataType dataType, object? value) { // Recorded nowhere: the MTConnect driver streams no node properties, and a capture that // threw here would fail a driver that legitimately started to. } private sealed class State { public List Folders { get; } = []; public List Variables { get; } = []; } private sealed class Handle(CapturedVariable variable) : IVariableHandle { public string FullReference => variable.Attr.FullName; public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) { variable.AlarmConditions.Add(info); return new NullSink(); } private sealed class NullSink : IAlarmConditionSink { public void OnTransition(AlarmEventArgs args) { // No test here drives an alarm transition through discovery. } } } } /// One folder captured from a discovery stream. /// Slash-joined path of the folder itself. /// Slash-joined path of the scope it was added to (empty at the root). /// The browse name the driver supplied. /// The display name the driver supplied. internal sealed record CapturedFolder(string Path, string ParentPath, string BrowseName, string DisplayName); /// One variable captured from a discovery stream. /// Slash-joined path of the folder it landed under. /// The browse name the driver supplied. /// The display name the driver supplied. /// The driver-side attribute metadata the driver stamped on it. internal sealed record CapturedVariable( string ParentPath, string BrowseName, string DisplayName, DriverAttributeInfo Attr) { /// Alarm conditions the driver marked this variable with, if any. public List AlarmConditions { get; } = []; }