using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy; namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.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 AbLegacyPollErrorHealthTests { /// A poll reader failure degrades health, preserving the last successful read timestamp. [Fact] public async Task PollReaderFailure_DegradesHealth_PreservesLastSuccessfulRead() { var opts = new AbLegacyDriverOptions { Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")], Probe = new AbLegacyProbeOptions { Enabled = false }, }; var drv = new AbLegacyDriver(opts, "ablegacy-1", new FakeAbLegacyTagFactory()); 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 opts = new AbLegacyDriverOptions { Devices = [new AbLegacyDeviceOptions("not-a-valid-address")], Probe = new AbLegacyProbeOptions { Enabled = false }, }; var drv = new AbLegacyDriver(opts, "ablegacy-1", new FakeAbLegacyTagFactory()); 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); } }