using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; [Trait("Category", "Unit")] public sealed class FocasHandleRecycleTests { [Fact] public async Task Recycle_loop_disposes_client_on_interval_reads_reopen_fresh_one() { var factory = new FakeFocasClientFactory(); var drv = new FocasDriver(new FocasDriverOptions { Devices = [new FocasDeviceOptions("focas://10.0.0.5:8193")], Tags = [new FocasTagDefinition("R", "focas://10.0.0.5:8193", "R100", FocasDataType.Byte)], Probe = new FocasProbeOptions { Enabled = false }, HandleRecycle = new FocasHandleRecycleOptions { Enabled = true, Interval = TimeSpan.FromMilliseconds(80), }, }, "drv-1", factory); await drv.InitializeAsync("{}", CancellationToken.None); // First read forces the initial connect. await drv.ReadAsync(["R"], CancellationToken.None); var initialClients = factory.Clients.Count; initialClients.ShouldBe(1); // Wait for a recycle tick, then read again — a new client must have been created. await WaitFor(() => factory.Clients[0].DisposeCount > 0, TimeSpan.FromSeconds(3)); await drv.ReadAsync(["R"], CancellationToken.None); factory.Clients.Count.ShouldBeGreaterThan(initialClients); await drv.ShutdownAsync(CancellationToken.None); } [Fact] public async Task Recycle_loop_stays_off_when_not_enabled() { var factory = new FakeFocasClientFactory(); var drv = new FocasDriver(new FocasDriverOptions { Devices = [new FocasDeviceOptions("focas://10.0.0.5:8193")], Tags = [new FocasTagDefinition("R", "focas://10.0.0.5:8193", "R100", FocasDataType.Byte)], Probe = new FocasProbeOptions { Enabled = false }, }, "drv-1", factory); await drv.InitializeAsync("{}", CancellationToken.None); await drv.ReadAsync(["R"], CancellationToken.None); await Task.Delay(150); // With recycle off the same client stays live — no Dispose during the window. factory.Clients.Count.ShouldBe(1); factory.Clients[0].DisposeCount.ShouldBe(0); await drv.ShutdownAsync(CancellationToken.None); } private static async Task WaitFor(Func pred, TimeSpan timeout) { var deadline = DateTime.UtcNow + timeout; while (DateTime.UtcNow < deadline) { if (pred()) return; await Task.Delay(20); } } }