using Microsoft.Extensions.DependencyInjection; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.Interfaces; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin; namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests; /// /// End-to-end round-trip via the cluster: /// admin node asks node-B's DriverHostActor for a snapshot via ActorSelection. /// Verifies the cross-node Ask/Reply works and the snapshot reflects the target node's /// view (NodeId + CurrentRevision after a deploy). /// public sealed class FleetDiagnosticsRoundTripTests { private static CancellationToken Ct => TestContext.Current.CancellationToken; [Fact] public async Task GetDiagnostics_returns_snapshot_with_target_NodeId() { await using var harness = await TwoNodeClusterHarness.StartAsync(); // Resolve target NodeId from the cluster — second member ordered by address. var members = Akka.Cluster.Cluster.Get(harness.NodeASystem).State.Members .OrderBy(m => m.Address.ToString()) .ToArray(); members.Length.ShouldBe(2); var targetAddress = members[1].Address; var targetNodeId = Commons.Types.NodeId.Parse($"{targetAddress.Host}:{targetAddress.Port}"); await using var scope = harness.NodeA.Services.CreateAsyncScope(); var client = scope.ServiceProvider.GetRequiredService(); var snapshot = await client.GetDiagnosticsAsync(targetNodeId, Ct); snapshot.NodeId.ShouldBe(targetNodeId); snapshot.Drivers.ShouldBeEmpty(); // No driver children yet (F7). snapshot.AsOfUtc.ShouldBeGreaterThan(DateTime.UtcNow.AddSeconds(-30)); } [Fact] public async Task GetDiagnostics_after_deploy_reports_current_revision() { await using var harness = await TwoNodeClusterHarness.StartAsync(); await using var scope = harness.NodeA.Services.CreateAsyncScope(); var adminOps = scope.ServiceProvider.GetRequiredService(); var diagnostics = scope.ServiceProvider.GetRequiredService(); var deploy = await adminOps.StartDeploymentAsync(createdBy: "alice@test", Ct); deploy.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); var expectedRev = deploy.RevisionHash!.Value; // Wait until both DriverHostActors have caught up to the deployed revision. var members = Akka.Cluster.Cluster.Get(harness.NodeASystem).State.Members .OrderBy(m => m.Address.ToString()) .Select(m => Commons.Types.NodeId.Parse($"{m.Address.Host}:{m.Address.Port}")) .ToArray(); foreach (var nodeId in members) { await WaitForAsync(async () => { var snap = await diagnostics.GetDiagnosticsAsync(nodeId, Ct); return snap.CurrentRevision == expectedRev; }, TimeSpan.FromSeconds(15)); } } private static async Task WaitForAsync(Func> condition, TimeSpan timeout) { var deadline = DateTime.UtcNow + timeout; while (DateTime.UtcNow < deadline) { if (await condition()) return; await Task.Delay(200); } throw new TimeoutException($"Condition not met within {timeout}"); } }