using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.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 /// . /// [Trait("Category", "Unit")] public sealed class TwinCATPollErrorHealthTests { private const string Host = "ads://5.23.91.23.1.1:851"; /// A poll reader failure degrades health, preserving the last successful read timestamp. [Fact] public async Task PollReaderFailure_DegradesHealth_PreservesLastSuccessfulRead() { var drv = new TwinCATDriver(new TwinCATDriverOptions { Devices = [new TwinCATDeviceOptions(Host)], Probe = new TwinCATProbeOptions { Enabled = false }, EnableControllerBrowse = false, }, "twincat-1", new FakeTwinCATClientFactory()); 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.ShouldNotBeNull(); after.LastError.ShouldContain("poll boom"); } /// A poll reader failure never downgrades a pre-existing Faulted state. [Fact] public async Task PollReaderFailure_NeverDowngradesFaulted() { var drv = new TwinCATDriver(new TwinCATDriverOptions { Devices = [new TwinCATDeviceOptions("not-a-valid-address")], Probe = new TwinCATProbeOptions { Enabled = false }, EnableControllerBrowse = false, }, "twincat-1", new FakeTwinCATClientFactory()); 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); } }