using System.Net.Sockets; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests; /// /// 05/STAB-9 — the poll engine's onError sink must be wired to the driver health /// surface: a poll-loop reader failure degrades health while preserving /// , and must never downgrade a pre-existing /// (the S7 nuance ported fleet-wide). /// [Trait("Category", "Unit")] public sealed class ModbusPollErrorHealthTests { private sealed class NoopTransport : IModbusTransport { public Task ConnectAsync(CancellationToken ct) => Task.CompletedTask; public Task SendAsync(byte unitId, byte[] pdu, CancellationToken ct) => Task.FromResult(new byte[] { 0x03, 0x00 }); public ValueTask DisposeAsync() => ValueTask.CompletedTask; } /// A poll reader failure degrades health, preserving the last successful read timestamp. [Fact] public async Task PollReaderFailure_DegradesHealth_PreservesLastSuccessfulRead() { var opts = new ModbusDriverOptions { Host = "fake", Probe = new ModbusProbeOptions { Enabled = false } }; var drv = new ModbusDriver(opts, "modbus-1", _ => new NoopTransport()); await drv.InitializeAsync("{}", CancellationToken.None); var before = drv.GetHealth(); before.State.ShouldBe(DriverState.Healthy); before.LastSuccessfulRead.ShouldNotBeNull(); drv.HandlePollError(new InvalidOperationException("poll boom")); var after = drv.GetHealth(); after.State.ShouldBe(DriverState.Degraded); after.LastSuccessfulRead.ShouldBe(before.LastSuccessfulRead); after.LastError.ShouldContain("poll boom"); } /// A poll reader failure never downgrades a pre-existing Faulted state. [Fact] public async Task PollReaderFailure_NeverDowngradesFaulted() { var opts = new ModbusDriverOptions { Host = "fake", Probe = new ModbusProbeOptions { Enabled = false } }; var drv = new ModbusDriver(opts, "modbus-1", _ => throw new SocketException()); // Init faults (the throwing factory) → Faulted, then rethrows. await Should.ThrowAsync(() => drv.InitializeAsync("{}", CancellationToken.None)); drv.GetHealth().State.ShouldBe(DriverState.Faulted); drv.HandlePollError(new InvalidOperationException("poll boom")); drv.GetHealth().State.ShouldBe(DriverState.Faulted); } }