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; } = [];
}