using System; using System.Threading; using System.Threading.Tasks; using Shouldly; using Xunit; using ZB.MOM.WW.LmxOpcUa.Host.Configuration; using ZB.MOM.WW.LmxOpcUa.Host.Domain; using ZB.MOM.WW.LmxOpcUa.Host.Metrics; using ZB.MOM.WW.LmxOpcUa.Host.MxAccess; using ZB.MOM.WW.LmxOpcUa.Tests.Helpers; namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess { public class MxAccessClientReadWriteTests : IDisposable { private readonly StaComThread _staThread; private readonly FakeMxProxy _proxy; private readonly PerformanceMetrics _metrics; private readonly MxAccessClient _client; public MxAccessClientReadWriteTests() { _staThread = new StaComThread(); _staThread.Start(); _proxy = new FakeMxProxy(); _metrics = new PerformanceMetrics(); var config = new MxAccessConfiguration { ReadTimeoutSeconds = 2, WriteTimeoutSeconds = 2 }; _client = new MxAccessClient(_staThread, _proxy, config, _metrics); } public void Dispose() { _client.Dispose(); _staThread.Dispose(); _metrics.Dispose(); } [Fact] public async Task Read_NotConnected_ReturnsBad() { var result = await _client.ReadAsync("Tag.Attr"); result.Quality.ShouldBe(Quality.BadNotConnected); } [Fact] public async Task Read_ReturnsValueOnDataChange() { await _client.ConnectAsync(); // Start read in background var readTask = _client.ReadAsync("TestTag.Attr"); // Give it a moment to set up subscription, then simulate data change await Task.Delay(50); _proxy.SimulateDataChangeByAddress("TestTag.Attr", 42, 192); var result = await readTask; result.Value.ShouldBe(42); result.Quality.ShouldBe(Quality.Good); } [Fact] public async Task Read_Timeout_ReturnsBadCommFailure() { await _client.ConnectAsync(); // No data change simulated, so it will timeout var result = await _client.ReadAsync("TestTag.Attr"); result.Quality.ShouldBe(Quality.BadCommFailure); } [Fact] public async Task Write_NotConnected_ReturnsFalse() { var result = await _client.WriteAsync("Tag.Attr", 42); result.ShouldBe(false); } [Fact] public async Task Write_Success_ReturnsTrue() { await _client.ConnectAsync(); _proxy.WriteCompleteStatus = 0; var result = await _client.WriteAsync("TestTag.Attr", 42); result.ShouldBe(true); _proxy.WrittenValues.ShouldContain(w => w.Address == "TestTag.Attr" && (int)w.Value == 42); } [Fact] public async Task Write_ErrorCode_ReturnsFalse() { await _client.ConnectAsync(); _proxy.WriteCompleteStatus = 1012; // Wrong data type var result = await _client.WriteAsync("TestTag.Attr", "bad_value"); result.ShouldBe(false); } [Fact] public async Task Read_RecordsMetrics() { await _client.ConnectAsync(); var readTask = _client.ReadAsync("TestTag.Attr"); await Task.Delay(50); _proxy.SimulateDataChangeByAddress("TestTag.Attr", 1, 192); await readTask; var stats = _metrics.GetStatistics(); stats.ShouldContainKey("Read"); stats["Read"].TotalCount.ShouldBe(1); } [Fact] public async Task Write_RecordsMetrics() { await _client.ConnectAsync(); await _client.WriteAsync("TestTag.Attr", 42); var stats = _metrics.GetStatistics(); stats.ShouldContainKey("Write"); stats["Write"].TotalCount.ShouldBe(1); } } }