using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests; /// /// Unit-level coverage for the DriverHealth.Diagnostics dictionary the S7 driver /// exposes through . Wire-level reads of /// S7.NegotiatedPduSize against a real handshake live in the Snap7 integration /// suite (where the simulator negotiates a fixed 240-byte PDU); this suite covers the /// before-connect / after-shutdown contract on the same key without booting a fixture. /// [Trait("Category", "Unit")] public sealed class S7DriverDiagnosticsTests { [Fact] public void NegotiatedPduSize_diagnostics_key_starts_at_zero_before_initialize() { // Before InitializeAsync the driver hasn't talked to a PLC, so the negotiated PDU // is unknown. We surface 0 (rather than omitting the key) so the Admin UI driver- // diagnostics panel can render a stable row even pre-connect — operators see "0" // and immediately know the driver hasn't completed a handshake yet. using var drv = new S7Driver(new S7DriverOptions { Host = "192.0.2.1" }, "s7-pre-init"); var health = drv.GetHealth(); // Pre-init the driver's state is Unknown and Diagnostics is null (the empty dict); // the field-backed counter is what we directly assert here. drv.NegotiatedPduSize.ShouldBe(0); // The DriverHealth surface should also be safe to consume. health.DiagnosticsOrEmpty.ContainsKey("S7.NegotiatedPduSize").ShouldBeFalse( "pre-init Diagnostics is null and DiagnosticsOrEmpty returns the empty dict"); } [Fact] public async Task NegotiatedPduSize_resets_to_zero_after_shutdown() { // Even when InitializeAsync fails (unreachable host), the field must remain at 0 // so a subsequent ShutdownAsync doesn't carry a stale value forward into the next // re-init attempt. RFC 5737 documentation IP guarantees a fast TCP timeout. var opts = new S7DriverOptions { Host = "192.0.2.1", Timeout = TimeSpan.FromMilliseconds(250) }; using var drv = new S7Driver(opts, "s7-shutdown"); await Should.ThrowAsync(async () => await drv.InitializeAsync("{}", TestContext.Current.CancellationToken)); // The Faulted path in InitializeAsync's catch block doesn't touch _negotiatedPduSize // (we never reached the post-OpenAsync capture) so it should still be 0. drv.NegotiatedPduSize.ShouldBe(0); await drv.ShutdownAsync(TestContext.Current.CancellationToken); drv.NegotiatedPduSize.ShouldBe(0, "ShutdownAsync must zero the negotiated PDU snapshot"); } }