using System.IO.Pipes; using System.Security.Principal; using Serilog; using Serilog.Core; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Ipc; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Ipc; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Tests; /// /// End-to-end IPC test: (from Galaxy.Host) accepts a connection from /// the Proxy's . Verifies the Hello handshake, shared-secret /// check, and heartbeat round-trip. Uses the current user's SID so the ACL allows the /// localhost test process. Skipped on non-Windows (pipe ACL is Windows-only). /// [Trait("Category", "Integration")] public sealed class IpcHandshakeIntegrationTests { [Fact] public async Task Hello_handshake_with_correct_secret_succeeds_and_heartbeat_round_trips() { if (!OperatingSystem.IsWindows()) return; // pipe ACL is Windows-only if (IsAdministrator()) return; // ACL explicitly denies Administrators — skip on admin shells using var currentIdentity = WindowsIdentity.GetCurrent(); var allowedSid = currentIdentity.User!; var pipeName = $"OtOpcUaGalaxyTest-{Guid.NewGuid():N}"; const string secret = "test-secret-2026"; Logger log = new LoggerConfiguration().CreateLogger(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var server = new PipeServer(pipeName, allowedSid, secret, log); var serverTask = Task.Run(() => server.RunOneConnectionAsync(new StubFrameHandler(), cts.Token)); await using var client = await GalaxyIpcClient.ConnectAsync( pipeName, secret, TimeSpan.FromSeconds(5), cts.Token); // Heartbeat round-trip via the stub handler. var ack = await client.CallAsync( MessageKind.Heartbeat, new Heartbeat { SequenceNumber = 42, UtcUnixMs = 1000 }, MessageKind.HeartbeatAck, cts.Token); ack.SequenceNumber.ShouldBe(42L); cts.Cancel(); try { await serverTask; } catch (OperationCanceledException) { } server.Dispose(); } [Fact] public async Task Hello_with_wrong_secret_is_rejected() { if (!OperatingSystem.IsWindows()) return; if (IsAdministrator()) return; using var currentIdentity = WindowsIdentity.GetCurrent(); var allowedSid = currentIdentity.User!; var pipeName = $"OtOpcUaGalaxyTest-{Guid.NewGuid():N}"; Logger log = new LoggerConfiguration().CreateLogger(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var server = new PipeServer(pipeName, allowedSid, "real-secret", log); var serverTask = Task.Run(() => server.RunOneConnectionAsync(new StubFrameHandler(), cts.Token)); await Should.ThrowAsync(() => GalaxyIpcClient.ConnectAsync(pipeName, "wrong-secret", TimeSpan.FromSeconds(5), cts.Token)); cts.Cancel(); try { await serverTask; } catch { /* server loop ends */ } server.Dispose(); } /// /// The production ACL explicitly denies Administrators. On dev boxes the interactive user /// is often an Administrator, so the allow rule gets overridden by the deny — the pipe /// refuses the connection. Skip in that case; the production install runs as a dedicated /// non-admin service account. /// private static bool IsAdministrator() { if (!OperatingSystem.IsWindows()) return false; using var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } }