using System.Runtime.CompilerServices; using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests; internal class FakeTwinCATClient : ITwinCATClient { public bool IsConnected { get; private set; } public int ConnectCount { get; private set; } public int DisposeCount { get; private set; } public bool ThrowOnConnect { get; set; } public bool ThrowOnRead { get; set; } public bool ThrowOnWrite { get; set; } public bool ThrowOnProbe { get; set; } public Exception? Exception { get; set; } public Dictionary Values { get; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary ReadStatuses { get; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary WriteStatuses { get; } = new(StringComparer.OrdinalIgnoreCase); public List<(string symbol, TwinCATDataType type, int? bit, object? value)> WriteLog { get; } = new(); public bool ProbeResult { get; set; } = true; public virtual Task ConnectAsync(TwinCATAmsAddress address, TimeSpan timeout, CancellationToken ct) { ConnectCount++; if (ThrowOnConnect) throw Exception ?? new InvalidOperationException(); IsConnected = true; return Task.CompletedTask; } public virtual Task<(object? value, uint status)> ReadValueAsync( string symbolPath, TwinCATDataType type, int? bitIndex, CancellationToken ct) { if (ThrowOnRead) throw Exception ?? new InvalidOperationException(); var status = ReadStatuses.TryGetValue(symbolPath, out var s) ? s : TwinCATStatusMapper.Good; var value = Values.TryGetValue(symbolPath, out var v) ? v : null; return Task.FromResult((value, status)); } public virtual Task WriteValueAsync( string symbolPath, TwinCATDataType type, int? bitIndex, object? value, CancellationToken ct) { if (ThrowOnWrite) throw Exception ?? new InvalidOperationException(); WriteLog.Add((symbolPath, type, bitIndex, value)); Values[symbolPath] = value; var status = WriteStatuses.TryGetValue(symbolPath, out var s) ? s : TwinCATStatusMapper.Good; return Task.FromResult(status); } public virtual Task ProbeAsync(CancellationToken ct) { if (ThrowOnProbe) return Task.FromResult(false); return Task.FromResult(ProbeResult); } public virtual void Dispose() { DisposeCount++; IsConnected = false; } // ---- notification fake ---- public List Notifications { get; } = new(); public bool ThrowOnAddNotification { get; set; } public virtual Task AddNotificationAsync( string symbolPath, TwinCATDataType type, int? bitIndex, TimeSpan cycleTime, Action onChange, CancellationToken cancellationToken) { if (ThrowOnAddNotification) throw Exception ?? new InvalidOperationException("fake AddNotification failure"); var reg = new FakeNotification(symbolPath, type, bitIndex, onChange, this); Notifications.Add(reg); return Task.FromResult(reg); } /// Fire a change event through the registered callback for . public void FireNotification(string symbolPath, object? value) { foreach (var n in Notifications) if (!n.Disposed && string.Equals(n.SymbolPath, symbolPath, StringComparison.OrdinalIgnoreCase)) n.OnChange(symbolPath, value); } // ---- symbol browser fake ---- public List BrowseResults { get; } = new(); public bool ThrowOnBrowse { get; set; } public virtual async IAsyncEnumerable BrowseSymbolsAsync( [EnumeratorCancellation] CancellationToken cancellationToken) { if (ThrowOnBrowse) throw Exception ?? new InvalidOperationException("fake browse failure"); await Task.CompletedTask; foreach (var sym in BrowseResults) { if (cancellationToken.IsCancellationRequested) yield break; yield return sym; } } public sealed class FakeNotification( string symbolPath, TwinCATDataType type, int? bitIndex, Action onChange, FakeTwinCATClient owner) : ITwinCATNotificationHandle { public string SymbolPath { get; } = symbolPath; public TwinCATDataType Type { get; } = type; public int? BitIndex { get; } = bitIndex; public Action OnChange { get; } = onChange; public bool Disposed { get; private set; } public void Dispose() { Disposed = true; owner.Notifications.Remove(this); } } } internal sealed class FakeTwinCATClientFactory : ITwinCATClientFactory { public List Clients { get; } = new(); public Func? Customise { get; set; } public ITwinCATClient Create() { var client = Customise?.Invoke() ?? new FakeTwinCATClient(); Clients.Add(client); return client; } }