using Opc.Ua.Client; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Client.Shared.Adapters; namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Adapters; /// /// A deleted subscription must also leave the SDK session's Subscriptions list, or every alarm re-target in the UI /// leaves a dead behind until the session is disposed. /// public class DefaultSubscriptionAdapterTests { /// DeleteAsync detaches the wrapped subscription exactly once, even when Dispose follows. [Fact] public async Task DeleteAsync_detaches_the_subscription_from_its_session_once() { var subscription = new Subscription(); var detached = new List(); var adapter = new DefaultSubscriptionAdapter(subscription, (s, _) => { lock (detached) detached.Add(s); return Task.CompletedTask; }); await adapter.DeleteAsync(TestContext.Current.CancellationToken); adapter.Dispose(); detached.ShouldHaveSingleItem().ShouldBeSameAs(subscription); } /// Dispose alone (no prior DeleteAsync) detaches too. [Fact] public void Dispose_detaches_the_subscription_from_its_session() { var detached = 0; var adapter = new DefaultSubscriptionAdapter(new Subscription(), (_, _) => { Interlocked.Increment(ref detached); return Task.CompletedTask; }); adapter.Dispose(); detached.ShouldBe(1); } /// A failing detach is logged, not thrown: teardown stays best effort. [Fact] public async Task A_failing_detach_does_not_throw_from_DeleteAsync() { var adapter = new DefaultSubscriptionAdapter(new Subscription(), (_, _) => throw new InvalidOperationException("boom")); await Should.NotThrowAsync(() => adapter.DeleteAsync(TestContext.Current.CancellationToken)); } }