using Opc.Ua; using ZB.MOM.WW.LmxOpcUa.Client.Shared.Adapters; namespace ZB.MOM.WW.LmxOpcUa.Client.Shared.Tests.Fakes; internal sealed class FakeSessionAdapter : ISessionAdapter { private readonly List _createdSubscriptions = []; private Action? _keepAliveCallback; public bool Closed { get; private set; } public bool Disposed { get; private set; } public int ReadCount { get; private set; } public int WriteCount { get; private set; } public int BrowseCount { get; private set; } public int BrowseNextCount { get; private set; } public int HasChildrenCount { get; private set; } public int HistoryReadRawCount { get; private set; } public int HistoryReadAggregateCount { get; private set; } // Configurable responses public DataValue? ReadResponse { get; set; } public Func? ReadResponseFunc { get; set; } public StatusCode WriteResponse { get; set; } = StatusCodes.Good; public bool ThrowOnRead { get; set; } public bool ThrowOnWrite { get; set; } public bool ThrowOnBrowse { get; set; } public ReferenceDescriptionCollection BrowseResponse { get; set; } = []; public byte[]? BrowseContinuationPoint { get; set; } public ReferenceDescriptionCollection BrowseNextResponse { get; set; } = []; public byte[]? BrowseNextContinuationPoint { get; set; } public bool HasChildrenResponse { get; set; } = false; public List HistoryReadRawResponse { get; set; } = []; public List HistoryReadAggregateResponse { get; set; } = []; public bool ThrowOnHistoryReadRaw { get; set; } public bool ThrowOnHistoryReadAggregate { get; set; } /// /// The next FakeSubscriptionAdapter to return from CreateSubscriptionAsync. /// If null, a new one is created automatically. /// public FakeSubscriptionAdapter? NextSubscription { get; set; } public IReadOnlyList CreatedSubscriptions => _createdSubscriptions; public bool Connected { get; set; } = true; public string SessionId { get; set; } = "ns=0;i=12345"; public string SessionName { get; set; } = "FakeSession"; public string EndpointUrl { get; set; } = "opc.tcp://localhost:4840"; public string ServerName { get; set; } = "FakeServer"; public string SecurityMode { get; set; } = "None"; public string SecurityPolicyUri { get; set; } = "http://opcfoundation.org/UA/SecurityPolicy#None"; public NamespaceTable NamespaceUris { get; set; } = new(); public void RegisterKeepAliveHandler(Action callback) { _keepAliveCallback = callback; } public Task ReadValueAsync(NodeId nodeId, CancellationToken ct) { ReadCount++; if (ThrowOnRead) throw new ServiceResultException(StatusCodes.BadNodeIdUnknown, "Node not found"); if (ReadResponseFunc != null) return Task.FromResult(ReadResponseFunc(nodeId)); return Task.FromResult(ReadResponse ?? new DataValue(new Variant(0), StatusCodes.Good)); } public Task WriteValueAsync(NodeId nodeId, DataValue value, CancellationToken ct) { WriteCount++; if (ThrowOnWrite) throw new ServiceResultException(StatusCodes.BadNodeIdUnknown, "Node not found"); return Task.FromResult(WriteResponse); } public Task<(byte[]? ContinuationPoint, ReferenceDescriptionCollection References)> BrowseAsync( NodeId nodeId, uint nodeClassMask, CancellationToken ct) { BrowseCount++; if (ThrowOnBrowse) throw new ServiceResultException(StatusCodes.BadNodeIdUnknown, "Node not found"); return Task.FromResult((BrowseContinuationPoint, BrowseResponse)); } public Task<(byte[]? ContinuationPoint, ReferenceDescriptionCollection References)> BrowseNextAsync( byte[] continuationPoint, CancellationToken ct) { BrowseNextCount++; return Task.FromResult((BrowseNextContinuationPoint, BrowseNextResponse)); } public Task HasChildrenAsync(NodeId nodeId, CancellationToken ct) { HasChildrenCount++; return Task.FromResult(HasChildrenResponse); } public Task> HistoryReadRawAsync( NodeId nodeId, DateTime startTime, DateTime endTime, int maxValues, CancellationToken ct) { HistoryReadRawCount++; if (ThrowOnHistoryReadRaw) throw new ServiceResultException(StatusCodes.BadHistoryOperationUnsupported, "History not supported"); return Task.FromResult>(HistoryReadRawResponse); } public Task> HistoryReadAggregateAsync( NodeId nodeId, DateTime startTime, DateTime endTime, NodeId aggregateId, double intervalMs, CancellationToken ct) { HistoryReadAggregateCount++; if (ThrowOnHistoryReadAggregate) throw new ServiceResultException(StatusCodes.BadHistoryOperationUnsupported, "History not supported"); return Task.FromResult>(HistoryReadAggregateResponse); } public Task CreateSubscriptionAsync(int publishingIntervalMs, CancellationToken ct) { var sub = NextSubscription ?? new FakeSubscriptionAdapter(); NextSubscription = null; _createdSubscriptions.Add(sub); return Task.FromResult(sub); } public Task CloseAsync(CancellationToken ct) { Closed = true; Connected = false; return Task.CompletedTask; } public void Dispose() { Disposed = true; Connected = false; } /// /// Simulates a keep-alive event. /// public void SimulateKeepAlive(bool isGood) { _keepAliveCallback?.Invoke(isGood); } }