using System; using System.Threading; using System.Threading.Tasks; using MxGateway.Contracts; using MxGateway.Worker.Bootstrap; using MxGateway.Worker.Ipc; namespace MxGateway.Worker.Tests.Bootstrap; public sealed class WorkerApplicationTests { [Fact] public void Run_WithValidBootstrapArguments_ReturnsSuccessAndLogsRedactedNonce() { MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret"); MemoryWorkerLogger logger = new(); int exitCode = MxGateway.Worker.WorkerApplication.Run( ValidArgs(), environment, logger, new SucceedingPipeClient()); Assert.Equal((int)WorkerExitCode.Success, exitCode); Assert.Equal(2, logger.Entries.Count); MemoryWorkerLogEntry entry = logger.Entries[0]; Assert.Equal("Information", entry.Level); Assert.Equal("WorkerBootstrapSucceeded", entry.EventName); Assert.Equal("session-1", entry.Fields["session_id"]); Assert.Equal("mxaccess-gateway-123-session-1", entry.Fields["pipe_name"]); Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, entry.Fields["protocol_version"]); Assert.Equal("[redacted]", entry.Fields["nonce"]); Assert.Equal("WorkerPipeSessionCompleted", logger.Entries[1].EventName); } [Fact] public void Run_WithMissingRequiredArguments_ReturnsInvalidArguments() { MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret"); MemoryWorkerLogger logger = new(); int exitCode = MxGateway.Worker.WorkerApplication.Run( [], environment, logger); Assert.Equal((int)WorkerExitCode.InvalidArguments, exitCode); MemoryWorkerLogEntry entry = Assert.Single(logger.Entries); Assert.Equal("Error", entry.Level); Assert.Equal("WorkerBootstrapFailed", entry.EventName); Assert.Equal(WorkerExitCode.InvalidArguments, entry.Fields["exit_code"]); } [Fact] public void Run_WithInvalidProtocolVersion_ReturnsInvalidProtocolVersion() { MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret"); MemoryWorkerLogger logger = new(); int exitCode = MxGateway.Worker.WorkerApplication.Run( ValidArgs(protocolVersion: "999"), environment, logger); Assert.Equal((int)WorkerExitCode.InvalidProtocolVersion, exitCode); } [Fact] public void Run_WithMissingNonce_ReturnsMissingNonce() { MemoryWorkerEnvironment environment = new(); MemoryWorkerLogger logger = new(); int exitCode = MxGateway.Worker.WorkerApplication.Run( ValidArgs(), environment, logger); Assert.Equal((int)WorkerExitCode.MissingNonce, exitCode); } [Fact] public void Run_WithPipeProtocolFailure_ReturnsProtocolViolation() { MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret"); MemoryWorkerLogger logger = new(); int exitCode = MxGateway.Worker.WorkerApplication.Run( ValidArgs(), environment, logger, new ThrowingPipeClient(new WorkerFrameProtocolException( WorkerFrameProtocolErrorCode.NonceMismatch, "Bad nonce."))); Assert.Equal((int)WorkerExitCode.ProtocolViolation, exitCode); Assert.Equal("WorkerPipeProtocolFailure", logger.Entries[1].EventName); } [Fact] public void Run_WithUnexpectedBootstrapFailure_ReturnsUnexpectedFailure() { MemoryWorkerEnvironment environment = new(new InvalidOperationException("environment failed")); MemoryWorkerLogger logger = new(); int exitCode = MxGateway.Worker.WorkerApplication.Run( ValidArgs(), environment, logger); Assert.Equal((int)WorkerExitCode.UnexpectedFailure, exitCode); MemoryWorkerLogEntry entry = Assert.Single(logger.Entries); Assert.Equal("WorkerBootstrapUnexpectedFailure", entry.EventName); Assert.Equal(WorkerExitCode.UnexpectedFailure, entry.Fields["exit_code"]); Assert.Equal(typeof(InvalidOperationException).FullName, entry.Fields["exception_type"]); } private static string[] ValidArgs(string? protocolVersion = null) { return [ "--session-id", "session-1", "--pipe-name", "mxaccess-gateway-123-session-1", "--protocol-version", protocolVersion ?? GatewayContractInfo.WorkerProtocolVersion.ToString(), ]; } private static MemoryWorkerEnvironment CreateEnvironment(string nonce) { MemoryWorkerEnvironment environment = new(); environment.Set(WorkerOptions.NonceEnvironmentVariableName, nonce); return environment; } private sealed class SucceedingPipeClient : IWorkerPipeClient { public Task RunAsync( WorkerOptions options, CancellationToken cancellationToken = default) { return Task.CompletedTask; } } private sealed class ThrowingPipeClient : IWorkerPipeClient { private readonly Exception _exception; public ThrowingPipeClient(Exception exception) { _exception = exception; } public Task RunAsync( WorkerOptions options, CancellationToken cancellationToken = default) { throw _exception; } } }