using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using ScadaLink.Commons.Interfaces.Protocol; using ScadaLink.Commons.Types.Enums; using ScadaLink.DataConnectionLayer.Adapters; namespace ScadaLink.DataConnectionLayer.Tests; /// /// WP-7: Tests for OPC UA adapter. /// public class OpcUaDataConnectionTests { private readonly IOpcUaClient _mockClient; private readonly IOpcUaClientFactory _mockFactory; private readonly OpcUaDataConnection _adapter; public OpcUaDataConnectionTests() { _mockClient = Substitute.For(); _mockFactory = Substitute.For(); _mockFactory.Create().Returns(_mockClient); _adapter = new OpcUaDataConnection(_mockFactory, NullLogger.Instance); } [Fact] public async Task Connect_SetsStatusToConnected() { _mockClient.IsConnected.Returns(true); await _adapter.ConnectAsync(new Dictionary { ["EndpointUrl"] = "opc.tcp://localhost:4840" }); Assert.Equal(ConnectionHealth.Connected, _adapter.Status); await _mockClient.Received(1).ConnectAsync("opc.tcp://localhost:4840", Arg.Any()); } [Fact] public async Task Disconnect_SetsStatusToDisconnected() { _mockClient.IsConnected.Returns(true); await _adapter.ConnectAsync(new Dictionary()); await _adapter.DisconnectAsync(); Assert.Equal(ConnectionHealth.Disconnected, _adapter.Status); } [Fact] public async Task Subscribe_DelegatesAndReturnsId() { _mockClient.IsConnected.Returns(true); _mockClient.CreateSubscriptionAsync(Arg.Any(), Arg.Any>(), Arg.Any()) .Returns("sub-001"); await _adapter.ConnectAsync(new Dictionary()); var subId = await _adapter.SubscribeAsync("ns=2;s=Tag1", (_, _) => { }); Assert.Equal("sub-001", subId); } [Fact] public async Task Write_Success_ReturnsGoodResult() { _mockClient.IsConnected.Returns(true); _mockClient.WriteValueAsync("ns=2;s=Tag1", 42, Arg.Any()) .Returns((uint)0); await _adapter.ConnectAsync(new Dictionary()); var result = await _adapter.WriteAsync("ns=2;s=Tag1", 42); Assert.True(result.Success); Assert.Null(result.ErrorMessage); } [Fact] public async Task Write_Failure_ReturnsError() { _mockClient.IsConnected.Returns(true); _mockClient.WriteValueAsync("ns=2;s=Tag1", 42, Arg.Any()) .Returns(0x80000000u); await _adapter.ConnectAsync(new Dictionary()); var result = await _adapter.WriteAsync("ns=2;s=Tag1", 42); Assert.False(result.Success); Assert.Contains("0x80000000", result.ErrorMessage); } [Fact] public async Task Read_BadStatus_ReturnsBadResult() { _mockClient.IsConnected.Returns(true); _mockClient.ReadValueAsync("ns=2;s=Tag1", Arg.Any()) .Returns((null, DateTime.UtcNow, 0x80000000u)); await _adapter.ConnectAsync(new Dictionary()); var result = await _adapter.ReadAsync("ns=2;s=Tag1"); Assert.False(result.Success); } [Fact] public async Task Read_GoodStatus_ReturnsValue() { _mockClient.IsConnected.Returns(true); _mockClient.ReadValueAsync("ns=2;s=Tag1", Arg.Any()) .Returns((42.5, DateTime.UtcNow, 0u)); await _adapter.ConnectAsync(new Dictionary()); var result = await _adapter.ReadAsync("ns=2;s=Tag1"); Assert.True(result.Success); Assert.NotNull(result.Value); Assert.Equal(42.5, result.Value!.Value); Assert.Equal(QualityCode.Good, result.Value.Quality); } [Fact] public async Task ReadBatch_ReadsAllTags() { _mockClient.IsConnected.Returns(true); _mockClient.ReadValueAsync(Arg.Any(), Arg.Any()) .Returns((1.0, DateTime.UtcNow, 0u)); await _adapter.ConnectAsync(new Dictionary()); var results = await _adapter.ReadBatchAsync(["tag1", "tag2", "tag3"]); Assert.Equal(3, results.Count); Assert.All(results.Values, r => Assert.True(r.Success)); } [Fact] public async Task NotConnected_ThrowsOnOperations() { _mockClient.IsConnected.Returns(false); await Assert.ThrowsAsync(() => _adapter.ReadAsync("tag1")); } [Fact] public async Task DisposeAsync_CleansUp() { _mockClient.IsConnected.Returns(true); await _adapter.ConnectAsync(new Dictionary()); await _adapter.DisposeAsync(); Assert.Equal(ConnectionHealth.Disconnected, _adapter.Status); } }