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