using System; using System.Threading.Tasks; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Host.Utilities; namespace ZB.MOM.WW.OtOpcUa.Tests.Utilities { /// /// Tests for the bounded sync-over-async wrapper introduced by stability review 2026-04-13 /// Finding 3. The wrapper is a backstop applied at every LmxNodeManager sync-over-async site /// (Read, Write, HistoryRead*, BuildAddressSpace probe sync). /// public class SyncOverAsyncTests { [Fact] public void WaitSync_CompletedTask_ReturnsResult() { var task = Task.FromResult(42); SyncOverAsync.WaitSync(task, TimeSpan.FromSeconds(1), "test").ShouldBe(42); } [Fact] public void WaitSync_CompletedNonGenericTask_Returns() { var task = Task.CompletedTask; Should.NotThrow(() => SyncOverAsync.WaitSync(task, TimeSpan.FromSeconds(1), "test")); } [Fact] public void WaitSync_NeverCompletingTask_ThrowsTimeoutException() { var tcs = new TaskCompletionSource(); var ex = Should.Throw(() => SyncOverAsync.WaitSync(tcs.Task, TimeSpan.FromMilliseconds(100), "op")); ex.Message.ShouldContain("op"); } [Fact] public void WaitSync_NeverCompletingNonGenericTask_ThrowsTimeoutException() { var tcs = new TaskCompletionSource(); Should.Throw(() => SyncOverAsync.WaitSync((Task)tcs.Task, TimeSpan.FromMilliseconds(100), "op")); } [Fact] public void WaitSync_FaultedNonGenericTask_UnwrapsInnerException() { var task = Task.FromException(new InvalidOperationException("boom")); Should.Throw(() => SyncOverAsync.WaitSync(task, TimeSpan.FromSeconds(1), "op")); } [Fact] public void WaitSync_FaultedGenericTask_UnwrapsInnerException() { var task = Task.FromException(new InvalidOperationException("boom")); Should.Throw(() => SyncOverAsync.WaitSync(task, TimeSpan.FromSeconds(1), "op")); } [Fact] public void WaitSync_NullTask_ThrowsArgumentNullException() { Should.Throw(() => SyncOverAsync.WaitSync((Task)null!, TimeSpan.FromSeconds(1), "op")); Should.Throw(() => SyncOverAsync.WaitSync((Task)null!, TimeSpan.FromSeconds(1), "op")); } } }