using System.Collections.Concurrent;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests;
///
/// In-memory transport whose every send is answered by a test-supplied handler; falls back to the
/// register/coil bank fake () when the handler returns null.
/// Counts sends per unit and dispose calls; the connect can be blocked on a gate.
///
internal sealed class ScriptedTransport : IModbusTransport
{
private readonly ConcurrentDictionary _sendsByUnit = new();
private int _disposeCount;
/// The bank answering un-scripted sends.
public ModbusDriverTests.FakeTransport Bank { get; } = new();
/// Per-send handler; return null to fall through to .
public Func?>? Handler { get; set; }
/// When set, waits for it before completing.
public TaskCompletionSource? ConnectGate { get; set; }
/// Signalled when is entered.
public TaskCompletionSource ConnectEntered { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
public int DisposeCount => Volatile.Read(ref _disposeCount);
public int SendsTo(byte unit) => _sendsByUnit.TryGetValue(unit, out var n) ? n : 0;
public int TotalSends => _sendsByUnit.Values.Sum();
public async Task ConnectAsync(CancellationToken ct)
{
ConnectEntered.TrySetResult();
if (ConnectGate is { } gate) await gate.Task.WaitAsync(ct);
}
public Task SendAsync(byte unitId, byte[] pdu, CancellationToken ct)
{
_sendsByUnit.AddOrUpdate(unitId, 1, (_, n) => n + 1);
var scripted = Handler?.Invoke(unitId, pdu, ct);
return scripted ?? Bank.SendAsync(unitId, pdu, ct);
}
public ValueTask DisposeAsync()
{
Interlocked.Increment(ref _disposeCount);
return ValueTask.CompletedTask;
}
}