using System; using System.IO; using System.IO.Pipes; using System.Threading; using System.Threading.Tasks; using Google.Protobuf.WellKnownTypes; using MxGateway.Contracts; using MxGateway.Contracts.Proto; using MxGateway.Worker.Bootstrap; using MxGateway.Worker.Ipc; using MxGateway.Worker.MxAccess; using MxGateway.Worker.Sta; namespace MxGateway.Worker.Tests.Ipc; public sealed class WorkerPipeClientTests { [Fact] public async Task RunAsync_ConnectsToPipeAndCompletesHandshake() { string pipeName = $"mxaccess-gateway-test-{Guid.NewGuid():N}"; WorkerOptions workerOptions = new( "session-1", pipeName, GatewayContractInfo.WorkerProtocolVersion, "nonce-secret"); WorkerFrameProtocolOptions frameOptions = new(workerOptions); using NamedPipeServerStream server = new( pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); WorkerPipeClient client = new( connectTimeoutMilliseconds: 5000, (stream, options) => CreateSession(stream, options)); Task clientTask = client.RunAsync(workerOptions); await Task.Factory.FromAsync(server.BeginWaitForConnection, server.EndWaitForConnection, null); WorkerFrameReader reader = new(server, frameOptions); WorkerFrameWriter writer = new(server, frameOptions); await writer.WriteAsync(new WorkerEnvelope { ProtocolVersion = GatewayContractInfo.WorkerProtocolVersion, SessionId = "session-1", Sequence = 1, GatewayHello = new GatewayHello { SupportedProtocolVersion = GatewayContractInfo.WorkerProtocolVersion, Nonce = "nonce-secret", GatewayVersion = "test-gateway", }, }); WorkerEnvelope hello = await reader.ReadAsync(); Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerHello, hello.BodyCase); Assert.Equal("nonce-secret", hello.WorkerHello.Nonce); WorkerEnvelope ready = await reader.ReadAsync(); Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerReady, ready.BodyCase); await writer.WriteAsync(new WorkerEnvelope { ProtocolVersion = GatewayContractInfo.WorkerProtocolVersion, SessionId = "session-1", Sequence = 2, WorkerShutdown = new WorkerShutdown { GracePeriod = Duration.FromTimeSpan(TimeSpan.FromSeconds(1)), Reason = "test-complete", }, }); WorkerEnvelope shutdownAck = await reader.ReadAsync(); Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerShutdownAck, shutdownAck.BodyCase); await clientTask; } private static WorkerPipeSession CreateSession( Stream stream, WorkerFrameProtocolOptions options) { return new WorkerPipeSession( new WorkerFrameReader(stream, options), new WorkerFrameWriter(stream, options), options, () => 1234, new WorkerPipeSessionOptions { HeartbeatInterval = TimeSpan.FromSeconds(30), HeartbeatGrace = TimeSpan.FromSeconds(30), }, () => new FakeRuntimeSession()); } private sealed class FakeRuntimeSession : IWorkerRuntimeSession { public Task StartAsync( string sessionId, int workerProcessId, CancellationToken cancellationToken = default) { return Task.FromResult(new WorkerReady { WorkerProcessId = workerProcessId, MxaccessProgid = MxGateway.Worker.MxAccess.MxAccessInteropInfo.ProgId, MxaccessClsid = MxGateway.Worker.MxAccess.MxAccessInteropInfo.Clsid, ReadyTimestamp = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), }); } public Task DispatchAsync(StaCommand command) { return Task.FromResult(new MxCommandReply { SessionId = command.SessionId, CorrelationId = command.CorrelationId, Kind = command.Kind, ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok, Message = "OK", }, }); } public WorkerRuntimeHeartbeatSnapshot CaptureHeartbeat() { return new WorkerRuntimeHeartbeatSnapshot( DateTimeOffset.UtcNow, pendingCommandCount: 0, outboundEventQueueDepth: 0, lastEventSequence: 0, currentCommandCorrelationId: string.Empty); } public void RequestShutdown() { } public void Dispose() { } } }