using Microsoft.Extensions.Logging; using ZB.MOM.WW.MxGateway.Server.Workers; using ZB.MOM.WW.MxGateway.Tests.TestSupport; namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Workers; /// Protects bounded teardown, fault observation, and safe diagnostic output. public sealed class FakeWorkerDiagnosticsTests { /// Repeated gateway logs remain bounded and never format sensitive state or messages. [Fact] public void Log_WhenFaultsRepeat_ReportsOnlyBoundedMetadata() { using FakeWorkerDiagnostics diagnostics = new(); List output = []; diagnostics.SetOutput(output.Add); for (int i = 0; i < 32; i++) { diagnostics.Log(LogLevel.Warning, new EventId(1), "secret-command", new WorkerFrameProtocolException(WorkerFrameProtocolErrorCode.InvalidEnvelope, "secret-item"), (state, exception) => $"{state} {exception}"); } Assert.NotEmpty(output); Assert.InRange(output.Count, 1, 16); Assert.All(output, line => { Assert.Contains("type=WorkerFrameProtocolException", line); Assert.Contains("code=InvalidEnvelope", line); Assert.DoesNotContain("secret", line); }); } /// A late fault is observed after timeout without calling the completed output sink. /// The asynchronous test operation. [Fact] public async Task DrainAsync_WhenWorkerOutlivesBudget_ObservesLateFaultWithoutLateOutput() { FakeWorkerDiagnostics diagnostics = new(); List output = []; diagnostics.SetOutput(output.Add); TaskCompletionSource worker = new(TaskCreationOptions.RunContinuationsAsynchronously); diagnostics.Track(worker.Task); await diagnostics.DrainAsync(TimeSpan.Zero); Assert.Contains(output, line => line.Contains("source=teardown") && line.Contains("type=TimeoutException")); int beforeFault = output.Count; worker.SetException(new WorkerFrameProtocolException(WorkerFrameProtocolErrorCode.InvalidEnvelope, "secret-item")); await diagnostics.DrainAsync(TimeSpan.FromSeconds(5)); Assert.Equal(beforeFault, output.Count); } /// Expected cancellation does not add fault diagnostics. /// The asynchronous test operation. [Fact] public async Task DrainAsync_WhenWorkerIsCancelled_DoesNotReportFault() { FakeWorkerDiagnostics diagnostics = new(); List output = []; diagnostics.SetOutput(output.Add); diagnostics.Track(Task.FromCanceled(new CancellationToken(true))); await diagnostics.DrainAsync(TimeSpan.FromSeconds(5)); Assert.Empty(output); } /// An unavailable output helper cannot fault the observer or replace the primary failure. /// The asynchronous test operation. [Fact] public async Task DrainAsync_WhenOutputThrows_PreservesPrimaryFailure() { InvalidOperationException primary = new("primary"); FakeWorkerDiagnostics diagnostics = new(); diagnostics.SetOutput(_ => throw new InvalidOperationException("completed output")); Exception? failure = await Record.ExceptionAsync(async () => { try { throw primary; } finally { diagnostics.Track(Task.FromException(new WorkerFrameProtocolException( WorkerFrameProtocolErrorCode.InvalidEnvelope, "secret-item"))); await diagnostics.DrainAsync(TimeSpan.FromSeconds(5)); } }); Assert.Same(primary, failure); } }