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);
}
}