using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Resilience; namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Resilience; [Trait("Category", "Unit")] public sealed class InFlightCounterTests { [Fact] public void StartThenComplete_NetsToZero() { var tracker = new DriverResilienceStatusTracker(); tracker.RecordCallStart("drv", "host-a"); tracker.RecordCallComplete("drv", "host-a"); tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(0); } [Fact] public void NestedStarts_SumDepth() { var tracker = new DriverResilienceStatusTracker(); tracker.RecordCallStart("drv", "host-a"); tracker.RecordCallStart("drv", "host-a"); tracker.RecordCallStart("drv", "host-a"); tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(3); tracker.RecordCallComplete("drv", "host-a"); tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(2); } [Fact] public void CompleteBeforeStart_ClampedToZero() { var tracker = new DriverResilienceStatusTracker(); tracker.RecordCallComplete("drv", "host-a"); // A stray Complete without a matching Start shouldn't drive the counter negative. tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(0); } [Fact] public void DifferentHosts_TrackIndependently() { var tracker = new DriverResilienceStatusTracker(); tracker.RecordCallStart("drv", "host-a"); tracker.RecordCallStart("drv", "host-a"); tracker.RecordCallStart("drv", "host-b"); tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(2); tracker.TryGet("drv", "host-b")!.CurrentInFlight.ShouldBe(1); } [Fact] public void ConcurrentStarts_DoNotLose_Count() { var tracker = new DriverResilienceStatusTracker(); Parallel.For(0, 500, _ => tracker.RecordCallStart("drv", "host-a")); tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(500); } [Fact] public async Task CapabilityInvoker_IncrementsTracker_DuringExecution() { var tracker = new DriverResilienceStatusTracker(); var invoker = new CapabilityInvoker( new DriverResiliencePipelineBuilder(), "drv-live", () => new DriverResilienceOptions { Tier = DriverTier.A }, driverType: "Modbus", statusTracker: tracker); var observedMidCall = -1; await invoker.ExecuteAsync( DriverCapability.Read, "plc-1", async _ => { observedMidCall = tracker.TryGet("drv-live", "plc-1")?.CurrentInFlight ?? -1; await Task.Yield(); return 42; }, CancellationToken.None); observedMidCall.ShouldBe(1, "during call, in-flight == 1"); tracker.TryGet("drv-live", "plc-1")!.CurrentInFlight.ShouldBe(0, "post-call, counter decremented"); } [Fact] public async Task CapabilityInvoker_ExceptionPath_DecrementsCounter() { var tracker = new DriverResilienceStatusTracker(); var invoker = new CapabilityInvoker( new DriverResiliencePipelineBuilder(), "drv-live", () => new DriverResilienceOptions { Tier = DriverTier.A }, statusTracker: tracker); await Should.ThrowAsync(async () => await invoker.ExecuteAsync( DriverCapability.Write, "plc-1", _ => throw new InvalidOperationException("boom"), CancellationToken.None)); tracker.TryGet("drv-live", "plc-1")!.CurrentInFlight.ShouldBe(0, "finally-block must decrement even when call-site throws"); } [Fact] public async Task CapabilityInvoker_WithoutTracker_DoesNotThrow() { var invoker = new CapabilityInvoker( new DriverResiliencePipelineBuilder(), "drv-live", () => new DriverResilienceOptions { Tier = DriverTier.A }, statusTracker: null); var result = await invoker.ExecuteAsync( DriverCapability.Read, "host-1", _ => ValueTask.FromResult(7), CancellationToken.None); result.ShouldBe(7); } }