using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Tests; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests; [Trait("Category", "Unit")] public sealed class FixtureCleanupTests { [Fact] public async Task Successful_body_runs_one_awaited_cleanup() { var calls = 0; var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var run = FixtureCleanup.RunAsync(() => Task.CompletedTask, async () => { calls++; await release.Task; }, _ => throw new Exception("unexpected report")); run.IsCompleted.ShouldBeFalse(); release.SetResult(); await run; calls.ShouldBe(1); } [Fact] public async Task Body_fault_remains_same_exception_and_original_throwing_stack() { var primary = new InvalidOperationException("body"); var cleanupCalls = 0; var actual = await Should.ThrowAsync(() => FixtureCleanup.RunAsync( () => ThrowFromBody(primary), () => { cleanupCalls++; return Task.CompletedTask; }, _ => throw new Exception("unexpected report"))); actual.ShouldBeSameAs(primary); actual.StackTrace.ShouldNotBeNull().ShouldContain(nameof(ThrowFromBody)); cleanupCalls.ShouldBe(1); } [Fact] public async Task Cleanup_only_fault_is_thrown_exactly() { var cleanup = new ApplicationException("cleanup"); var actual = await Should.ThrowAsync(() => FixtureCleanup.RunAsync( () => Task.CompletedTask, () => Task.FromException(cleanup), _ => throw new Exception("unexpected report"))); actual.ShouldBeSameAs(cleanup); } [Fact] public async Task Both_faults_preserve_primary_and_record_secondary_once() { var primary = new InvalidOperationException("body"); var cleanup = new ApplicationException("cleanup"); var reported = new List(); var actual = await Should.ThrowAsync(() => FixtureCleanup.RunAsync( () => ThrowFromBody(primary), () => Task.FromException(cleanup), reported.Add)); actual.ShouldBeSameAs(primary); actual.StackTrace.ShouldNotBeNull().ShouldContain(nameof(ThrowFromBody)); actual.Data[FixtureCleanup.SecondaryExceptionKey].ShouldBeSameAs(cleanup); reported.Count.ShouldBe(1); reported[0].ShouldBeSameAs(cleanup); } [Fact] public async Task Reporting_fault_does_not_replace_primary() { var primary = new InvalidOperationException("body"); var cleanup = new ApplicationException("cleanup"); var reportCalls = 0; var actual = await Should.ThrowAsync(() => FixtureCleanup.RunAsync( () => ThrowFromBody(primary), () => Task.FromException(cleanup), _ => { reportCalls++; throw new Exception("report"); })); actual.ShouldBeSameAs(primary); actual.Data[FixtureCleanup.SecondaryExceptionKey].ShouldBeSameAs(cleanup); reportCalls.ShouldBe(1); } private static async Task ThrowFromBody(Exception failure) { await Task.Yield(); throw failure; } }